1use crate::density_iteration::density_iteration;
10use crate::equation_of_state::{IdealGas, Residual};
11use crate::errors::{EosError, EosResult};
12use crate::ReferenceSystem;
13use cache::Cache;
14use ndarray::prelude::*;
15use num_dual::*;
16use quantity::*;
17use std::fmt;
18use std::ops::Sub;
19use std::sync::{Arc, Mutex};
20use typenum::{N1, N2, P1, Z0};
21
22mod builder;
23mod cache;
24mod properties;
25mod residual_properties;
26mod statevec;
27pub use builder::StateBuilder;
28pub use statevec::StateVec;
29
30#[derive(Clone, Copy, PartialEq)]
32#[cfg_attr(feature = "python", pyo3::pyclass(eq))]
33pub enum Contributions {
34 IdealGas,
36 Residual,
38 Total,
42}
43
44#[derive(Clone, Copy)]
46pub enum DensityInitialization {
47 Vapor,
49 Liquid,
51 InitialDensity(Density),
53 None,
56}
57
58#[derive(Clone, Debug)]
64pub struct StateHD<D: DualNum<f64>> {
65 pub temperature: D,
67 pub volume: D,
69 pub moles: Array1<D>,
71 pub molefracs: Array1<D>,
73 pub partial_density: Array1<D>,
75}
76
77impl<D: DualNum<f64> + Copy> StateHD<D> {
78 pub fn new(temperature: D, volume: D, moles: Array1<D>) -> Self {
80 let total_moles = moles.sum();
81 let partial_density = moles.mapv(|n| n / volume);
82 let molefracs = moles.mapv(|n| n / total_moles);
83
84 Self {
85 temperature,
86 volume,
87 moles,
88 molefracs,
89 partial_density,
90 }
91 }
92
93 pub(crate) fn new_virial(temperature: D, density: D, molefracs: Array1<f64>) -> Self {
96 let volume = D::one();
97 let partial_density = molefracs.mapv(|x| density * x);
98 let moles = partial_density.mapv(|pd| pd * volume);
99 let molefracs = molefracs.mapv(D::from);
100 Self {
101 temperature,
102 volume,
103 moles,
104 molefracs,
105 partial_density,
106 }
107 }
108}
109
110#[derive(Debug)]
139pub struct State<E> {
140 pub eos: Arc<E>,
142 pub temperature: Temperature,
144 pub volume: Volume,
146 pub moles: Moles<Array1<f64>>,
148 pub total_moles: Moles,
150 pub partial_density: Density<Array1<f64>>,
152 pub density: Density,
154 pub molefracs: Array1<f64>,
156 reduced_temperature: f64,
158 reduced_volume: f64,
160 reduced_moles: Array1<f64>,
162 cache: Mutex<Cache>,
164}
165
166impl<E> Clone for State<E> {
167 fn clone(&self) -> Self {
168 Self {
169 eos: self.eos.clone(),
170 total_moles: self.total_moles,
171 temperature: self.temperature,
172 volume: self.volume,
173 moles: self.moles.clone(),
174 partial_density: self.partial_density.clone(),
175 density: self.density,
176 molefracs: self.molefracs.clone(),
177 reduced_temperature: self.reduced_temperature,
178 reduced_volume: self.reduced_volume,
179 reduced_moles: self.reduced_moles.clone(),
180 cache: Mutex::new(self.cache.lock().unwrap().clone()),
181 }
182 }
183}
184
185impl<E: Residual> fmt::Display for State<E> {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 if self.eos.components() == 1 {
188 write!(f, "T = {:.5}, ρ = {:.5}", self.temperature, self.density)
189 } else {
190 write!(
191 f,
192 "T = {:.5}, ρ = {:.5}, x = {:.5}",
193 self.temperature, self.density, self.molefracs
194 )
195 }
196 }
197}
198
199#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug, PartialOrd, Ord)]
201#[allow(non_camel_case_types)]
202pub enum Derivative {
203 DV,
205 DT,
207 DN(usize),
209}
210
211#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
212pub(crate) enum PartialDerivative {
213 Zeroth,
214 First(Derivative),
215 Second(Derivative),
216 SecondMixed(Derivative, Derivative),
217 Third(Derivative),
218}
219
220impl<E: Residual> State<E> {
222 pub fn new_nvt(
228 eos: &Arc<E>,
229 temperature: Temperature,
230 volume: Volume,
231 moles: &Moles<Array1<f64>>,
232 ) -> EosResult<Self> {
233 eos.validate_moles(Some(moles))?;
234 validate(temperature, volume, moles)?;
235
236 Ok(Self::new_nvt_unchecked(eos, temperature, volume, moles))
237 }
238
239 pub(super) fn new_nvt_unchecked(
240 eos: &Arc<E>,
241 temperature: Temperature,
242 volume: Volume,
243 moles: &Moles<Array1<f64>>,
244 ) -> Self {
245 let t = temperature.to_reduced();
246 let v = volume.to_reduced();
247 let m = moles.to_reduced();
248
249 let total_moles = moles.sum();
250 let partial_density = moles / volume;
251 let density = total_moles / volume;
252 let molefracs = &m / total_moles.to_reduced();
253
254 State {
255 eos: eos.clone(),
256 total_moles,
257 temperature,
258 volume,
259 moles: moles.to_owned(),
260 partial_density,
261 density,
262 molefracs,
263 reduced_temperature: t,
264 reduced_volume: v,
265 reduced_moles: m,
266 cache: Mutex::new(Cache::with_capacity(eos.components())),
267 }
268 }
269
270 pub fn new_pure(eos: &Arc<E>, temperature: Temperature, density: Density) -> EosResult<Self> {
277 let moles = Moles::from_reduced(arr1(&[1.0]));
278 Self::new_nvt(eos, temperature, Moles::from_reduced(1.0) / density, &moles)
279 }
280
281 #[expect(clippy::too_many_arguments)]
295 pub fn new(
296 eos: &Arc<E>,
297 temperature: Option<Temperature>,
298 volume: Option<Volume>,
299 density: Option<Density>,
300 partial_density: Option<&Density<Array1<f64>>>,
301 total_moles: Option<Moles>,
302 moles: Option<&Moles<Array1<f64>>>,
303 molefracs: Option<&Array1<f64>>,
304 pressure: Option<Pressure>,
305 density_initialization: DensityInitialization,
306 ) -> EosResult<Self> {
307 Self::_new(
308 eos,
309 temperature,
310 volume,
311 density,
312 partial_density,
313 total_moles,
314 moles,
315 molefracs,
316 pressure,
317 density_initialization,
318 )?
319 .map_err(|_| EosError::UndeterminedState(String::from("Missing input parameters.")))
320 }
321
322 #[expect(clippy::too_many_arguments)]
323 fn _new(
324 eos: &Arc<E>,
325 temperature: Option<Temperature>,
326 volume: Option<Volume>,
327 density: Option<Density>,
328 partial_density: Option<&Density<Array1<f64>>>,
329 total_moles: Option<Moles>,
330 moles: Option<&Moles<Array1<f64>>>,
331 molefracs: Option<&Array1<f64>>,
332 pressure: Option<Pressure>,
333 density_initialization: DensityInitialization,
334 ) -> EosResult<Result<Self, Option<Moles<Array1<f64>>>>> {
335 if density.and(partial_density).is_some() {
337 return Err(EosError::UndeterminedState(String::from(
338 "Both density and partial density given.",
339 )));
340 }
341 let rho = density.or_else(|| partial_density.map(|pd| pd.sum()));
342
343 if moles.and(total_moles).is_some() {
345 return Err(EosError::UndeterminedState(String::from(
346 "Both moles and total moles given.",
347 )));
348 }
349 let mut n = total_moles.or_else(|| moles.map(|m| m.sum()));
350
351 if rho.and(n).and(volume).is_some() {
353 return Err(EosError::UndeterminedState(String::from(
354 "Density is overdetermined.",
355 )));
356 }
357 n = n.or_else(|| rho.and_then(|d| volume.map(|v| v * d)));
358
359 if partial_density.and(moles).is_some() {
361 return Err(EosError::UndeterminedState(String::from(
362 "Composition is overdetermined.",
363 )));
364 }
365 let x = partial_density
366 .map(|pd| pd / pd.sum())
367 .or_else(|| moles.map(|ms| ms / ms.sum()))
368 .map(Quantity::into_value);
369 let x_u = match (x, molefracs, eos.components()) {
370 (Some(_), Some(_), _) => {
371 return Err(EosError::UndeterminedState(String::from(
372 "Composition is overdetermined.",
373 )))
374 }
375 (Some(x), None, _) => x,
376 (None, Some(x), _) => x.clone(),
377 (None, None, 1) => arr1(&[1.0]),
378 _ => {
379 return Err(EosError::UndeterminedState(String::from(
380 "Missing composition.",
381 )))
382 }
383 };
384
385 if let (None, None) = (volume, n) {
387 n = Some(Moles::from_reduced(1.0))
388 }
389 let n_i = n.map(|n| &x_u * n / x_u.sum());
390 let v = volume.or_else(|| rho.and_then(|d| n.map(|n| n / d)));
391
392 if let (Some(v), Some(t), Some(n_i)) = (v, temperature, &n_i) {
394 return Ok(Ok(State::new_nvt(eos, t, v, n_i)?));
395 }
396
397 if let (Some(p), Some(t), Some(n_i)) = (pressure, temperature, &n_i) {
399 return Ok(Ok(State::new_npt(eos, t, p, n_i, density_initialization)?));
400 }
401 if let (Some(p), Some(t), Some(v)) = (pressure, temperature, v) {
402 return Ok(Ok(State::new_npvx(
403 eos,
404 t,
405 p,
406 v,
407 &x_u,
408 density_initialization,
409 )?));
410 }
411 Ok(Err(n_i.to_owned()))
412 }
413
414 pub fn new_npt(
417 eos: &Arc<E>,
418 temperature: Temperature,
419 pressure: Pressure,
420 moles: &Moles<Array1<f64>>,
421 density_initialization: DensityInitialization,
422 ) -> EosResult<Self> {
423 match density_initialization {
425 DensityInitialization::InitialDensity(rho0) => {
426 return density_iteration(eos, temperature, pressure, moles, rho0)
427 }
428 DensityInitialization::Vapor => {
429 return density_iteration(
430 eos,
431 temperature,
432 pressure,
433 moles,
434 pressure / temperature / RGAS,
435 )
436 }
437 DensityInitialization::Liquid => {
438 return density_iteration(
439 eos,
440 temperature,
441 pressure,
442 moles,
443 eos.max_density(Some(moles))?,
444 )
445 }
446 DensityInitialization::None => (),
447 }
448
449 let max_density = eos.max_density(Some(moles))?;
451 let liquid = density_iteration(eos, temperature, pressure, moles, max_density);
452
453 if pressure < max_density * temperature * RGAS {
454 let vapor = density_iteration(
455 eos,
456 temperature,
457 pressure,
458 moles,
459 pressure / temperature / RGAS,
460 );
461 match (&liquid, &vapor) {
462 (Ok(_), Err(_)) => liquid,
463 (Err(_), Ok(_)) => vapor,
464 (Ok(l), Ok(v)) => {
465 if l.residual_gibbs_energy() > v.residual_gibbs_energy() {
466 vapor
467 } else {
468 liquid
469 }
470 }
471 _ => Err(EosError::UndeterminedState(String::from(
472 "Density iteration did not find a solution.",
473 ))),
474 }
475 } else {
476 liquid
477 }
478 }
479
480 pub fn new_npvx(
482 eos: &Arc<E>,
483 temperature: Temperature,
484 pressure: Pressure,
485 volume: Volume,
486 molefracs: &Array1<f64>,
487 density_initialization: DensityInitialization,
488 ) -> EosResult<Self> {
489 let moles = molefracs * Moles::from_reduced(1.0);
490 let state = Self::new_npt(eos, temperature, pressure, &moles, density_initialization)?;
491 let moles = state.partial_density * volume;
492 Self::new_nvt(eos, temperature, volume, &moles)
493 }
494}
495
496impl<E: Residual + IdealGas> State<E> {
497 #[expect(clippy::too_many_arguments)]
512 pub fn new_full(
513 eos: &Arc<E>,
514 temperature: Option<Temperature>,
515 volume: Option<Volume>,
516 density: Option<Density>,
517 partial_density: Option<&Density<Array1<f64>>>,
518 total_moles: Option<Moles>,
519 moles: Option<&Moles<Array1<f64>>>,
520 molefracs: Option<&Array1<f64>>,
521 pressure: Option<Pressure>,
522 molar_enthalpy: Option<MolarEnergy>,
523 molar_entropy: Option<MolarEntropy>,
524 molar_internal_energy: Option<MolarEnergy>,
525 density_initialization: DensityInitialization,
526 initial_temperature: Option<Temperature>,
527 ) -> EosResult<Self> {
528 let state = Self::_new(
529 eos,
530 temperature,
531 volume,
532 density,
533 partial_density,
534 total_moles,
535 moles,
536 molefracs,
537 pressure,
538 density_initialization,
539 )?;
540
541 let ti = initial_temperature;
542 match state {
543 Ok(state) => Ok(state),
544 Err(n_i) => {
545 if let (Some(p), Some(h), Some(n_i)) = (pressure, molar_enthalpy, &n_i) {
547 return State::new_nph(eos, p, h, n_i, density_initialization, ti);
548 }
549 if let (Some(p), Some(s), Some(n_i)) = (pressure, molar_entropy, &n_i) {
550 return State::new_nps(eos, p, s, n_i, density_initialization, ti);
551 }
552 if let (Some(t), Some(h), Some(n_i)) = (temperature, molar_enthalpy, &n_i) {
553 return State::new_nth(eos, t, h, n_i, density_initialization);
554 }
555 if let (Some(t), Some(s), Some(n_i)) = (temperature, molar_entropy, &n_i) {
556 return State::new_nts(eos, t, s, n_i, density_initialization);
557 }
558 if let (Some(u), Some(v), Some(n_i)) = (molar_internal_energy, volume, &n_i) {
559 return State::new_nvu(eos, v, u, n_i, ti);
560 }
561 Err(EosError::UndeterminedState(String::from(
562 "Missing input parameters.",
563 )))
564 }
565 }
566 }
567
568 pub fn new_nph(
570 eos: &Arc<E>,
571 pressure: Pressure,
572 molar_enthalpy: MolarEnergy,
573 moles: &Moles<Array1<f64>>,
574 density_initialization: DensityInitialization,
575 initial_temperature: Option<Temperature>,
576 ) -> EosResult<Self> {
577 let t0 = initial_temperature.unwrap_or(Temperature::from_reduced(298.15));
578 let mut density = density_initialization;
579 let f = |x0| {
580 let s = State::new_npt(eos, x0, pressure, moles, density)?;
581 let dfx = s.molar_isobaric_heat_capacity(Contributions::Total);
582 let fx = s.molar_enthalpy(Contributions::Total) - molar_enthalpy;
583 density = DensityInitialization::InitialDensity(s.density);
584 Ok((fx, dfx, s))
585 };
586 newton(t0, f, Temperature::from_reduced(1.0e-8))
587 }
588
589 pub fn new_nth(
591 eos: &Arc<E>,
592 temperature: Temperature,
593 molar_enthalpy: MolarEnergy,
594 moles: &Moles<Array1<f64>>,
595 density_initialization: DensityInitialization,
596 ) -> EosResult<Self> {
597 let rho0 = match density_initialization {
598 DensityInitialization::InitialDensity(r) => r,
599 DensityInitialization::Liquid => eos.max_density(Some(moles))?,
600 DensityInitialization::Vapor => 1.0e-5 * eos.max_density(Some(moles))?,
601 DensityInitialization::None => 0.01 * eos.max_density(Some(moles))?,
602 };
603 let n_inv = 1.0 / moles.sum();
604 let f = |x0| {
605 let s = State::new_nvt(eos, temperature, moles.sum() / x0, moles)?;
606 let dfx = -s.volume / s.density
607 * n_inv
608 * (s.volume * s.dp_dv(Contributions::Total)
609 + temperature * s.dp_dt(Contributions::Total));
610 let fx = s.molar_enthalpy(Contributions::Total) - molar_enthalpy;
611 Ok((fx, dfx, s))
612 };
613 newton(rho0, f, Density::from_reduced(1.0e-12))
614 }
615
616 pub fn new_nts(
618 eos: &Arc<E>,
619 temperature: Temperature,
620 molar_entropy: MolarEntropy,
621 moles: &Moles<Array1<f64>>,
622 density_initialization: DensityInitialization,
623 ) -> EosResult<Self> {
624 let rho0 = match density_initialization {
625 DensityInitialization::InitialDensity(r) => r,
626 DensityInitialization::Liquid => eos.max_density(Some(moles))?,
627 DensityInitialization::Vapor => 1.0e-5 * eos.max_density(Some(moles))?,
628 DensityInitialization::None => 0.01 * eos.max_density(Some(moles))?,
629 };
630 let n_inv = 1.0 / moles.sum();
631 let f = |x0| {
632 let s = State::new_nvt(eos, temperature, moles.sum() / x0, moles)?;
633 let dfx = -n_inv * s.volume / s.density * s.dp_dt(Contributions::Total);
634 let fx = s.molar_entropy(Contributions::Total) - molar_entropy;
635 Ok((fx, dfx, s))
636 };
637 newton(rho0, f, Density::from_reduced(1.0e-12))
638 }
639
640 pub fn new_nps(
642 eos: &Arc<E>,
643 pressure: Pressure,
644 molar_entropy: MolarEntropy,
645 moles: &Moles<Array1<f64>>,
646 density_initialization: DensityInitialization,
647 initial_temperature: Option<Temperature>,
648 ) -> EosResult<Self> {
649 let t0 = initial_temperature.unwrap_or(Temperature::from_reduced(298.15));
650 let mut density = density_initialization;
651 let f = |x0| {
652 let s = State::new_npt(eos, x0, pressure, moles, density)?;
653 let dfx = s.molar_isobaric_heat_capacity(Contributions::Total) / s.temperature;
654 let fx = s.molar_entropy(Contributions::Total) - molar_entropy;
655 density = DensityInitialization::InitialDensity(s.density);
656 Ok((fx, dfx, s))
657 };
658 newton(t0, f, Temperature::from_reduced(1.0e-8))
659 }
660
661 pub fn new_nvu(
663 eos: &Arc<E>,
664 volume: Volume,
665 molar_internal_energy: MolarEnergy,
666 moles: &Moles<Array1<f64>>,
667 initial_temperature: Option<Temperature>,
668 ) -> EosResult<Self> {
669 let t0 = initial_temperature.unwrap_or(Temperature::from_reduced(298.15));
670 let f = |x0| {
671 let s = State::new_nvt(eos, x0, volume, moles)?;
672 let fx = s.molar_internal_energy(Contributions::Total) - molar_internal_energy;
673 let dfx = s.molar_isochoric_heat_capacity(Contributions::Total);
674 Ok((fx, dfx, s))
675 };
676 newton(t0, f, Temperature::from_reduced(1.0e-8))
677 }
678}
679
680impl<E: Residual> State<E> {
681 pub fn update_temperature(&self, temperature: Temperature) -> EosResult<Self> {
683 Self::new_nvt(&self.eos, temperature, self.volume, &self.moles)
684 }
685
686 pub fn derive0(&self) -> StateHD<f64> {
688 StateHD::new(
689 self.reduced_temperature,
690 self.reduced_volume,
691 self.reduced_moles.clone(),
692 )
693 }
694
695 pub fn derive1(&self, derivative: Derivative) -> StateHD<Dual64> {
697 let mut t = Dual64::from(self.reduced_temperature);
698 let mut v = Dual64::from(self.reduced_volume);
699 let mut n = self.reduced_moles.mapv(Dual64::from);
700 match derivative {
701 Derivative::DT => t = t.derivative(),
702 Derivative::DV => v = v.derivative(),
703 Derivative::DN(i) => n[i] = n[i].derivative(),
704 }
705 StateHD::new(t, v, n)
706 }
707
708 pub fn derive2(&self, derivative: Derivative) -> StateHD<Dual2_64> {
710 let mut t = Dual2_64::from(self.reduced_temperature);
711 let mut v = Dual2_64::from(self.reduced_volume);
712 let mut n = self.reduced_moles.mapv(Dual2_64::from);
713 match derivative {
714 Derivative::DT => t = t.derivative(),
715 Derivative::DV => v = v.derivative(),
716 Derivative::DN(i) => n[i] = n[i].derivative(),
717 }
718 StateHD::new(t, v, n)
719 }
720
721 pub fn derive2_mixed(
723 &self,
724 derivative1: Derivative,
725 derivative2: Derivative,
726 ) -> StateHD<HyperDual64> {
727 let mut t = HyperDual64::from(self.reduced_temperature);
728 let mut v = HyperDual64::from(self.reduced_volume);
729 let mut n = self.reduced_moles.mapv(HyperDual64::from);
730 match derivative1 {
731 Derivative::DT => t = t.derivative1(),
732 Derivative::DV => v = v.derivative1(),
733 Derivative::DN(i) => n[i] = n[i].derivative1(),
734 }
735 match derivative2 {
736 Derivative::DT => t = t.derivative2(),
737 Derivative::DV => v = v.derivative2(),
738 Derivative::DN(i) => n[i] = n[i].derivative2(),
739 }
740 StateHD::new(t, v, n)
741 }
742
743 pub fn derive3(&self, derivative: Derivative) -> StateHD<Dual3_64> {
745 let mut t = Dual3_64::from(self.reduced_temperature);
746 let mut v = Dual3_64::from(self.reduced_volume);
747 let mut n = self.reduced_moles.mapv(Dual3_64::from);
748 match derivative {
749 Derivative::DT => t = t.derivative(),
750 Derivative::DV => v = v.derivative(),
751 Derivative::DN(i) => n[i] = n[i].derivative(),
752 };
753 StateHD::new(t, v, n)
754 }
755}
756
757fn is_close<U: Copy>(
758 x: Quantity<f64, U>,
759 y: Quantity<f64, U>,
760 atol: Quantity<f64, U>,
761 rtol: f64,
762) -> bool {
763 (x - y).abs() <= atol + rtol * y.abs()
764}
765
766fn newton<E: Residual, F, X: Copy, Y>(
767 mut x0: Quantity<f64, X>,
768 mut f: F,
769 atol: Quantity<f64, X>,
770) -> EosResult<State<E>>
771where
772 Y: Sub<X> + Sub<<Y as Sub<X>>::Output, Output = X>,
773 F: FnMut(
774 Quantity<f64, X>,
775 ) -> EosResult<(
776 Quantity<f64, Y>,
777 Quantity<f64, <Y as Sub<X>>::Output>,
778 State<E>,
779 )>,
780{
781 let rtol = 1e-10;
782 let maxiter = 50;
783
784 for _ in 0..maxiter {
785 let (fx, dfx, state) = f(x0)?;
786 let x = x0 - fx / dfx;
787 if is_close(x, x0, atol, rtol) {
788 return Ok(state);
789 }
790 x0 = x;
791 }
792 Err(EosError::NotConverged("newton".to_owned()))
793}
794
795fn validate(temperature: Temperature, volume: Volume, moles: &Moles<Array1<f64>>) -> EosResult<()> {
804 let t = temperature.to_reduced();
805 let v = volume.to_reduced();
806 let m = moles.to_reduced();
807 if !t.is_finite() || t.is_sign_negative() {
808 return Err(EosError::InvalidState(
809 String::from("validate"),
810 String::from("temperature"),
811 t,
812 ));
813 }
814 if !v.is_finite() || v.is_sign_negative() {
815 return Err(EosError::InvalidState(
816 String::from("validate"),
817 String::from("volume"),
818 v,
819 ));
820 }
821 for &n in m.iter() {
822 if !n.is_finite() || n.is_sign_negative() {
823 return Err(EosError::InvalidState(
824 String::from("validate"),
825 String::from("moles"),
826 n,
827 ));
828 }
829 }
830 Ok(())
831}
832
833#[derive(Clone, Copy)]
834pub enum TPSpec {
835 Temperature(Temperature),
836 Pressure(Pressure),
837}
838
839impl From<Temperature> for TPSpec {
840 fn from(temperature: Temperature) -> Self {
841 Self::Temperature(temperature)
842 }
843}
844
845impl From<Quantity<f64, SIUnit<N2, N1, P1, Z0, Z0, Z0, Z0>>> for TPSpec {
849 fn from(pressure: Pressure) -> Self {
850 Self::Pressure(pressure)
851 }
852}
853
854mod critical_point;
855
856#[cfg(test)]
857mod tests {
858 use super::*;
859 use typenum::P3;
860
861 #[test]
862 fn test_validate() {
863 let temperature = 298.15 * KELVIN;
864 let volume = 3000.0 * METER.powi::<P3>();
865 let moles = &arr1(&[0.03, 0.02, 0.05]) * MOL;
866 assert!(validate(temperature, volume, &moles).is_ok());
867 }
868
869 #[test]
870 fn test_negative_temperature() {
871 let temperature = -298.15 * KELVIN;
872 let volume = 3000.0 * METER.powi::<P3>();
873 let moles = &arr1(&[0.03, 0.02, 0.05]) * MOL;
874 assert!(validate(temperature, volume, &moles).is_err());
875 }
876
877 #[test]
878 fn test_nan_temperature() {
879 let temperature = f64::NAN * KELVIN;
880 let volume = 3000.0 * METER.powi::<P3>();
881 let moles = &arr1(&[0.03, 0.02, 0.05]) * MOL;
882 assert!(validate(temperature, volume, &moles).is_err());
883 }
884
885 #[test]
886 fn test_negative_mole_number() {
887 let temperature = 298.15 * KELVIN;
888 let volume = 3000.0 * METER.powi::<P3>();
889 let moles = &arr1(&[-0.03, 0.02, 0.05]) * MOL;
890 assert!(validate(temperature, volume, &moles).is_err());
891 }
892
893 #[test]
894 fn test_nan_mole_number() {
895 let temperature = 298.15 * KELVIN;
896 let volume = 3000.0 * METER.powi::<P3>();
897 let moles = &arr1(&[f64::NAN, 0.02, 0.05]) * MOL;
898 assert!(validate(temperature, volume, &moles).is_err());
899 }
900
901 #[test]
902 fn test_negative_volume() {
903 let temperature = 298.15 * KELVIN;
904 let volume = -3000.0 * METER.powi::<P3>();
905 let moles = &arr1(&[0.01, 0.02, 0.05]) * MOL;
906 assert!(validate(temperature, volume, &moles).is_err());
907 }
908}