use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct MpptPerturb<S: ControlScalar> {
pub step: S,
pub v_min: S,
pub v_max: S,
v_ref: S,
p_prev: S,
v_prev: S,
eps: S,
}
impl<S: ControlScalar> MpptPerturb<S> {
pub fn new(v_init: S, step: S, v_min: S, v_max: S) -> Self {
Self {
step,
v_min,
v_max,
v_ref: v_init,
p_prev: S::ZERO,
v_prev: v_init,
eps: S::from_f64(1e-6),
}
}
pub fn update(&mut self, v: S, i: S) -> S {
let p = v * i;
let dp = p - self.p_prev;
let dv = v - self.v_prev;
if dp.abs() > self.eps {
let same_direction = (dp > S::ZERO && dv > S::ZERO) || (dp < S::ZERO && dv < S::ZERO);
if same_direction {
self.v_ref += self.step;
} else {
self.v_ref -= self.step;
}
self.v_ref = self.v_ref.clamp_val(self.v_min, self.v_max);
}
self.p_prev = p;
self.v_prev = v;
self.v_ref
}
pub fn v_ref(&self) -> S {
self.v_ref
}
pub fn power(&self) -> S {
self.p_prev
}
pub fn reset(&mut self, v_init: S) {
self.v_ref = v_init;
self.p_prev = S::ZERO;
self.v_prev = v_init;
}
}
#[derive(Debug, Clone, Copy)]
pub struct MpptInc<S: ControlScalar> {
pub step: S,
pub v_min: S,
pub v_max: S,
v_ref: S,
v_prev: S,
i_prev: S,
eps: S,
}
impl<S: ControlScalar> MpptInc<S> {
pub fn new(v_init: S, step: S, v_min: S, v_max: S) -> Self {
Self {
step,
v_min,
v_max,
v_ref: v_init,
v_prev: v_init,
i_prev: S::ZERO,
eps: S::from_f64(1e-6),
}
}
pub fn update(&mut self, v: S, i: S) -> S {
let dv = v - self.v_prev;
let di = i - self.i_prev;
if dv.abs() < self.eps {
if di.abs() > self.eps {
if di > S::ZERO {
self.v_ref += self.step;
} else {
self.v_ref -= self.step;
}
}
} else {
let cond = if v.abs() > self.eps {
i / v + di / dv
} else {
di / dv
};
if cond.abs() < self.eps {
} else if cond > S::ZERO {
self.v_ref += self.step;
} else {
self.v_ref -= self.step;
}
}
self.v_ref = self.v_ref.clamp_val(self.v_min, self.v_max);
self.v_prev = v;
self.i_prev = i;
self.v_ref
}
pub fn v_ref(&self) -> S {
self.v_ref
}
pub fn reset(&mut self, v_init: S) {
self.v_ref = v_init;
self.v_prev = v_init;
self.i_prev = S::ZERO;
}
}
#[derive(Debug, Clone, Copy)]
pub struct SolarPanel<S: ControlScalar> {
pub i_sc: S,
pub v_oc: S,
pub v_mpp: S,
pub i_mpp: S,
}
impl<S: ControlScalar> SolarPanel<S> {
pub fn current(&self, v: S) -> S {
let v_clamped = v.clamp_val(S::ZERO, self.v_oc);
if v_clamped <= self.v_mpp {
let slope = (self.i_sc - self.i_mpp) / self.v_mpp;
self.i_sc - slope * v_clamped
} else {
let slope = self.i_mpp / (self.v_oc - self.v_mpp);
self.i_mpp - slope * (v_clamped - self.v_mpp)
}
}
pub fn power(&self, v: S) -> S {
v * self.current(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn typical_panel() -> SolarPanel<f64> {
SolarPanel {
i_sc: 10.0,
v_oc: 40.0,
v_mpp: 33.0,
i_mpp: 9.3,
}
}
#[test]
fn po_converges_to_mpp() {
let panel = typical_panel();
let mut mppt = MpptPerturb::new(20.0_f64, 0.5, 5.0, 39.0);
for _ in 0..200 {
let v = mppt.v_ref();
let i = panel.current(v);
mppt.update(v, i);
}
let v_final = mppt.v_ref();
assert!(
(v_final - 33.0).abs() < 2.0,
"P&O: v_ref={:.2}V (expect ~33V)",
v_final
);
}
#[test]
fn inc_converges_to_mpp() {
let panel = typical_panel();
let mut mppt = MpptInc::new(20.0_f64, 0.5, 5.0, 39.0);
for _ in 0..300 {
let v = mppt.v_ref();
let i = panel.current(v);
mppt.update(v, i);
}
let v_final = mppt.v_ref();
assert!(
(v_final - 33.0).abs() < 2.0,
"InC: v_ref={:.2}V (expect ~33V)",
v_final
);
}
#[test]
fn panel_mpp_is_maximum() {
let panel = typical_panel();
let p_mpp = panel.power(33.0_f64);
let p_low = panel.power(20.0_f64);
let p_high = panel.power(38.0_f64);
assert!(p_mpp > p_low, "MPP should give more power than low V");
assert!(p_mpp > p_high, "MPP should give more power than high V");
}
#[test]
fn mppt_resets_cleanly() {
let mut mppt = MpptPerturb::new(20.0_f64, 0.5, 5.0, 39.0);
mppt.update(25.0, 8.0);
mppt.reset(20.0);
assert!((mppt.v_ref() - 20.0).abs() < 1e-10);
}
}