0
# Spectral Modeling
1
2
Model spectral irradiance distribution and calculate spectral mismatch factors for photovoltaic systems. Comprehensive tools for spectral analysis, correction factors, and performance modeling under varying atmospheric conditions.
3
4
## Capabilities
5
6
### Spectral Irradiance Models
7
8
Calculate detailed spectral irradiance distribution across wavelengths.
9
10
```python { .api }
11
def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
12
surface_pressure, relative_humidity, precipitable_water,
13
ozone=0.31, nitrogen_dioxide=0.0002, aerosol_turbidity_500nm=0.1,
14
dayofyear=1, wavelengths=None):
15
"""
16
Calculate spectral irradiance using SPECTRL2 model.
17
18
Parameters:
19
- apparent_zenith: numeric, apparent solar zenith angle in degrees
20
- aoi: numeric, angle of incidence on surface in degrees
21
- surface_tilt: numeric, surface tilt angle in degrees
22
- ground_albedo: numeric, ground reflectance (0-1)
23
- surface_pressure: numeric, surface pressure in millibars
24
- relative_humidity: numeric, relative humidity (0-100)
25
- precipitable_water: numeric, precipitable water in cm
26
- ozone: numeric, ozone column thickness in atm-cm
27
- nitrogen_dioxide: numeric, nitrogen dioxide column in atm-cm
28
- aerosol_turbidity_500nm: numeric, aerosol optical depth at 500nm
29
- dayofyear: int, day of year (1-366)
30
- wavelengths: array-like, wavelengths in nm (280-4000)
31
32
Returns:
33
tuple: (wavelengths, total_irradiance, direct_irradiance, diffuse_irradiance,
34
global_horizontal_irradiance, direct_normal_irradiance)
35
"""
36
37
def get_reference_spectra(wavelengths=None, standard="ASTM G173-03"):
38
"""
39
Get standard reference solar spectra.
40
41
Parameters:
42
- wavelengths: array-like, wavelengths in nm for interpolation
43
- standard: str, reference standard ('ASTM G173-03', 'AM15G', 'AM15D', 'AM0')
44
45
Returns:
46
pandas.DataFrame with wavelengths and spectral irradiance columns
47
"""
48
49
def average_photon_energy(spectra):
50
"""
51
Calculate average photon energy of spectrum.
52
53
Parameters:
54
- spectra: pandas.DataFrame with wavelengths (nm) and irradiance columns
55
56
Returns:
57
pandas.Series with average photon energies in eV
58
"""
59
```
60
61
### Spectral Mismatch Factors
62
63
Calculate correction factors for spectral mismatch between reference and field conditions.
64
65
```python { .api }
66
def spectral_factor_sapm(airmass_absolute, module):
67
"""
68
Calculate SAPM spectral correction factor.
69
70
Parameters:
71
- airmass_absolute: numeric, absolute airmass
72
- module: dict, module parameters containing spectral coefficients
73
Required keys: 'a0', 'a1', 'a2', 'a3', 'a4'
74
75
Returns:
76
numeric, spectral correction factor (typically 0.8-1.2)
77
"""
78
79
def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
80
module_type, coefficients=None):
81
"""
82
Calculate First Solar spectral correction factor.
83
84
Parameters:
85
- precipitable_water: numeric, precipitable water in cm
86
- airmass_absolute: numeric, absolute airmass
87
- module_type: str, module technology ('cdte', 'multijunction', 'cigs')
88
- coefficients: dict, custom spectral coefficients
89
90
Returns:
91
numeric, spectral correction factor
92
"""
93
94
def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500,
95
alpha=1.14, module_type='csi'):
96
"""
97
Calculate Caballero spectral correction factor.
98
99
Parameters:
100
- precipitable_water: numeric, precipitable water in cm
101
- airmass_absolute: numeric, absolute airmass
102
- aod500: numeric, aerosol optical depth at 500nm
103
- alpha: numeric, Angstrom alpha parameter
104
- module_type: str, module technology ('csi', 'asi', 'cdte', 'pc')
105
106
Returns:
107
numeric, spectral correction factor
108
"""
109
110
def spectral_factor_pvspec(airmass_absolute, clearsky_index,
111
module_type='monosi',
112
coefficients=None):
113
"""
114
Calculate PVSPEC spectral correction factor.
115
116
Parameters:
117
- airmass_absolute: numeric, absolute airmass (1.0-10.0)
118
- clearsky_index: numeric, clearsky index (0.0-1.0)
119
- module_type: str, module type ('monosi', 'xsi', 'cdte', 'asi', 'cigs', 'perovskite')
120
- coefficients: dict, custom model coefficients
121
122
Returns:
123
numeric, spectral correction factor
124
"""
125
126
def spectral_factor_jrc(airmass, clearsky_index, module_type=None,
127
pwat=None, am_coeff=None):
128
"""
129
Calculate JRC spectral correction factor.
130
131
Parameters:
132
- airmass: numeric, airmass (1.0-10.0)
133
- clearsky_index: numeric, clearsky index (0.0-1.0)
134
- module_type: str, technology type ('csi', 'asi', 'cdte', 'cigs')
135
- pwat: numeric, precipitable water in cm (optional)
136
- am_coeff: dict, custom airmass coefficients
137
138
Returns:
139
numeric, spectral correction factor
140
"""
141
142
def calc_spectral_mismatch_field(sr, e_sun, e_ref=None):
143
"""
144
Calculate spectral mismatch for field conditions.
145
146
Parameters:
147
- sr: pandas.Series, spectral response vs wavelength (nm)
148
- e_sun: pandas.Series, solar spectral irradiance vs wavelength
149
- e_ref: pandas.Series, reference spectral irradiance (default AM1.5G)
150
151
Returns:
152
numeric, spectral mismatch factor
153
"""
154
```
155
156
### Spectral Response Functions
157
158
Work with photovoltaic device spectral response characteristics.
159
160
```python { .api }
161
def get_example_spectral_response(wavelength=None):
162
"""
163
Get example spectral response functions for common PV technologies.
164
165
Parameters:
166
- wavelength: array-like, wavelengths in nm for interpolation
167
168
Returns:
169
dict with technology names as keys and spectral response arrays as values
170
Technologies: 'si_c', 'si_pc', 'cdte', 'cigs', 'asi', 'gaas', 'organic'
171
"""
172
173
def sr_to_qe(sr, wavelength=None, normalize=False):
174
"""
175
Convert spectral response to quantum efficiency.
176
177
Parameters:
178
- sr: pandas.Series, spectral response (A/W) vs wavelength
179
- wavelength: array-like, wavelengths in nm (default from sr index)
180
- normalize: bool, normalize QE to peak value
181
182
Returns:
183
pandas.Series, quantum efficiency vs wavelength
184
"""
185
186
def qe_to_sr(qe, wavelength=None, normalize=False):
187
"""
188
Convert quantum efficiency to spectral response.
189
190
Parameters:
191
- qe: pandas.Series, quantum efficiency vs wavelength
192
- wavelength: array-like, wavelengths in nm (default from qe index)
193
- normalize: bool, normalize SR to peak value
194
195
Returns:
196
pandas.Series, spectral response (A/W) vs wavelength
197
"""
198
```
199
200
### Advanced Spectral Analysis
201
202
Detailed spectral analysis tools for research and development.
203
204
```python { .api }
205
def spectral_factor_photovoltaic(airmass_absolute, precipitable_water,
206
aerosol_optical_depth, spectral_response,
207
wavelengths=None):
208
"""
209
Calculate spectral correction factor using custom spectral response.
210
211
Parameters:
212
- airmass_absolute: numeric, absolute airmass
213
- precipitable_water: numeric, precipitable water in cm
214
- aerosol_optical_depth: numeric, aerosol optical depth at 500nm
215
- spectral_response: array-like, device spectral response
216
- wavelengths: array-like, corresponding wavelengths in nm
217
218
Returns:
219
numeric, spectral correction factor
220
"""
221
222
def integrated_spectral_response(spectral_irradiance, spectral_response,
223
wavelengths=None):
224
"""
225
Calculate integrated spectral response for given irradiance spectrum.
226
227
Parameters:
228
- spectral_irradiance: array-like, spectral irradiance (W/m²/nm)
229
- spectral_response: array-like, device spectral response (A/W or QE)
230
- wavelengths: array-like, wavelengths in nm
231
232
Returns:
233
numeric, integrated response
234
"""
235
236
def bandwidth_utilization_factor(spectral_response, reference_spectrum,
237
field_spectrum, wavelengths=None):
238
"""
239
Calculate bandwidth utilization factor comparing field to reference conditions.
240
241
Parameters:
242
- spectral_response: array-like, device spectral response
243
- reference_spectrum: array-like, reference spectral irradiance
244
- field_spectrum: array-like, field spectral irradiance
245
- wavelengths: array-like, wavelengths in nm
246
247
Returns:
248
numeric, bandwidth utilization factor
249
"""
250
```
251
252
## Usage Examples
253
254
### Basic Spectral Correction Factors
255
256
```python
257
import pvlib
258
from pvlib import spectrum, atmosphere, solarposition
259
import pandas as pd
260
import numpy as np
261
import matplotlib.pyplot as plt
262
263
# Location and time
264
lat, lon = 37.8756, -122.2441 # Berkeley, CA
265
times = pd.date_range('2023-06-21 06:00', '2023-06-21 18:00',
266
freq='H', tz='US/Pacific')
267
268
# Calculate solar position and atmospheric parameters
269
solar_pos = solarposition.get_solarposition(times, lat, lon)
270
airmass_rel = atmosphere.get_relative_airmass(solar_pos['zenith'])
271
airmass_abs = atmosphere.get_absolute_airmass(airmass_rel)
272
273
# Atmospheric conditions
274
precipitable_water = 2.5 # cm
275
aod500 = 0.15 # aerosol optical depth at 500nm
276
277
# Calculate spectral correction factors for different PV technologies
278
technologies = {
279
'c-Si': {'type': 'csi', 'sapm_params': {'a0': 0.928, 'a1': 0.068, 'a2': -0.0077, 'a3': 0.0001, 'a4': -0.000002}},
280
'CdTe': {'type': 'cdte', 'sapm_params': {'a0': 0.87, 'a1': 0.122, 'a2': -0.0047, 'a3': -0.000085, 'a4': 0.0000020}},
281
'a-Si': {'type': 'asi', 'sapm_params': {'a0': 1.12, 'a1': -0.047, 'a2': -0.0085, 'a3': 0.000047, 'a4': 0.0000024}}
282
}
283
284
spectral_factors = {}
285
286
for tech_name, params in technologies.items():
287
# SAPM spectral factor
288
sapm_factor = spectrum.spectral_factor_sapm(airmass_abs, params['samp_params'])
289
290
# First Solar factor (for CdTe)
291
if tech_name == 'CdTe':
292
fs_factor = spectrum.spectral_factor_firstsolar(
293
precipitable_water, airmass_abs, 'cdte'
294
)
295
else:
296
fs_factor = None
297
298
# Caballero factor
299
caballero_factor = spectrum.spectral_factor_caballero(
300
precipitable_water, airmass_abs, aod500, module_type=params['type']
301
)
302
303
spectral_factors[tech_name] = {
304
'sapm': samp_factor,
305
'firstsolar': fs_factor,
306
'caballero': caballero_factor
307
}
308
309
# Plot spectral correction factors throughout the day
310
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
311
312
# SAPM factors
313
for tech in technologies.keys():
314
axes[0, 0].plot(times, spectral_factors[tech]['sapm'],
315
label=tech, marker='o', linewidth=2)
316
axes[0, 0].set_title('SAPM Spectral Correction Factors')
317
axes[0, 0].set_ylabel('Spectral Factor')
318
axes[0, 0].legend()
319
axes[0, 0].grid(True)
320
321
# Caballero factors
322
for tech in technologies.keys():
323
axes[0, 1].plot(times, spectral_factors[tech]['caballero'],
324
label=tech, marker='s', linewidth=2)
325
axes[0, 1].set_title('Caballero Spectral Correction Factors')
326
axes[0, 1].set_ylabel('Spectral Factor')
327
axes[0, 1].legend()
328
axes[0, 1].grid(True)
329
330
# Airmass variation
331
axes[1, 0].plot(times, airmass_abs, 'ko-', linewidth=2)
332
axes[1, 0].set_title('Absolute Airmass')
333
axes[1, 0].set_ylabel('Airmass')
334
axes[1, 0].grid(True)
335
336
# Solar elevation
337
axes[1, 1].plot(times, 90 - solar_pos['zenith'], 'ro-', linewidth=2)
338
axes[1, 1].set_title('Solar Elevation Angle')
339
axes[1, 1].set_ylabel('Elevation (degrees)')
340
axes[1, 1].set_xlabel('Time')
341
axes[1, 1].grid(True)
342
343
plt.tight_layout()
344
plt.show()
345
346
# Print summary statistics
347
print("Daily Average Spectral Correction Factors:")
348
for tech in technologies.keys():
349
sapm_avg = np.mean(spectral_factors[tech]['samp'])
350
caballero_avg = np.mean(spectral_factors[tech]['caballero'])
351
print(f"{tech:4s}: SAPM = {sapm_avg:.3f}, Caballero = {caballero_avg:.3f}")
352
```
353
354
### Detailed Spectral Irradiance Modeling
355
356
```python
357
import pvlib
358
from pvlib import spectrum, atmosphere, solarposition, clearsky
359
import pandas as pd
360
import numpy as np
361
import matplotlib.pyplot as plt
362
363
# Location and conditions
364
lat, lon = 40.0150, -105.2705 # Boulder, CO
365
elevation = 1655 # meters
366
time = pd.Timestamp('2023-06-21 12:00:00', tz='US/Mountain')
367
368
# Solar position
369
solar_pos = solarposition.get_solarposition(time, lat, lon)
370
zenith = solar_pos['zenith'].iloc[0]
371
azimuth = solar_pos['azimuth'].iloc[0]
372
373
# Atmospheric conditions
374
surface_pressure = atmosphere.alt2pres(elevation)
375
precipitable_water = 1.5 # cm
376
ozone = 0.31 # atm-cm
377
aod500 = 0.1 # aerosol optical depth
378
relative_humidity = 45 # percent
379
380
# Surface parameters
381
surface_tilt = 30 # degrees
382
surface_azimuth = 180 # degrees (south-facing)
383
ground_albedo = 0.2
384
385
# Calculate angle of incidence
386
from pvlib import irradiance
387
aoi = irradiance.aoi(surface_tilt, surface_azimuth, zenith, azimuth)
388
389
# Calculate spectral irradiance using SPECTRL2
390
wavelengths = np.arange(300, 1200, 5) # 300-1200 nm in 5nm steps
391
392
spectral_result = spectrum.spectrl2(
393
apparent_zenith=zenith,
394
aoi=aoi,
395
surface_tilt=surface_tilt,
396
ground_albedo=ground_albedo,
397
surface_pressure=surface_pressure/100, # convert Pa to mbar
398
relative_humidity=relative_humidity,
399
precipitable_water=precipitable_water,
400
ozone=ozone,
401
aerosol_turbidity_500nm=aod500,
402
dayofyear=172, # June 21
403
wavelengths=wavelengths
404
)
405
406
wavelengths_out = spectral_result[0]
407
poa_total = spectral_result[1]
408
poa_direct = spectral_result[2]
409
poa_diffuse = spectral_result[3]
410
ghi_spectral = spectral_result[4]
411
dni_spectral = spectral_result[5]
412
413
# Get reference spectrum for comparison
414
reference_spectra = spectrum.get_reference_spectra()
415
am15g_spectrum = reference_spectra['wavelength'], reference_spectra['global_tilt_37']
416
417
# Plot spectral irradiance
418
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
419
420
# POA spectral components
421
axes[0, 0].plot(wavelengths_out, poa_total, label='Total POA', linewidth=2)
422
axes[0, 0].plot(wavelengths_out, poa_direct, label='Direct POA', linewidth=2)
423
axes[0, 0].plot(wavelengths_out, poa_diffuse, label='Diffuse POA', linewidth=2)
424
axes[0, 0].set_xlabel('Wavelength (nm)')
425
axes[0, 0].set_ylabel('Spectral Irradiance (W/m²/nm)')
426
axes[0, 0].set_title('Plane-of-Array Spectral Irradiance Components')
427
axes[0, 0].legend()
428
axes[0, 0].grid(True)
429
430
# Horizontal spectral components
431
axes[0, 1].plot(wavelengths_out, ghi_spectral, label='GHI', linewidth=2)
432
axes[0, 1].plot(wavelengths_out, dni_spectral, label='DNI', linewidth=2)
433
axes[0, 1].set_xlabel('Wavelength (nm)')
434
axes[0, 1].set_ylabel('Spectral Irradiance (W/m²/nm)')
435
axes[0, 1].set_title('Horizontal Spectral Irradiance')
436
axes[0, 1].legend()
437
axes[0, 1].grid(True)
438
439
# Comparison with AM1.5G reference
440
# Interpolate reference to same wavelengths for comparison
441
ref_interp = np.interp(wavelengths_out, am15g_spectrum[0], am15g_spectrum[1])
442
axes[1, 0].plot(wavelengths_out, poa_total, label='SPECTRL2 POA', linewidth=2)
443
axes[1, 0].plot(wavelengths_out, ref_interp, label='AM1.5G Reference',
444
linewidth=2, linestyle='--')
445
axes[1, 0].set_xlabel('Wavelength (nm)')
446
axes[1, 0].set_ylabel('Spectral Irradiance (W/m²/nm)')
447
axes[1, 0].set_title('Comparison with AM1.5G Reference')
448
axes[1, 0].legend()
449
axes[1, 0].grid(True)
450
451
# Spectral ratio
452
spectral_ratio = poa_total / ref_interp
453
axes[1, 1].plot(wavelengths_out, spectral_ratio, 'r-', linewidth=2)
454
axes[1, 1].set_xlabel('Wavelength (nm)')
455
axes[1, 1].set_ylabel('Ratio (Field/Reference)')
456
axes[1, 1].set_title('Spectral Ratio vs AM1.5G')
457
axes[1, 1].grid(True)
458
axes[1, 1].axhline(y=1.0, color='k', linestyle='--', alpha=0.5)
459
460
plt.tight_layout()
461
plt.show()
462
463
# Calculate integrated values
464
poa_integrated = np.trapz(poa_total, wavelengths_out)
465
ghi_integrated = np.trapz(ghi_spectral, wavelengths_out)
466
ref_integrated = np.trapz(ref_interp, wavelengths_out)
467
468
print(f"\nIntegrated Irradiance Values:")
469
print(f"POA Total: {poa_integrated:.1f} W/m²")
470
print(f"GHI: {ghi_integrated:.1f} W/m²")
471
print(f"AM1.5G Reference: {ref_integrated:.1f} W/m²")
472
print(f"POA/Reference Ratio: {poa_integrated/ref_integrated:.3f}")
473
```
474
475
### Spectral Mismatch Analysis
476
477
```python
478
import pvlib
479
from pvlib import spectrum
480
import pandas as pd
481
import numpy as np
482
import matplotlib.pyplot as plt
483
484
# Get example spectral response functions
485
example_sr = spectrum.get_example_spectral_response()
486
487
# Define wavelength range
488
wavelengths = np.arange(300, 1200, 2) # 300-1200 nm in 2nm steps
489
490
# Get different spectral responses for analysis
491
technologies = ['si_c', 'si_pc', 'cdte', 'cigs', 'asi']
492
spectral_responses = {}
493
494
for tech in technologies:
495
if tech in example_sr:
496
# Interpolate to common wavelength grid
497
sr_interp = np.interp(wavelengths, example_sr['wavelength'],
498
example_sr[tech], left=0, right=0)
499
spectral_responses[tech] = pd.Series(sr_interp, index=wavelengths)
500
501
# Get reference spectra
502
ref_spectra = spectrum.get_reference_spectra(wavelengths=wavelengths)
503
am15g = ref_spectra['global_tilt_37']
504
am15d = ref_spectra['direct_normal']
505
506
# Simulate different field conditions by modifying reference spectrum
507
# Condition 1: High airmass (red-shifted)
508
am_high = am15g * np.exp(-0.1 * (wavelengths - 600) / 300) # Red shift
509
510
# Condition 2: High water vapor (IR absorption)
511
water_absorption = 1 - 0.3 * np.exp(-((wavelengths - 940) / 40)**2) # 940nm absorption
512
am_humid = am15g * water_absorption
513
514
# Condition 3: High aerosols (blue scattering)
515
blue_scattering = 1 - 0.2 * np.exp(-((wavelengths - 400) / 100)**2) # Blue reduction
516
am_aerosol = am15g * blue_scattering
517
518
field_conditions = {
519
'AM1.5G Reference': am15g,
520
'High Airmass': am_high,
521
'High Humidity': am_humid,
522
'High Aerosols': am_aerosol
523
}
524
525
# Calculate spectral mismatch factors for each technology and condition
526
mismatch_results = {}
527
528
for tech_name, sr in spectral_responses.items():
529
mismatch_results[tech_name] = {}
530
531
for condition_name, spectrum_field in field_conditions.items():
532
if condition_name == 'AM1.5G Reference':
533
mismatch_factor = 1.0 # Reference condition
534
else:
535
mismatch_factor = spectrum.calc_spectral_mismatch_field(
536
sr, spectrum_field, am15g
537
)
538
539
mismatch_results[tech_name][condition_name] = mismatch_factor
540
541
# Create results dataframe
542
results_df = pd.DataFrame(mismatch_results).T
543
results_df = results_df.round(4)
544
545
print("Spectral Mismatch Factors:")
546
print(results_df)
547
548
# Plot spectral responses and field conditions
549
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
550
551
# Spectral responses
552
for tech_name, sr in spectral_responses.items():
553
axes[0, 0].plot(wavelengths, sr, label=tech_name, linewidth=2)
554
axes[0, 0].set_xlabel('Wavelength (nm)')
555
axes[0, 0].set_ylabel('Spectral Response')
556
axes[0, 0].set_title('PV Technology Spectral Response')
557
axes[0, 0].legend()
558
axes[0, 0].grid(True)
559
560
# Field spectra compared to reference
561
for condition_name, spectrum_field in field_conditions.items():
562
if condition_name != 'AM1.5G Reference':
563
ratio = spectrum_field / am15g
564
axes[0, 1].plot(wavelengths, ratio, label=condition_name, linewidth=2)
565
566
axes[0, 1].set_xlabel('Wavelength (nm)')
567
axes[0, 1].set_ylabel('Ratio to AM1.5G')
568
axes[0, 1].set_title('Field Conditions vs AM1.5G Reference')
569
axes[0, 1].legend()
570
axes[0, 1].grid(True)
571
axes[0, 1].axhline(y=1.0, color='k', linestyle='--', alpha=0.5)
572
573
# Mismatch factors by technology
574
x_pos = np.arange(len(technologies))
575
width = 0.2
576
conditions = ['High Airmass', 'High Humidity', 'High Aerosols']
577
colors = ['red', 'blue', 'green']
578
579
for i, condition in enumerate(conditions):
580
values = [mismatch_results[tech][condition] for tech in technologies]
581
axes[1, 0].bar(x_pos + i*width, values, width, label=condition,
582
color=colors[i], alpha=0.7)
583
584
axes[1, 0].set_xlabel('PV Technology')
585
axes[1, 0].set_ylabel('Spectral Mismatch Factor')
586
axes[1, 0].set_title('Spectral Mismatch by Technology and Condition')
587
axes[1, 0].set_xticks(x_pos + width)
588
axes[1, 0].set_xticklabels(technologies)
589
axes[1, 0].legend()
590
axes[1, 0].grid(True, axis='y')
591
axes[1, 0].axhline(y=1.0, color='k', linestyle='--', alpha=0.5)
592
593
# Technology ranking by spectral sensitivity
594
sensitivity = {}
595
for tech in technologies:
596
# Calculate standard deviation of mismatch factors (excluding reference)
597
values = [mismatch_results[tech][cond] for cond in conditions]
598
sensitivity[tech] = np.std(values)
599
600
sorted_techs = sorted(sensitivity.items(), key=lambda x: x[1])
601
tech_names = [item[0] for item in sorted_techs]
602
sens_values = [item[1] for item in sorted_techs]
603
604
axes[1, 1].barh(tech_names, sens_values, color='orange', alpha=0.7)
605
axes[1, 1].set_xlabel('Spectral Sensitivity (Std Dev of Mismatch)')
606
axes[1, 1].set_title('Technology Ranking by Spectral Sensitivity')
607
axes[1, 1].grid(True, axis='x')
608
609
plt.tight_layout()
610
plt.show()
611
612
# Find best and worst performing technologies
613
print(f"\nMost spectrally stable technology: {sorted_techs[0][0]} (σ = {sorted_techs[0][1]:.4f})")
614
print(f"Most spectrally sensitive technology: {sorted_techs[-1][0]} (σ = {sorted_techs[-1][1]:.4f})")
615
616
# Calculate potential energy differences
617
print(f"\nPotential annual energy impact (assuming constant conditions):")
618
for tech in technologies:
619
for condition in conditions:
620
impact = (mismatch_results[tech][condition] - 1.0) * 100
621
print(f"{tech} in {condition}: {impact:+.1f}%")
622
```
623
624
### Advanced Spectral Analysis with Atmospheric Variations
625
626
```python
627
import pvlib
628
from pvlib import spectrum, atmosphere, clearsky, solarposition
629
import pandas as pd
630
import numpy as np
631
import matplotlib.pyplot as plt
632
633
# Location and time range
634
lat, lon = 35.0522, -106.5408 # Albuquerque, NM (high altitude, arid)
635
times = pd.date_range('2023-01-01', '2023-12-31', freq='MS') # Monthly
636
637
# Calculate seasonal variations
638
seasonal_analysis = []
639
640
for time in times:
641
# Solar position at solar noon
642
solar_pos = solarposition.get_solarposition(time.replace(hour=12), lat, lon)
643
zenith = solar_pos['zenith'].iloc[0]
644
645
# Atmospheric parameters (seasonal variations)
646
month = time.month
647
648
# Seasonal precipitable water (higher in summer)
649
pw_base = 1.0 # cm
650
pw_seasonal = pw_base * (1 + 0.5 * np.sin(2 * np.pi * (month - 3) / 12))
651
652
# Aerosol optical depth (higher in summer dust season)
653
aod_base = 0.08
654
aod_seasonal = aod_base * (1 + 0.3 * np.sin(2 * np.pi * (month - 6) / 12))
655
656
# Calculate airmass
657
airmass_rel = atmosphere.get_relative_airmass(zenith)
658
airmass_abs = atmosphere.get_absolute_airmass(airmass_rel, pressure=85000) # High altitude
659
660
# Calculate clearsky index (simulate seasonal cloud variations)
661
clearsky_model = clearsky.ineichen(zenith, airmass_abs, linke_turbidity=3.0)
662
ghi_clear = clearsky_model['ghi']
663
664
# Simulate measured GHI with seasonal cloud patterns
665
cloud_factor = 0.9 - 0.2 * np.sin(2 * np.pi * (month - 1) / 12) # More clouds in winter
666
ghi_measured = ghi_clear * cloud_factor
667
clearsky_idx = ghi_measured / ghi_clear if ghi_clear > 0 else 0
668
669
# Calculate spectral correction factors for c-Si
670
sapm_params = {'a0': 0.928, 'a1': 0.068, 'a2': -0.0077, 'a3': 0.0001, 'a4': -0.000002}
671
672
spectral_sapm = spectrum.spectral_factor_sapm(airmass_abs, sapm_params)
673
spectral_fs = spectrum.spectral_factor_firstsolar(pw_seasonal, airmass_abs, 'csi')
674
spectral_caballero = spectrum.spectral_factor_caballero(
675
pw_seasonal, airmass_abs, aod_seasonal, module_type='csi'
676
)
677
spectral_pvspec = spectrum.spectral_factor_pvspec(
678
airmass_abs, clearsky_idx, module_type='monosi'
679
)
680
681
seasonal_analysis.append({
682
'month': month,
683
'zenith': zenith,
684
'airmass': airmass_abs,
685
'precipitable_water': pw_seasonal,
686
'aod500': aod_seasonal,
687
'clearsky_index': clearsky_idx,
688
'spectral_sapm': spectral_sapm,
689
'spectral_firstsolar': spectral_fs,
690
'spectral_caballero': spectral_caballero,
691
'spectral_pvspec': spectral_pvspec
692
})
693
694
# Convert to DataFrame
695
seasonal_df = pd.DataFrame(seasonal_analysis)
696
seasonal_df.set_index('month', inplace=True)
697
698
# Plot seasonal variations
699
fig, axes = plt.subplots(3, 2, figsize=(15, 12))
700
701
# Atmospheric parameters
702
axes[0, 0].plot(seasonal_df.index, seasonal_df['airmass'], 'bo-', linewidth=2)
703
axes[0, 0].set_title('Seasonal Airmass Variation')
704
axes[0, 0].set_ylabel('Airmass')
705
axes[0, 0].grid(True)
706
707
axes[0, 1].plot(seasonal_df.index, seasonal_df['precipitable_water'], 'go-', linewidth=2, label='PW')
708
ax_twin = axes[0, 1].twinx()
709
ax_twin.plot(seasonal_df.index, seasonal_df['aod500'], 'ro-', linewidth=2, label='AOD')
710
axes[0, 1].set_title('Precipitable Water and AOD')
711
axes[0, 1].set_ylabel('Precipitable Water (cm)', color='g')
712
ax_twin.set_ylabel('AOD at 500nm', color='r')
713
axes[0, 1].grid(True)
714
715
# Clearsky conditions
716
axes[1, 0].plot(seasonal_df.index, seasonal_df['clearsky_index'], 'ko-', linewidth=2)
717
axes[1, 0].set_title('Seasonal Clearsky Index')
718
axes[1, 0].set_ylabel('Clearsky Index')
719
axes[1, 0].grid(True)
720
721
# Spectral correction factors
722
spectral_cols = ['spectral_sapm', 'spectral_firstsolar', 'spectral_caballero', 'spectral_pvspec']
723
colors = ['blue', 'red', 'green', 'orange']
724
labels = ['SAPM', 'First Solar', 'Caballero', 'PVSPEC']
725
726
for i, (col, color, label) in enumerate(zip(spectral_cols, colors, labels)):
727
axes[1, 1].plot(seasonal_df.index, seasonal_df[col], 'o-',
728
color=color, linewidth=2, label=label)
729
730
axes[1, 1].set_title('Spectral Correction Factors')
731
axes[1, 1].set_ylabel('Spectral Factor')
732
axes[1, 1].legend()
733
axes[1, 1].grid(True)
734
735
# Annual performance impact
736
reference_factor = 1.0
737
performance_impact = {}
738
739
for col in spectral_cols:
740
# Calculate monthly energy impact
741
monthly_impact = (seasonal_df[col] - reference_factor) * 100
742
annual_avg_impact = monthly_impact.mean()
743
performance_impact[col] = {
744
'monthly': monthly_impact,
745
'annual_avg': annual_avg_impact,
746
'seasonal_range': monthly_impact.max() - monthly_impact.min()
747
}
748
749
# Plot performance impacts
750
months = seasonal_df.index
751
for i, (col, color, label) in enumerate(zip(spectral_cols, colors, labels)):
752
axes[2, 0].plot(months, performance_impact[col]['monthly'], 'o-',
753
color=color, linewidth=2, label=label)
754
755
axes[2, 0].set_title('Monthly Performance Impact')
756
axes[2, 0].set_xlabel('Month')
757
axes[2, 0].set_ylabel('Performance Impact (%)')
758
axes[2, 0].legend()
759
axes[2, 0].grid(True)
760
axes[2, 0].axhline(y=0, color='k', linestyle='--', alpha=0.5)
761
762
# Summary statistics
763
model_names = [label.replace(' ', '_') for label in labels]
764
annual_avgs = [performance_impact[col]['annual_avg'] for col in spectral_cols]
765
seasonal_ranges = [performance_impact[col]['seasonal_range'] for col in spectral_cols]
766
767
x_pos = np.arange(len(model_names))
768
width = 0.35
769
770
bars1 = axes[2, 1].bar(x_pos - width/2, annual_avgs, width, label='Annual Avg', alpha=0.7)
771
bars2 = axes[2, 1].bar(x_pos + width/2, seasonal_ranges, width, label='Seasonal Range', alpha=0.7)
772
773
axes[2, 1].set_title('Annual Impact Summary')
774
axes[2, 1].set_xlabel('Spectral Model')
775
axes[2, 1].set_ylabel('Performance Impact (%)')
776
axes[2, 1].set_xticks(x_pos)
777
axes[2, 1].set_xticklabels(model_names, rotation=45)
778
axes[2, 1].legend()
779
axes[2, 1].grid(True, axis='y')
780
781
plt.tight_layout()
782
plt.show()
783
784
# Print summary statistics
785
print("\nAnnual Spectral Performance Analysis:")
786
print("="*50)
787
for i, col in enumerate(spectral_cols):
788
label = labels[i]
789
avg_impact = performance_impact[col]['annual_avg']
790
range_impact = performance_impact[col]['seasonal_range']
791
792
print(f"{label:12s}: Annual Avg = {avg_impact:+.2f}%, Seasonal Range = {range_impact:.2f}%")
793
794
print(f"\nLocation: {lat:.2f}°N, {lon:.2f}°W (High altitude, arid climate)")
795
print("Analysis shows seasonal variations in spectral performance due to:")
796
print("- Solar angle variations (airmass)")
797
print("- Seasonal atmospheric moisture")
798
print("- Dust/aerosol loading")
799
print("- Cloud cover patterns")
800
```