pub trait Mount: Send + Sync {
fn get_surface_tilt(&self) -> f64;
fn get_surface_azimuth(&self) -> f64;
}
#[derive(Debug, Clone)]
pub struct FixedMount {
pub surface_tilt: f64,
pub surface_azimuth: f64,
}
impl Mount for FixedMount {
fn get_surface_tilt(&self) -> f64 { self.surface_tilt }
fn get_surface_azimuth(&self) -> f64 { self.surface_azimuth }
}
#[derive(Debug, Clone)]
pub struct SingleAxisTrackerMount {
pub axis_tilt: f64,
pub axis_azimuth: f64,
pub max_angle: f64,
pub backtrack: bool,
pub gcr: f64,
}
impl Mount for SingleAxisTrackerMount {
fn get_surface_tilt(&self) -> f64 { self.axis_tilt } fn get_surface_azimuth(&self) -> f64 { self.axis_azimuth }
}
pub struct Array {
pub mount: Box<dyn Mount>,
pub nameplate_dc: f64,
pub gamma_pdc: f64,
pub modules_per_string: u32,
pub strings: u32,
pub albedo: f64,
}
pub struct PVSystem {
pub arrays: Vec<Array>,
pub inverter_capacity: f64,
}
impl PVSystem {
pub fn new(arrays: Vec<Array>, inverter_capacity: f64) -> Self {
Self { arrays, inverter_capacity }
}
#[inline]
pub fn get_nameplate_dc_total(&self) -> f64 {
self.arrays.iter().map(|a| a.nameplate_dc).sum()
}
#[inline]
pub fn get_dc_power_total(&self, poa_global: f64, temp_cell: f64) -> f64 {
let mut total_power = 0.0;
for array in &self.arrays {
let pdc = array.nameplate_dc * (poa_global / 1000.0) * (1.0 + array.gamma_pdc * (temp_cell - 25.0));
total_power += pdc.max(0.0);
}
total_power
}
}
const KB_EV: f64 = 8.617333262e-05;
const KB_J: f64 = 1.380649e-23;
const Q_C: f64 = 1.602176634e-19;
#[derive(Debug, Clone, Copy)]
pub struct SDMParams {
pub photocurrent: f64,
pub saturation_current: f64,
pub resistance_series: f64,
pub resistance_shunt: f64,
pub n_ns_vth: f64,
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub fn calcparams_desoto(
effective_irradiance: f64,
temp_cell: f64,
alpha_sc: f64,
a_ref: f64,
i_l_ref: f64,
i_o_ref: f64,
r_sh_ref: f64,
r_s: f64,
eg_ref: f64,
d_eg_dt: f64,
) -> SDMParams {
let irrad_ref = 1000.0;
let temp_ref = 25.0;
let tref_k = temp_ref + 273.15;
let tcell_k = temp_cell + 273.15;
let eg = eg_ref * (1.0 + d_eg_dt * (tcell_k - tref_k));
let n_ns_vth = a_ref * (tcell_k / tref_k);
let photocurrent = effective_irradiance / irrad_ref
* (i_l_ref + alpha_sc * (tcell_k - tref_k));
let saturation_current = i_o_ref
* (tcell_k / tref_k).powi(3)
* (eg_ref / (KB_EV * tref_k) - eg / (KB_EV * tcell_k)).exp();
let resistance_shunt = if effective_irradiance > 0.0 {
r_sh_ref * (irrad_ref / effective_irradiance)
} else {
f64::INFINITY
};
SDMParams {
photocurrent,
saturation_current,
resistance_series: r_s,
resistance_shunt,
n_ns_vth,
}
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub fn calcparams_cec(
effective_irradiance: f64,
temp_cell: f64,
alpha_sc: f64,
a_ref: f64,
i_l_ref: f64,
i_o_ref: f64,
r_sh_ref: f64,
r_s: f64,
adjust: f64,
eg_ref: f64,
d_eg_dt: f64,
) -> SDMParams {
calcparams_desoto(
effective_irradiance,
temp_cell,
alpha_sc * (1.0 - adjust / 100.0),
a_ref,
i_l_ref,
i_o_ref,
r_sh_ref,
r_s,
eg_ref,
d_eg_dt,
)
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub fn calcparams_pvsyst(
effective_irradiance: f64,
temp_cell: f64,
alpha_sc: f64,
gamma_ref: f64,
mu_gamma: f64,
i_l_ref: f64,
i_o_ref: f64,
r_sh_ref: f64,
r_sh_0: f64,
r_s: f64,
cells_in_series: u32,
eg_ref: f64,
) -> SDMParams {
let irrad_ref = 1000.0;
let temp_ref = 25.0;
let r_sh_exp: f64 = 5.5;
let tref_k = temp_ref + 273.15;
let tcell_k = temp_cell + 273.15;
let gamma = gamma_ref + mu_gamma * (temp_cell - temp_ref);
let n_ns_vth = gamma * KB_J / Q_C * (cells_in_series as f64) * tcell_k;
let photocurrent = effective_irradiance / irrad_ref
* (i_l_ref + alpha_sc * (tcell_k - tref_k));
let saturation_current = i_o_ref
* (tcell_k / tref_k).powi(3)
* ((Q_C * eg_ref) / (KB_J * gamma) * (1.0 / tref_k - 1.0 / tcell_k)).exp();
let rsh_tmp = (r_sh_ref - r_sh_0 * (-r_sh_exp).exp()) / (1.0 - (-r_sh_exp).exp());
let rsh_base = rsh_tmp.max(0.0);
let resistance_shunt = rsh_base
+ (r_sh_0 - rsh_base) * (-r_sh_exp * effective_irradiance / irrad_ref).exp();
SDMParams {
photocurrent,
saturation_current,
resistance_series: r_s,
resistance_shunt,
n_ns_vth,
}
}
#[derive(Debug, Clone, Copy)]
pub struct SAPMParams {
pub isco: f64,
pub impo: f64,
pub voco: f64,
pub vmpo: f64,
pub aisc: f64,
pub aimp: f64,
pub bvoco: f64,
pub mbvoc: f64,
pub bvmpo: f64,
pub mbvmp: f64,
pub n: f64,
pub cells_in_series: u32,
pub c0: f64,
pub c1: f64,
pub c2: f64,
pub c3: f64,
pub a0: f64,
pub a1: f64,
pub a2: f64,
pub a3: f64,
pub a4: f64,
pub b0: f64,
pub b1: f64,
pub b2: f64,
pub b3: f64,
pub b4: f64,
pub b5: f64,
pub fd: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct SAPMOutput {
pub i_sc: f64,
pub i_mp: f64,
pub v_oc: f64,
pub v_mp: f64,
pub p_mp: f64,
}
#[inline]
pub fn sapm(effective_irradiance: f64, temp_cell: f64, module: &SAPMParams) -> SAPMOutput {
let irradiance_ref = 1000.0;
let temperature_ref = 25.0;
let ee = effective_irradiance / irradiance_ref;
let dt = temp_cell - temperature_ref;
let delta = module.n * KB_J * (temp_cell + 273.15) / Q_C;
let cells = module.cells_in_series as f64;
let bvmpo = module.bvmpo + module.mbvmp * (1.0 - ee);
let bvoco = module.bvoco + module.mbvoc * (1.0 - ee);
let log_ee = if ee > 0.0 { ee.ln() } else { f64::NEG_INFINITY };
let i_sc = module.isco * ee * (1.0 + module.aisc * dt);
let i_mp = module.impo * (module.c0 * ee + module.c1 * ee.powi(2))
* (1.0 + module.aimp * dt);
let v_oc = (module.voco + cells * delta * log_ee + bvoco * dt).max(0.0);
let v_mp = (module.vmpo
+ module.c2 * cells * delta * log_ee
+ module.c3 * cells * (delta * log_ee).powi(2)
+ bvmpo * dt)
.max(0.0);
let p_mp = i_mp * v_mp;
SAPMOutput { i_sc, i_mp, v_oc, v_mp, p_mp }
}
fn sapm_spectral_factor(airmass_absolute: f64, module: &SAPMParams) -> f64 {
let am = airmass_absolute;
let f1 = module.a0
+ module.a1 * am
+ module.a2 * am.powi(2)
+ module.a3 * am.powi(3)
+ module.a4 * am.powi(4);
if f1.is_nan() { 0.0 } else { f1.max(0.0) }
}
#[inline]
pub fn sapm_effective_irradiance(
poa_direct: f64,
poa_diffuse: f64,
airmass_absolute: f64,
aoi_val: f64,
module: &SAPMParams,
) -> f64 {
let f1 = sapm_spectral_factor(airmass_absolute, module);
let f2 = crate::iam::sapm(aoi_val, module.b0, module.b1, module.b2, module.b3, module.b4, module.b5);
f1 * (poa_direct * f2 + module.fd * poa_diffuse)
}