A traditionally fixed tilt systems points toward the equator to have symetric sunlight for both morning and evening. A dual tilt system is an alternative with half the system facing east and the other half west in alternating rows. This notebook will do a quick analysis of a dual tilt versus traditional fixed tilt system. We will follow the traditional modeling steps outlined at the Sandia PV PMC website and the pvlib python package.
import pathlib
import os
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from pvlib import solarposition, iotools, irradiance, pvsystem, iam, temperature
sns.set(font_scale=1.5)
# keys to get measured data from NREL
NREL_API_KEY_FILE = '../NREL_API_KEY_BERKELEY'
# NREL_API_KEY_FILE = '../NREL_API_KEY_SUNPOWER'
NREL_API_KEY_EMAIL = 'mikofski@berkeley.edu'
# NREL_API_KEY_EMAIL = 'mark.mikofski@dnvgl.com'
NREL_API_KEY = None # set the NREL API KEY if you have one
# look for key in environment variables
if not NREL_API_KEY:
NREL_API_KEY = os.getenv('NREL_API_KEY')
if not NREL_API_KEY:
with pathlib.Path(NREL_API_KEY_FILE).open() as f:
NREL_API_KEY = f.read()
PVMOD = pvsystem.retrieve_sam('SandiaMod')
INVDB = pvsystem.retrieve_sam('SandiaInverter')
Where in the world is this site located?
# Fictitious PV farm outside of Abq., NM. Note the winter sun here is spectacular!
latitude = 35.105
longitude = -106.8
timezone = 'Etc/GMT+7'
# Las Vegas, NV, not as good winter sun
# latitude = 36.105
# longitude = -115.8
# timezone = 'Etc/GMT+8'
# Ashville, NC
# latitude = 35.54
# longitude = -82.635
# timezone = 'Etc/GMT+5'
# NIST, Gaithersburg, MD
# latitude = 39.1500
# longitude = -77.2500
# timezone = 'Etc/GMT+5'
# Tucson, AZ
# latitude = 32.2
# longitude = -110.9
# timezone = 'Etc/GMT+7'
Define some system info like surface tilt & azimuth and module & inverter parameters
# system info
system_tilt = 30.0 # degrees
east_az = 90.0 # degrees
west_az = 270.0 # degrees
fixed_tilt = 20.0 # degrees
south_az = 180.0 # degrees
# replace non-standard characters with underscores
pvmodule = 'Canadian Solar CS6X-300M [2013]'
#pvmodule = 'SunPower SPR-315E-WHT [2007 (E)]'
pvmodule = pvmodule.replace(' ', '_').replace('-', '_')
pvmodule = pvmodule.replace('[', '_').replace(']', '_')
pvmodule = pvmodule.replace('(', '_').replace(')', '_')
pvmodule = PVMOD[pvmodule]
inverter = 'SMA America: ST36 [240V]'
inverter = inverter.replace(' ', '_').replace(':', '_')
inverter = inverter.replace('[', '_').replace(']', '_')
inverter = INVDB[inverter]
num_modules_per_string = 10
num_strings_per_inverter = 14
# check string voltage not too high
string_voltage = num_modules_per_string*pvmodule.Voco
print(f'string voltage = {string_voltage:g} [V], max = {inverter.Vdcmax} [V]')
assert string_voltage < inverter.Vdcmax
# check inverter current not too high
inverter_current = num_strings_per_inverter*pvmodule.Isco
print(f'inverter current = {inverter_current:g} [A], max = {inverter.Idcmax:g} [A]')
assert inverter_current < inverter.Idcmax
nameplate = pvmodule.Impo*pvmodule.Vmpo
print(f'nameplate power = {nameplate:g} [W]')
array_peak_power = num_modules_per_string*num_strings_per_inverter*nameplate
print(f'array peak power = {array_peak_power:g} [W] vs. inverter power = {inverter.Paco} [W]')
dcac_ratio = array_peak_power / inverter.Paco
print(f'DC/AC ratio = {dcac_ratio:g}')
string voltage = 435.918 [V], max = 480.0 [V] inverter current = 120.943 [A], max = 121.261 [A] nameplate power = 284.375 [W] array peak power = 39812.5 [W] vs. inverter power = 36000.0 [W] DC/AC ratio = 1.1059
If we use TMY or typical meteorological year data, then each month will be taken from the year with median weather, so we set the times to an arbitrary year of interest, like 1990.
# hours that we're interested in, shifted to middle of hour
times = pd.date_range(start='1990-01-01 00:30:00', periods=8760, freq='H', tz=timezone)
We can either use measured weather data or calculate the clear sky conditions for the site. We can get measured data from NREL's PSM3 model using pvlib.
# get measured weather for our site from NREL's PSM3
headers, weather = iotools.get_psm3(latitude, longitude, NREL_API_KEY, NREL_API_KEY_EMAIL)
weather.index = times # set the times to match the one's we're interested in
weather[['DNI', 'GHI', 'DHI']].resample('M').mean().plot(figsize=(16, 10))
plt.title(f'Source: {headers["Source"]}, Latitude: {headers["Latitude"]}, Longitude: {headers["Longitude"]}')
plt.ylabel('Irradiance [W/m^2]')
Text(0, 0.5, 'Irradiance [W/m^2]')
# we'll need these values later
elevation = headers['Elevation'] # altitude in meters
pressure = weather['Pressure'].values*100 # convert mbar to Pa
tamb = weather['Temperature'].values # ambient temperature in C
Use pvlib to calculate the solar positions for the hours that we're interested.
solpos = solarposition.get_solarposition(
times, latitude=latitude, longitude=longitude, altitude=elevation,
pressure=pressure, temperature=tamb)
This is where the magic happens. What is the plane of array for a dual-tilt system facing east and west?
# we can use the default isotropic model, or Hay-Davies, which is a little better
dni_extra = irradiance.get_extra_radiation(times) # we'll need this
# half the modules face east
poa_east = irradiance.get_total_irradiance(
surface_tilt=system_tilt, surface_azimuth=east_az,
dni=weather['DNI'], ghi=weather['GHI'], dhi=weather['DHI'], dni_extra=dni_extra,
solar_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'],
model='haydavies')
# the other half face west
poa_west = irradiance.get_total_irradiance(
surface_tilt=system_tilt, surface_azimuth=west_az,
dni=weather['DNI'], ghi=weather['GHI'], dhi=weather['DHI'], dni_extra=dni_extra,
solar_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'],
model='haydavies')
# the other half face west
poa_south = irradiance.get_total_irradiance(
surface_tilt=fixed_tilt, surface_azimuth=south_az,
dni=weather['DNI'], ghi=weather['GHI'], dhi=weather['DHI'], dni_extra=dni_extra,
solar_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'],
model='haydavies')
poa_global = pd.concat([poa_east['poa_global'], poa_west['poa_global'], poa_south['poa_global']], axis=1)
poa_global.columns = ['east', 'west', 'south']
poa_global.resample('M').mean().plot(figsize=(16, 10))
plt.ylabel('Irradiance [W/m^2]')
plt.title('POA Global')
Text(0.5, 1.0, 'POA Global')
poa_global = pd.concat([poa_east['poa_direct'], poa_west['poa_direct'], poa_south['poa_direct']], axis=1)
poa_global.columns = ['east', 'west', 'south']
poa_global.resample('M').mean().plot(figsize=(16, 10))
plt.ylabel('Irradiance [W/m^2]')
plt.title('POA Direct')
Text(0.5, 1.0, 'POA Direct')
We need to know the AOI for reflection off the panel.
aoi_east = irradiance.aoi(
surface_tilt=system_tilt, surface_azimuth=east_az,
solar_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'])
aoi_west = irradiance.aoi(
surface_tilt=system_tilt, surface_azimuth=west_az,
solar_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'])
aoi_south = irradiance.aoi(
surface_tilt=fixed_tilt, surface_azimuth=south_az,
solar_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'])
If the incident sunlight is at too steep an angle, then it will reflect off the panels. This fraction of normal inciendence is called the IAM.
iam_east = iam.sapm(aoi_east, pvmodule)
iam_west = iam.sapm(aoi_west, pvmodule)
iam_south = iam.sapm(aoi_south, pvmodule)
Since we only care about max power, we can use the Sandia Array Performance Model (SAPM) which uses only effective irradiance and temperature to compute the max power point. The effective irradnace $E_e$ is the sunshine that includes IAM and spectral effects, although since this is a mono-Si module, we'll just ignore spectrum
eff_irr_east = poa_east['poa_direct']*iam_east + poa_east['poa_diffuse']
eff_irr_west = poa_west['poa_direct']*iam_west + poa_west['poa_diffuse']
eff_irr_south = poa_south['poa_direct']*iam_south + poa_south['poa_diffuse']
Hot modules don't perform as well, and modules in the sun get hot, so let's check that temperature.
a, b, dt = -3.56, -0.075, 3.0
celltemp_east = temperature.sapm_cell(poa_east['poa_global'], tamb, weather['Wind Speed'], a, b, dt)
celltemp_west = temperature.sapm_cell(poa_west['poa_global'], tamb, weather['Wind Speed'], a, b, dt)
celltemp_south = temperature.sapm_cell(poa_south['poa_global'], tamb, weather['Wind Speed'], a, b, dt)
Now we can see the effect of dual tilt
array_power_east = pvsystem.sapm(effective_irradiance=eff_irr_east, temp_cell=celltemp_east, module=pvmodule)
array_power_west = pvsystem.sapm(effective_irradiance=eff_irr_west, temp_cell=celltemp_west, module=pvmodule)
array_power_south = pvsystem.sapm(effective_irradiance=eff_irr_south, temp_cell=celltemp_south, module=pvmodule)
# fillna is critical for mean!
array_power_east['p_mp'] = array_power_east['p_mp'].fillna(0)
array_power_west['p_mp'] = array_power_west['p_mp'].fillna(0)
array_power_south['p_mp'] = array_power_south['p_mp'].fillna(0)
dual_tilt = pd.DataFrame((array_power_east['p_mp'] + array_power_west['p_mp'])/2)
fixed_tilt = pd.DataFrame(array_power_south['p_mp'])
dual_tilt['Ee_east'] = eff_irr_east
dual_tilt['Ee_west'] = eff_irr_west
fixed_tilt['Ee'] = eff_irr_south
dual_tilt['Tc_east'] = celltemp_east
dual_tilt['Tc_west'] = celltemp_west
fixed_tilt['Tc'] = celltemp_south
summer_start, summer_end = [
pd.to_datetime(ts).tz_localize(timezone) for ts in ('1990-06-20 06:00:00','1990-06-20 19:00:00')]
winter_start, winter_end = [
pd.to_datetime(ts).tz_localize(timezone) for ts in ('1990-12-21 06:00:00','1990-12-21 19:00:00')]
spring_start, spring_end = [
pd.to_datetime(ts).tz_localize(timezone) for ts in ('1990-03-21 06:00:00','1990-03-21 19:00:00')]
fall_start, fall_end = [
pd.to_datetime(ts).tz_localize(timezone) for ts in ('1990-09-21 06:00:00','1990-09-21 19:00:00')]
hours = [h+0.5 for h in range(6, 19)]
dual_tilt[summer_start:summer_end]
p_mp | Ee_east | Ee_west | Tc_east | Tc_west | |
---|---|---|---|---|---|
1990-06-20 06:30:00-07:00 | 75.832329 | 532.812791 | 39.388956 | 39.844119 | 27.005962 |
1990-06-20 07:30:00-07:00 | 103.838969 | 772.273152 | 45.028821 | 48.097516 | 30.112645 |
1990-06-20 08:30:00-07:00 | 138.578023 | 927.056156 | 188.825634 | 54.294012 | 36.862966 |
1990-06-20 09:30:00-07:00 | 173.691356 | 998.378829 | 424.247026 | 59.353350 | 44.547345 |
1990-06-20 10:30:00-07:00 | 198.863098 | 1018.607911 | 633.245700 | 61.502258 | 51.286045 |
1990-06-20 11:30:00-07:00 | 213.193480 | 988.003322 | 817.530776 | 62.927378 | 58.612662 |
1990-06-20 12:30:00-07:00 | 213.816751 | 866.010453 | 961.623777 | 61.299310 | 63.731258 |
1990-06-20 13:30:00-07:00 | 200.560769 | 690.759406 | 1022.035923 | 56.977410 | 66.134938 |
1990-06-20 14:30:00-07:00 | 176.695891 | 489.100394 | 1013.908318 | 51.529701 | 66.588063 |
1990-06-20 15:30:00-07:00 | 143.238332 | 255.655166 | 960.981405 | 45.549978 | 65.126923 |
1990-06-20 16:30:00-07:00 | 102.112307 | 90.913268 | 753.566682 | 39.399017 | 58.109773 |
1990-06-20 17:30:00-07:00 | 61.848659 | 108.651523 | 372.250133 | 37.302150 | 45.410200 |
1990-06-20 18:30:00-07:00 | 48.618626 | 28.251937 | 345.662376 | 31.870332 | 41.912590 |
fixed_tilt[summer_start:summer_end]
p_mp | Ee | Tc | |
---|---|---|---|
1990-06-20 06:30:00-07:00 | 36.725302 | 135.961062 | 30.329942 |
1990-06-20 07:30:00-07:00 | 99.938409 | 374.883778 | 38.712344 |
1990-06-20 08:30:00-07:00 | 154.415610 | 601.123832 | 46.439574 |
1990-06-20 09:30:00-07:00 | 197.653256 | 803.089518 | 53.989890 |
1990-06-20 10:30:00-07:00 | 227.884799 | 957.207167 | 59.392435 |
1990-06-20 11:30:00-07:00 | 236.846488 | 1025.621970 | 64.403293 |
1990-06-20 12:30:00-07:00 | 235.797938 | 1032.670308 | 66.296634 |
1990-06-20 13:30:00-07:00 | 228.192748 | 990.593833 | 64.976010 |
1990-06-20 14:30:00-07:00 | 202.093069 | 858.034994 | 61.647572 |
1990-06-20 15:30:00-07:00 | 161.641479 | 665.724876 | 56.742820 |
1990-06-20 16:30:00-07:00 | 106.882181 | 422.223709 | 48.791252 |
1990-06-20 17:30:00-07:00 | 48.530907 | 186.873788 | 40.063421 |
1990-06-20 18:30:00-07:00 | 7.624450 | 31.664260 | 32.256464 |
dual_tilt[winter_start:winter_end]
p_mp | Ee_east | Ee_west | Tc_east | Tc_west | |
---|---|---|---|---|---|
1990-12-21 06:30:00-07:00 | 0.000000 | 0.000000 | 0.000000 | -4.000000 | -4.000000 |
1990-12-21 07:30:00-07:00 | 37.317448 | 229.800871 | 11.353508 | 4.895796 | -1.670849 |
1990-12-21 08:30:00-07:00 | 79.679912 | 513.295132 | 20.022691 | 15.323205 | 0.580480 |
1990-12-21 09:30:00-07:00 | 95.708920 | 624.230830 | 27.515200 | 19.247528 | 1.352655 |
1990-12-21 10:30:00-07:00 | 123.907567 | 642.246815 | 203.280986 | 22.228175 | 9.844749 |
1990-12-21 11:30:00-07:00 | 145.765973 | 577.191897 | 412.797868 | 20.769602 | 16.140938 |
1990-12-21 12:30:00-07:00 | 147.547502 | 444.103902 | 558.888801 | 17.351432 | 20.445304 |
1990-12-21 13:30:00-07:00 | 127.795699 | 243.349947 | 633.121258 | 13.170732 | 23.274780 |
1990-12-21 14:30:00-07:00 | 97.026868 | 46.663521 | 627.378215 | 7.444388 | 23.697819 |
1990-12-21 15:30:00-07:00 | 81.983957 | 23.146177 | 533.115219 | 2.689355 | 18.334975 |
1990-12-21 16:30:00-07:00 | 46.192540 | 15.915622 | 286.477503 | 0.470825 | 8.754099 |
1990-12-21 17:30:00-07:00 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
1990-12-21 18:30:00-07:00 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
fixed_tilt[winter_start:winter_end]
p_mp | Ee | Tc | |
---|---|---|---|
1990-12-21 06:30:00-07:00 | 0.000000 | 0.000000 | -4.000000 |
1990-12-21 07:30:00-07:00 | 30.164469 | 97.761974 | 1.420946 |
1990-12-21 08:30:00-07:00 | 116.593804 | 383.468186 | 11.669145 |
1990-12-21 09:30:00-07:00 | 180.077278 | 611.506666 | 18.866802 |
1990-12-21 10:30:00-07:00 | 220.601826 | 777.231165 | 26.271695 |
1990-12-21 11:30:00-07:00 | 241.794149 | 864.767907 | 29.071692 |
1990-12-21 12:30:00-07:00 | 244.109430 | 873.305089 | 29.099657 |
1990-12-21 13:30:00-07:00 | 223.974305 | 794.764937 | 27.698170 |
1990-12-21 14:30:00-07:00 | 183.664390 | 639.153397 | 24.037233 |
1990-12-21 15:30:00-07:00 | 126.782080 | 423.324972 | 15.126409 |
1990-12-21 16:30:00-07:00 | 43.702944 | 142.103824 | 4.848379 |
1990-12-21 17:30:00-07:00 | 0.000000 | 0.000000 | 0.000000 |
1990-12-21 18:30:00-07:00 | 0.000000 | 0.000000 | 0.000000 |
f, ax = plt.subplots(1,2, figsize=(16, 10), sharey=True)
dual_tilt[summer_start:summer_end].p_mp.plot(ax=ax[0], label='dual-tilt')
fixed_tilt[summer_start:summer_end].p_mp.plot(ax=ax[0], label='fixed-tilt')
ax[0].legend()
ax[0].set_ylabel('power [W]')
ax[0].set_title('Summer day hourly output')
dual_tilt[winter_start:winter_end].p_mp.plot(ax=ax[1], label='dual tilt')
fixed_tilt[winter_start:winter_end].p_mp.plot(ax=ax[1], label='fixed-tilt')
ax[1].legend()
ax[1].set_title('Winter day hourly output')
plt.tight_layout()
f, ax = plt.subplots(2, 1, figsize=(16, 10))
dual_tilt['1990-06-01':'1990-07-01'].p_mp.plot(ax=ax[0], label='dual-tilt')
fixed_tilt['1990-06-01':'1990-07-01'].p_mp.plot(ax=ax[0], label='fixed-tilt')
ax[0].legend()
ax[0].set_ylabel('Summer power [W]')
ax[0].set_title('Summer vs. winter daily hourly output')
ax[0].set_ylim([0, 400])
dual_tilt['1990-12-01':'1990-12-31'].p_mp.plot(ax=ax[1], label='dual tilt')
fixed_tilt['1990-12-01':'1990-12-31'].p_mp.plot(ax=ax[1], label='fixed-tilt')
ax[1].legend()
ax[1].set_ylabel('Winter power [W]')
ax[1].set_ylim([0, 400])
plt.tight_layout()
f, ax = plt.subplots(2, 1, figsize=(16, 10))
(dual_tilt['1990-06-01':'1990-07-01'].p_mp.resample('D').sum()/nameplate).plot(ax=ax[0], label='dual-tilt')
(fixed_tilt['1990-06-01':'1990-07-01'].p_mp.resample('D').sum()/nameplate).plot(ax=ax[0], label='fixed-tilt')
ax[0].legend()
ax[0].set_ylabel('Summer power yield [Wh/Wp]')
ax[0].set_ylim([0, 10])
ax[0].set_title('Summer vs. winter daily hourly output')
(dual_tilt['1990-12-01':'1990-12-31'].p_mp.resample('D').sum()/nameplate).plot(ax=ax[1], label='dual tilt')
(fixed_tilt['1990-12-01':'1990-12-31'].p_mp.resample('D').sum()/nameplate).plot(ax=ax[1], label='fixed-tilt')
ax[1].legend()
ax[1].set_ylabel('Winter power yield [Wh/Wp]')
ax[1].set_ylim([0, 10])
plt.tight_layout()
print(f"summer dual-tilt: {(dual_tilt['1990-06-01':'1990-07-01'].p_mp.resample('D').sum()/nameplate).sum():g}[kWh/kWp]")
print(f"summer fixed-tilt: {(fixed_tilt['1990-06-01':'1990-07-01'].p_mp.resample('D').sum()/nameplate).sum():g}[kWh/kWp]")
print(f"winter dual-tilt: {(dual_tilt['1990-12-01':'1990-12-31'].p_mp.resample('D').sum()/nameplate).sum():g}[kWh/kWp]")
print(f"winter fixed-tilt: {(fixed_tilt['1990-12-01':'1990-12-31'].p_mp.resample('D').sum()/nameplate).sum():g}[kWh/kWp]")
summer dual-tilt: 205.937[kWh/kWp] summer fixed-tilt: 211.87[kWh/kWp] winter dual-tilt: 88.7373[kWh/kWp] winter fixed-tilt: 140.083[kWh/kWp]
f, ax = plt.subplots(1, 2, figsize=(16, 10), sharey=True)
dual_tilt[spring_start:spring_end].p_mp.plot(ax=ax[0], label='dual-tilt')
fixed_tilt[spring_start:spring_end].p_mp.plot(ax=ax[0], label='fixed-tilt')
ax[0].legend()
ax[0].set_ylabel('power [W]')
ax[0].set_title('Spring day hourly output')
dual_tilt[fall_start:fall_end].p_mp.plot(ax=ax[1], label='dual tilt')
fixed_tilt[fall_start:fall_end].p_mp.plot(ax=ax[1], label='fixed-tilt')
ax[1].legend()
ax[1].set_title('Fall day hourly output')
plt.tight_layout()
f, ax = plt.subplots(1, 2, figsize=(16, 10), sharey=True)
(dual_tilt[spring_start:spring_end].p_mp
/ dual_tilt[spring_start:spring_end].p_mp.max()).plot(ax=ax[0], label='dual-tilt')
(fixed_tilt[spring_start:spring_end].p_mp
/ fixed_tilt[spring_start:spring_end].p_mp.max()).plot(ax=ax[0], label='fixed-tilt')
ax[0].legend()
ax[0].set_ylabel('power / pmax [non-dimensional]')
ax[0].set_title('Spring day hourly output')
(dual_tilt[fall_start:fall_end].p_mp
/ dual_tilt[fall_start:fall_end].p_mp.max()).plot(ax=ax[1], label='dual tilt')
(fixed_tilt[fall_start:fall_end].p_mp
/ fixed_tilt[fall_start:fall_end].p_mp.max()).plot(ax=ax[1], label='fixed-tilt')
ax[1].legend()
ax[1].set_title('Fall day hourly output')
plt.tight_layout()
f, ax = plt.subplots(1, 2, figsize=(16, 10), sharey=True)
ax[0].plot(hours, weather[summer_start:summer_end].GHI.values, label='June')
ax[0].plot(hours, weather[winter_start:winter_end].GHI.values, label='December')
ax[0].legend()
ax[0].set_ylabel('GHI [W/m^2]')
ax[1].plot(hours, weather[spring_start:spring_end].GHI.values, label='March')
ax[1].plot(hours, weather[fall_start:fall_end].GHI.values, label='September')
ax[1].legend()
plt.tight_layout()
f, ax = plt.subplots(1, 2, figsize=(16, 10), sharey=True)
ax[0].plot(hours, weather[summer_start:summer_end].DNI.values, label='June')
ax[0].plot(hours, weather[winter_start:winter_end].DNI.values, label='December')
ax[0].legend()
ax[0].set_ylabel('DNI [W/m^2]')
ax[1].plot(hours, weather[spring_start:spring_end].DNI.values, label='March')
ax[1].plot(hours, weather[fall_start:fall_end].DNI.values, label='September')
ax[1].legend()
plt.tight_layout()
f, ax = plt.subplots(1, 2, figsize=(16, 10), sharey=True)
(fixed_tilt.p_mp.resample('M').sum()/nameplate).plot(ax=ax[0])
(dual_tilt.p_mp.resample('M').sum()/nameplate).plot(ax=ax[1])
ax[0].set_ylabel('Max Power Yield [kWh/kWp]')
ax[0].set_title('fixed-tilt')
ax[1].set_title('dual-tilt')
plt.tight_layout()
f, ax = plt.subplots(1, 2, figsize=(16, 10), sharey=True)
fixed_tilt.Ee.resample('M').mean().plot(ax=ax[0])
dual_tilt[['Ee_east', 'Ee_west']].resample('M').mean().plot(ax=ax[1])
ax[0].set_ylabel('Effective Irradiance [W/m^2]')
ax[0].set_title('fixed-tilt')
ax[1].set_title('dual-tilt')
plt.tight_layout()
f, ax = plt.subplots(1, 2, figsize=(16, 10), sharey=True)
fixed_tilt.Tc.resample('M').mean().plot(ax=ax[0])
dual_tilt[['Tc_east', 'Tc_west']].resample('M').mean().plot(ax=ax[1])
ax[0].set_ylabel('Cell Temperature [C]')
ax[0].set_title('fixed-tilt')
ax[1].set_title('dual-tilt')
plt.tight_layout()
print(f'dual-tilt tot. annual: {dual_tilt.p_mp.sum()/nameplate:g} [kWh/kWp]')
print(f'fixed-tilt tot. annual: {fixed_tilt.p_mp.sum()/nameplate:g} [kWh/kWp]')
dual-tilt tot. annual: 1772.08 [kWh/kWp] fixed-tilt tot. annual: 2154.13 [kWh/kWp]