feos_core/phase_equilibria/
phase_envelope.rs1use super::{PhaseDiagram, PhaseEquilibrium};
2use crate::SolverOptions;
3use crate::equation_of_state::Residual;
4use crate::errors::FeosResult;
5use crate::state::{Contributions, State};
6use nalgebra::DVector;
7use quantity::{Pressure, Temperature};
8
9impl<E: Residual> PhaseDiagram<E, 2> {
10 pub fn bubble_point_line(
12 eos: &E,
13 molefracs: &DVector<f64>,
14 min_temperature: Temperature,
15 npoints: usize,
16 critical_temperature: Option<Temperature>,
17 options: (SolverOptions, SolverOptions),
18 ) -> FeosResult<Self> {
19 let mut states = Vec::with_capacity(npoints);
20
21 let sc = State::critical_point(
22 eos,
23 Some(molefracs),
24 critical_temperature,
25 None,
26 SolverOptions::default(),
27 )?;
28
29 let max_temperature = min_temperature
30 + (sc.temperature - min_temperature) * ((npoints - 2) as f64 / (npoints - 1) as f64);
31 let temperatures = Temperature::linspace(min_temperature, max_temperature, npoints - 1);
32
33 let mut vle: Option<PhaseEquilibrium<E, 2>> = None;
34 for ti in &temperatures {
35 let p_init = vle
37 .as_ref()
38 .map(|vle| vle.vapor().pressure(Contributions::Total));
39 let vapor_molefracs = vle.as_ref().map(|vle| &vle.vapor().molefracs);
40 vle = PhaseEquilibrium::bubble_point(
41 eos,
42 ti,
43 molefracs,
44 p_init,
45 vapor_molefracs,
46 options,
47 )
48 .ok();
49
50 if let Some(vle) = vle.as_ref() {
51 states.push(vle.clone());
52 }
53 }
54 states.push(PhaseEquilibrium::from_states(sc.clone(), sc));
55
56 Ok(PhaseDiagram::new(states))
57 }
58
59 pub fn dew_point_line(
61 eos: &E,
62 molefracs: &DVector<f64>,
63 min_temperature: Temperature,
64 npoints: usize,
65 critical_temperature: Option<Temperature>,
66 options: (SolverOptions, SolverOptions),
67 ) -> FeosResult<Self> {
68 let mut states = Vec::with_capacity(npoints);
69
70 let sc = State::critical_point(
71 eos,
72 Some(molefracs),
73 critical_temperature,
74 None,
75 SolverOptions::default(),
76 )?;
77
78 let n_t = npoints / 2;
79 let max_temperature = min_temperature
80 + (sc.temperature - min_temperature) * ((n_t - 2) as f64 / (n_t - 1) as f64);
81 let temperatures = Temperature::linspace(min_temperature, max_temperature, n_t - 1);
82
83 let mut vle: Option<PhaseEquilibrium<E, 2>> = None;
84 for ti in &temperatures {
85 let p_init = vle
86 .as_ref()
87 .map(|vle| vle.vapor().pressure(Contributions::Total));
88 let liquid_molefracs = vle.as_ref().map(|vle| &vle.liquid().molefracs);
89 vle =
90 PhaseEquilibrium::dew_point(eos, ti, molefracs, p_init, liquid_molefracs, options)
91 .ok();
92 if let Some(vle) = vle.as_ref() {
93 states.push(vle.clone());
94 }
95 }
96
97 let n_p = npoints - n_t;
98 if vle.is_none() {
99 return Ok(PhaseDiagram::new(states));
100 }
101
102 let min_pressure = vle.as_ref().unwrap().vapor().pressure(Contributions::Total);
103 let p_c = sc.pressure(Contributions::Total);
104 let max_pressure =
105 min_pressure + (p_c - min_pressure) * ((n_p - 2) as f64 / (n_p - 1) as f64);
106 let pressures = Pressure::linspace(min_pressure, max_pressure, n_p);
107
108 for pi in &pressures {
109 let t_init = vle.as_ref().map(|vle| vle.vapor().temperature);
110 let liquid_molefracs = vle.as_ref().map(|vle| &vle.liquid().molefracs);
111 vle =
112 PhaseEquilibrium::dew_point(eos, pi, molefracs, t_init, liquid_molefracs, options)
113 .ok();
114 if let Some(vle) = vle.as_ref() {
115 states.push(vle.clone());
116 }
117 }
118
119 states.push(PhaseEquilibrium::from_states(sc.clone(), sc));
120
121 Ok(PhaseDiagram::new(states))
122 }
123
124 pub fn spinodal(
126 eos: &E,
127 molefracs: &DVector<f64>,
128 min_temperature: Temperature,
129 npoints: usize,
130 critical_temperature: Option<Temperature>,
131 options: SolverOptions,
132 ) -> FeosResult<Self> {
133 let mut states = Vec::with_capacity(npoints);
134
135 let sc = State::critical_point(
136 eos,
137 Some(molefracs),
138 critical_temperature,
139 None,
140 SolverOptions::default(),
141 )?;
142
143 let max_temperature = min_temperature
144 + (sc.temperature - min_temperature) * ((npoints - 2) as f64 / (npoints - 1) as f64);
145 let temperatures = Temperature::linspace(min_temperature, max_temperature, npoints - 1);
146
147 for ti in &temperatures {
148 let spinodal = State::spinodal(eos, ti, Some(molefracs), options).ok();
149 if let Some(spinodal) = spinodal {
150 states.push(PhaseEquilibrium(spinodal));
151 }
152 }
153 states.push(PhaseEquilibrium::from_states(sc.clone(), sc));
154
155 Ok(PhaseDiagram::new(states))
156 }
157}