1use cobia::*;
2use crate::salt_water_calculator;
3use crate::property_tables;
4use crate::phase_equilibrium_type::PhaseEquilibriumType;
5use strum::{EnumCount, IntoEnumIterator};
6
7#[cape_object_implementation(
31 interfaces = {
32 cape_open_1_2::ICapeUtilities,
33 cape_open_1_2::ICapeIdentification,
34 cape_open_1_2::ICapeThermoMaterialContext,
35 cape_open_1_2::ICapeThermoCompounds,
36 cape_open_1_2::ICapeThermoPhases,
37 cape_open_1_2::ICapeThermoPropertyRoutine,
38 cape_open_1_2::ICapeThermoEquilibriumRoutine,
39 cape_open_1_2::ICapeThermoUniversalConstant
40 }
41 )]
42pub(crate) struct SaltWaterPropertyPackage {
43 name: String,
45 description: String,
47 material: Option<cape_open_1_2::CapeThermoMaterial>,
49 constant_property_map: CapeOpenMap<[CapeValueContent;2]>,
51 material_compound_indices: Vec<usize>,
53 material_nacl_index: Option<usize>,
55 mat_comp_ids : CapeArrayStringVec,
60 mat_formulae : CapeArrayStringVec,
62 mat_comp_names : CapeArrayStringVec,
64 mat_boil_temps : CapeArrayRealVec,
66 mat_molecular_weights : CapeArrayRealVec,
68 mat_cas_registry_numbers : CapeArrayStringVec,
70 phase_list : CapeArrayStringVec,
72 phase_status : CapeArrayEnumerationVec<cape_open_1_2::CapePhaseStatus>,
74 property_value : CapeArrayRealVec,
76 scalar_property_value : CapeArrayRealScalar,
78 fraction : CapeStringImpl,
83 temperature: CapeStringImpl,
85 pressure : CapeStringImpl,
87 phase_fraction : CapeStringImpl,
89 mole : CapeStringImpl,
91 enthalpy : CapeStringImpl,
93 entropy : CapeStringImpl,
95 empty_string : CapeStringImpl,
97 h2o : CapeStringConstNoCase,
99 nacl : CapeStringConstNoCase,
101 liquid: CapeStringConstNoCase,
103}
104
105impl std::default::Default for SaltWaterPropertyPackage {
108 fn default() -> Self {
109 Self {
110 cobia_object_data: Default::default(),
111 name: Self::NAME.to_string(),
112 description: Self::DESCRIPTION.to_string(),
113 material : None,
114 constant_property_map : CapeOpenMap::new(),
115 material_compound_indices : Vec::new(),
116 material_nacl_index:None,
117 mat_comp_ids : CapeArrayStringVec::new(),
118 mat_formulae : CapeArrayStringVec::new(),
119 mat_comp_names : CapeArrayStringVec::new(),
120 mat_boil_temps : CapeArrayRealVec::new(),
121 mat_molecular_weights : CapeArrayRealVec::new(),
122 mat_cas_registry_numbers : CapeArrayStringVec::new(),
123 phase_list : CapeArrayStringVec::new(),
124 phase_status : CapeArrayEnumerationVec::<cape_open_1_2::CapePhaseStatus>::new(),
125 property_value : CapeArrayRealVec::new(),
126 scalar_property_value: CapeArrayRealScalar::new(),
127 fraction : CapeStringImpl::from_string("fraction"),
128 temperature: CapeStringImpl::from_string("temperature"),
129 pressure : CapeStringImpl::from_string("pressure"),
130 phase_fraction : CapeStringImpl::from_string("phaseFraction"),
131 mole : CapeStringImpl::from_string("mole"),
132 enthalpy : CapeStringImpl::from_string("enthalpy"),
133 entropy : CapeStringImpl::from_string("entropy"),
134 empty_string : CapeStringImpl::new(),
135 h2o : CapeStringConstNoCase::from_string("H2O"),
136 nacl : CapeStringConstNoCase::from_string("NaCl"),
137 liquid: CapeStringConstNoCase::from_string("Liquid"),
138 }
139 }
140}
141
142impl SaltWaterPropertyPackage {
143 const NAME: &'static str = "Salt Water";
145 const DESCRIPTION: &'static str = "Salt water property package";
147 const PROGID: &'static str = "SaltWater.SaltWater";
149
150 const COMP_FORMULAS: [&'static str;2]=["H2O","NaCl"];
152 const COMP_NAMES: [&'static str;2]=["Water","Sodium Chloride"];
154 const COMP_BOIL_TEMPS: [f64;2]=[373.15,1738.0];
156 const COMP_MELT_TEMPS: [f64;2]=[273.15,1073.8];
158 pub(crate) const COMP_MOLWTS: [f64;2]=[18.01528,58.443];
160 const COMP_CASNOS: [&'static str;2]=["7732-18-5","7647-14-5"];
162 const COMP_SMILES: [&'static str;2]=["O","[Na+].[Cl-]"];
164 const COMP_IUPAC_NAMES: [&'static str;2]=["oxidane","sodium;chloride"];
166
167
168 fn check_context_material(&mut self) -> Result<(), COBIAError> {
181 if self.material_compound_indices.is_empty() {
182 self.material_nacl_index=None;
183 match &self.material {
184 Some(material) => {
185 match cape_open_1_2::CapeThermoCompounds::from_object(material) {
186 Ok(compounds) => {
187 compounds.get_compound_list(
188 &mut self.mat_comp_ids,
189 &mut self.mat_formulae,
190 &mut self.mat_comp_names,
191 &mut self.mat_boil_temps,
192 &mut self.mat_molecular_weights,
193 &mut self.mat_cas_registry_numbers)?;
194 if self.mat_comp_ids.is_empty() {
195 return Err(COBIAError::Message("material objects has no compounds".to_string()));
196 }
197 for comp in self.mat_comp_ids.iter() {
198 if self.h2o==*comp {
201 self.material_compound_indices.push(0);
202 } else if self.nacl==*comp {
203 self.material_nacl_index=Some(self.material_compound_indices.len());
204 self.material_compound_indices.push(1);
205 } else {
206 self.material_compound_indices.clear();
207 return Err(COBIAError::Message(format!("Unknown compound ID: {}",comp)));
208 }
209 }
210 let mut used:[bool;2]=[false,false];
212 for index in self.material_compound_indices.iter() {
213 if used[*index] {
214 self.material_compound_indices.clear();
215 return Err(COBIAError::Message("duplicate compounds on material object".to_string()));
216 }
217 used[*index]=true;
218 }
219 if !used[0] {
220 self.material_compound_indices.clear();
222 return Err(COBIAError::Message("material object does not contain water".to_string()));
223 }
224 Ok(())
225 },
226 Err(_) => Err(COBIAError::Message("material object does not implement ICapeThermoCompounds".to_string()))
227 }
228 },
229 None => Err(COBIAError::Message("Material Object is not set".to_string()))
230 }
231 } else {
232 Ok(())
233 }
234 }
235}
236
237impl std::fmt::Display for SaltWaterPropertyPackage {
244 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
245 write!(f, "{} package \"{}\"", Self::NAME, self.name)
246 }
247}
248
249impl PMCRegisterationInfo for SaltWaterPropertyPackage {
255
256 fn get_uuid() -> CapeUUID {
259 CapeUUID::from_slice(&[0xC3u8,0xC4u8,0x3Fu8,0xDDu8,0x95u8,0x8Du8,0x4Eu8,0xD8u8,0xBFu8,0x4Du8,0xB8u8,0x01u8,0xD6u8,0xD7u8,0x53u8,0x28u8])
261 }
262
263 fn registration_details(registrar: &CapeRegistrar) -> Result<(), COBIAError> {
270 registrar.put_name(Self::NAME)?;
271 registrar.put_description(Self::DESCRIPTION)?;
272 registrar.put_cape_version("1.2")?;
273 registrar.put_component_version(env!("CARGO_PKG_VERSION"))?;
274 registrar.put_vendor_url("https://www.amsterchem.com/")?;
275 registrar.put_about(Self::DESCRIPTION)?;
276 registrar.put_uuid(&Self::get_uuid())?;
277 registrar.put_version_independent_prog_id(Self::PROGID)?;
278 registrar.put_prog_id(&format!("{}.1",Self::PROGID))?;
279 registrar.add_cat_id(&cape_open::CATEGORYID_STANDALONEPROPERTYPACKAGE)?;
280 registrar.add_cat_id(&cape_open_1_2::CATEGORYID_COMPONENT_1_2)?;
281 Ok(())
282 }
283}
284
285impl cape_open_1_2::ICapeIdentification for SaltWaterPropertyPackage {
291
292 fn get_component_name(&mut self,name:&mut CapeStringOut) -> Result<(), COBIAError> {
301 name.set_string(&self.name)?;
302 Ok(())
303 }
304
305 fn get_component_description(&mut self,description:&mut CapeStringOut) -> Result<(), COBIAError> {
314 description.set_string(&self.description)?;
315 Ok(())
316 }
317
318 fn set_component_name(&mut self, name: &CapeStringIn) -> Result<(), COBIAError> {
327 self.name = name.to_string();
328 Ok(())
329 }
330
331 fn set_component_description(&mut self, desc: &CapeStringIn) -> Result<(), COBIAError> {
340 self.description = desc.to_string();
341 Ok(())
342 }
343}
344
345impl cape_open_1_2::ICapeUtilities for SaltWaterPropertyPackage {
348
349 fn get_parameters(&mut self) -> Result<cape_open_1_2::CapeCollection<cape_open_1_2::CapeParameter>,COBIAError> {
360 Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
361 }
362
363 fn set_simulation_context(&mut self,_:cape_open_1_2::CapeSimulationContext) -> Result<(),COBIAError> {
375 Ok(()) }
377
378 fn initialize(&mut self) -> Result<(),COBIAError> {
391 Ok(()) }
393
394 fn terminate(&mut self) -> Result<(),COBIAError> {
403 self.material=None;
405 Ok(())
406 }
407
408 fn edit(&mut self,_:CapeWindowId) -> Result<cape_open_1_2::CapeEditResult,COBIAError> {
423 Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED)) }
425}
426
427impl cape_open_1_2::ICapeThermoMaterialContext for SaltWaterPropertyPackage {
433
434 fn set_material(&mut self,material:cape_open_1_2::CapeThermoMaterial) -> Result<(),COBIAError> {
442 self.material=Some(material);
443 self.material_compound_indices.clear();
444 Ok(())
445 }
446 fn unset_material(&mut self) -> Result<(),COBIAError> {
451 self.material=None;
452 self.material_compound_indices.clear();
453 Ok(())
454 }
455}
456
457impl cape_open_1_2::ICapeThermoCompounds for SaltWaterPropertyPackage {
461
462 fn get_compound_constant(&mut self,props:&CapeArrayStringIn,comp_ids:&CapeArrayStringIn,contains_missing_values:&mut CapeBoolean,prop_vals:&mut CapeArrayValueOut) -> Result<(),COBIAError> {
474 if self.constant_property_map.is_empty() {
476 self.constant_property_map.insert("normalboilingpoint".into(),[CapeValueContent::Real(Self::COMP_BOIL_TEMPS[0]),CapeValueContent::Real(Self::COMP_BOIL_TEMPS[1])]);
478 self.constant_property_map.insert("molecularweight".into(),[CapeValueContent::Real(Self::COMP_MOLWTS[0]),CapeValueContent::Real(Self::COMP_MOLWTS[1])]);
479 self.constant_property_map.insert("casregistrynumber".into(),[CapeValueContent::String(Self::COMP_CASNOS[0].to_string()),CapeValueContent::String(Self::COMP_CASNOS[1].to_string())]);
480 self.constant_property_map.insert("chemicalformula".into(),[CapeValueContent::String(Self::COMP_FORMULAS[0].to_string()),CapeValueContent::String(Self::COMP_FORMULAS[1].to_string())]);
481 self.constant_property_map.insert("iupacname".into(),[CapeValueContent::String(Self::COMP_IUPAC_NAMES[0].to_string()),CapeValueContent::String(Self::COMP_IUPAC_NAMES[1].to_string())]);
482 self.constant_property_map.insert("normalfreezingpoint".into(),[CapeValueContent::Real(Self::COMP_MELT_TEMPS[0]),CapeValueContent::Real(Self::COMP_MELT_TEMPS[1])]);
483 self.constant_property_map.insert("smilesformula".into(),[CapeValueContent::String(Self::COMP_SMILES[0].to_string()),CapeValueContent::String(Self::COMP_SMILES[1].to_string())]);
484 }
485 let mut comp_indices: Vec<u32>=Vec::new();
487 if comp_ids.is_empty() {
488 comp_indices.push(0);
489 comp_indices.push(1);
490 } else {
491 for comp_id in comp_ids.iter() {
492 if self.h2o==comp_id {
494 comp_indices.push(0);
495 } else if self.nacl==comp_id {
496 comp_indices.push(1);
497 } else {
498 return Err(COBIAError::Message(format!("Unknown compound ID: {}",comp_id)));
499 }
500 }
501 }
502 *contains_missing_values=false as CapeBoolean;
504 let mut results: Vec<CapeValueContent>=Vec::new();
505 results.reserve(comp_ids.size()*props.size());
506 for prop in props.iter() {
507 let values: &[CapeValueContent;2]=
509 match self.constant_property_map.get(&prop) {
510 Some(values) => {
511 values
512 },
513 None => {
514 return Err(COBIAError::Message(format!("Unsupported compound constant: {}",prop)));
515 }
516 };
517 for comp_index in comp_indices.iter() {
518 results.push(values[*comp_index as usize].clone());
519 }
520 }
521 prop_vals.put_array(&results)?;
522 Ok(())
523 }
524
525 fn get_compound_list(&mut self,comp_ids:&mut CapeArrayStringOut,formulae:&mut CapeArrayStringOut,names:&mut CapeArrayStringOut,boil_temps:&mut CapeArrayRealOut,molwts:&mut CapeArrayRealOut,casnos:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
539 comp_ids.put_array(&Self::COMP_FORMULAS)?;
540 formulae.put_array(&Self::COMP_FORMULAS)?;
541 names.put_array(&Self::COMP_NAMES)?;
542 boil_temps.put_array(&Self::COMP_BOIL_TEMPS)?;
543 molwts.put_array(&Self::COMP_MOLWTS)?;
544 casnos.put_array(&Self::COMP_CASNOS)?;
545 Ok(())
546 }
547
548 fn get_const_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
556 props.put_array(&[
557 "normalBoilingPoint",
558 "molecularWeight",
559 "casRegistryNumber",
560 "chemicalFormula",
561 "iupacName",
562 "normalFreezingPoint",
563 "SMILESformula"])?;
564 Ok(())
565 }
566
567 fn get_num_compounds(&mut self) -> Result<CapeInteger,COBIAError> {
573 Ok(2)
574 }
575
576 fn get_pdependent_property(&mut self,_:&CapeArrayStringIn,_:CapeReal,_:&CapeArrayStringIn,_:&mut CapeBoolean,_:&mut CapeArrayRealOut) -> Result<(),COBIAError> {
584 Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
586 }
587
588 fn get_pdependent_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
599 let empty_list:[&str;0]=[];
601 props.put_array(&empty_list)?;
602 Ok(())
603 }
604
605 fn get_tdependent_property(&mut self,_:&CapeArrayStringIn,_:CapeReal,_:&CapeArrayStringIn,_:&mut CapeBoolean,_:&mut CapeArrayRealOut) -> Result<(),COBIAError> {
613 Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
615 }
616
617 fn get_tdependent_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
627 let empty_list:[&str;0]=[];
629 props.put_array(&empty_list)?;
630 Ok(())
631 }
632}
633
634impl cape_open_1_2::ICapeThermoPhases for SaltWaterPropertyPackage {
637
638 fn get_num_phases(&mut self) -> Result<CapeInteger,COBIAError> {
646 Ok(1) }
648
649 fn get_phase_info(&mut self,phase_label:&CapeStringIn,phase_attribute:&CapeStringIn,value:&mut CapeValueOut) -> Result<(),COBIAError> {
659 if self.liquid==*phase_label {
660 match phase_attribute.as_string().to_lowercase().as_str() {
664 "stateofaggregation" => {value.set_string("Liquid")?;Ok(())}
665 "keycompoundid" => {value.set_string("H2O")?;Ok(())}
666 "excludedcompoundid" => {value.set_empty()?;Ok(())}
667 _ => return Err(COBIAError::Message(format!("Unsupported phase attribute: {}",phase_attribute)))
668 }
669 } else {
670 Err(COBIAError::Message(format!("Unsupported phase: {}",phase_label)))
671 }
672 }
673
674 fn get_phase_list(&mut self,phase_labels:&mut CapeArrayStringOut,state_of_aggregation:&mut CapeArrayStringOut,key_compound_id:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
689 phase_labels.put_array(&["Liquid"])?;
690 state_of_aggregation.put_array(&["Liquid"])?;
691 key_compound_id.put_array(&["H2O"])?;
692 Ok(())
693 }
694}
695
696impl cape_open_1_2::ICapeThermoPropertyRoutine for SaltWaterPropertyPackage {
699
700 fn calc_and_get_ln_phi(&mut self,_:&CapeStringIn,_:CapeReal,_:CapeReal,_:&CapeArrayRealIn,_:CapeInteger,_:&mut CapeArrayRealOut,_:&mut CapeArrayRealOut,_:&mut CapeArrayRealOut,_:&mut CapeArrayRealOut) -> Result<(),COBIAError> {
707 Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
709 }
710
711 fn calc_single_phase_prop(&mut self,props:&CapeArrayStringIn,phase_label:&CapeStringIn) -> Result<(),COBIAError> {
725 self.check_context_material()?;
726 if self.liquid!=*phase_label {
727 return Err(COBIAError::Message(format!("Unsupported phase: {}",phase_label)));
728 }
729 if !props.is_empty() {
730 let material=self.material.as_ref().unwrap(); let x_nacl=match self.material_nacl_index {
733 None => 0.0,
734 Some(nacl_index) => {
735 material.get_single_phase_prop(&self.fraction,phase_label,&self.mole,&mut self.property_value)?;
736 if self.property_value.size()!=self.material_compound_indices.len() {
737 return Err(COBIAError::Message("unexpected number of values for mole fraction".to_string()));
738 }
739 self.property_value[nacl_index]
740 }
741 };
742 material.get_single_phase_prop(&self.temperature,phase_label,&self.empty_string,&mut self.scalar_property_value)?;
743 let temperature = self.scalar_property_value.value();
744 material.get_single_phase_prop(&self.pressure,phase_label,&self.empty_string,&mut self.scalar_property_value)?;
745 let pressure = self.scalar_property_value.value();
746 let prop_table = &property_tables::PROPERTYTABLES;
747 for prop in props.iter() {
748 match prop_table.get_single_phase_property(&prop) {
749 Some(single_phase_property) => {
750 match single_phase_property {
751 property_tables::SinglePhaseProperty::Viscosity => {
752 match salt_water_calculator::viscosity(temperature,x_nacl) {
753 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(value))?;},
754 Err(msg) => {return Err(COBIAError::Message(msg));}
755 }
756 },
757 property_tables::SinglePhaseProperty::ViscositydTemperature => {
758 match salt_water_calculator::viscosity_d_temperature(temperature,x_nacl) {
759 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(value))?;},
760 Err(msg) => {return Err(COBIAError::Message(msg));}
761 }
762 },
763 property_tables::SinglePhaseProperty::ViscositydPressure => {
764 match salt_water_calculator::viscosity_d_pressure(temperature,x_nacl) {
765 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(value))?;},
766 Err(msg) => {return Err(COBIAError::Message(msg));}
767 }
768 },
769 property_tables::SinglePhaseProperty::ViscositydMoles => {
770 if self.material_compound_indices.len()==1 {
771 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
772 } else {
773 match salt_water_calculator::intenstive_dn(salt_water_calculator::viscosity_d_x_nacl(temperature,x_nacl),x_nacl) {
774 Ok(value) => {
775 let mut values=[0.0,0.0];
776 for i in 0..self.material_compound_indices.len() {
777 values[i]=value[self.material_compound_indices[i]];
778 }
779 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealSlice::new(&values))?;
780
781 },
782 Err(msg) => {return Err(COBIAError::Message(msg));}
783 }
784 }
785 },
786 property_tables::SinglePhaseProperty::ViscositydMolFraction => {
787 if self.material_compound_indices.len()==1 {
788 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
789 } else {
790 match salt_water_calculator::unconstrained_dx(salt_water_calculator::viscosity_d_x_nacl(temperature,x_nacl)) {
791 Ok(value) => {
792 let mut values=[0.0,0.0];
793 for i in 0..self.material_compound_indices.len() {
794 values[i]=value[self.material_compound_indices[i]];
795 }
796 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealSlice::new(&values))?;
797
798 },
799 Err(msg) => {return Err(COBIAError::Message(msg));}
800 }
801 }
802 },
803 property_tables::SinglePhaseProperty::ThermalConductivity=> {
804 match salt_water_calculator::thermal_conductivity(temperature,x_nacl) {
805 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(value))?;},
806 Err(msg) => {return Err(COBIAError::Message(msg));}
807 }
808 },
809 property_tables::SinglePhaseProperty::ThermalConductivitydTemperature => {
810 match salt_water_calculator::thermal_conductivity_d_temperature(temperature,x_nacl) {
811 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(value))?;},
812 Err(msg) => {return Err(COBIAError::Message(msg));}
813 }
814 },
815 property_tables::SinglePhaseProperty::ThermalConductivitydPressure => {
816 match salt_water_calculator::thermal_conductivity_d_pressure(temperature,x_nacl) {
817 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(value))?;},
818 Err(msg) => {return Err(COBIAError::Message(msg));}
819 }
820 },
821 property_tables::SinglePhaseProperty::ThermalConductivitydMoles => {
822 if self.material_compound_indices.len()==1 {
823 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
824 } else {
825 match salt_water_calculator::intenstive_dn(salt_water_calculator::thermal_conductivity_d_x_nacl(temperature,x_nacl),x_nacl) {
826 Ok(value) => {
827 let mut values=[0.0,0.0];
828 for i in 0..self.material_compound_indices.len() {
829 values[i]=value[self.material_compound_indices[i]];
830 }
831 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealSlice::new(&values))?;
832
833 },
834 Err(msg) => {return Err(COBIAError::Message(msg));}
835 }
836 }
837 },
838 property_tables::SinglePhaseProperty::ThermalConductivitydMolFraction => {
839 if self.material_compound_indices.len()==1 {
840 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
841 } else {
842 match salt_water_calculator::unconstrained_dx(salt_water_calculator::thermal_conductivity_d_x_nacl(temperature,x_nacl)) {
843 Ok(value) => {
844 let mut values=[0.0,0.0];
845 for i in 0..self.material_compound_indices.len() {
846 values[i]=value[self.material_compound_indices[i]];
847 }
848 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealSlice::new(&values))?;
849
850 },
851 Err(msg) => {return Err(COBIAError::Message(msg));}
852 }
853 }
854 },
855 property_tables::SinglePhaseProperty::Enthalpy => {
856 match salt_water_calculator::enthalpy(temperature,pressure,x_nacl) {
857 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
858 Err(msg) => {return Err(COBIAError::Message(msg));}
859 }
860 },
861 property_tables::SinglePhaseProperty::EnthalpydTemperature => {
862 match salt_water_calculator::enthalpy_d_temperature(temperature,pressure,x_nacl) {
863 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
864 Err(msg) => {return Err(COBIAError::Message(msg));}
865 }
866 },
867 property_tables::SinglePhaseProperty::EnthalpydPressure => {
868 match salt_water_calculator::enthalpy_d_pressure(temperature,pressure,x_nacl) {
869 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
870 Err(msg) => {return Err(COBIAError::Message(msg));}
871 }
872 },
873 property_tables::SinglePhaseProperty::EnthalpydMoles => {
874 if self.material_compound_indices.len()==1 {
875 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
876 } else {
877 match salt_water_calculator::extenstive_dn(salt_water_calculator::enthalpy_d_x_nacl(temperature,pressure,x_nacl),salt_water_calculator::enthalpy(temperature,pressure,x_nacl),x_nacl) {
878 Ok(value) => {
879 let mut values=[0.0,0.0];
880 for i in 0..self.material_compound_indices.len() {
881 values[i]=value[self.material_compound_indices[i]];
882 }
883 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
884
885 },
886 Err(msg) => {return Err(COBIAError::Message(msg));}
887 }
888 }
889 },
890 property_tables::SinglePhaseProperty::EnthalpydMolFraction => {
891 if self.material_compound_indices.len()==1 {
892 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
893 } else {
894 match salt_water_calculator::unconstrained_dx(salt_water_calculator::enthalpy_d_x_nacl(temperature,pressure,x_nacl)) {
895 Ok(value) => {
896 let mut values=[0.0,0.0];
897 for i in 0..self.material_compound_indices.len() {
898 values[i]=value[self.material_compound_indices[i]];
899 }
900 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
901
902 },
903 Err(msg) => {return Err(COBIAError::Message(msg));}
904 }
905 }
906 },
907 property_tables::SinglePhaseProperty::Entropy => {
908 match salt_water_calculator::entropy(temperature,pressure,x_nacl) {
909 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
910 Err(msg) => {return Err(COBIAError::Message(msg));}
911 }
912 },
913 property_tables::SinglePhaseProperty::EntropydTemperature => {
914 match salt_water_calculator::entropy_d_temperature(temperature,pressure,x_nacl) {
915 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
916 Err(msg) => {return Err(COBIAError::Message(msg));}
917 }
918 },
919 property_tables::SinglePhaseProperty::EntropydPressure => {
920 match salt_water_calculator::entropy_d_pressure(temperature,pressure,x_nacl) {
921 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
922 Err(msg) => {return Err(COBIAError::Message(msg));}
923 }
924 },
925 property_tables::SinglePhaseProperty::EntropydMoles => {
926 if self.material_compound_indices.len()==1 {
927 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
928 } else {
929 match salt_water_calculator::extenstive_dn(salt_water_calculator::entropy_d_x_nacl(temperature,pressure,x_nacl),salt_water_calculator::entropy(temperature,pressure,x_nacl),x_nacl) {
930 Ok(value) => {
931 let mut values=[0.0,0.0];
932 for i in 0..self.material_compound_indices.len() {
933 values[i]=value[self.material_compound_indices[i]];
934 }
935 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
936
937 },
938 Err(msg) => {return Err(COBIAError::Message(msg));}
939 }
940 }
941 },
942 property_tables::SinglePhaseProperty::EntropydMolFraction => {
943 if self.material_compound_indices.len()==1 {
944 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
945 } else {
946 match salt_water_calculator::unconstrained_dx(salt_water_calculator::entropy_d_x_nacl(temperature,pressure,x_nacl)) {
947 Ok(value) => {
948 let mut values=[0.0,0.0];
949 for i in 0..self.material_compound_indices.len() {
950 values[i]=value[self.material_compound_indices[i]];
951 }
952 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
953
954 },
955 Err(msg) => {return Err(COBIAError::Message(msg));}
956 }
957 }
958 },
959 property_tables::SinglePhaseProperty::Density => {
960 match salt_water_calculator::density(temperature,pressure,x_nacl) {
961 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
962 Err(msg) => {return Err(COBIAError::Message(msg));}
963 }
964 },
965 property_tables::SinglePhaseProperty::DensitydTemperature => {
966 match salt_water_calculator::density_d_temperature(temperature,pressure,x_nacl) {
967 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
968 Err(msg) => {return Err(COBIAError::Message(msg));}
969 }
970 },
971 property_tables::SinglePhaseProperty::DensitydPressure => {
972 match salt_water_calculator::density_d_pressure(temperature,pressure,x_nacl) {
973 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(value))?;},
974 Err(msg) => {return Err(COBIAError::Message(msg));}
975 }
976 },
977 property_tables::SinglePhaseProperty::DensitydMoles => {
978 if self.material_compound_indices.len()==1 {
979 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
980 } else {
981 match salt_water_calculator::intenstive_dn(salt_water_calculator::density_d_x_nacl(temperature,pressure,x_nacl),x_nacl) {
982 Ok(value) => {
983 let mut values=[0.0,0.0];
984 for i in 0..self.material_compound_indices.len() {
985 values[i]=value[self.material_compound_indices[i]];
986 }
987 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
988
989 },
990 Err(msg) => {return Err(COBIAError::Message(msg));}
991 }
992 }
993 },
994 property_tables::SinglePhaseProperty::DensitydMolFraction => {
995 if self.material_compound_indices.len()==1 {
996 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
997 } else {
998 match salt_water_calculator::unconstrained_dx(salt_water_calculator::density_d_x_nacl(temperature,pressure,x_nacl)) {
999 Ok(value) => {
1000 let mut values=[0.0,0.0];
1001 for i in 0..self.material_compound_indices.len() {
1002 values[i]=value[self.material_compound_indices[i]];
1003 }
1004 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
1005
1006 },
1007 Err(msg) => {return Err(COBIAError::Message(msg));}
1008 }
1009 }
1010 },
1011 property_tables::SinglePhaseProperty::Volume => {
1012 match salt_water_calculator::density(temperature,pressure,x_nacl) {
1013 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(1.0/value))?;},
1014 Err(msg) => {return Err(COBIAError::Message(msg));}
1015 }
1016 },
1017 property_tables::SinglePhaseProperty::VolumedTemperature => {
1018 match salt_water_calculator::density_d_temperature(temperature,pressure,x_nacl) {
1019 Ok(derivative_value) => {
1020 match salt_water_calculator::density(temperature,pressure,x_nacl) {
1021 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(-derivative_value/(value*value)))?;},
1022 Err(msg) => {return Err(COBIAError::Message(msg));}
1023 }
1024 },
1025 Err(msg) => {return Err(COBIAError::Message(msg));}
1026 }
1027 },
1028 property_tables::SinglePhaseProperty::VolumedPressure => {
1029 match salt_water_calculator::density_d_pressure(temperature,pressure,x_nacl) {
1030 Ok(derivative_value) => {
1031 match salt_water_calculator::density(temperature,pressure,x_nacl) {
1032 Ok(value) => {material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealScalar::from(-derivative_value/(value*value)))?;},
1033 Err(msg) => {return Err(COBIAError::Message(msg));}
1034 }
1035 },
1036 Err(msg) => {return Err(COBIAError::Message(msg));}
1037 }
1038 },
1039 property_tables::SinglePhaseProperty::VolumedMoles => {
1040 if self.material_compound_indices.len()==1 {
1041 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
1042 } else {
1043 match salt_water_calculator::extenstive_reciprocal_dn(salt_water_calculator::density_d_x_nacl(temperature,pressure,x_nacl),salt_water_calculator::density(temperature,pressure,x_nacl),x_nacl) {
1044 Ok(value) => {
1045 let mut values=[0.0,0.0];
1046 for i in 0..self.material_compound_indices.len() {
1047 values[i]=value[self.material_compound_indices[i]];
1048 }
1049 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
1050
1051 },
1052 Err(msg) => {return Err(COBIAError::Message(msg));}
1053 }
1054 }
1055 },
1056 property_tables::SinglePhaseProperty::VolumedMolFraction => {
1057 if self.material_compound_indices.len()==1 {
1058 material.set_single_phase_prop(&prop,phase_label,&self.empty_string,&CapeArrayRealScalar::from(0.0))?;
1059 } else {
1060 match salt_water_calculator::unconstrained_dx(salt_water_calculator::density_d_x_nacl(temperature,pressure,x_nacl)) {
1061 Ok(derivative_value) => {
1062 match salt_water_calculator::density(temperature,pressure,x_nacl) {
1063 Ok(value) => {
1064 let mut values=[0.0,0.0];
1065 for i in 0..self.material_compound_indices.len() {
1066 values[i]=-derivative_value[self.material_compound_indices[i]]/(value*value);
1067 }
1068 material.set_single_phase_prop(&prop,phase_label,&self.mole,&CapeArrayRealSlice::new(&values))?;
1069 },
1070 Err(msg) => {return Err(COBIAError::Message(msg));}
1071 }
1072
1073 },
1074 Err(msg) => {return Err(COBIAError::Message(msg));}
1075 }
1076 }
1077 },
1078 }
1079 }
1080 None => {return Err(COBIAError::Message(format!("Unsupported single phase property: {}",prop)));}
1081 }
1082 }
1083 }
1084 Ok(())
1085 }
1086
1087 fn calc_two_phase_prop(&mut self,_:&CapeArrayStringIn,_:&CapeArrayStringIn) -> Result<(),COBIAError> {
1094 Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
1096 }
1097
1098 fn check_single_phase_prop_spec(&mut self,property:&CapeStringIn,phase_label:&CapeStringIn) -> Result<CapeBoolean,COBIAError> {
1110 let mut is_supported:CapeBoolean=false as CapeBoolean;
1111 if self.liquid==*phase_label {
1112 match (&property_tables::PROPERTYTABLES).get_single_phase_property(property) {
1113 Some(_) => {is_supported=true as CapeBoolean},
1114 None => {}
1115 }
1116 }
1117 Ok(is_supported)
1118 }
1119
1120 fn check_two_phase_prop_spec(&mut self,_:&CapeStringIn,_:&CapeArrayStringIn) -> Result<CapeBoolean,COBIAError> {
1127 Ok(false as CapeBoolean)
1129 }
1130
1131 fn get_single_phase_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
1143 let n=property_tables::SinglePhaseProperty::COUNT;
1144 let mut prop_list:Vec<String>=Vec::with_capacity(n);
1145 for prop in property_tables::SinglePhaseProperty::iter() {
1146 prop_list.push(prop.name().to_string());
1147 }
1148 props.put_array(&prop_list)?;
1149 Ok(())
1150 }
1151
1152 fn get_two_phase_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
1161 let empty_list:[&str;0]=[];
1163 props.put_array(&empty_list)?;
1164 Ok(())
1165 }
1166}
1167
1168impl cape_open_1_2::ICapeThermoEquilibriumRoutine for SaltWaterPropertyPackage {
1171
1172 fn calc_equilibrium(&mut self,specification1:&CapeArrayStringIn,specification2:&CapeArrayStringIn,solution_type:&CapeStringIn) -> Result<(),COBIAError> {
1188 self.check_context_material()?;
1189 let material=self.material.as_ref().unwrap(); material.get_present_phases(&mut self.phase_list,&mut self.phase_status)?;
1191 if self.phase_list.size()!=1 || self.liquid!=self.phase_list[0] {
1192 return Err(COBIAError::Message("Unsupported list of allowed phases: only single phase liquid flash is supported".to_string()));
1193 }
1194 let flash_type=PhaseEquilibriumType::new(specification1,specification2,solution_type)?;
1195 let mut temperature=f64::NAN;
1196 let mut pressure=f64::NAN;
1197 let x_nacl=match self.material_nacl_index {
1199 None => 0.0,
1200 Some(nacl_index) => {
1201 material.get_overall_prop(&self.fraction,&self.mole,&mut self.property_value)?;
1202 if self.property_value.size()!=self.material_compound_indices.len() {
1203 return Err(COBIAError::Message("unexpected number of values for mole fraction".to_string()));
1204 }
1205 self.property_value[nacl_index]
1206 }
1207 };
1208 if x_nacl<0.0 || x_nacl>0.04033898281 {
1209 return Err(COBIAError::Message("Salinity outside of supported range of [0,0.04033898281]".to_string()));
1211 }
1212 match flash_type {
1213 PhaseEquilibriumType::TemperaturePressure => {},
1214 PhaseEquilibriumType::PressureEnthalpy => {
1215 material.get_overall_prop(&self.pressure,&self.empty_string,&mut self.scalar_property_value)?;
1217 pressure = self.scalar_property_value.value();
1218 material.get_overall_prop(&self.enthalpy,&self.mole,&mut self.scalar_property_value)?;
1219 let enthalpy=self.scalar_property_value.value();
1220 match salt_water_calculator::solve_temperature_from_enthalpy(enthalpy,pressure,x_nacl) {
1222 Ok(value) => temperature=value,
1223 Err(e) => return Err(COBIAError::Message(e))
1224 }
1225 },
1226 PhaseEquilibriumType::PressureEntropy => {
1227 material.get_overall_prop(&self.pressure,&self.empty_string,&mut self.scalar_property_value)?;
1229 pressure = self.scalar_property_value.value();
1230 material.get_overall_prop(&self.entropy,&self.mole,&mut self.scalar_property_value)?;
1231 let entropy=self.scalar_property_value.value();
1232 let x_nacl=match self.material_nacl_index {
1233 None => 0.0,
1234 Some(nacl_index) => {
1235 material.get_overall_prop(&self.fraction,&self.mole,&mut self.property_value)?;
1236 if self.property_value.size()!=self.material_compound_indices.len() {
1237 return Err(COBIAError::Message("unexpected number of values for mole fraction".to_string()));
1238 }
1239 self.property_value[nacl_index]
1240 }
1241 };
1242 match salt_water_calculator::solve_temperature_from_entropy(entropy,pressure,x_nacl) {
1244 Ok(value) => temperature=value,
1245 Err(e) => return Err(COBIAError::Message(e))
1246 }
1247 },
1248 }
1249 if temperature.is_nan() {
1251 material.get_overall_prop(&self.temperature,&self.empty_string,&mut self.scalar_property_value)?;
1253 temperature = self.scalar_property_value.value();
1254 } else {
1255 self.scalar_property_value.set(temperature);
1257 material.set_overall_prop(&self.temperature,&self.empty_string,&self.scalar_property_value)?;
1258 }
1259 if pressure.is_nan() {
1260 material.get_overall_prop(&self.pressure,&self.empty_string,&mut self.scalar_property_value)?;
1262 pressure = self.scalar_property_value.value();
1263 } else {
1264 self.scalar_property_value.set(pressure);
1266 material.set_overall_prop(&self.pressure,&self.empty_string,&self.scalar_property_value)?;
1267 }
1268 if temperature<273.15 || temperature>393.15 {
1271 return Err(COBIAError::Message("Temperature outside of supported range of [0,120] °C".to_string()));
1272 }
1273 if pressure<0.0 || pressure>12.0e6 {
1274 return Err(COBIAError::Message("Pressure outside of supported range of [0,12] MPa".to_string()));
1275 }
1276 self.phase_list.resize(1);
1278 self.phase_list[0].set_string("Liquid");
1279 self.phase_status.resize(1,cape_open_1_2::CapePhaseStatus::CapeUnknownphasestatus);
1280 self.phase_status[0]=cape_open_1_2::CapePhaseStatus::CapeAtequilibrium;
1281 material.set_present_phases(&self.phase_list,&self.phase_status)?;
1282 self.scalar_property_value.set(temperature);
1284 material.set_single_phase_prop(&self.temperature,&self.phase_list[0],&self.empty_string,&self.scalar_property_value)?;
1285 self.scalar_property_value.set(pressure);
1287 material.set_single_phase_prop(&self.pressure,&self.phase_list[0],&self.empty_string,&self.scalar_property_value)?;
1288 self.scalar_property_value.set(1.0);
1290 material.set_single_phase_prop(&self.phase_fraction,&self.phase_list[0],&self.mole,&self.scalar_property_value)?;
1291 material.get_overall_prop(&self.fraction,&self.mole,&mut self.property_value)?;
1293 material.set_single_phase_prop(&self.fraction,&self.phase_list[0],&self.mole,&self.property_value)?;
1295 Ok(())
1297 }
1298
1299 fn check_equilibrium_spec(&mut self,specification1:&CapeArrayStringIn,specification2:&CapeArrayStringIn,solution_type:&CapeStringIn) -> Result<CapeBoolean,COBIAError> {
1309 self.check_context_material()?;
1310 let material=self.material.as_ref().unwrap(); material.get_present_phases(&mut self.phase_list,&mut self.phase_status)?;
1312 if self.phase_list.size()!=1 || self.liquid!=self.phase_list[0] {
1313 return Ok(false as CapeBoolean); }
1315 match PhaseEquilibriumType::new(specification1,specification2,solution_type) {
1316 Ok(_) => Ok(true as CapeBoolean), Err(_) => Ok(false as CapeBoolean), }
1319 }
1320}
1321
1322
1323impl cape_open_1_2::ICapeThermoUniversalConstant for SaltWaterPropertyPackage {
1326
1327 fn get_universal_constant(&mut self,constant_id:&CapeStringIn,constant_value:&mut CapeValueOut) -> Result<(),COBIAError> {
1340 match constant_id.as_string().to_lowercase().as_str() {
1341 "avogadroconstant" => {constant_value.set_real(6.02214199e23)?;Ok(())},
1342 "boltzmannconstant" => {constant_value.set_real(1.3806503e-23)?;Ok(())},
1343 "idealgasstatereferencepressure" => {constant_value.set_real(101325.0)?;Ok(())},
1344 "molargasconstant" => {constant_value.set_real(8.314472)?;Ok(())},
1345 "speedoflightinvacuum" => {constant_value.set_real(299792458.0e8)?;Ok(())},
1346 "standardaccelerationofgravity" => {constant_value.set_real(9.80665)?;Ok(())},
1347 _ => Err(COBIAError::Message(format!("Unknown universal constant: {}",constant_id)))
1348 }
1349 }
1350
1351 fn get_universal_constant_list(&mut self,constant_id_list:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
1359 constant_id_list.put_array(&["avogadroConstant","boltzmannConstant","idealGasStateReferencePressure","molarGasConstant","speedOfLightInVacuum","standardAccelerationOfGravity"])?;
1360 Ok(())
1361 }
1362}
1363