use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::num::NonZeroU32;
use rust_decimal::Decimal;
use time::OffsetDateTime;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::ids::MeloId;
use crate::precision::{FORMULA_QUOTIENT_DP, FORMULA_QUOTIENT_STRATEGY};
use crate::series::interval::{Direction, IntervalError, MeterInterval, QualityFlag};
use crate::series::{Series, SeriesError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Operator {
Addition,
Subtraktion,
Divisor,
Dividend,
Faktor,
Positivwert,
}
impl Operator {
pub const ALL: [Self; 6] = [
Self::Addition,
Self::Subtraktion,
Self::Divisor,
Self::Dividend,
Self::Faktor,
Self::Positivwert,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Addition => "Z69",
Self::Subtraktion => "Z70",
Self::Divisor => "Z80",
Self::Dividend => "Z81",
Self::Faktor => "Z82",
Self::Positivwert => "Z83",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FlowDirection {
Verbrauch,
Erzeugung,
}
impl FlowDirection {
pub const ALL: [Self; 2] = [Self::Verbrauch, Self::Erzeugung];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Verbrauch => "Z71",
Self::Erzeugung => "Z72",
}
}
#[must_use]
pub const fn direction(self) -> Direction {
match self {
Self::Verbrauch => Direction::Import,
Self::Erzeugung => Direction::Export,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FactorKind {
VerlustfaktorTrafo,
VerlustfaktorLeitung,
Aufteilungsfaktor,
}
impl FactorKind {
pub const ALL: [Self; 3] = [
Self::VerlustfaktorTrafo,
Self::VerlustfaktorLeitung,
Self::Aufteilungsfaktor,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::VerlustfaktorTrafo => "Z16",
Self::VerlustfaktorLeitung => "ZB2",
Self::Aufteilungsfaktor => "ZG6",
}
}
}
crate::ids::codes::string_codes! {
Operator;
FlowDirection;
FactorKind;
}
const FACTOR_DP: u32 = 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LossFactor(Decimal);
impl LossFactor {
pub fn new(value: Decimal) -> Result<Self, FormulaError> {
if value > Decimal::ZERO && value != Decimal::ONE && places(value) <= FACTOR_DP {
Ok(Self(value.normalize()))
} else {
Err(FormulaError::InvalidLossFactor { value })
}
}
#[must_use]
pub const fn get(self) -> Decimal {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SplitFactor(Decimal);
impl SplitFactor {
pub fn new(value: Decimal) -> Result<Self, FormulaError> {
if value > Decimal::ZERO && value <= Decimal::ONE && places(value) <= FACTOR_DP {
Ok(Self(value.normalize()))
} else {
Err(FormulaError::InvalidSplitFactor { value })
}
}
#[must_use]
pub const fn get(self) -> Decimal {
self.0
}
}
fn places(value: Decimal) -> u32 {
value.normalize().scale()
}
#[cfg(feature = "serde")]
macro_rules! factor_serde {
($ty:ty) => {
impl Serialize for $ty {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
crate::wire::decimal::serialize(&self.0, serializer)
}
}
impl<'de> Deserialize<'de> for $ty {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = crate::wire::decimal::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
};
}
#[cfg(feature = "serde")]
factor_serde!(LossFactor);
#[cfg(feature = "serde")]
factor_serde!(SplitFactor);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "u32", into = "u32"))]
pub struct StepId(NonZeroU32);
impl StepId {
pub const MAX: u32 = 99_999;
pub fn new(id: u32) -> Result<Self, FormulaError> {
NonZeroU32::new(id)
.filter(|n| n.get() <= Self::MAX)
.map(Self)
.ok_or(FormulaError::InvalidStepId { id })
}
#[must_use]
pub const fn get(self) -> u32 {
self.0.get()
}
}
impl TryFrom<u32> for StepId {
type Error = FormulaError;
fn try_from(id: u32) -> Result<Self, Self::Error> {
Self::new(id)
}
}
impl From<StepId> for u32 {
fn from(id: StepId) -> Self {
id.get()
}
}
impl fmt::Display for StepId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MeloOperand {
pub melo: MeloId,
pub direction: FlowDirection,
pub loss_trafo: Option<LossFactor>,
pub loss_line: Option<LossFactor>,
pub split: Option<SplitFactor>,
}
impl MeloOperand {
#[must_use]
pub const fn new(melo: MeloId, direction: FlowDirection) -> Self {
Self {
melo,
direction,
loss_trafo: None,
loss_line: None,
split: None,
}
}
#[must_use]
pub const fn trafo(mut self, factor: LossFactor) -> Self {
self.loss_trafo = Some(factor);
self
}
#[must_use]
pub const fn line(mut self, factor: LossFactor) -> Self {
self.loss_line = Some(factor);
self
}
#[must_use]
pub const fn split(mut self, factor: SplitFactor) -> Self {
self.split = Some(factor);
self
}
pub fn with_factor(mut self, kind: FactorKind, value: Decimal) -> Result<Self, FormulaError> {
let taken = match kind {
FactorKind::VerlustfaktorTrafo => self.loss_trafo.is_some(),
FactorKind::VerlustfaktorLeitung => self.loss_line.is_some(),
FactorKind::Aufteilungsfaktor => self.split.is_some(),
};
if taken {
return Err(FormulaError::DuplicateFactor {
melo: self.melo,
kind,
});
}
match kind {
FactorKind::VerlustfaktorTrafo => self.loss_trafo = Some(LossFactor::new(value)?),
FactorKind::VerlustfaktorLeitung => self.loss_line = Some(LossFactor::new(value)?),
FactorKind::Aufteilungsfaktor => self.split = Some(SplitFactor::new(value)?),
}
Ok(self)
}
fn scale(&self, register: Decimal) -> Option<Decimal> {
[
self.loss_trafo.map(LossFactor::get),
self.loss_line.map(LossFactor::get),
self.split.map(SplitFactor::get),
]
.into_iter()
.flatten()
.try_fold(register, Decimal::checked_mul)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum Operand {
Melo(MeloOperand),
Step(StepId),
}
impl From<MeloOperand> for Operand {
fn from(melo: MeloOperand) -> Self {
Self::Melo(melo)
}
}
impl From<StepId> for Operand {
fn from(step: StepId) -> Self {
Self::Step(step)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Sign {
#[cfg_attr(feature = "serde", serde(rename = "Z69"))]
Plus,
#[cfg_attr(feature = "serde", serde(rename = "Z70"))]
Minus,
}
impl Sign {
#[must_use]
pub const fn operator(self) -> Operator {
match self {
Self::Plus => Operator::Addition,
Self::Minus => Operator::Subtraktion,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum Step {
Sum(Vec<(Sign, Operand)>),
Quotient {
dividend: Operand,
divisor: Operand,
},
Product(Vec<Operand>),
Positive(StepId),
}
impl Step {
fn operands(&self) -> Vec<Operand> {
match self {
Self::Sum(terms) => terms.iter().map(|(_, o)| *o).collect(),
Self::Quotient { dividend, divisor } => vec![*dividend, *divisor],
Self::Product(factors) => factors.clone(),
Self::Positive(step) => vec![Operand::Step(*step)],
}
}
fn components(&self) -> usize {
self.operands().len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Component {
pub step: StepId,
pub operator: Operator,
pub operand: Operand,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum FormulaError {
#[error("step id {id} is outside 1..=99999")]
InvalidStepId {
id: u32,
},
#[error("loss factor {value} must be > 0, ≠ 1 and have at most 6 decimal places")]
InvalidLossFactor {
value: Decimal,
},
#[error("split factor {value} must be > 0, ≤ 1 and have at most 6 decimal places")]
InvalidSplitFactor {
value: Decimal,
},
#[error("{kind} is given twice on the component for {melo}")]
DuplicateFactor {
melo: MeloId,
kind: FactorKind,
},
#[error("the result step {result} does not exist")]
MissingResult {
result: StepId,
},
#[error("step {referrer} references the missing step {step}")]
UnknownStep {
referrer: StepId,
step: StepId,
},
#[error("step {step} references itself")]
SelfReference {
step: StepId,
},
#[error("the steps form a cycle: {cycle:?}")]
Cycle {
cycle: Vec<StepId>,
},
#[error("step {step} is not referenced by the result")]
Unreferenced {
step: StepId,
},
#[error("step {step} has no component")]
EmptyStep {
step: StepId,
},
#[error("the formula has {count} components, more than 99999")]
TooManyComponents {
count: usize,
},
#[error("step {step} combines the operators {operators:?}, which no step kind permits")]
MixedStep {
step: StepId,
operators: Vec<Operator>,
},
#[error("step {step} applies Positivwert to a Messlokation")]
PositivwertOverMelo {
step: StepId,
},
#[error("{melo} is missing from, or repeated in, the participants")]
Participants {
melo: MeloId,
},
#[error("no series for {melo} {direction}")]
MissingSeries {
melo: MeloId,
direction: FlowDirection,
},
#[error(
"the series for {melo} {direction} is on a different grid (first difference at {at:?})"
)]
GridMismatch {
melo: MeloId,
direction: FlowDirection,
at: Option<OffsetDateTime>,
},
#[error("the {melo} {direction} value at {at} is negative")]
NegativeValue {
melo: MeloId,
direction: FlowDirection,
at: OffsetDateTime,
},
#[error("step {step} overflows at {at}")]
Overflow {
step: StepId,
at: OffsetDateTime,
},
#[error(transparent)]
Series(#[from] SeriesError),
#[error(transparent)]
Interval(#[from] IntervalError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "RawFormula", into = "RawFormula"))]
pub struct Formula {
steps: BTreeMap<StepId, Step>,
result: StepId,
order: Vec<StepId>,
}
#[cfg(feature = "serde")]
#[derive(Serialize, Deserialize)]
struct RawFormula {
result: StepId,
steps: BTreeMap<StepId, Step>,
}
#[cfg(feature = "serde")]
impl TryFrom<RawFormula> for Formula {
type Error = FormulaError;
fn try_from(raw: RawFormula) -> Result<Self, Self::Error> {
Self::new(raw.result, raw.steps)
}
}
#[cfg(feature = "serde")]
impl From<Formula> for RawFormula {
fn from(formula: Formula) -> Self {
Self {
result: formula.result,
steps: formula.steps,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ZeroDivisor {
pub step: StepId,
#[cfg_attr(feature = "serde", serde(with = "crate::wire::rfc3339"))]
pub at: OffsetDateTime,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct Evaluation {
pub series: Series,
pub zero_divisors: Vec<ZeroDivisor>,
}
impl Formula {
pub fn new(
result: StepId,
steps: impl IntoIterator<Item = (StepId, Step)>,
) -> Result<Self, FormulaError> {
let steps: BTreeMap<StepId, Step> = steps.into_iter().collect();
let count: usize = steps.values().map(Step::components).sum();
if count > StepId::MAX as usize {
return Err(FormulaError::TooManyComponents { count });
}
if !steps.contains_key(&result) {
return Err(FormulaError::MissingResult { result });
}
for (&id, step) in &steps {
let empty = match step {
Step::Sum(terms) => terms.is_empty(),
Step::Product(factors) => factors.is_empty(),
Step::Quotient { .. } | Step::Positive(_) => false,
};
if empty {
return Err(FormulaError::EmptyStep { step: id });
}
for target in references(step) {
if target == id {
return Err(FormulaError::SelfReference { step: id });
}
if !steps.contains_key(&target) {
return Err(FormulaError::UnknownStep {
referrer: id,
step: target,
});
}
}
}
let order = topological(&steps, result)?;
Ok(Self {
steps,
result,
order,
})
}
pub fn from_components(
result: StepId,
components: impl IntoIterator<Item = Component>,
) -> Result<Self, FormulaError> {
let mut groups: BTreeMap<StepId, Vec<(Operator, Operand)>> = BTreeMap::new();
let mut count = 0usize;
for c in components {
count += 1;
groups
.entry(c.step)
.or_default()
.push((c.operator, c.operand));
}
if count > StepId::MAX as usize {
return Err(FormulaError::TooManyComponents { count });
}
let steps = groups
.into_iter()
.map(|(id, group)| Ok((id, step_of(id, &group)?)))
.collect::<Result<Vec<_>, FormulaError>>()?;
Self::new(result, steps)
}
pub fn melo(operand: MeloOperand) -> Result<Self, FormulaError> {
Self::sum([operand])
}
pub fn sum(operands: impl IntoIterator<Item = MeloOperand>) -> Result<Self, FormulaError> {
let one = first();
let terms = operands
.into_iter()
.map(|o| (Sign::Plus, Operand::Melo(o)))
.collect();
Self::new(one, [(one, Step::Sum(terms))])
}
pub fn residual(
total: MeloOperand,
subtract: impl IntoIterator<Item = MeloOperand>,
) -> Result<Self, FormulaError> {
let one = first();
let terms = std::iter::once((Sign::Plus, Operand::Melo(total)))
.chain(
subtract
.into_iter()
.map(|o| (Sign::Minus, Operand::Melo(o))),
)
.collect();
Self::new(one, [(one, Step::Sum(terms))])
}
pub fn constant_share(
tenant: MeloId,
plant: MeloId,
factor: SplitFactor,
) -> Result<Self, FormulaError> {
let [s1, s2, s3] = ids::<3>();
let generation = MeloOperand::new(plant, FlowDirection::Erzeugung).split(factor);
Self::new(
s3,
[
(s1, Step::Product(vec![generation.into()])),
(
s2,
Step::Sum(vec![
(Sign::Minus, s1.into()),
(Sign::Plus, verbrauch(tenant)),
]),
),
(s3, Step::Positive(s2)),
],
)
}
pub fn proportional_share(
tenant: MeloId,
plant: MeloId,
participants: &[MeloId],
) -> Result<Self, FormulaError> {
let mut seen = BTreeSet::new();
if let Some(twice) = participants.iter().find(|m| !seen.insert(**m)) {
return Err(FormulaError::Participants { melo: *twice });
}
if !seen.contains(&tenant) {
return Err(FormulaError::Participants { melo: tenant });
}
let [s1, s2, s3, s4, s5] = ids::<5>();
let denominator = participants
.iter()
.map(|m| (Sign::Plus, verbrauch(*m)))
.collect();
Self::new(
s5,
[
(s1, Step::Sum(denominator)),
(
s2,
Step::Quotient {
dividend: verbrauch(tenant),
divisor: s1.into(),
},
),
(
s3,
Step::Product(vec![
s2.into(),
MeloOperand::new(plant, FlowDirection::Erzeugung).into(),
]),
),
(
s4,
Step::Sum(vec![
(Sign::Plus, verbrauch(tenant)),
(Sign::Minus, s3.into()),
]),
),
(s5, Step::Positive(s4)),
],
)
}
#[must_use]
pub const fn steps(&self) -> &BTreeMap<StepId, Step> {
&self.steps
}
#[must_use]
pub const fn result(&self) -> StepId {
self.result
}
#[must_use]
pub fn inputs(&self) -> BTreeSet<(MeloId, FlowDirection)> {
self.steps
.values()
.flat_map(Step::operands)
.filter_map(|o| match o {
Operand::Melo(m) => Some((m.melo, m.direction)),
Operand::Step(_) => None,
})
.collect()
}
pub fn eval<'a>(
&self,
sources: impl Fn(&MeloId, Direction) -> Option<&'a Series>,
) -> Result<Evaluation, FormulaError> {
let keys: Vec<(MeloId, FlowDirection)> = self.inputs().into_iter().collect();
let mut inputs: Vec<&Series> = Vec::with_capacity(keys.len());
for &(melo, direction) in &keys {
let series = sources(&melo, direction.direction())
.ok_or(FormulaError::MissingSeries { melo, direction })?;
if let Some(first) = inputs.first()
&& let Err(at) = same_grid(first, series)
{
return Err(FormulaError::GridMismatch {
melo,
direction,
at,
});
}
if let Some(iv) = series.iter().find(|iv| iv.value() < Decimal::ZERO) {
return Err(FormulaError::NegativeValue {
melo,
direction,
at: iv.from(),
});
}
inputs.push(series);
}
let Some(&grid) = inputs.first() else {
return Err(FormulaError::MissingResult {
result: self.result,
});
};
let slot: BTreeMap<StepId, usize> = self
.order
.iter()
.enumerate()
.map(|(i, id)| (*id, i))
.collect();
let input: BTreeMap<(MeloId, FlowDirection), usize> =
keys.iter().enumerate().map(|(i, k)| (*k, i)).collect();
let mut out = Vec::with_capacity(grid.len());
let mut zero_divisors = Vec::new();
let mut values = vec![Decimal::ZERO; self.order.len()];
for (k, reference) in grid.iter().enumerate() {
let at = reference.from();
let read = |operand: &Operand, values: &[Decimal]| -> Option<Decimal> {
match operand {
Operand::Step(id) => values.get(*slot.get(id)?).copied(),
Operand::Melo(m) => {
let series = inputs.get(*input.get(&(m.melo, m.direction))?)?;
m.scale(series.as_slice().get(k)?.value())
}
}
};
for (pos, id) in self.order.iter().enumerate() {
let overflow = FormulaError::Overflow { step: *id, at };
let value = match self.steps.get(id) {
Some(Step::Sum(terms)) => {
terms.iter().try_fold(Decimal::ZERO, |acc, (s, o)| {
let v = read(o, &values)?;
match s {
Sign::Plus => acc.checked_add(v),
Sign::Minus => acc.checked_sub(v),
}
})
}
Some(Step::Product(factors)) => factors
.iter()
.try_fold(Decimal::ONE, |acc, o| acc.checked_mul(read(o, &values)?)),
Some(Step::Quotient { dividend, divisor }) => {
match (read(dividend, &values), read(divisor, &values)) {
(Some(_), Some(d)) if d.is_zero() => {
zero_divisors.push(ZeroDivisor { step: *id, at });
Some(Decimal::ZERO)
}
(Some(n), Some(d)) => quotient(n, d),
_ => None,
}
}
Some(Step::Positive(step)) => {
read(&Operand::Step(*step), &values).map(|v| v.max(Decimal::ZERO))
}
None => None,
};
let slot_value = values.get_mut(pos).ok_or_else(|| overflow.clone())?;
*slot_value = value.ok_or(overflow)?;
}
let result = values
.get(slot.get(&self.result).copied().unwrap_or(usize::MAX))
.copied()
.ok_or(FormulaError::MissingResult {
result: self.result,
})?;
let quality = QualityFlag::worst_of(
inputs
.iter()
.filter_map(|s| s.as_slice().get(k).map(MeterInterval::quality)),
);
out.push(MeterInterval::new(at, reference.to(), result, quality)?);
}
Ok(Evaluation {
series: Series::new(grid.resolution(), grid.boundary(), out)?,
zero_divisors,
})
}
}
pub(crate) fn quotient(dividend: Decimal, divisor: Decimal) -> Option<Decimal> {
let mut q = dividend
.checked_div(divisor)?
.round_dp_with_strategy(FORMULA_QUOTIENT_DP, FORMULA_QUOTIENT_STRATEGY);
if q.checked_mul(divisor)?.abs() > dividend.abs() {
let unit = Decimal::new(1, FORMULA_QUOTIENT_DP);
q = if q.is_sign_negative() {
q.checked_add(unit)?
} else {
q.checked_sub(unit)?
};
}
Some(q)
}
pub(crate) fn same_grid(a: &Series, b: &Series) -> Result<(), Option<OffsetDateTime>> {
let span = |iv: &MeterInterval| (iv.from(), iv.to());
if let Some((x, y)) = a
.iter()
.map(span)
.zip(b.iter().map(span))
.find(|(x, y)| x != y)
{
return Err(Some(x.0.min(y.0)));
}
if a.len() != b.len() {
let extra = a
.as_slice()
.get(b.len())
.or_else(|| b.as_slice().get(a.len()));
return Err(extra.map(MeterInterval::from));
}
if a.resolution() != b.resolution() || a.boundary() != b.boundary() {
return Err(None);
}
Ok(())
}
fn references(step: &Step) -> Vec<StepId> {
step.operands()
.into_iter()
.filter_map(|o| match o {
Operand::Step(id) => Some(id),
Operand::Melo(_) => None,
})
.collect()
}
fn topological(
steps: &BTreeMap<StepId, Step>,
result: StepId,
) -> Result<Vec<StepId>, FormulaError> {
#[derive(Clone, Copy, PartialEq)]
enum Mark {
Open,
Done,
}
fn visit(
id: StepId,
steps: &BTreeMap<StepId, Step>,
marks: &mut BTreeMap<StepId, Mark>,
path: &mut Vec<StepId>,
order: &mut Vec<StepId>,
) -> Result<(), FormulaError> {
match marks.get(&id) {
Some(Mark::Done) => return Ok(()),
Some(Mark::Open) => {
let start = path.iter().position(|s| *s == id).unwrap_or(0);
let mut cycle = path.get(start..).unwrap_or_default().to_vec();
cycle.push(id);
return Err(FormulaError::Cycle { cycle });
}
None => {}
}
marks.insert(id, Mark::Open);
path.push(id);
for next in steps.get(&id).map(references).unwrap_or_default() {
visit(next, steps, marks, path, order)?;
}
path.pop();
marks.insert(id, Mark::Done);
order.push(id);
Ok(())
}
let mut marks = BTreeMap::new();
let mut order = Vec::with_capacity(steps.len());
visit(result, steps, &mut marks, &mut Vec::new(), &mut order)?;
let reached: BTreeSet<StepId> = order.iter().copied().collect();
let orphans: Vec<StepId> = steps
.keys()
.filter(|id| !reached.contains(id))
.copied()
.collect();
let mut scratch = Vec::new();
for id in &orphans {
visit(*id, steps, &mut marks, &mut Vec::new(), &mut scratch)?;
}
match orphans.first() {
Some(step) => Err(FormulaError::Unreferenced { step: *step }),
None => Ok(order),
}
}
fn step_of(id: StepId, group: &[(Operator, Operand)]) -> Result<Step, FormulaError> {
use Operator as Op;
let mixed = || FormulaError::MixedStep {
step: id,
operators: group.iter().map(|(op, _)| *op).collect(),
};
let all = |pred: fn(Op) -> bool| group.iter().all(|(op, _)| pred(*op));
if all(|op| matches!(op, Op::Addition | Op::Subtraktion)) {
return Ok(Step::Sum(
group
.iter()
.map(|(op, o)| {
let sign = if *op == Op::Addition {
Sign::Plus
} else {
Sign::Minus
};
(sign, *o)
})
.collect(),
));
}
if all(|op| op == Op::Faktor) {
return Ok(Step::Product(group.iter().map(|(_, o)| *o).collect()));
}
match group {
[(Op::Positivwert, Operand::Step(step))] => Ok(Step::Positive(*step)),
[(Op::Positivwert, Operand::Melo(_))] => {
Err(FormulaError::PositivwertOverMelo { step: id })
}
[(Op::Dividend, n), (Op::Divisor, d)] | [(Op::Divisor, d), (Op::Dividend, n)] => {
Ok(Step::Quotient {
dividend: *n,
divisor: *d,
})
}
_ => Err(mixed()),
}
}
fn first() -> StepId {
StepId(NonZeroU32::MIN)
}
fn ids<const N: usize>() -> [StepId; N] {
std::array::from_fn(|i| {
let n = u32::try_from(i).unwrap_or(0).saturating_add(1);
StepId(NonZeroU32::new(n).unwrap_or(NonZeroU32::MIN))
})
}
fn verbrauch(melo: MeloId) -> Operand {
MeloOperand::new(melo, FlowDirection::Verbrauch).into()
}