use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerturbObserve {
pub v_op: f64,
prev_power: f64,
prev_voltage: f64,
pub delta_v: f64,
pub v_min: f64,
pub v_max: f64,
}
impl PerturbObserve {
pub fn new(v_init: f64, delta_v: f64, v_min: f64, v_max: f64) -> Self {
Self {
v_op: v_init,
prev_power: -f64::INFINITY,
prev_voltage: v_init,
delta_v,
v_min,
v_max,
}
}
pub fn update(&mut self, v: f64, i: f64) -> f64 {
let p = v * i;
let dp = p - self.prev_power;
let dv = v - self.prev_voltage;
let direction = if dp.abs() < 1e-9 {
0.0 } else if dp > 0.0 {
if dv >= 0.0 {
1.0
} else {
-1.0
} } else if dv >= 0.0 {
-1.0 } else {
1.0
};
self.prev_power = p;
self.prev_voltage = v;
self.v_op = (v + direction * self.delta_v).clamp(self.v_min, self.v_max);
self.v_op
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IncrementalConductance {
pub v_ref: f64,
prev_v: f64,
prev_i: f64,
pub delta_v: f64,
pub v_min: f64,
pub v_max: f64,
}
impl IncrementalConductance {
pub fn new(v_init: f64, delta_v: f64, v_min: f64, v_max: f64) -> Self {
Self {
v_ref: v_init,
prev_v: v_init,
prev_i: 0.0,
delta_v,
v_min,
v_max,
}
}
pub fn update(&mut self, v: f64, i: f64) -> f64 {
let dv = v - self.prev_v;
let di = i - self.prev_i;
let direction = if dv.abs() < 1e-9 {
if di.abs() < 1e-9 {
0.0 } else if di > 0.0 {
1.0
} else {
-1.0
}
} else {
let inc_cond = di / dv; let inst_cond = -i / v.max(1e-9); let err = inc_cond - inst_cond;
if err.abs() < 1e-6 {
0.0 } else if err > 0.0 {
1.0 } else {
-1.0 }
};
self.prev_v = v;
self.prev_i = i;
self.v_ref = (v + direction * self.delta_v).clamp(self.v_min, self.v_max);
self.v_ref
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::renewable::solar::pv_cell::{diode_current, find_mpp, SingleDiodeParams};
fn params() -> SingleDiodeParams {
SingleDiodeParams::crystalline_si_250w()
}
#[test]
fn test_po_converges_to_mpp() {
let p = params();
let mpp = find_mpp(&p, 1000.0, 298.15);
let mut ctrl = PerturbObserve::new(mpp.voltage * 0.5, 0.5, 1.0, 50.0);
for _ in 0..200 {
let v = ctrl.v_op;
let i = diode_current(&p, v, 1000.0, 298.15);
ctrl.update(v, i);
}
let v_final = ctrl.v_op;
let i_final = diode_current(&p, v_final, 1000.0, 298.15);
let p_final = v_final * i_final;
assert!(
(p_final - mpp.power).abs() / mpp.power < 0.02,
"P&O: p_final={:.2} mpp={:.2}",
p_final,
mpp.power
);
}
#[test]
fn test_inc_converges_to_mpp() {
let p = params();
let mpp = find_mpp(&p, 1000.0, 298.15);
let mut ctrl = IncrementalConductance::new(mpp.voltage * 0.5, 0.3, 1.0, 50.0);
for _ in 0..300 {
let v = ctrl.v_ref;
let i = diode_current(&p, v, 1000.0, 298.15);
ctrl.update(v, i);
}
let v_final = ctrl.v_ref;
let i_final = diode_current(&p, v_final, 1000.0, 298.15);
let p_final = v_final * i_final;
assert!(
(p_final - mpp.power).abs() / mpp.power < 0.02,
"InC: p_final={:.2} mpp={:.2}",
p_final,
mpp.power
);
}
}