twine_thermo/flow/
work.rs1use std::cmp::Ordering;
2
3use twine_core::constraint::{Constrained, ConstraintError, StrictlyPositive};
4use uom::{ConstZero, si::f64::Power};
5
6#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum WorkFlow {
21 In(Constrained<Power, StrictlyPositive>),
23 Out(Constrained<Power, StrictlyPositive>),
25 None,
27}
28
29impl WorkFlow {
30 pub fn incoming(work_rate: Power) -> Result<Self, ConstraintError> {
36 Ok(Self::In(Constrained::new(work_rate)?))
37 }
38
39 pub fn outgoing(work_rate: Power) -> Result<Self, ConstraintError> {
45 Ok(Self::Out(Constrained::new(work_rate)?))
46 }
47
48 pub fn from_signed(work_rate: Power) -> Result<Self, ConstraintError> {
58 match work_rate.partial_cmp(&Power::ZERO) {
59 Some(Ordering::Greater) => Self::incoming(work_rate),
60 Some(Ordering::Less) => Self::outgoing(-work_rate),
61 Some(Ordering::Equal) => Ok(Self::None),
62 None => Err(ConstraintError::NotANumber),
63 }
64 }
65
66 #[must_use]
72 pub fn signed(&self) -> Power {
73 match self {
74 Self::In(work_rate) => work_rate.into_inner(),
75 Self::Out(work_rate) => -work_rate.into_inner(),
76 Self::None => Power::ZERO,
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 use approx::assert_relative_eq;
86 use uom::si::{f64::Power, power::watt};
87
88 #[test]
89 fn incoming_is_positive() {
90 let w_dot = Power::new::<watt>(150.0);
91 let flow = WorkFlow::incoming(w_dot).unwrap();
92 assert!(matches!(flow, WorkFlow::In(_)));
93 assert_relative_eq!(flow.signed().get::<watt>(), 150.0);
94 }
95
96 #[test]
97 fn outgoing_is_negative() {
98 let w_dot = Power::new::<watt>(250.0);
99 let flow = WorkFlow::outgoing(w_dot).unwrap();
100 assert!(matches!(flow, WorkFlow::Out(_)));
101 assert_relative_eq!(flow.signed().get::<watt>(), -250.0);
102 }
103
104 #[test]
105 fn none_is_zero() {
106 let flow = WorkFlow::None;
107 assert_relative_eq!(flow.signed().get::<watt>(), 0.0);
108 }
109
110 #[test]
111 fn from_signed_work_rate_classifies_correctly() {
112 let in_flow = WorkFlow::from_signed(Power::new::<watt>(75.0)).unwrap();
113 let out_flow = WorkFlow::from_signed(Power::new::<watt>(-50.0)).unwrap();
114 let none_flow = WorkFlow::from_signed(Power::new::<watt>(0.0)).unwrap();
115
116 assert!(matches!(in_flow, WorkFlow::In(_)));
117 assert!(matches!(out_flow, WorkFlow::Out(_)));
118 assert!(matches!(none_flow, WorkFlow::None));
119 }
120
121 #[test]
122 fn rejects_nan_input() {
123 let w_dot = Power::new::<watt>(f64::NAN);
124 let result = WorkFlow::from_signed(w_dot);
125 assert!(matches!(result, Err(ConstraintError::NotANumber)));
126 }
127
128 #[test]
129 fn rejects_negative_incoming() {
130 let w_dot = Power::new::<watt>(-1.0);
131 assert!(WorkFlow::incoming(w_dot).is_err());
132 }
133
134 #[test]
135 fn rejects_zero_incoming() {
136 let w_dot = Power::new::<watt>(0.0);
137 assert!(WorkFlow::incoming(w_dot).is_err());
138 }
139}