1use std::collections::HashMap;
4
5use crate::FlowMapDescriptor;
6use nalgebra_sparse::CooMatrix;
7use ndarray::Array2;
8
9macro_rules! almost_equal {
10 ($i:expr, $base:expr, $eps:expr) => {
11 (($i - $base).abs() < $eps)
12 };
13}
14
15macro_rules! round_if_needed {
28 ($val:expr, $base:expr, $tol:expr) => {{
29 if almost_equal!($val, $base, $tol) {
30 $base
31 } else {
32 $val
33 }
34 }};
35}
36fn get_transition_from_fm(fm: Array2<f64>) -> (CooMatrix<f64>, Vec<f64>) {
37 let n_compartments: usize = fm.nrows();
38
39 let mut transition = CooMatrix::new(n_compartments, n_compartments);
40 let mut row_sum = vec![0.; n_compartments];
41
42 (0..n_compartments).for_each(|i_row| {
43 for i_col in 0..n_compartments {
44 if i_row != i_col {
45 let val = *fm.get((i_row, i_col)).expect("Bad formated flowmap");
46 if val != 0.0 {
47 transition.push(i_row, i_col, val);
48 row_sum[i_row] += val;
49 }
50 }
51 }
52 });
53
54 (0..n_compartments).for_each(|i_row| {
55 let val = row_sum[i_row];
56 transition.push(i_row, i_row, -val);
57 });
58
59 (transition, row_sum)
60}
61
62#[cfg(feature = "probability")]
63fn get_probability(liquid_neighors: &Array2<usize>, transition: &CooMatrix<f64>) -> Array2<f64> {
64 use nalgebra_sparse::CscMatrix;
65
66 let shape = liquid_neighors.dim();
72 let mut proba = Array2::<f64>::ones(shape);
74 let transition_csc = CscMatrix::from(transition);
75 let ghost_neighor = shape.0 + 1;
76 (0..shape.0).for_each(|i_compartment| {
77 let mut cumsum = 0.;
78 let out_flow = round_if_needed!(
79 transition_csc
80 .index_entry(i_compartment, i_compartment)
81 .into_value()
82 .abs(),
83 0.,
84 1e-12
85 );
86
87 let mut count_neighbor = 0;
90 liquid_neighors.row(i_compartment).for_each(|&i_neighbor| {
91 if i_neighbor != ghost_neighor {
92 let proba_out: f64 = if out_flow != 0. {
93 transition_csc
94 .index_entry(i_compartment, i_neighbor)
95 .into_value()
96 / out_flow
97 } else {
98 0.
99 };
100 debug_assert!(proba_out >= 0.);
101 let p_cp = round_if_needed!(proba_out + cumsum, 1., 1e-8);
103
104 *proba
105 .get_mut((i_compartment, count_neighbor))
106 .expect("Probability out of bound") = p_cp;
107
108 cumsum += proba_out;
109 }
110 count_neighbor += 1;
111 });
112 });
123
124 proba
125}
126
127pub struct HydroState {
128 pub transition: CooMatrix<f64>,
129 pub out_flows: Vec<f64>,
130 pub volumes: Vec<f64>,
131 pub inverse_volume: Vec<f64>,
132}
133
134impl HydroState {
135 #[inline(always)]
136 pub fn get_volume(&self) -> &[f64] {
137 &self.volumes
138 }
139 #[inline(always)]
140 pub fn get_transition(&self) -> &CooMatrix<f64> {
141 &self.transition
142 }
143 #[inline(always)]
144 pub fn n_compartments(&self) -> usize {
145 self.volumes.len()
146 }
147
148 #[inline(always)]
149 pub fn total_volume(&self) -> f64 {
150 self.volumes.iter().sum()
151 }
152}
153
154impl From<FlowMapDescriptor> for HydroState {
155 fn from(value: FlowMapDescriptor) -> Self {
156 let inverse = value.volumes.iter().map(|val| 1. / val).collect();
157 let (transition, out_flows) = get_transition_from_fm(value.flowmap);
158 HydroState {
159 volumes: value.volumes,
160 out_flows,
161 inverse_volume: inverse,
162 transition,
163 }
164 }
165}
166
167pub struct IterationState {
168 pub liquid: HydroState,
169 pub gas: Option<HydroState>,
170 pub liquid_neighors: Array2<usize>,
171 #[cfg(feature = "probability")]
172 pub liquid_cumulative_probability: Array2<f64>,
173 pub misc: HashMap<String, Vec<f64>>,
174}
175
176impl IterationState {
177 pub fn new(
178 liq: FlowMapDescriptor,
179 gas: Option<FlowMapDescriptor>,
180 misc: HashMap<String, Vec<f64>>,
181 ) -> Self {
182 let liquid_neighors = liq.neighbors.clone(); let liq_state: HydroState = liq.into();
185
186 #[cfg(feature = "probability")]
190 let liquid_cumulative_probability =
191 get_probability(&liquid_neighors, &liq_state.transition);
192
193 let ret = Self {
194 liquid: liq_state,
195 gas: gas.map(|_gas| _gas.into()),
196 liquid_neighors,
197 #[cfg(feature = "probability")]
198 liquid_cumulative_probability,
199 misc,
200 };
201
202 if let Some(gas1) = &ret.gas {
203 assert!(gas1.n_compartments() == ret.liquid.n_compartments());
204 }
205
206 ret
207 }
208
209 #[inline(always)]
210 pub fn get(&self, info: &str) -> Option<&[f64]> {
211 self.misc.get(info).map(|v| &**v)
212 }
213
214 #[inline(always)]
215 pub fn n_compartments(&self) -> usize {
216 self.liquid.n_compartments()
217 }
218}
219
220#[cfg(test)]
221mod test {
222 use std::collections::HashMap;
223
224 use crate::test_utils::chain_of_three;
225 use crate::{FlowMapDescriptor, IterationState};
226
227 fn synthetic_descriptor() -> FlowMapDescriptor {
228 let (flow, volume) = chain_of_three();
229 FlowMapDescriptor::from_raw_data(&flow, &volume).unwrap()
230 }
231
232 #[test]
233 fn transition_diagonal_is_minus_out_flow() {
234 let descriptor = synthetic_descriptor();
235 let state = IterationState::new(descriptor, None, HashMap::new());
236
237 assert_eq!(state.liquid.out_flows, vec![1.0, 2.5, 0.25]);
240
241 let dense = nalgebra_sparse::CsrMatrix::from(&state.liquid.transition);
242 for (i_compartment, out_flow) in state.liquid.out_flows.iter().enumerate() {
243 let diagonal = dense
244 .get_entry(i_compartment, i_compartment)
245 .unwrap()
246 .into_value();
247 assert!((diagonal + out_flow).abs() < 1e-12);
248 }
249
250 for row in dense.row_iter() {
252 let sum: f64 = row.values().iter().sum();
253 assert!(sum.abs() < 1e-12, "row sum {} is not zero", sum);
254 }
255
256 assert_eq!(state.n_compartments(), 3);
257 assert_eq!(state.liquid.total_volume(), 7.0);
258 assert_eq!(state.liquid.inverse_volume, vec![1.0, 0.5, 0.25]);
259 }
260
261 #[test]
262 fn gas_phase_must_match_liquid_compartment_count() {
263 let state = IterationState::new(
264 synthetic_descriptor(),
265 Some(synthetic_descriptor()),
266 HashMap::new(),
267 );
268
269 assert_eq!(state.gas.unwrap().n_compartments(), 3);
270 }
271
272 #[test]
273 fn construct_itstate_liquid_only() {
274 let _flow_cma = std::env::var("CUVE_SLDMSH_FLOW_PATH");
275 let _volume_cma = std::env::var("CUVE_SLDMSH_VOLUME_PATH");
276
277 if let (Ok(flow_cma), Ok(volume_cma)) = (_flow_cma, _volume_cma) {
278 let descriptor = FlowMapDescriptor::from_path(flow_cma, volume_cma).unwrap();
279
280 let vol_ref = descriptor.volumes.clone();
281
282 let state = IterationState::new(descriptor, None, HashMap::new());
283
284 assert!(state.liquid.volumes == vol_ref);
285 }
286 }
287}