@@ -20,10 +20,11 @@ is well-tested procedural code that implements PV system models.
20
20
pvlib-python also provides a collection of classes for users
21
21
that prefer object-oriented programming.
22
22
These classes can help users keep track of data in a more organized way,
23
- and can help to simplify the modeling process.
24
- The classes do not add any functionality beyond the procedural code.
25
- Most of the object methods are simple wrappers around the
26
- corresponding procedural code.
23
+ provide some "smart" functions with more flexible inputs,
24
+ and simplify the modeling process for common situations.
25
+ The classes do not add any algorithms beyond what's available
26
+ in the procedural code, and most of the object methods
27
+ are simple wrappers around the corresponding procedural code.
27
28
28
29
Let's use each of these pvlib modeling paradigms
29
30
to calculate the yearly energy yield for a given hardware
@@ -33,23 +34,31 @@ configuration at a handful of sites listed below.
33
34
34
35
import pandas as pd
35
36
import matplotlib.pyplot as plt
37
+
38
+ # seaborn makes the plots look nicer
36
39
import seaborn as sns
37
40
sns.set_color_codes()
38
41
39
42
times = pd.DatetimeIndex(start = ' 2015' , end = ' 2016' , freq = ' 1h' )
40
43
41
44
# very approximate
42
- coordinates = [(30 , - 110 , ' Tucson' ),
43
- (35 , - 105 , ' Albuquerque' ),
44
- (40 , - 120 , ' San Francisco' ),
45
- (50 , 10 , ' Berlin' )]
45
+ # latitude, longitude, name, altitude
46
+ coordinates = [(30 , - 110 , ' Tucson' , 700 ),
47
+ (35 , - 105 , ' Albuquerque' , 1500 ),
48
+ (40 , - 120 , ' San Francisco' , 10 ),
49
+ (50 , 10 , ' Berlin' , 34 )]
46
50
47
51
import pvlib
48
52
53
+ # get the module and inverter specifications from SAM
49
54
sandia_modules = pvlib.pvsystem.retrieve_sam(' SandiaMod' )
50
55
sapm_inverters = pvlib.pvsystem.retrieve_sam(' sandiainverter' )
51
56
module = sandia_modules[' Canadian_Solar_CS5P_220M___2009_' ]
52
57
inverter = sapm_inverters[' ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_' ]
58
+
59
+ # specify constant ambient air temp and wind for simplicity
60
+ temp_air = 20
61
+ wind_speed = 0
53
62
54
63
55
64
Procedural
@@ -67,13 +76,15 @@ to accomplish our system modeling goal:
67
76
' surface_azimuth' : 180 }
68
77
69
78
energies = {}
70
- for latitude, longitude, name in coordinates:
79
+ for latitude, longitude, name, altitude in coordinates:
71
80
system[' surface_tilt' ] = latitude
72
- cs = pvlib.clearsky.ineichen(times, latitude, longitude)
81
+ cs = pvlib.clearsky.ineichen(times, latitude, longitude, altitude = altitude )
73
82
solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
74
83
dni_extra = pvlib.irradiance.extraradiation(times)
75
84
dni_extra = pd.Series(dni_extra, index = times)
76
85
airmass = pvlib.atmosphere.relativeairmass(solpos[' apparent_zenith' ])
86
+ pressure = pvlib.atmosphere.alt2pres(altitude)
87
+ am_abs = pvlib.atmosphere.absoluteairmass(airmass, pressure)
77
88
aoi = pvlib.irradiance.aoi(system[' surface_tilt' ], system[' surface_azimuth' ],
78
89
solpos[' apparent_zenith' ], solpos[' azimuth' ])
79
90
total_irrad = pvlib.irradiance.total_irrad(system[' surface_tilt' ],
@@ -83,10 +94,11 @@ to accomplish our system modeling goal:
83
94
cs[' dni' ], cs[' ghi' ], cs[' dhi' ],
84
95
dni_extra = dni_extra,
85
96
model = ' haydavies' )
86
- temps = pvlib.pvsystem.sapm_celltemp(total_irrad[' poa_global' ], 0 , 20 )
97
+ temps = pvlib.pvsystem.sapm_celltemp(total_irrad[' poa_global' ],
98
+ wind_speed, temp_air)
87
99
dc = pvlib.pvsystem.sapm(module, total_irrad[' poa_direct' ],
88
100
total_irrad[' poa_diffuse' ], temps[' temp_cell' ],
89
- airmass , aoi)
101
+ am_abs , aoi)
90
102
ac = pvlib.pvsystem.snlinverter(inverter, dc[' v_mp' ], dc[' p_mp' ])
91
103
annual_energy = ac.sum()
92
104
energies[name] = annual_energy
@@ -105,11 +117,11 @@ Object oriented (Location, PVSystem, ModelChain)
105
117
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
106
118
107
119
The first object oriented paradigm uses a model where
108
- a :class: `PVSystem < pvlib.pvsystem.PVSystem> ` object represents an
120
+ a :py: class: `~ pvlib.pvsystem.PVSystem ` object represents an
109
121
assembled collection of modules, inverters, etc.,
110
- a :class: `Location < pvlib.location.Location> ` object represents a
122
+ a :py: class: `~ pvlib.location.Location ` object represents a
111
123
particular place on the planet,
112
- and a :class: `ModelChain < pvlib.modelchain.ModelChain> ` object describes
124
+ and a :py: class: `~ pvlib.modelchain.ModelChain ` object describes
113
125
the modeling chain used to calculate PV output at that Location.
114
126
This can be a useful paradigm if you prefer to think about
115
127
the PV system and its location as separate concepts or if
@@ -118,9 +130,9 @@ It can also be helpful if you make extensive use of Location-specific
118
130
methods for other calculations.
119
131
120
132
The following code demonstrates how to use
121
- :class: `Location < pvlib.location.Location> `,
122
- :class: `PVSystem < pvlib.pvsystem.PVSystem> `, and
123
- :class: `ModelChain < pvlib.modelchain.ModelChain> `
133
+ :py: class: `~ pvlib.location.Location `,
134
+ :py: class: `~ pvlib.pvsystem.PVSystem `, and
135
+ :py: class: `~ pvlib.modelchain.ModelChain `
124
136
objects to accomplish our system modeling goal:
125
137
126
138
.. ipython :: python
@@ -129,57 +141,86 @@ objects to accomplish our system modeling goal:
129
141
from pvlib.location import Location
130
142
from pvlib.modelchain import ModelChain
131
143
132
- system = PVSystem(module, inverter, ** other_params)
144
+ system = PVSystem(module_parameters = module,
145
+ inverter_parameters = inverter)
133
146
134
147
energies = {}
135
- for latitude, longitude, name in coordinates:
136
- location = Location(latitude, longitude)
137
- # not yet clear what, exactly, goes into ModelChain(s)
138
- mc = ModelChain(system, location, times,
139
- ' south_at_latitude' , ** other_modelchain_params)
140
- output = mc.run_model()
141
- annual_energy = output[' power' ].sum()
148
+ for latitude, longitude, name, altitude in coordinates:
149
+ location = Location(latitude, longitude, name = name, altitude = altitude)
150
+ # very experimental
151
+ mc = ModelChain(system, location,
152
+ orientation_strategy = ' south_at_latitude_tilt' )
153
+ # optional parameters for irradiance and weather data
154
+ dc, ac = mc.run_model(times)
155
+ annual_energy = ac.sum()
142
156
energies[name] = annual_energy
143
157
144
- # energies = pd.DataFrame(energies)
145
- # energies.plot()
158
+ energies = pd.Series(energies)
159
+
160
+ # based on the parameters specified above, these are in W*hrs
161
+ print (energies.round(0 ))
162
+
163
+ energies.plot(kind = ' bar' , rot = 0 )
164
+ @savefig modelchain -energies.png width=6in
165
+ plt.ylabel(' Yearly energy yield (W hr)' )
146
166
147
167
148
168
Object oriented (LocalizedPVSystem)
149
169
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
150
170
151
171
The second object oriented paradigm uses a model where a
152
- :class: `LocalizedPVSystem < pvlib.pvsystem.LocalizedPVSystem> ` represents a
172
+ :py: class: `~ pvlib.pvsystem.LocalizedPVSystem ` represents a
153
173
PV system at a particular place on the planet.
154
174
This can be a useful paradigm if you're thinking about
155
175
a power plant that already exists.
156
176
157
177
The following code demonstrates how to use a
158
- :class: `LocalizedPVSystem < pvlib.pvsystem.LocalizedPVSystem> `
178
+ :py: class: `~ pvlib.pvsystem.LocalizedPVSystem `
159
179
object to accomplish our modeling goal:
160
180
161
181
.. ipython :: python
162
182
163
- from pvlib.pvsystem import PVSystem, LocalizedPVSystem
164
-
165
- module =
166
- inverter =
167
- other_system_params = {} # sometime helpful to break apart
168
- base_system = PVSystem(module, inverter, ** other_system_params)
183
+ from pvlib.pvsystem import LocalizedPVSystem
169
184
170
185
energies = {}
171
- for latitude, longitude, name in coordinates:
172
- localized_system = base_system.localize(latitude, longitude, name = name)
173
- localized_system.surface_tilt = latitude
174
- cs = localized_system.get_clearsky(times)
175
- solpos = localized_system.get_solarposition(times)
176
- total_irrad = localized_system.get_irradiance(times, ** solpos, ** cs)
177
- power = localized_system.get_power(stuff)
178
- annual_energy = power.sum()
186
+ for latitude, longitude, name, altitude in coordinates:
187
+ localized_system = LocalizedPVSystem(module_parameters = module,
188
+ inverter_parameters = inverter,
189
+ surface_tilt = latitude,
190
+ surface_azimuth = 180 ,
191
+ latitude = latitude,
192
+ longitude = longitude,
193
+ name = name,
194
+ altitude = altitude)
195
+ clearsky = localized_system.get_clearsky(times)
196
+ solar_position = localized_system.get_solarposition(times)
197
+ total_irrad = localized_system.get_irradiance(solar_position[' apparent_zenith' ],
198
+ solar_position[' azimuth' ],
199
+ clearsky[' dni' ],
200
+ clearsky[' ghi' ],
201
+ clearsky[' dhi' ])
202
+ temps = localized_system.sapm_celltemp(total_irrad[' poa_global' ],
203
+ wind_speed, temp_air)
204
+ aoi = localized_system.get_aoi(solar_position[' apparent_zenith' ],
205
+ solar_position[' azimuth' ])
206
+ airmass = localized_system.get_airmass(solar_position = solar_position)
207
+ dc = localized_system.sapm(total_irrad[' poa_direct' ],
208
+ total_irrad[' poa_diffuse' ],
209
+ temps[' temp_cell' ],
210
+ airmass[' airmass_absolute' ],
211
+ aoi)
212
+ ac = localized_system.snlinverter(dc[' v_mp' ], dc[' p_mp' ])
213
+ annual_energy = ac.sum()
179
214
energies[name] = annual_energy
180
215
181
- # energies = pd.DataFrame(energies)
182
- # energies.plot()
216
+ energies = pd.Series(energies)
217
+
218
+ # based on the parameters specified above, these are in W*hrs
219
+ print (energies.round(0 ))
220
+
221
+ energies.plot(kind = ' bar' , rot = 0 )
222
+ @savefig localized -pvsystem-energies.png width=6in
223
+ plt.ylabel(' Yearly energy yield (W hr)' )
183
224
184
225
185
226
User extensions
0 commit comments