PVLIB_Ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d3ae3a823ad98dd6eaad6afeea6fc3cc3ff5902
4
+ data.tar.gz: 9791749f4b811b64481206aec941f5289d1d543c
5
+ SHA512:
6
+ metadata.gz: 8043fbd98a76d68a1deb94449201eb34fca2365d54c5055878637531f62b445d236f810c85245de32b6774a25dd34fe3aa49e02102bc4490df4c35445ee9d0d2
7
+ data.tar.gz: d920617b1a837ffd83d763d3b921ab0bee605aa8aa575564eb390a22f63613c5f587d9e7e628fe33636c05895f45e04c0f9eac33a7bf6f1a0f3085b146406c2e
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ PVLIB_Ruby
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.2
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in PVLIB_Ruby.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem 'pry-byebug'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Tadatoshi Takahashi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pvlib_ruby/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "PVLIB_Ruby"
8
+ spec.version = PvlibRuby::VERSION
9
+ spec.authors = ["Tadatoshi Takahashi"]
10
+ spec.email = ["tadatoshi@gmail.com"]
11
+
12
+ spec.summary = %q{PVLIB written in Ruby.}
13
+ spec.description = %q{PVLIB_MatLab translated to Ruby code in Object-Oriented way.}
14
+ spec.homepage = "https://github.com/tadatoshi/PVLIB_Ruby"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "activemodel"
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.9"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rspec"
35
+ end
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # PVLIB_Ruby
2
+
3
+ PVLIB_MatLab translated to Ruby code in Object-Oriented way.
4
+
5
+ Since the naming convention is PVLIB_[language name], e.g. PVLIB_MatLab, PVLIB_Python, this gem is following the same naming convention, i.e. PVLIB_Ruby.
6
+
7
+ However, internally, this gem is following Ruby naming convention. Hence, the code is under 'lib/pvlib_ruby'.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'PVLIB_Ruby'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install PVLIB_Ruby
24
+
25
+ ## Usage
26
+
27
+ See examples under examples directory.
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ 1. Fork it ( https://github.com/[my-github-username]/PVLIB_Ruby/fork )
38
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
39
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
40
+ 4. Push to the branch (`git push origin my-new-feature`)
41
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "pvlib_ruby"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ "name","Vac","Pac0","Pdc0","Vdc0","Ps0","C0","C1","C2","C3","Pnt","Vdcmax","Idcmax","MPPTLow","MPPTHi","LibraryType","LibraryName",
2
+ "SunPower Corp (Original Mfg - PV Powered): SPR-2500 240V [CEC 2006]",240,2500,2630.0909,219.2317,41.3618,-1.265e-05,6.6747e-05,0.0017417,0.00061375,3.9,500,20,140,450,"SandiaInverter","Sandia Inverters",
3
+
@@ -0,0 +1,3 @@
1
+ "name","vintage","material","area","AlphaIsc","AlphaImp","Isc0","Imp0","Voc0","Vmp0","BetaVoc","BetaVmp","mBetaVoc","mBetaVmp","Ns","Np","delT","fd","n","Ix0","Ixx0","a_wind","b_wind","c",,,,,,,,"a",,,,,"b",,,,,,
2
+ "Sample Module",2006,"c-Si",1.244,0.000232,-0.00036,5.988,5.56,48.53,40.03,-0.152,-0.162,0,0,72,1,3,1,1.241,5.93,4.12,-3.62,-0.075,1.0072,-0.0072,0.32304,-3.4984,0.9966,0.0034,1.0827,-0.0827,-0.0001223,0.002416,-0.01912,0.07365,0.9259,-2.99e-09,5.35e-07,-3.4e-05,0.000862,-0.00699,1,
3
+
@@ -0,0 +1,120 @@
1
+ # NOTE: PVLIB_Ruby gem must be installed in local machine in order to run this code.
2
+ # Execute "rake install" to use the code in this gem.
3
+ # Or if you use the gem downloaded from RubyGems.org, you don't have to execute "rake install" and just do "gem install PVLIB_Ruby"
4
+
5
+ require 'pvlib_ruby'
6
+
7
+ require 'bigdecimal'
8
+
9
+ DATA_DIRECTORY = File.expand_path('../data', __FILE__)
10
+
11
+ # for 360th row in PVSC40Tutorial_Master (360/30 = 12, i.e. noon, note: measurement is every two minutes)
12
+ direct_normal_irradiance = BigDecimal('631.3100')
13
+ global_horizontal_irradiance = BigDecimal('862.0619')
14
+ diffuse_horizontal_irradiance = BigDecimal('405.3100')
15
+ day_of_year = BigDecimal('294')
16
+ utc_offset = '-07:00'
17
+ # TODO: Get time from year and day_of_year:
18
+ time = Time.new(2008, 10, 20, 11, 58, 12, utc_offset)
19
+ albedo = BigDecimal('0.1500')
20
+ array_tilt = BigDecimal('35')
21
+ array_azimuth = BigDecimal('180')
22
+ latitude = BigDecimal('35.0500')
23
+ longitude = BigDecimal('-106.5400')
24
+ altitude = BigDecimal('1660')
25
+ location = Location.new(latitude, longitude, altitude)
26
+ pressure = BigDecimal('62963')
27
+ reference_solar_irradiance = BigDecimal('1000')
28
+ wind_speed = BigDecimal('2.8786')
29
+ air_temperature = BigDecimal('20.7700')
30
+
31
+ solar_ephemeris = SolarEphemeris.new(time, location, pressure: pressure, temperature: air_temperature)
32
+
33
+ sun_azimuth = solar_ephemeris.sun_azimuth
34
+ apparent_sun_elevation = solar_ephemeris.apparent_sun_elevation
35
+
36
+ puts "------------- Solar Ephemeris -------------"
37
+ puts " Sun Azimuth [º]: #{sun_azimuth.round(4).to_s('F')}" # Slight difference from 182.5229 for 360th row in PVSC40Tutorial_Master
38
+ puts " Apparent Sun Elevation [º]: #{apparent_sun_elevation.round(4).to_s('F')}" # Slight difference from 44.2308 for 360th row in PVSC40Tutorial_Master
39
+ puts "-------------------------------------------"
40
+ puts ""
41
+
42
+ sun_zenith = BigDecimal('90') - apparent_sun_elevation
43
+
44
+ plain_of_array_irradiance = PlainOfArrayIrradiance.new(direct_normal_irradiance, global_horizontal_irradiance, diffuse_horizontal_irradiance, day_of_year, albedo, array_tilt, array_azimuth, sun_zenith, sun_azimuth)
45
+
46
+ angle_of_incidence = plain_of_array_irradiance.angle_of_incidence
47
+
48
+ beam_irradiance = plain_of_array_irradiance.beam_irradiance
49
+ ground_diffuse_irradiance = plain_of_array_irradiance.ground_diffuse_irradiance
50
+ sky_diffuse_irradiance = plain_of_array_irradiance.sky_diffuse_irradiance
51
+
52
+ puts "----- Plain Of Array (POA) Irradiance -----"
53
+ puts " Angle of Incidence [º]: #{angle_of_incidence.round(4).to_s('F')}" # Slight difference from 10.8703 for 360th row in PVSC40Tutorial_Master
54
+ puts ""
55
+ puts " Beam Irradiance [W/m^2]: #{beam_irradiance.round(4).to_s('F')}" # Slight difference from 619.9822 for 360th row in PVSC40Tutorial_Master
56
+ puts " Ground Diffuse Irradiance [W/m^2]: #{ground_diffuse_irradiance.round(4).to_s('F')}" # Matches to 11.6927 for 360th row in PVSC40Tutorial_Master
57
+ puts " Sky Diffuse Irradiance [W/m^2]: #{sky_diffuse_irradiance.round(4).to_s('F')}" # Slight difference from 464.8004 for 360th row in PVSC40Tutorial_Master
58
+ puts "-------------------------------------------"
59
+ puts ""
60
+
61
+ solar_irradiance_incident_on_module_surface = beam_irradiance + ground_diffuse_irradiance + sky_diffuse_irradiance
62
+
63
+ sandia_module_data_filepath = File.join(DATA_DIRECTORY, 'sandia_module_example.csv')
64
+ sandia_pv_module = PvModule.create(sandia_module_data_filepath)
65
+
66
+ pv_temperature = PvTemperature.new(sandia_pv_module, solar_irradiance_incident_on_module_surface, reference_solar_irradiance, wind_speed, air_temperature)
67
+
68
+ puts "------------- PV temperature --------------"
69
+ puts " Estimated Cell Temperature [ºC]: #{pv_temperature.cell_temperature.round(4).to_s('F')}" # Slight difference from 47.7235 for 360th row in PVSC40Tutorial_Master
70
+ puts " Estimated Module Temperature [ºC]: #{pv_temperature.module_temperature.round(4).to_s('F')}" # Slight difference from 44.4341 for 360th row in PVSC40Tutorial_Master
71
+ puts "-------------------------------------------"
72
+ puts ""
73
+
74
+ estimated_cell_temperature = pv_temperature.cell_temperature
75
+
76
+ air_mass = AirMass.new(sun_zenith, pressure)
77
+
78
+ puts "---------------- Air Mass -----------------"
79
+ puts " Absolute Air Mass: #{air_mass.absolute_air_mass.round(4).to_s('F')}" # Slight difference from 0.8894 for 360th row in PVSC40Tutorial_Master
80
+ puts "-------------------------------------------"
81
+ puts ""
82
+
83
+ absolute_air_mass = air_mass.absolute_air_mass
84
+
85
+ pv_performance_characterization = PvPerformanceCharacterization.new(sandia_pv_module, absolute_air_mass, angle_of_incidence,
86
+ beam_irradiance, ground_diffuse_irradiance, sky_diffuse_irradiance,
87
+ estimated_cell_temperature)
88
+
89
+ puts "--- PV module IV curve characterization ---"
90
+ puts " Short Circuit Current [A]: #{pv_performance_characterization.short_circuit_current.round(4).to_s('F')}" # Slight difference from 6.4151 for 360th row in PVSC40Tutorial_Master
91
+ puts " Maximum Power Point Current [A]: #{pv_performance_characterization.maximum_power_point_current.round(4).to_s('F')}" # Matches to 5.8741 for 360th row in PVSC40Tutorial_Master
92
+ puts " Open Circuit Voltage [V]: #{pv_performance_characterization.open_circuit_voltage.round(4).to_s('F')}" # Matches to 45.2333 for 360th row in PVSC40Tutorial_Master
93
+ puts " Maximum Power Point Voltage [V]: #{pv_performance_characterization.maximum_power_point_voltage.round(4).to_s('F')}" # Matches to 36.3984 for 360th row in PVSC40Tutorial_Master
94
+ puts " Fourth Point Current [A]: #{pv_performance_characterization.fourth_point_current.round(4).to_s('F')}" # Matches to 6.3544 for 360th row in PVSC40Tutorial_Master
95
+ puts " Fifth Point Current [A]: #{pv_performance_characterization.fifth_point_current.round(4).to_s('F')}" # Slight difference from 4.3899 for 360th row in PVSC40Tutorial_Master
96
+ puts "-------------------------------------------"
97
+ puts ""
98
+
99
+ parallel_string = BigDecimal('1')
100
+ series_modules = BigDecimal('5')
101
+ array_current = pv_performance_characterization.maximum_power_point_current * parallel_string
102
+ array_voltage = pv_performance_characterization.maximum_power_point_voltage * series_modules
103
+ array_power = array_current * array_voltage
104
+
105
+ puts "------------- PV array output -------------"
106
+ puts " Array current [A]: #{array_current.round(4).to_s('F')}" # Matches to 5.8741 for 360th row in PVSC40Tutorial_Master
107
+ puts " Array voltage [V]: #{array_voltage.round(4).to_s('F')}" # Slight difference from 181.9919 for 360th row in PVSC40Tutorial_Master
108
+ puts " Array power [W]: #{array_power.round(4).to_s('F')}" # Matches to 1069.0 (with rounding) for 360th row in PVSC40Tutorial_Master
109
+ puts "-------------------------------------------"
110
+ puts ""
111
+
112
+ sandia_inverter_data_filename = File.join(DATA_DIRECTORY, 'sandia_inverter_example.csv')
113
+ sandia_inverter = Inverter.create(sandia_inverter_data_filename)
114
+
115
+ dc_to_ac_conversion = DcToAcConversion.new(sandia_inverter)
116
+
117
+ puts "--- Inverter DC to AC conversion ---"
118
+ puts " AC power [W]: #{dc_to_ac_conversion.ac_power(array_voltage, array_power).round(4).to_s('F')}" # Matches to 1016.3 (with rounding) for 360th row in PVSC40Tutorial_Master
119
+ puts "------------------------------------"
120
+ puts ""
@@ -0,0 +1,26 @@
1
+ require 'bigdecimal'
2
+
3
+ # The default model for relative air mass is 'kastenyoung1989'. Different model is realized by subclass.
4
+ # 'kastenyoung1989' model is based on:
5
+ # Fritz Kasten and Andrew Young. "Revised optical air mass tables and approximation formula". Applied Optics 28:4735–4738
6
+ class AirMass
7
+ include CalculationHelper
8
+
9
+ def initialize(sun_zenith, pressure)
10
+ @sun_zenith = sun_zenith
11
+ @pressure = pressure
12
+ end
13
+
14
+ # Relative air mass at sea level
15
+ def relative_air_mass
16
+ relative_air_mass = BigDecimal('1') / (Math.cos(degree_to_radian(@sun_zenith).to_f) + BigDecimal('0.50572') * ((BigDecimal('6.07995') + (BigDecimal('90') - @sun_zenith)).power(-1.6364)))
17
+ relative_air_mass = BigDecimal('0') if relative_air_mass.nan?
18
+ relative_air_mass
19
+ end
20
+
21
+ # Airmass for locations not at sea level (i.e. not at standard pressure)
22
+ def absolute_air_mass
23
+ relative_air_mass * @pressure / BigDecimal('101325')
24
+ end
25
+
26
+ end
@@ -0,0 +1,43 @@
1
+ module CalculationHelper
2
+
3
+ def degree_to_radian(degree)
4
+ degree * BigDecimal(Math::PI.to_s) / BigDecimal('180')
5
+ end
6
+
7
+ def radian_to_degree(radian)
8
+ radian * BigDecimal('180') / BigDecimal(Math::PI.to_s)
9
+ end
10
+
11
+ def bigdecimal_exp(bigdecimal_value)
12
+ BigDecimal(Math.exp(bigdecimal_value.to_f).to_s)
13
+ end
14
+
15
+ def bigdecimal_cos(angle_in_radian)
16
+ BigDecimal(Math.cos(angle_in_radian.to_f).to_s)
17
+ end
18
+
19
+ def bigdecimal_sin(angle_in_radian)
20
+ BigDecimal(Math.sin(angle_in_radian.to_f).to_s)
21
+ end
22
+
23
+ def bigdecimal_tan(angle_in_radian)
24
+ BigDecimal(Math.tan(angle_in_radian.to_f).to_s)
25
+ end
26
+
27
+ def bigdecimal_asin(sin_value)
28
+ BigDecimal(Math.asin(sin_value.to_f).to_s)
29
+ end
30
+
31
+ def bigdecimal_acos(cos_value)
32
+ BigDecimal(Math.acos(cos_value.to_f).to_s)
33
+ end
34
+
35
+ def bigdecimal_atan2(y, x)
36
+ BigDecimal(Math.atan2(y.to_f, x.to_f).to_s)
37
+ end
38
+
39
+ def bigdecimal_sqrt(bigdecimal_value)
40
+ BigDecimal(Math.sqrt(bigdecimal_value.to_f).to_s)
41
+ end
42
+
43
+ end
@@ -0,0 +1,40 @@
1
+ require 'bigdecimal'
2
+
3
+ # This class uses Sandia's Grid-Connected PV Inverter model, which is based on:
4
+ # SAND2007-5036, "Performance Model for Grid-Connected Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. Boyson
5
+ # Since this paper was not available any more, we referred to https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/
6
+ # Other inverter models are realized by subclasses.
7
+ class DcToAcConversion
8
+
9
+ def initialize(inverter)
10
+ @inverter = inverter
11
+ end
12
+
13
+ def ac_power(dc_input_voltage, dc_input_power)
14
+ # A = Pdc0 * ( 1 + C1 * (Vdc - Vdc0) )
15
+ dc_power_adjustment_parameter = @inverter.dc_power_for_maximum_ac_power_rating * (1 + @inverter.coefficient_for_dc_power_for_maximum_ac_power_rating * (dc_input_voltage - @inverter.dc_power_level_for_maximum_ac_power_rating))
16
+
17
+ # B = Ps0 * ( 1 + C2 * (Vdc - Vdc0) )
18
+ starting_dc_power_adjustment_parameter = @inverter.starting_dc_power * (1 + @inverter.coefficient_for_starting_dc_power * (dc_input_voltage - @inverter.dc_power_level_for_maximum_ac_power_rating))
19
+
20
+ # C = C0 * ( 1 + C3 * (Vdc - Vdc0) )
21
+ coefficient_adjustment_parameter = @inverter.power_adjustment_coefficient * (1 + @inverter.coefficient_for_power_adjustment_coefficient * (dc_input_voltage - @inverter.dc_power_level_for_maximum_ac_power_rating))
22
+
23
+ ac_power = (@inverter.maximum_ac_power_rating / (dc_power_adjustment_parameter - starting_dc_power_adjustment_parameter) - coefficient_adjustment_parameter * (dc_power_adjustment_parameter - starting_dc_power_adjustment_parameter)) * (dc_input_power - starting_dc_power_adjustment_parameter) +
24
+ coefficient_adjustment_parameter * (dc_input_power - starting_dc_power_adjustment_parameter).power(2)
25
+
26
+ adjust_ac_power(ac_power)
27
+ end
28
+
29
+ private
30
+ def adjust_ac_power(ac_power)
31
+ if ac_power > @inverter.maximum_ac_power_rating # Inverter clipping at maximum rated AC Power
32
+ maximum_ac_power_rating
33
+ elsif ac_power < @inverter.starting_dc_power # Inverter night tare losses
34
+ -(@inverter.night_tare_loss.abs)
35
+ else
36
+ ac_power
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,60 @@
1
+ require 'active_model'
2
+ require 'csv'
3
+
4
+ # This is a model in model-view-controller design pattern.
5
+ # It is analogous to ActiveRecord. Hence, it's a placeholder for data from CSV file.
6
+ # Borrowed the idea from "http://kyle.conarro.com/importing-from-csv-in-rails".
7
+ class ActiveCsv
8
+ include ActiveModel::Model
9
+
10
+ def self.create(csv_filepath)
11
+ new_instance = self.new
12
+ new_instance.load_data(csv_filepath)
13
+ new_instance
14
+ end
15
+
16
+ def persisted?
17
+ false
18
+ end
19
+
20
+ def valid?
21
+ # TODO: implement this
22
+ end
23
+
24
+ def load_data(csv_filepath)
25
+ csv = CSV.new(File.new(csv_filepath), headers: true, header_converters: :symbol)
26
+
27
+ data = csv.shift
28
+
29
+ previous_header = nil;
30
+ index = 0;
31
+
32
+ data.headers.each do |header|
33
+
34
+ if "#{header}=" == "="
35
+
36
+ unless previous_header.blank?
37
+
38
+ assigned_data_for_previous_header = self.send(previous_header.to_sym)
39
+
40
+ if assigned_data_for_previous_header.instance_of? Array
41
+ assigned_data_for_previous_header << data[index]
42
+ self.send("#{previous_header}=".to_sym, assigned_data_for_previous_header)
43
+ else
44
+ self.send("#{previous_header}=".to_sym, [assigned_data_for_previous_header,data[index]])
45
+ end
46
+
47
+ end
48
+
49
+ else
50
+ self.send("#{header}=".to_sym, data[header])
51
+ end
52
+
53
+ previous_header = header unless header.blank?
54
+ index = index + 1;
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,46 @@
1
+ require 'bigdecimal'
2
+
3
+ # This is a model in model-view-controller design pattern.
4
+ # In this case, it is expending ActiveCsv, i.e. it is analogous to ActiveRecord. Hence, it's a placeholder for data from CSV file.
5
+ class Inverter < ActiveCsv
6
+
7
+ attr_accessor :name, :vac, :pac0, :pdc0, :vdc0, :ps0, :c0, :c1, :c2, :c3
8
+ attr_accessor :pnt, :vdcmax, :idcmax, :mpptlow, :mppthi, :librarytype, :libraryname
9
+
10
+ def maximum_ac_power_rating
11
+ BigDecimal(self.pac0.to_s)
12
+ end
13
+
14
+ def dc_power_for_maximum_ac_power_rating
15
+ BigDecimal(self.pdc0.to_s)
16
+ end
17
+
18
+ def coefficient_for_dc_power_for_maximum_ac_power_rating
19
+ BigDecimal(self.c1.to_s)
20
+ end
21
+
22
+ def dc_power_level_for_maximum_ac_power_rating
23
+ BigDecimal(self.vdc0.to_s)
24
+ end
25
+
26
+ def starting_dc_power
27
+ BigDecimal(self.ps0.to_s)
28
+ end
29
+
30
+ def coefficient_for_starting_dc_power
31
+ BigDecimal(self.c2.to_s)
32
+ end
33
+
34
+ def power_adjustment_coefficient
35
+ BigDecimal(self.c0.to_s)
36
+ end
37
+
38
+ def coefficient_for_power_adjustment_coefficient
39
+ BigDecimal(self.c3.to_s)
40
+ end
41
+
42
+ def night_tare_loss
43
+ BigDecimal(self.pnt.to_s)
44
+ end
45
+
46
+ end
@@ -0,0 +1,11 @@
1
+ class Location
2
+
3
+ attr_accessor :latitude, :longitude, :altitude
4
+
5
+ def initialize(latitude, longitude, altitude)
6
+ @latitude = latitude
7
+ @longitude = longitude
8
+ @altitude = altitude
9
+ end
10
+
11
+ end
@@ -0,0 +1,100 @@
1
+ require 'bigdecimal'
2
+
3
+ # This is a model in model-view-controller design pattern.
4
+ # In this case, it is expending ActiveCsv, i.e. it is analogous to ActiveRecord. Hence, it's a placeholder for data from CSV file.
5
+ class PvModule < ActiveCsv
6
+
7
+ attr_accessor :name, :vintage, :material, :area, :alphaisc, :alphaimp, :isc0, :imp0, :voc0, :vmp0
8
+ attr_accessor :betavoc, :betavmp, :mbetavoc, :mbetavmp, :ns, :np, :delt, :fd, :n, :ix0, :ixx0, :a_wind, :b_wind
9
+ attr_accessor :c, :a, :b
10
+
11
+ def short_circuit_current_0
12
+ BigDecimal(self.isc0.to_s)
13
+ end
14
+
15
+ def air_mass_variation_polynomial_coefficients
16
+ convert_coefficients_to_bigdecimal(self.a)
17
+ end
18
+
19
+ def angle_of_incidence_polynominal_coefficients
20
+ convert_coefficients_to_bigdecimal(self.b)
21
+ end
22
+
23
+ def diffuse_irradiance_factor
24
+ BigDecimal(self.fd)
25
+ end
26
+
27
+ def normalized_temperature_coefficient_for_short_circuit_current
28
+ BigDecimal(self.alphaisc.to_s)
29
+ end
30
+
31
+ def maximum_power_point_current_0
32
+ BigDecimal(self.imp0.to_s)
33
+ end
34
+
35
+ def performance_coefficients
36
+ convert_coefficients_to_bigdecimal(self.c)
37
+ end
38
+
39
+ def normalized_temperature_coefficient_for_maximum_power_point_current
40
+ BigDecimal(self.alphaimp.to_s)
41
+ end
42
+
43
+ def open_circuit_voltage_0
44
+ BigDecimal(self.voc0.to_s)
45
+ end
46
+
47
+ def number_of_cells_in_series
48
+ BigDecimal(self.ns.to_s)
49
+ end
50
+
51
+ def diode_factor
52
+ BigDecimal(self.n.to_s)
53
+ end
54
+
55
+ def temperature_coefficient_for_open_circuit_voltage
56
+ BigDecimal(self.betavoc.to_s)
57
+ end
58
+
59
+ def irradiance_dependent_temperature_coefficient_for_open_circuit_voltage
60
+ BigDecimal(self.mbetavoc.to_s)
61
+ end
62
+
63
+ def maximum_power_point_voltage_0
64
+ BigDecimal(self.vmp0.to_s)
65
+ end
66
+
67
+ def temperature_coefficient_for_maximum_power_point_voltage
68
+ BigDecimal(self.betavmp.to_s)
69
+ end
70
+
71
+ def irradiance_dependent_temperature_coefficient_for_maximum_power_point_voltage
72
+ BigDecimal(self.mbetavmp.to_s)
73
+ end
74
+
75
+ def fourth_point_current_0
76
+ BigDecimal(self.ix0.to_s)
77
+ end
78
+
79
+ def fifth_point_current_0
80
+ BigDecimal(self.ixx0.to_s)
81
+ end
82
+
83
+ def temperature_upper_limit_coefficient
84
+ BigDecimal(self.a_wind.to_s)
85
+ end
86
+
87
+ def wind_speed_decrease_rate_coefficient
88
+ BigDecimal(self.b_wind.to_s)
89
+ end
90
+
91
+ def cell_module_back_surface_temperature_difference
92
+ BigDecimal(self.delt.to_s)
93
+ end
94
+
95
+ private
96
+ def convert_coefficients_to_bigdecimal(coefficients)
97
+ coefficients.map {|coefficient| BigDecimal(coefficient.to_s)}.compact
98
+ end
99
+
100
+ end
@@ -0,0 +1,125 @@
1
+ require 'bigdecimal'
2
+
3
+ # For Sky Diffuse Irradiance, "Reindl's 1990 model" is used.
4
+ # Other models are realized by subclasses.
5
+ #
6
+ # References
7
+ # [1] Loutzenhiser P.G. et. al. "Empirical validation of models to compute
8
+ # solar irradiance on inclined surfaces for building energy simulation"
9
+ # 2007, Solar Energy vol. 81. pp. 254-267
10
+ # [2] Reindl, D.T., Beckmann, W.A., Duffie, J.A., 1990a. Diffuse fraction
11
+ # correlations. Solar Energy 45 (1), 1–7.
12
+ # [3] Reindl, D.T., Beckmann, W.A., Duffie, J.A., 1990b. Evaluation of hourly
13
+ # tilted surface radiation models. Solar Energy 45 (1), 9–17.
14
+ # [4] Solar radiation basics http://solardat.uoregon.edu/SolarRadiationBasics.html
15
+ #
16
+ # TODO: Consider to pass necessary parameters to the respective methods instead of passing all of them to the constructor.
17
+ # Haven't decided which way would be better.
18
+ class PlainOfArrayIrradiance
19
+ include CalculationHelper
20
+
21
+ AVEARGE_EXTRATERRESTRIAL_IRRADIANCE = BigDecimal('1367') # [W/m^2]
22
+ SMALL_VALUE_FOR_SKY_DIFFUSE_IRRADIANCE = BigDecimal('0.000001') # In order to make the calculated value consistent with the one by PVLIB_MatLab
23
+
24
+ def initialize(direct_normal_irradiance, global_horizontal_irradiance, diffuse_horizontal_irradiance, day_of_year, albedo, surface_tilt, surface_azimuth, sun_zenith, sun_azimuth)
25
+ @direct_normal_irradiance = direct_normal_irradiance # DNI [W/m^2]
26
+ @global_horizontal_irradiance = global_horizontal_irradiance # GHI [W/m^2]
27
+ @diffuse_horizontal_irradiance = diffuse_horizontal_irradiance # DHI [W/m^2]
28
+ @day_of_year = day_of_year
29
+ @albedo = albedo
30
+ @surface_tilt = surface_tilt
31
+ @surface_azimuth = surface_azimuth
32
+ @sun_zenith = sun_zenith
33
+ @sun_azimuth = sun_azimuth
34
+ end
35
+
36
+ def beam_irradiance
37
+ @direct_normal_irradiance * bigdecimal_cos(degree_to_radian(angle_of_incidence))
38
+ end
39
+
40
+ def ground_diffuse_irradiance
41
+ @global_horizontal_irradiance * @albedo * (1 - bigdecimal_cos(degree_to_radian(@surface_tilt))) * BigDecimal('0.5')
42
+ end
43
+
44
+ # Based on the equation (5) of [3]
45
+ # The comment in PVLIB_MatLab says it's based on the modified version of the equation (8) of [1].
46
+ # But I observe that the code is exactly same as the equation (5) of [3].
47
+ def sky_diffuse_irradiance
48
+ @diffuse_horizontal_irradiance * (anisotropy_index * geometric_factor +
49
+ (1 - anisotropy_index) * ((BigDecimal('1') + bigdecimal_cos(degree_to_radian(@surface_tilt))) / BigDecimal('2')) * anisotropic_correction_factor)
50
+ end
51
+
52
+ # This is used only for calculating Sky Diffuse Irradiance.
53
+ # But it's made public method just in case some other code needs it.
54
+ # Reference: [4]
55
+ def extraterrestrial_irradiance
56
+ earth_orbit_angle_in_radian = BigDecimal('2') * BigDecimal(Math::PI.to_s) * @day_of_year / BigDecimal('365')
57
+ sun_earth_distance_factor_square = BigDecimal('1.00011') + BigDecimal('0.034221') * bigdecimal_cos(earth_orbit_angle_in_radian) + BigDecimal('0.001280') * bigdecimal_sin(earth_orbit_angle_in_radian) +
58
+ BigDecimal('0.000719') * bigdecimal_cos(BigDecimal('2') * earth_orbit_angle_in_radian) + BigDecimal('0.000077') * bigdecimal_sin(BigDecimal('2') * earth_orbit_angle_in_radian)
59
+ AVEARGE_EXTRATERRESTRIAL_IRRADIANCE * sun_earth_distance_factor_square
60
+ end
61
+
62
+ def angle_of_incidence
63
+ cosine_of_angle_of_incidence = bigdecimal_cos(degree_to_radian(@sun_zenith)) * bigdecimal_cos(degree_to_radian(@surface_tilt)) +
64
+ bigdecimal_sin(degree_to_radian(@surface_tilt)) * bigdecimal_sin(degree_to_radian(@sun_zenith)) * bigdecimal_cos(degree_to_radian(@sun_azimuth - @surface_azimuth))
65
+
66
+ if cosine_of_angle_of_incidence > BigDecimal('1')
67
+ cosine_of_angle_of_incidence = BigDecimal('1')
68
+ elsif cosine_of_angle_of_incidence < BigDecimal('-1')
69
+ cosine_of_angle_of_incidence = BigDecimal('-1')
70
+ end
71
+
72
+ radian_to_degree(bigdecimal_acos(cosine_of_angle_of_incidence))
73
+ end
74
+
75
+ private
76
+ # Rb
77
+ # Reference:
78
+ # 3. Titled Surface Models
79
+ # in [3]
80
+ def geometric_factor
81
+ hourly_beam_radiation_on_tilted_surface / hourly_beam_radiation_on_horizontal_surface
82
+ end
83
+
84
+ # I[b,T]
85
+ def hourly_beam_radiation_on_tilted_surface
86
+ hourly_bean_radiation = bigdecimal_cos(degree_to_radian(@surface_tilt)) * bigdecimal_cos(degree_to_radian(@sun_zenith)) +
87
+ bigdecimal_sin(degree_to_radian(@surface_tilt)) * bigdecimal_sin(degree_to_radian(@sun_zenith)) * bigdecimal_cos(degree_to_radian(@sun_azimuth - @surface_azimuth))
88
+ [hourly_bean_radiation, BigDecimal('0')].max
89
+ end
90
+
91
+ # Ib
92
+ def hourly_beam_radiation_on_horizontal_surface
93
+ [bigdecimal_cos(degree_to_radian(@sun_zenith)), BigDecimal('0.01745')].max
94
+ end
95
+
96
+ # AI
97
+ # "defines the portion of the diffuse radiation to be treated as circumsolar with the remaining portion considered isotropic"[3]
98
+ def anisotropy_index
99
+ @direct_normal_irradiance / extraterrestrial_irradiance
100
+ end
101
+
102
+ def anisotropic_correction_factor
103
+ sin_cube_of_half_surface_tilt = (bigdecimal_sin(degree_to_radian(@surface_tilt/BigDecimal('2')))).power(3)
104
+ BigDecimal('1') + horizontal_brighting_correction_factor * sin_cube_of_half_surface_tilt
105
+ end
106
+
107
+ # f: Horizontal brighting correction factor
108
+ # page 13 of [3]
109
+ def horizontal_brighting_correction_factor
110
+ adjusted_global_horizontal_irradiance = @global_horizontal_irradiance < SMALL_VALUE_FOR_SKY_DIFFUSE_IRRADIANCE ? SMALL_VALUE_FOR_SKY_DIFFUSE_IRRADIANCE : @global_horizontal_irradiance
111
+ # In PVLIB_MatLab as well as PVLIB_Python, there is a following adjustment.
112
+ # However, all the negative numbers are already converted to SMALL_VALUE_FOR_SKY_DIFFUSE_IRRADIANCE in the above statement,
113
+ # the following statement doesn't do anything.
114
+ # I'm just writing it to remind that it is there in PVLIB_MatLab and PVLIB_Python.
115
+ # adjusted_global_horizontal_irradiance = @global_horizontal_irradiance < BigDecimal('0') ? BigDecimal('0') : @global_horizontal_irradiance
116
+ bigdecimal_sqrt(horizontal_beam_irradiance / adjusted_global_horizontal_irradiance)
117
+ end
118
+
119
+ # HB
120
+ def horizontal_beam_irradiance
121
+ calculated_horizontal_beam_irradiance = @direct_normal_irradiance * bigdecimal_cos(degree_to_radian(@sun_zenith))
122
+ calculated_horizontal_beam_irradiance < BigDecimal('0') ? BigDecimal('0') : calculated_horizontal_beam_irradiance
123
+ end
124
+
125
+ end
@@ -0,0 +1,111 @@
1
+ require 'bigdecimal'
2
+ require 'bigdecimal/math'
3
+
4
+ # This class uses Sandia PV Array Performance Model, which is based on:
5
+ # King, D. et al, 2004, "Sandia Photovoltaic Array Performance Model", SAND Report 3535, Sandia National Laboratories, Albuquerque, NM
6
+ # Other models are realized by subclasses.
7
+ class PvPerformanceCharacterization
8
+
9
+ REFERECE_SOLAR_IRRADIANCE = BigDecimal('1000') # E0 [W/m^2]
10
+ REFERECE_CELL_TEMPERATURE = BigDecimal('25') # T0 [ºC]
11
+ BOLTZMANN_CONSTANT = BigDecimal('1.38066E-23') # k [J/K]
12
+ ELEMENTARY_CHARGE = BigDecimal('1.60218E-19') # q [C(coulomb)]
13
+
14
+ def initialize(pv_module, absolute_air_mass, angle_of_incidence, beam_irradiance, ground_irradiance, sky_diffuse_irradiance, estimated_cell_temperature)
15
+ @pv_module = pv_module
16
+ @effective_solar_irradiance = empirical_effective_solar_irradiance(absolute_air_mass, angle_of_incidence, beam_irradiance, ground_irradiance, sky_diffuse_irradiance)
17
+ @estimated_cell_temperature = estimated_cell_temperature
18
+ end
19
+
20
+ # Isc
21
+ def short_circuit_current
22
+ @pv_module.short_circuit_current_0 * @effective_solar_irradiance * (1 + @pv_module.normalized_temperature_coefficient_for_short_circuit_current * (@estimated_cell_temperature - REFERECE_CELL_TEMPERATURE))
23
+ end
24
+
25
+ alias :isc :short_circuit_current
26
+
27
+ # Imp
28
+ def maximum_power_point_current
29
+ @pv_module.maximum_power_point_current_0 * (@pv_module.performance_coefficients[0] * @effective_solar_irradiance + @pv_module.performance_coefficients[1] * @effective_solar_irradiance.power(2)) *
30
+ (1 + @pv_module.normalized_temperature_coefficient_for_maximum_power_point_current * (@estimated_cell_temperature - REFERECE_CELL_TEMPERATURE))
31
+ end
32
+
33
+ alias :imp :maximum_power_point_current
34
+
35
+ # Voc
36
+ def open_circuit_voltage
37
+ @pv_module.open_circuit_voltage_0 + @pv_module.number_of_cells_in_series * thermal_voltage_per_cell * BigMath.log(@effective_solar_irradiance, 4) +
38
+ temperature_coefficient_for_open_circuit_voltage * (@estimated_cell_temperature - REFERECE_CELL_TEMPERATURE)
39
+ end
40
+
41
+ alias :voc :open_circuit_voltage
42
+
43
+ # Vmp
44
+ def maximum_power_point_voltage
45
+ @pv_module.maximum_power_point_voltage_0 + @pv_module.performance_coefficients[2] * @pv_module.number_of_cells_in_series * thermal_voltage_per_cell * BigMath.log(@effective_solar_irradiance, 4) +
46
+ @pv_module.performance_coefficients[3] * @pv_module.number_of_cells_in_series * (thermal_voltage_per_cell * BigMath.log(@effective_solar_irradiance, 4)).power(2) +
47
+ temperature_coefficient_for_maximum_power_point_voltage * (@estimated_cell_temperature - REFERECE_CELL_TEMPERATURE)
48
+ end
49
+
50
+ alias :vmp :maximum_power_point_voltage
51
+
52
+ # Ix
53
+ def fourth_point_current
54
+ @pv_module.fourth_point_current_0 * (@pv_module.performance_coefficients[4] * @effective_solar_irradiance + @pv_module.performance_coefficients[5] * @effective_solar_irradiance.power(2)) *
55
+ (1 + @pv_module.normalized_temperature_coefficient_for_short_circuit_current * (@estimated_cell_temperature - REFERECE_CELL_TEMPERATURE))
56
+ end
57
+
58
+ alias :ix :fourth_point_current
59
+
60
+ # Ixx
61
+ def fifth_point_current
62
+ @pv_module.fifth_point_current_0 * (@pv_module.performance_coefficients[6] * @effective_solar_irradiance + @pv_module.performance_coefficients[7] * @effective_solar_irradiance.power(2)) *
63
+ (1 + @pv_module.normalized_temperature_coefficient_for_maximum_power_point_current * (@estimated_cell_temperature - REFERECE_CELL_TEMPERATURE))
64
+ end
65
+
66
+ alias :ixx :fifth_point_current
67
+
68
+ private
69
+ # Ee
70
+ def empirical_effective_solar_irradiance(absolute_air_mass, angle_of_incidence, beam_irradiance, ground_irradiance, sky_diffuse_irradiance)
71
+ diffuse_irradiance = ground_irradiance + sky_diffuse_irradiance
72
+
73
+ empirical_solar_spectral_influence_on_short_circuit_current(absolute_air_mass) *
74
+ ((beam_irradiance * empirical_opitical_influence_on_short_circuit_current(angle_of_incidence) + @pv_module.diffuse_irradiance_factor * diffuse_irradiance)/REFERECE_SOLAR_IRRADIANCE)
75
+ end
76
+
77
+ # f1(AMa)
78
+ def empirical_solar_spectral_influence_on_short_circuit_current(absolute_air_mass)
79
+ @pv_module.air_mass_variation_polynomial_coefficients[4] +
80
+ @pv_module.air_mass_variation_polynomial_coefficients[3] * absolute_air_mass +
81
+ @pv_module.air_mass_variation_polynomial_coefficients[2] * absolute_air_mass.power(2) +
82
+ @pv_module.air_mass_variation_polynomial_coefficients[1] * absolute_air_mass.power(3) +
83
+ @pv_module.air_mass_variation_polynomial_coefficients[0] * absolute_air_mass.power(4)
84
+ end
85
+
86
+ # f2(AOI)
87
+ def empirical_opitical_influence_on_short_circuit_current(angle_of_incidence)
88
+ @pv_module.angle_of_incidence_polynominal_coefficients[5] +
89
+ @pv_module.angle_of_incidence_polynominal_coefficients[4] * angle_of_incidence +
90
+ @pv_module.angle_of_incidence_polynominal_coefficients[3] * angle_of_incidence.power(2) +
91
+ @pv_module.angle_of_incidence_polynominal_coefficients[2] * angle_of_incidence.power(3) +
92
+ @pv_module.angle_of_incidence_polynominal_coefficients[1] * angle_of_incidence.power(4) +
93
+ @pv_module.angle_of_incidence_polynominal_coefficients[0] * angle_of_incidence.power(5)
94
+ end
95
+
96
+ # delta(Tc)
97
+ def thermal_voltage_per_cell
98
+ @pv_module.diode_factor * BOLTZMANN_CONSTANT * (@estimated_cell_temperature + BigDecimal('273.15')) / ELEMENTARY_CHARGE
99
+ end
100
+
101
+ # BetaVoc
102
+ def temperature_coefficient_for_open_circuit_voltage
103
+ @pv_module.temperature_coefficient_for_open_circuit_voltage + @pv_module.irradiance_dependent_temperature_coefficient_for_open_circuit_voltage * (1-@effective_solar_irradiance)
104
+ end
105
+
106
+ # BetaVmp
107
+ def temperature_coefficient_for_maximum_power_point_voltage
108
+ @pv_module.temperature_coefficient_for_maximum_power_point_voltage + @pv_module.irradiance_dependent_temperature_coefficient_for_maximum_power_point_voltage * (1-@effective_solar_irradiance)
109
+ end
110
+
111
+ end
@@ -0,0 +1,26 @@
1
+ require 'bigdecimal'
2
+
3
+ # This class calculates cell and module temperatures using Sandia PV Array Performance Model, which is based on:
4
+ # King, D. et al, 2004, "Sandia Photovoltaic Array Performance Model", SAND Report 3535, Sandia National Laboratories, Albuquerque, NM
5
+ class PvTemperature
6
+ include CalculationHelper
7
+
8
+ def initialize(pv_module, solar_irradiance_incident_on_module_surface, reference_solar_irradiance, wind_speed, air_temperature)
9
+ @pv_module = pv_module
10
+ @solar_irradiance_incident_on_module_surface = solar_irradiance_incident_on_module_surface
11
+ @reference_solar_irradiance = reference_solar_irradiance
12
+ @wind_speed = wind_speed
13
+ @air_temperature = air_temperature
14
+ end
15
+
16
+ def cell_temperature
17
+ module_temperature +
18
+ (@solar_irradiance_incident_on_module_surface / @reference_solar_irradiance) * @pv_module.cell_module_back_surface_temperature_difference
19
+ end
20
+
21
+ def module_temperature
22
+ back_surface_module_temperature = @solar_irradiance_incident_on_module_surface * bigdecimal_exp(@pv_module.temperature_upper_limit_coefficient + @pv_module.wind_speed_decrease_rate_coefficient * @wind_speed) +
23
+ @air_temperature
24
+ end
25
+
26
+ end
@@ -0,0 +1,145 @@
1
+ require 'bigdecimal'
2
+
3
+ class SolarEphemeris
4
+ include CalculationHelper
5
+
6
+ def initialize(time, location, pressure: BigDecimal('101325'), temperature: BigDecimal('12'))
7
+ @time = time
8
+ @location = location
9
+ @pressure = pressure
10
+ @temperature = temperature
11
+
12
+ utc_offset_in_hours = BigDecimal(@time.utc_offset.to_s) / BigDecimal('3600')
13
+ dec_hours = BigDecimal(@time.hour.to_s) + BigDecimal(@time.min.to_s) / BigDecimal('60') + BigDecimal(@time.sec.to_s) / BigDecimal('3600')
14
+ @universal_date = BigDecimal(@time.yday.to_s) + ((dec_hours - utc_offset_in_hours) / BigDecimal('24')).floor
15
+ @universal_hour = (dec_hours - utc_offset_in_hours).modulo(BigDecimal('24'))
16
+ end
17
+
18
+ def sun_azimuth
19
+ calculated_sun_azimuth = radian_to_degree(bigdecimal_atan2(BigDecimal('-1') * bigdecimal_sin(degree_to_radian(hour_angle)),
20
+ bigdecimal_cos(degree_to_radian(@location.latitude)) * bigdecimal_tan(degree_to_radian(declination)) - bigdecimal_sin(degree_to_radian(@location.latitude)) * bigdecimal_cos(degree_to_radian(hour_angle))))
21
+ sun_azimuth = calculated_sun_azimuth < BigDecimal('0') ? calculated_sun_azimuth + BigDecimal('360') : calculated_sun_azimuth
22
+ sun_azimuth
23
+ end
24
+
25
+ def sun_elevation
26
+ # Caching because it's used several times for the calculation of apparent_sun_elevation
27
+ @sun_elevation ||= radian_to_degree(bigdecimal_asin(bigdecimal_cos(degree_to_radian(@location.latitude)) * bigdecimal_cos(degree_to_radian(declination)) * bigdecimal_cos(degree_to_radian(hour_angle)) +
28
+ bigdecimal_sin(degree_to_radian(@location.latitude)) * bigdecimal_sin(degree_to_radian(declination))))
29
+ end
30
+
31
+ def apparent_sun_elevation
32
+ sun_elevation + reflaction
33
+ end
34
+
35
+ def solar_time
36
+ # BigDecimal#sign returns 2 for positive value, -2 for negative value, 1 for 0 and -1 for -0.
37
+ # Hence, decided to use the following statement:
38
+ hour_angle_sign = hour_angle < BigDecimal('0') ? BigDecimal('-1') : BigDecimal('1')
39
+
40
+ adjusted_hour_angle = hour_angle.abs > BigDecimal('180') ? hour_angle - (BigDecimal('360') * hour_angle_sign) : hour_angle
41
+
42
+ (BigDecimal('180') + adjusted_hour_angle) / BigDecimal('15')
43
+ end
44
+
45
+ private
46
+ def hour_angle
47
+ loc_ast - rt_ascen
48
+ end
49
+
50
+ def declination
51
+ radian_to_degree(bigdecimal_asin(bigdecimal_sin(degree_to_radian(obliquity)) * bigdecimal_sin(degree_to_radian(ec_lon))))
52
+ end
53
+
54
+ # For hour_angle calculation
55
+ # LocAST in PVLIB_MatLab. Couldn't find what it stands for.
56
+ # Also for other variables names, couldn't find what they stand for.
57
+ def loc_ast
58
+ t = e_zero / 36525
59
+
60
+ gmst0 = BigDecimal('6') / BigDecimal('24') + BigDecimal('38') / BigDecimal('1440') +
61
+ (BigDecimal('45.836') + BigDecimal('8640184.542') * t + BigDecimal('0.0929') * t.power(2) ) / BigDecimal('86400')
62
+ gmst0 = BigDecimal('360') * (gmst0 - gmst0.floor)
63
+ gmsti = (gmst0 + BigDecimal('360') * (BigDecimal('1.0027379093') * @universal_hour / BigDecimal('24'))).modulo(BigDecimal('360'))
64
+
65
+ (BigDecimal('360') + gmsti + @location.longitude).modulo(BigDecimal('360'))
66
+ end
67
+
68
+ # For hour_angle calculation
69
+ # RtAscen in PVLIB_MatLab. Couldn't find what it stands for.
70
+ # Also for other variables names, couldn't find what they stand for.
71
+ def rt_ascen
72
+ radian_to_degree(bigdecimal_atan2(bigdecimal_cos(degree_to_radian(obliquity)) * bigdecimal_sin(degree_to_radian(ec_lon)), bigdecimal_cos(degree_to_radian(ec_lon))))
73
+ end
74
+
75
+ def obliquity
76
+ BigDecimal('23.452294') - BigDecimal('0.0130125') * t1 - BigDecimal('0.00000164') * t1.power(2) + BigDecimal('0.000000503') * t1.power(3)
77
+ end
78
+
79
+ def ec_lon
80
+ (ml_perigee + true_anom).modulo(BigDecimal('360')) - abber
81
+ end
82
+
83
+ def ml_perigee
84
+ BigDecimal('281.22083') + BigDecimal('0.0000470684') * epoch_date + BigDecimal('0.000453') * t1.power(2) + BigDecimal('0.000003') * t1.power(3)
85
+ end
86
+
87
+ def true_anom
88
+ # Cannot find a proper name for these values:
89
+ temp1 = ((BigDecimal('1') + eccen) / (BigDecimal('1') - eccen)).power(0.5) * bigdecimal_tan(degree_to_radian(eccen_anom / BigDecimal('2')))
90
+ temp2 = radian_to_degree(bigdecimal_atan2(temp1, BigDecimal('1')))
91
+ BigDecimal('2') * temp2.modulo(BigDecimal('360'))
92
+ end
93
+
94
+ def abber
95
+ BigDecimal('20') / BigDecimal('3600')
96
+ end
97
+
98
+ def eccen
99
+ BigDecimal('0.01675104') - BigDecimal('0.0000418') * t1 - BigDecimal('0.000000126') * t1.power(2)
100
+ end
101
+
102
+ def eccen_anom
103
+ mean_anom = (BigDecimal('358.47583') + BigDecimal('0.985600267') * epoch_date - BigDecimal('0.00015') * t1.power(2) - BigDecimal('0.000003') * t1.power(3)).modulo(BigDecimal('360'))
104
+
105
+ eccen_anom = mean_anom
106
+ e = BigDecimal('0')
107
+ while (mean_anom - e).abs > BigDecimal('0.0001')
108
+ e = eccen_anom
109
+ eccen_anom = mean_anom + radian_to_degree(eccen) * bigdecimal_sin(degree_to_radian(e))
110
+ end
111
+ eccen_anom
112
+ end
113
+
114
+ def t1
115
+ epoch_date / BigDecimal('36525')
116
+ end
117
+
118
+ def epoch_date
119
+ e_zero + @universal_hour / BigDecimal('24')
120
+ end
121
+
122
+ def e_zero
123
+ year_starting_from_1900 = BigDecimal(@time.year.to_s) - BigDecimal('1900')
124
+ year_begin = BigDecimal('365') * year_starting_from_1900 + ((year_starting_from_1900 - BigDecimal('1')) / BigDecimal('4')).floor - BigDecimal('0.5')
125
+ year_begin + @universal_date
126
+ end
127
+
128
+ # For apparent_sun_elevation calculation
129
+ def reflaction
130
+ tangent_of_elevation = bigdecimal_tan(degree_to_radian(sun_elevation))
131
+
132
+ raw_reflaction = if sun_elevation > BigDecimal('5') && sun_elevation <= BigDecimal('85')
133
+ BigDecimal('58.1') / tangent_of_elevation - BigDecimal('0.07') / (tangent_of_elevation.power(3)) + BigDecimal('.000086') / (tangent_of_elevation.power(5))
134
+ elsif sun_elevation > BigDecimal('-0.575') && sun_elevation <= BigDecimal('5')
135
+ sun_elevation * (BigDecimal('-518.2') + sun_elevation * (BigDecimal('103.4') + sun_elevation * (BigDecimal('-12.79') + sun_elevation * BigDecimal('0.711')))) + BigDecimal('1735')
136
+ elsif sun_elevation > BigDecimal('-1') && sun_elevation <= BigDecimal('-0.575')
137
+ BigDecimal('-20.774') / tangent_of_elevation
138
+ else
139
+ BigDecimal('0')
140
+ end
141
+
142
+ raw_reflaction * (BigDecimal('283') / (BigDecimal('273') + @temperature)) * @pressure / BigDecimal('101325') / BigDecimal('3600')
143
+ end
144
+
145
+ end
@@ -0,0 +1,3 @@
1
+ module PvlibRuby
2
+ VERSION = "0.1.0"
3
+ end
data/lib/PVLIB_Ruby.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'pry'
2
+ require 'pvlib_ruby/version'
3
+ require 'pvlib_ruby/models/active_csv'
4
+ require 'pvlib_ruby/models/pv_module'
5
+ require 'pvlib_ruby/models/inverter'
6
+ require 'pvlib_ruby/pv_performance_characterization'
7
+ require 'pvlib_ruby/dc_to_ac_conversion'
8
+ require 'pvlib_ruby/calculation_helper'
9
+ require 'pvlib_ruby/air_mass'
10
+ require 'pvlib_ruby/pv_temperature'
11
+ require 'pvlib_ruby/plain_of_array_irradiance'
12
+ require 'pvlib_ruby/models/location'
13
+ require 'pvlib_ruby/solar_ephemeris'
14
+
15
+ module PvlibRuby
16
+ # Your code goes here...
17
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: PVLIB_Ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tadatoshi Takahashi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: PVLIB_MatLab translated to Ruby code in Object-Oriented way.
70
+ email:
71
+ - tadatoshi@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".ruby-gemset"
79
+ - ".ruby-version"
80
+ - ".travis.yml"
81
+ - CODE_OF_CONDUCT.md
82
+ - Gemfile
83
+ - LICENSE.txt
84
+ - PVLIB_Ruby.gemspec
85
+ - README.md
86
+ - Rakefile
87
+ - bin/console
88
+ - bin/setup
89
+ - examples/data/sandia_inverter_example.csv
90
+ - examples/data/sandia_module_example.csv
91
+ - examples/pv_power_generation_example.rb
92
+ - lib/PVLIB_Ruby.rb
93
+ - lib/PVLIB_Ruby/air_mass.rb
94
+ - lib/PVLIB_Ruby/calculation_helper.rb
95
+ - lib/PVLIB_Ruby/dc_to_ac_conversion.rb
96
+ - lib/PVLIB_Ruby/models/active_csv.rb
97
+ - lib/PVLIB_Ruby/models/inverter.rb
98
+ - lib/PVLIB_Ruby/models/location.rb
99
+ - lib/PVLIB_Ruby/models/pv_module.rb
100
+ - lib/PVLIB_Ruby/plain_of_array_irradiance.rb
101
+ - lib/PVLIB_Ruby/pv_performance_characterization.rb
102
+ - lib/PVLIB_Ruby/pv_temperature.rb
103
+ - lib/PVLIB_Ruby/solar_ephemeris.rb
104
+ - lib/PVLIB_Ruby/version.rb
105
+ homepage: https://github.com/tadatoshi/PVLIB_Ruby
106
+ licenses:
107
+ - MIT
108
+ metadata:
109
+ allowed_push_host: https://rubygems.org
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.4.7
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: PVLIB written in Ruby.
130
+ test_files: []