1use std::collections::HashMap;
4use std::path::PathBuf;
5
6use super::PfrGlobalMassBalance;
7use super::generated_domain;
8use crate::data::FeedFlow;
9use crate::data::ParsedFeeds;
10use crate::parser::generated_domain::FeedFluxType;
11use crate::parser::generated_domain::FluxType;
12use crate::{
13 CMError,
14 data::{DomainInfo, FlowDirection},
15};
16use cmtool_data::DEFAULT_CASE_FILE_NAME;
17use cmtool_data::{PhaseCM, RawDataFlux};
18
19fn connection_per_phase(
20 info: &DomainInfo,
21 mass_balance: &mut PfrGlobalMassBalance,
22 phase_node: &[generated_domain::FluxType],
23 phase: PhaseCM,
24) -> Result<cmtool_data::RawDataFlux, CMError> {
25 let n_node = phase_node.len();
26
27 let mut rd = RawDataFlux::new(info.total_number_compartment, n_node);
28
29 for (node, flux) in phase_node.iter().zip(rd.fluxes.iter_mut()) {
30 if let (Some(absolute_source_id), Some(absolute_target_id)) = (
31 info.get_relative_compartment_number(&node.source.id, node.source.compartment_id),
32 info.get_relative_compartment_number(&node.target.id, node.target.compartment_id),
33 ) {
34 let flow = f64::from(node.value.content);
35 flux.id_source = absolute_source_id as u32;
36 flux.id_target = absolute_target_id as u32;
37 flux.flux_source_target = flow;
38 flux.flux_target_source = 0.; let f_pfr_source = info.pfr_names.contains(&node.source.id);
41 let is_same_node =
42 absolute_source_id == absolute_target_id && node.source.id == node.target.id;
43
44 if is_same_node && f_pfr_source {
45 mass_balance.update_flow(&node.target.id, phase, FlowDirection::Out, flow);
46 } else {
47 let f_pfr_target = info.pfr_names.contains(&node.target.id);
48 if f_pfr_target {
49 mass_balance.update_flow(&node.target.id, phase, FlowDirection::In, flow);
50 }
51 if f_pfr_source {
52 mass_balance.update_flow(&node.source.id, phase, FlowDirection::Out, flow);
53 }
54 }
55 } else {
56 return Err(CMError::Custom(format!(
58 "Connection {}[{}] -> {}[{}] does not resolve to a compartment of the domain",
59 node.source.id,
60 node.source.compartment_id,
61 node.target.id,
62 node.target.compartment_id
63 )));
64 }
65 }
66 Ok(rd)
67}
68
69pub fn parse_connection(
70 info: &DomainInfo,
71 connections: &generated_domain::ConnectionsType,
72 mass_balance: &mut PfrGlobalMassBalance,
73) -> Result<[RawDataFlux; 2], CMError> {
74 let liquid_connection: Vec<generated_domain::FluxType> = connections
75 .flux
76 .iter()
77 .filter(|f| f.phase == *"liquid")
78 .cloned()
79 .collect();
80
81 let gas_connection: Vec<generated_domain::FluxType> = connections
82 .flux
83 .iter()
84 .filter(|f| f.phase == *"gas")
85 .cloned()
86 .collect();
87
88 Ok([
89 connection_per_phase(info, mass_balance, &liquid_connection, PhaseCM::Liquid)?,
90 connection_per_phase(info, mass_balance, &gas_connection, PhaseCM::Gas)?,
91 ])
92}
93
94impl From<&FeedFluxType> for FluxType {
97 fn from(feed: &FeedFluxType) -> Self {
98 Self {
99 source: feed.source.clone(),
100 target: feed.target.clone(),
101 phase: feed.phase.clone(),
102 value: feed.value.clone(),
103 }
104 }
105}
106
107fn parse_feed_phase(
108 info: &DomainInfo,
109 feeds: &[&FeedFluxType],
110 phase: PhaseCM,
111 mass_balance: &mut PfrGlobalMassBalance,
112) -> Result<HashMap<String, FeedFlow>, CMError> {
113 let fluxes: Vec<generated_domain::FluxType> =
114 feeds.iter().map(|&feed| FluxType::from(feed)).collect();
115
116 let id: Vec<String> = feeds.iter().map(|feed| feed.id.clone()).collect();
117 let rd = connection_per_phase(info, mass_balance, &fluxes, phase)?;
118
119 Ok(rd
120 .fluxes
121 .iter()
122 .zip(id)
123 .map(|(flux, id)| {
124 (
125 id,
126 FeedFlow {
127 flow: flux.flux_source_target,
128 position: flux.id_source as usize,
129 output_position: None, },
131 )
132 })
133 .collect())
134}
135
136pub fn parse_feed(
137 info: &DomainInfo,
138 feeds: &generated_domain::FeedsType,
139 mass_balance: &mut PfrGlobalMassBalance,
140) -> Result<Option<ParsedFeeds>, CMError> {
141 let (liquid_feeds, gas_feeds): (Vec<_>, Vec<_>) =
142 feeds.flux.iter().partition(|f| f.phase == *"liquid");
143 if liquid_feeds.is_empty() && gas_feeds.is_empty() {
144 return Ok(None);
145 }
146 Ok(Some(ParsedFeeds {
147 liq: parse_feed_phase(info, &liquid_feeds, PhaseCM::Liquid, mass_balance)?,
148 gas: parse_feed_phase(info, &gas_feeds, PhaseCM::Gas, mass_balance)?,
149 }))
150}
151
152pub fn parse_reactor(reactors: &generated_domain::ReactorsType) -> Result<DomainInfo, CMError> {
153 let mut domain_info = DomainInfo::default();
154 let mut in_place_cumsum = 0;
155 for reactor in &reactors.content {
157 match reactor {
158 generated_domain::ReactorsTypeContent::Reactor0D(reactor0_dtype) => {
159 domain_info
160 .compartment_cumsum
161 .insert(reactor0_dtype.id.clone(), in_place_cumsum);
162 domain_info.total_number_compartment += 1;
163 in_place_cumsum += 1;
164 domain_info.is_two_phase_flow |= reactor0_dtype.volume_fraction.content != 0.;
165 }
166 generated_domain::ReactorsTypeContent::Reactor1D(current_pfr) => {
167 domain_info
168 .compartment_cumsum
169 .insert(current_pfr.id.clone(), in_place_cumsum);
170 let n_c = current_pfr.compartments;
171 domain_info.total_number_compartment += n_c.get();
172 domain_info.is_two_phase_flow |= current_pfr.volume_fraction.content != 0.;
173 domain_info.pfr_names.push(current_pfr.id.clone());
174 in_place_cumsum += n_c.get();
175 }
176 generated_domain::ReactorsTypeContent::ReactorFromFile(reactor_from_file) => {
177 let path = PathBuf::from(&reactor_from_file.path).join(DEFAULT_CASE_FILE_NAME);
178 let case = cmtool_data::read_case(path.as_path())?;
179 domain_info
181 .compartment_cumsum
182 .insert(reactor_from_file.id.clone(), in_place_cumsum);
183 let n_c = case.n_compartment() as usize;
184 domain_info.is_two_phase_flow |= case.is_two_phase_flow();
185 domain_info.total_number_compartment += n_c;
186 in_place_cumsum += n_c;
187 }
188 generated_domain::ReactorsTypeContent::Reactor3D(reactor3_dtype) => {
189 todo!("{:?}", reactor3_dtype)
190 }
191 }
192 }
193 Ok(domain_info)
194}
195
196#[cfg(test)]
197mod test {
198 use super::*;
199
200 fn reactor_0d(id: &str, gas_fraction: f32) -> generated_domain::ReactorsTypeContent {
201 generated_domain::ReactorsTypeContent::Reactor0D(generated_domain::Reactor0DType {
202 id: id.to_owned(),
203 volume_fraction: generated_domain::VolumeFractionType {
204 phase: None,
205 content: gas_fraction,
206 },
207 size: generated_domain::GeneralSizeType::Volume(generated_domain::DimensionType {
208 unit: None,
209 content: 1.,
210 }),
211 })
212 }
213
214 #[test]
216 fn test_keep_two_phase_flow() {
217 let reactors = generated_domain::ReactorsType {
218 content: vec![reactor_0d("gas", 0.1), reactor_0d("liquid", 0.)],
219 };
220
221 let info = parse_reactor(&reactors).unwrap();
222
223 assert!(info.is_two_phase_flow);
224 assert_eq!(info.total_number_compartment, 2);
225 }
226
227 #[test]
228 fn liquid_only_domain_is_not_two_phase() {
229 let reactors = generated_domain::ReactorsType {
230 content: vec![reactor_0d("liquid_1", 0.), reactor_0d("liquid_2", 0.)],
231 };
232
233 assert!(!parse_reactor(&reactors).unwrap().is_two_phase_flow);
234 }
235}