use super::*;
pub trait LocoTrait {
fn set_curr_pwr_max_out(
&mut self,
pwr_aux: Option<si::Power>,
elev_and_temp: Option<(si::Length, si::ThermodynamicTemperature)>,
train_mass: Option<si::Mass>,
train_speed: Option<si::Velocity>,
dt: si::Time,
) -> anyhow::Result<()>;
fn get_energy_loss(&self) -> anyhow::Result<si::Energy>;
}
#[serde_api]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(module = "altrios", subclass, eq))]
pub struct Pyo3VecLocoWrapper(pub Vec<Locomotive>);
#[pyo3_api]
impl Pyo3VecLocoWrapper {
#[new]
fn __new__(v: Vec<Locomotive>) -> Self {
Self(v)
}
}
impl Pyo3VecLocoWrapper {
pub fn new(value: Vec<Locomotive>) -> Self {
Self(value)
}
}
impl Init for Pyo3VecLocoWrapper {
fn init(&mut self) -> Result<(), Error> {
self.0.iter_mut().try_for_each(|l| l.init())?;
Ok(())
}
}
impl SerdeAPI for Pyo3VecLocoWrapper {}
pub trait SolvePower {
fn solve_positive_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
train_mass: Option<si::Mass>,
train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>>;
fn solve_negative_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
train_mass: Option<si::Mass>,
train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>>;
}
#[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
pub struct RESGreedy;
impl SolvePower for RESGreedy {
fn solve_positive_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
_train_mass: Option<si::Mass>,
_train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
let loco_pwr_out_vec: Vec<si::Power> = if *state
.pwr_out_deficit
.get_fresh(|| format_dbg!())?
== si::Power::ZERO
{
let mut loco_pwr_out_vec: Vec<si::Power> = vec![];
for loco in loco_vec {
loco_pwr_out_vec.push(match &loco.loco_type {
PowertrainType::ConventionalLoco(_) => si::Power::ZERO,
PowertrainType::HybridLoco(_) => {
*loco.state.pwr_out_max.get_fresh(|| format_dbg!())?
/ *state.pwr_out_max_reves.get_fresh(|| format_dbg!())?
* *state.pwr_out_req.get_fresh(|| format_dbg!())?
}
PowertrainType::BatteryElectricLoco(_) => {
*loco.state.pwr_out_max.get_fresh(|| format_dbg!())?
/ *state.pwr_out_max_reves.get_fresh(|| format_dbg!())?
* *state.pwr_out_req.get_fresh(|| format_dbg!())?
}
PowertrainType::DummyLoco(_) => {
*state.pwr_out_req.get_fresh(|| format_dbg!())?
}
})
}
loco_pwr_out_vec
} else {
let mut loco_pwr_out_vec: Vec<si::Power> = vec![];
for loco in loco_vec {
loco_pwr_out_vec.push( match &loco.loco_type {
PowertrainType::ConventionalLoco(_) => {
* loco.state.pwr_out_max.get_fresh(|| format_dbg!())? / *state.pwr_out_max_non_reves.get_fresh(|| format_dbg!())?
* *state.pwr_out_deficit.get_fresh(|| format_dbg!())?
}
PowertrainType::HybridLoco(_) => *loco.state.pwr_out_max.get_fresh(|| format_dbg!())?,
PowertrainType::BatteryElectricLoco(_) => *loco.state.pwr_out_max.get_fresh(|| format_dbg!())?,
PowertrainType::DummyLoco(_) => {
si::Power::ZERO
}
})
}
loco_pwr_out_vec
};
let loco_pwr_out_vec_sum: si::Power = loco_pwr_out_vec.iter().copied().sum();
ensure!(
utils::almost_eq_uom(
&loco_pwr_out_vec.iter().copied().sum(),
state.pwr_out_req.get_fresh(|| format_dbg!())?,
None,
),
format!(
"{}\n{}",
format_dbg!(loco_pwr_out_vec_sum.get::<si::kilowatt>()),
format_dbg!(state
.pwr_out_req
.get_fresh(|| format_dbg!())?
.get::<si::kilowatt>())
)
);
Ok(loco_pwr_out_vec)
}
fn solve_negative_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
train_mass: Option<si::Mass>,
train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
solve_negative_traction(loco_vec, state, train_mass, train_speed)
}
}
fn get_pwr_regen_vec(
loco_vec: &[Locomotive],
regen_frac: si::Ratio,
) -> anyhow::Result<Vec<si::Power>> {
let mut pwr_regen_vec: Vec<si::Power> = vec![];
for loco in loco_vec {
pwr_regen_vec.push(match &loco.loco_type {
PowertrainType::ConventionalLoco(_) => si::Power::ZERO,
PowertrainType::HybridLoco(_) => {
*loco.state.pwr_regen_max.get_fresh(|| format_dbg!())? * regen_frac
}
PowertrainType::BatteryElectricLoco(_) => {
*loco.state.pwr_regen_max.get_fresh(|| format_dbg!())? * regen_frac
}
PowertrainType::DummyLoco(_) => si::Power::ZERO,
})
}
Ok(pwr_regen_vec)
}
fn solve_negative_traction(
loco_vec: &[Locomotive],
consist_state: &ConsistState,
_train_mass: Option<si::Mass>,
_train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
let pwr_brake_req = -*consist_state.pwr_out_req.get_fresh(|| format_dbg!())?;
let regen_frac = if *consist_state.pwr_regen_max.get_fresh(|| format_dbg!())? == si::Power::ZERO
{
si::Ratio::ZERO
} else {
(pwr_brake_req / *consist_state.pwr_regen_max.get_fresh(|| format_dbg!())?).min(uc::R * 1.)
};
let pwr_out_vec: Vec<si::Power> = if *consist_state
.pwr_regen_deficit
.get_fresh(|| format_dbg!())?
== si::Power::ZERO
{
get_pwr_regen_vec(loco_vec, regen_frac)?
} else {
let pwr_regen_vec = get_pwr_regen_vec(loco_vec, regen_frac)?;
let pwr_surplus_vec: Vec<si::Power> = loco_vec
.iter()
.zip(&pwr_regen_vec)
.map(|(loco, pwr_regen)| {
loco.electric_drivetrain()
.expect("this `expect` might cause problems for DummyLoco")
.pwr_out_max
- *pwr_regen
})
.collect();
let pwr_surplus_sum = pwr_surplus_vec
.iter()
.fold(0.0 * uc::W, |acc, &curr| acc + curr);
let surplus_frac = *consist_state
.pwr_regen_deficit
.get_fresh(|| format_dbg!())?
/ pwr_surplus_sum;
ensure!(
surplus_frac >= si::Ratio::ZERO && surplus_frac <= uc::R,
format_dbg!(surplus_frac),
);
let pwr_dyn_brake_vec: Vec<si::Power> = pwr_surplus_vec
.iter()
.zip(pwr_regen_vec)
.map(|(pwr_surplus, pwr_regen)| *pwr_surplus * surplus_frac + pwr_regen)
.collect();
pwr_dyn_brake_vec
};
let pwr_out_vec: Vec<si::Power> = pwr_out_vec.iter().map(|x| -*x).collect();
Ok(pwr_out_vec)
}
#[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
pub struct Proportional;
#[allow(unused_variables)]
#[allow(unreachable_code)]
impl SolvePower for Proportional {
fn solve_positive_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
_train_mass: Option<si::Mass>,
_train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
todo!("this need some attention to make sure it handles the hybrid correctly");
let mut loco_pwr_vec: Vec<si::Power> = vec![];
for loco in loco_vec {
loco_pwr_vec.push(
*loco.state.pwr_out_max.get_fresh(|| format_dbg!())?
/ *state.pwr_out_max.get_fresh(|| format_dbg!())?
* *state.pwr_out_req.get_fresh(|| format_dbg!())?,
)
}
Ok(loco_pwr_vec)
}
fn solve_negative_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
train_mass: Option<si::Mass>,
train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
todo!("this need some attention to make sure it handles the hybrid correctly");
solve_negative_traction(loco_vec, state, train_mass, train_speed)
}
}
#[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
pub struct FrontAndBack;
impl SerdeAPI for FrontAndBack {}
impl Init for FrontAndBack {}
impl SolvePower for FrontAndBack {
fn solve_positive_traction(
&mut self,
_loco_vec: &[Locomotive],
_state: &ConsistState,
_train_mass: Option<si::Mass>,
_train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
todo!() }
fn solve_negative_traction(
&mut self,
_loco_vec: &[Locomotive],
_state: &ConsistState,
_train_mass: Option<si::Mass>,
_train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
todo!() }
}
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
pub enum PowerDistributionControlType {
RESGreedy(RESGreedy),
Proportional(Proportional),
FrontAndBack(FrontAndBack),
}
impl SolvePower for PowerDistributionControlType {
fn solve_negative_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
train_mass: Option<si::Mass>,
train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
match self {
Self::RESGreedy(res_greedy) => {
res_greedy.solve_negative_traction(loco_vec, state, train_mass, train_speed)
}
Self::Proportional(prop) => {
prop.solve_negative_traction(loco_vec, state, train_mass, train_speed)
}
Self::FrontAndBack(fab) => {
fab.solve_negative_traction(loco_vec, state, train_mass, train_speed)
}
}
}
fn solve_positive_traction(
&mut self,
loco_vec: &[Locomotive],
state: &ConsistState,
train_mass: Option<si::Mass>,
train_speed: Option<si::Velocity>,
) -> anyhow::Result<Vec<si::Power>> {
match self {
Self::RESGreedy(res_greedy) => {
res_greedy.solve_positive_traction(loco_vec, state, train_mass, train_speed)
}
Self::Proportional(prop) => {
prop.solve_positive_traction(loco_vec, state, train_mass, train_speed)
}
Self::FrontAndBack(fab) => {
fab.solve_positive_traction(loco_vec, state, train_mass, train_speed)
}
}
}
}
impl Default for PowerDistributionControlType {
fn default() -> Self {
Self::RESGreedy(RESGreedy)
}
}
impl Init for PowerDistributionControlType {}
impl SerdeAPI for PowerDistributionControlType {}