use cobre_core::{EfficiencyModel, HydraulicLossesModel, TailraceModel};
use super::geometry::{ForebayTable, evaluate_losses, evaluate_tailrace};
use super::tailrace::TailraceFamilies;
#[derive(Debug, Clone)]
pub(crate) enum TailraceSource {
Entity(Option<TailraceModel>),
Families {
families: TailraceFamilies,
downstream_level_m: Option<f64>,
},
}
const K: f64 = 9.81 / 1000.0;
#[derive(Debug, Clone)]
pub(crate) struct ProductionFunction {
forebay: ForebayTable,
tailrace: TailraceSource,
hydraulic_losses: Option<HydraulicLossesModel>,
efficiency: f64,
pub(crate) max_turbined_m3s: f64,
max_generation_mw: f64,
#[allow(dead_code)]
pub(crate) hydro_name: String,
}
impl ProductionFunction {
pub(crate) fn new(
forebay: ForebayTable,
tailrace: TailraceSource,
hydraulic_losses: Option<&HydraulicLossesModel>,
efficiency: Option<&EfficiencyModel>,
max_turbined_m3s: f64,
hydro_name: String,
) -> Self {
let efficiency_value = match efficiency {
Some(EfficiencyModel::Constant { value }) => *value,
None => 1.0,
};
Self {
forebay,
tailrace,
hydraulic_losses: hydraulic_losses.copied(),
efficiency: efficiency_value,
max_turbined_m3s,
max_generation_mw: f64::INFINITY,
hydro_name,
}
}
pub(crate) fn with_max_generation_mw(mut self, max_generation_mw: f64) -> Self {
self.max_generation_mw = if max_generation_mw > 0.0 {
max_generation_mw
} else {
f64::INFINITY
};
self
}
pub(crate) fn net_head(&self, v: f64, q: f64, s: f64) -> f64 {
let h_fore = self.forebay.height(v);
let q_out = q + s;
let h_tail = match &self.tailrace {
TailraceSource::Entity(model) => {
model.as_ref().map_or(0.0, |m| evaluate_tailrace(m, q_out))
}
TailraceSource::Families {
families,
downstream_level_m,
} => families.evaluate(q_out, *downstream_level_m),
};
let gross_head = h_fore - h_tail;
let h_loss = self
.hydraulic_losses
.as_ref()
.map_or(0.0, |m| evaluate_losses(m, gross_head, q));
let h_net = gross_head - h_loss;
h_net.max(0.0)
}
pub(crate) fn evaluate(&self, v: f64, q: f64, s: f64) -> f64 {
let h_net = self.net_head(v, q, s);
K * self.efficiency * q * h_net
}
pub(crate) fn evaluate_capped(&self, v: f64, q: f64, s: f64) -> f64 {
self.evaluate(v, q, s).min(self.max_generation_mw)
}
}