Skip to main content

cmtool_assemble/generators/
descriptors.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::CMError;
4use cmtool_data::PhaseCM;
5
6pub struct Reactor0DDescriptor {
7    liquid_volume: f64,
8    gas_volume: f64,
9}
10
11impl Reactor0DDescriptor {
12    pub fn liquid_volume(&self) -> f64 {
13        self.liquid_volume
14    }
15
16    pub fn gas_volume(&self) -> f64 {
17        self.gas_volume
18    }
19
20    pub fn is_valid(&self) -> Result<(), CMError> {
21        if !self.liquid_volume.is_finite() || self.liquid_volume <= 0.0 {
22            return Err(CMError::Descriptor(
23                "Liquid volume must be a finite positive number".to_owned(),
24            ));
25        }
26        //TODO
27        if self.gas_volume < 0. {
28            return Err(CMError::Descriptor(
29                "Gas volume must be a finite positive number".to_owned(),
30            ));
31        }
32
33        Ok(())
34    }
35
36    pub fn new<L, G>(liquid_volume: L, gas_volume: G) -> Result<Self, CMError>
37    where
38        L: Into<f64>,
39        G: Into<f64>,
40    {
41        let reactor = Self {
42            liquid_volume: liquid_volume.into(),
43            gas_volume: gas_volume.into(),
44        };
45
46        reactor.is_valid()?;
47        Ok(reactor)
48    }
49
50    pub fn from_fraction(total_volume: f64, gas_fraction: f64) -> Result<Self, CMError> {
51        if !(0. ..=1.).contains(&gas_fraction) {
52            return Err(CMError::Descriptor(format!(
53                "Gas fraction must be between 0 and 1, got {}",
54                gas_fraction
55            )));
56        }
57        let gas_volume = gas_fraction * total_volume;
58        let liquid_volume = total_volume - gas_volume;
59
60        Self::new(liquid_volume, gas_volume)
61    }
62}
63
64pub struct PFRDescription {
65    pub(super) n_compartment: usize,
66    pub(super) length: f64,
67    pub(super) diameter: f64,
68    pub(super) liquid_flow: f64,
69    pub(super) gas_flow: f64,
70    pub(super) gas_fraction: f64,
71    pub(super) axial_dispersion: f64,
72}
73
74impl PFRDescription {
75    pub fn new(
76        n_compartment: usize,
77        length: impl Into<f64>,
78        diameter: impl Into<f64>,
79        liquid_flow: impl Into<f64>,
80        gas_flow: impl Into<f64>,
81        gas_fraction: impl Into<f64>,
82        axial_dispersion: impl Into<f64>,
83    ) -> Result<PFRDescription, CMError> {
84        let pfr = Self {
85            n_compartment,
86            length: length.into(),
87            diameter: diameter.into(),
88            liquid_flow: liquid_flow.into(),
89            gas_flow: gas_flow.into(),
90            gas_fraction: gas_fraction.into(),
91            axial_dispersion: axial_dispersion.into(),
92        };
93
94        pfr.is_valid()?;
95        Ok(pfr)
96    }
97
98    pub fn get_liquid_flow(&self) -> f64 {
99        self.liquid_flow
100    }
101
102    pub fn get_gas_flow(&self) -> f64 {
103        self.gas_flow
104    }
105    pub fn get_gas_fraction(&self) -> f64 {
106        self.gas_fraction
107    }
108    pub fn get_liquid_fraction(&self) -> f64 {
109        1. - self.gas_fraction
110    }
111    //volume is h*pi*d^2/4
112    #[allow(unused)]
113    pub fn geometrical_volume(&self) -> f64 {
114        self.length * (self.diameter * self.diameter) * std::f64::consts::PI / 4.
115    }
116
117    pub fn extract_volume_flow(&self, phase: PhaseCM) -> (f64, f64) {
118        match phase {
119            PhaseCM::Liquid => (self.get_liquid_fraction(), self.get_liquid_flow()),
120            PhaseCM::Gas => (self.get_gas_fraction(), self.get_gas_flow()),
121        }
122    }
123
124    pub fn is_valid(&self) -> Result<(), CMError> {
125        if self.n_compartment == 0 {
126            return Err(CMError::Descriptor("n_compartment must be >= 1".to_owned()));
127        }
128        if !self.length.is_finite() || self.length <= 0.0 {
129            return Err(CMError::Descriptor(
130                "length must be a finite positive number".to_owned(),
131            ));
132        }
133        if !self.diameter.is_finite() || self.diameter <= 0.0 {
134            return Err(CMError::Descriptor(
135                "diameter must be a finite positive number".to_owned(),
136            ));
137        }
138        if !self.liquid_flow.is_finite() || self.liquid_flow < 0.0 {
139            return Err(CMError::Descriptor(
140                "liquid_flow must be a finite non-negative number".to_owned(),
141            ));
142        }
143        if !self.gas_flow.is_finite() || self.gas_flow < 0.0 {
144            return Err(CMError::Descriptor(
145                "gas_flow must be a finite non-negative number".to_owned(),
146            ));
147        }
148        if !self.gas_fraction.is_finite() || self.gas_fraction < 0.0 || self.gas_fraction > 1.0 {
149            return Err(CMError::Descriptor(
150                "gas_fraction must be between 0.0 and 1.0".to_owned(),
151            ));
152        }
153        if !self.axial_dispersion.is_finite() || self.axial_dispersion < 0.0 {
154            return Err(CMError::Descriptor(
155                "axial_dispersion must be a finite non-negative number".to_owned(),
156            ));
157        }
158
159        if self.liquid_flow == 0.0 && self.gas_flow == 0.0 {
160            return Err(CMError::Descriptor(
161                "At least one of liquid_flow or gas_flow must be positive".to_owned(),
162            ));
163        }
164        if (self.gas_flow > 0.0 && self.liquid_flow == 0.0)
165            && (self.gas_fraction < 0.0 || self.gas_fraction > 1.0)
166        {
167            return Err(CMError::Descriptor(
168                "Invalid gas_fraction given flows".to_owned(),
169            ));
170        }
171
172        // geometric length should be >= diameter
173        if self.length < self.diameter {
174            return Err(CMError::Descriptor(
175                "length should be greater than or equal to diameter".to_owned(),
176            ));
177        }
178
179        Ok(())
180    }
181}