salt_water/
salt_water_property_package.rs

1use cobia::*;
2use crate::salt_water_calculator;
3use crate::property_tables;
4use crate::phase_equilibrium_type::PhaseEquilibriumType;
5use strum::{EnumCount, IntoEnumIterator};
6
7///The SaltWaterPropertyPackage is an example of a property package that implements the 
8/// CAPE-OPEN 1.2 standard.
9///
10/// The salt water calculations are implemented in salt_water_calculator.rs.
11///
12/// The package defines a single liquid phase, and two compounds: water and sodium chloride.
13///
14/// A CAPE-OPEN 1.2 property package must implemented the following interfaces:
15/// - ICapeIdentification
16/// - ICapeUtilities
17/// - ICapeThermoMaterialContext
18/// - ICapeThermoCompounds
19/// - ICapeThermoPhases
20/// - ICapeThermoPropertyRoutine
21/// - ICapeThermoEquilibriumRoutine
22/// - ICapeThermoUniversalConstant (optional)
23///
24/// The package is creatable; the public CAPE-OPEN class factory is implemented in lib.rs;
25/// to facilitate the registration of this object into the COBIA registry, the object implements
26/// the PMCRegisterationInfo trait.
27///
28/// Once registered, the package can be instantiated and created in all CAPE-OPEN
29/// compliant simulators.
30#[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	/// The name of the components, which for a primary CAPE-OPEN PMC object must be modifiable
44	name: String,
45	/// The description of the component, which for a primary CAPE-OPEN PMC object should be modifiable
46	description: String,
47	/// The interface to the active material which contains calculation properties, and on which calculation results are stored
48	material: Option<cape_open_1_2::CapeThermoMaterial>,
49	/// Map of the constant properties by identifier, to the values for the two compounds. Filled on first use.
50	constant_property_map: CapeOpenMap<[CapeValueContent;2]>,
51	/// The indices of the compounds in the active material object. Water has index 0, NaCl has index 1.
52	material_compound_indices: Vec<usize>,
53	/// The index of the NaCl compound in the active material object. If not present, pure water is assumed.
54	material_nacl_index: Option<usize>,
55	//*******************************************
56	//temporary values that are cached and reused
57	//*******************************************
58	/// Compound IDs on the active material object
59	mat_comp_ids : CapeArrayStringVec,
60	/// Compound formulae on the active material object
61	mat_formulae : CapeArrayStringVec,
62	/// Compound names on the active material object
63	mat_comp_names : CapeArrayStringVec,
64	/// Compound boiling points on the active material object
65	mat_boil_temps : CapeArrayRealVec,
66	/// Compound molecular weights on the active material object
67	mat_molecular_weights : CapeArrayRealVec,
68	/// Compound CAS registry numbers on the active material object
69	mat_cas_registry_numbers : CapeArrayStringVec,
70	/// List of phases resulting from a flash calculation (contains only liquid)
71	phase_list : CapeArrayStringVec,
72	/// Phase status of the phases resulting from a flash calculation (at equilibrium)
73	phase_status : CapeArrayEnumerationVec<cape_open_1_2::CapePhaseStatus>,
74	/// Buffer for obtaining property values from the active material object
75	property_value : CapeArrayRealVec,
76	/// Buffer for scalar properties for obtaining property values from the active material object
77	scalar_property_value : CapeArrayRealScalar,
78	//****************
79	//constant strings
80	//****************
81	/// The string "fraction" used for the mole fraction property
82	fraction : CapeStringImpl,
83	/// The string "temperature" used for the temperature property
84	temperature: CapeStringImpl,
85	/// The string "pressure" used for the pressure property
86	pressure : CapeStringImpl,
87	/// The string "phaseFraction" used for the phase fraction property
88	phase_fraction : CapeStringImpl,
89	/// The string "mole" used for the basis of getting and setting properties
90	mole : CapeStringImpl,
91	/// The string "enthalpy" used for the enthalpy property
92	enthalpy : CapeStringImpl,
93	/// The string "entropy" used for the entropy property
94	entropy : CapeStringImpl,
95	/// The empty string used for the basis of getting and setting properties
96	empty_string : CapeStringImpl,
97	/// The string "H2O" used to check against the active material object compound list
98	h2o : CapeStringConstNoCase,
99	/// The string "NaCl" used to check against the active material object compound list
100	nacl : CapeStringConstNoCase,
101	/// The string "Liquid" used to check against the specified phase in property calculations
102	liquid: CapeStringConstNoCase,
103}
104
105/// Implementation of the Default trait is required for creatable CAPE-OPEN objects
106/// as the class factory does not allow for constructor parameters.
107impl 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	///The default name for new packages, and the object name as it appears in the COBIA registy
144	const NAME: &'static str = "Salt Water";
145	///The default description for new packages, and the object description as it appears in the COBIA registy
146	const DESCRIPTION: &'static str = "Salt water property package";
147	///The ProgID of the package, as it appears in the COBIA registry
148	const PROGID: &'static str = "SaltWater.SaltWater";
149
150	/// The list of compound IDs for the two compounds in the package
151	const COMP_FORMULAS: [&'static str;2]=["H2O","NaCl"];
152	/// The list of compound names for the two compounds in the package
153	const COMP_NAMES: [&'static str;2]=["Water","Sodium Chloride"];
154	/// The list of compound boiling points for the two compounds in the package
155	const COMP_BOIL_TEMPS: [f64;2]=[373.15,1738.0];
156	/// The list of compound melting points for the two compounds in the package
157	const COMP_MELT_TEMPS: [f64;2]=[273.15,1073.8];
158	/// The list of compound molecular weights for the two compounds in the package
159	pub(crate) const COMP_MOLWTS: [f64;2]=[18.01528,58.443];
160	/// The list of compound CAS registry numbers for the two compounds in the package
161	const COMP_CASNOS: [&'static str;2]=["7732-18-5","7647-14-5"];
162	/// The list of compound SMILES strings for the two compounds in the package
163	const COMP_SMILES: [&'static str;2]=["O","[Na+].[Cl-]"];
164	/// The list of compound IUPAC names for the two compounds in the package
165	const COMP_IUPAC_NAMES: [&'static str;2]=["oxidane","sodium;chloride"];
166
167
168	/// This function is called at the start of any function that requires the context 
169	/// material to be set. It checks whether a active material object is present, and if so,
170	/// it check which compounds are present on the active material object
171	///
172	/// It is possible for the PME to select a subset of the compounds supported by the
173	/// package. For this particular package, not having NaCl is acceptable, as this 
174	/// allows for calculations at zero salinity. Not having water is not acceptable,
175	/// and returns and error. 
176	///
177	/// Some other common errors, such as an empty list, multiple appearances of the 
178	/// same compound in the list, or unknown compounds, are also checked here
179
180	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								//for more compounds one should consider a more efficient search, e.g. a HashMap
199								//  note that the items that are compared against are of type CapeStringConstNoCase, which is encoded to the platform string at construction
200								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							//check no double items
211							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								//this package needs water on the MO
221								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
237/// The Display trait is required; it is used by the COBIA package to format the 
238/// object that is the source of an error, when a CAPE-OPEN error is returned.
239///
240/// The returned string should include the name and the type of the object so 
241/// that it can be identified in the environment by which the error is processed.
242
243impl 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
249/// The registration information is needed for PMC objects that can be created
250/// by extenal applicaitons, and therefore need to be in the COBIA registry. This
251/// defines the unique identifier for the object, but also how the object is
252/// visible to the end-user.
253
254impl PMCRegisterationInfo for SaltWaterPropertyPackage {
255
256	/// The UUID of the package, which is used to identify the package in the COBIA registry
257
258	fn get_uuid() -> CapeUUID {
259		//{C3C43FDD-958D-4ED8-BF4D-B801D6D75328}
260		CapeUUID::from_slice(&[0xC3u8,0xC4u8,0x3Fu8,0xDDu8,0x95u8,0x8Du8,0x4Eu8,0xD8u8,0xBFu8,0x4Du8,0xB8u8,0x01u8,0xD6u8,0xD7u8,0x53u8,0x28u8])
261	}
262
263	/// The registration details of the package, which are used to register the package in the COBIA registry
264	///
265	/// # Arguments
266	/// * `registrar` - The registrar object that is used to register the package in the COBIA registry
267
268
269	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
285/// The ICapeIdentification interface is used to identify any CAPE-OPEN object. It consists of a name
286/// and description. For primary PMC objects (creatable objects), the name must be read/write and the 
287/// description should be read/write. Also, for primary PMC objects, the initial name and description
288/// should match the information in the COBIA registry.
289
290impl cape_open_1_2::ICapeIdentification for SaltWaterPropertyPackage {
291
292	/// Obtain the name of the object
293	/// 
294	/// # Arguments
295	/// * `name` - The name of the object, which is returned through a CapeStringOut object
296	///
297	/// # Returns
298	/// * `Result` - A result object that indicates whether the operation was successful or not
299
300	fn get_component_name(&mut self,name:&mut CapeStringOut) -> Result<(), COBIAError> {
301		name.set_string(&self.name)?;
302		Ok(())
303	}
304
305	/// Obtain the description of the object
306	///
307	/// # Arguments
308	/// * `description` - The description of the object, which is returned through a CapeStringOut object
309	///
310	/// # Returns
311	/// * `Result` - A result object that indicates whether the operation was successful or not
312
313	fn get_component_description(&mut self,description:&mut CapeStringOut) -> Result<(), COBIAError> {
314		description.set_string(&self.description)?;
315		Ok(())
316	}
317
318	/// Set the name of the object
319	///
320	/// # Arguments
321	/// * `name` - The name of the object, which is specified through a CapeStringIn object
322	///
323	/// # Returns
324	/// * `Result` - A result object that indicates whether the operation was successful or not
325
326	fn set_component_name(&mut self, name: &CapeStringIn) -> Result<(), COBIAError> {
327		self.name = name.to_string();
328		Ok(())
329	}
330
331	/// Set the description of the object
332	///
333	/// # Arguments
334	/// * `desc` - The description of the object, which is specified through a CapeStringIn object
335	///
336	/// # Returns
337	/// * `Result` - A result object that indicates whether the operation was successful or not
338
339	fn set_component_description(&mut self, desc: &CapeStringIn) -> Result<(), COBIAError> {
340		self.description = desc.to_string();
341		Ok(())
342	}
343}
344
345/// The ICapeUtilities interface must be implemented by all CAPE-OPEN Primary PMC (creatable) objects.
346
347impl cape_open_1_2::ICapeUtilities for SaltWaterPropertyPackage {
348
349	/// Obtain the parameter collection of the object
350	/// 
351	/// This object has no parameters, and the method always returns a not implemented error.
352	///
353	/// # Arguments
354	/// * `params` - The parameter collection of the object, which is returned through a CapeCollection object
355	///
356	/// # Returns
357	/// * `Result` - A result object that indicates whether the operation was successful or not
358	
359	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	/// Set the simulation context of the object
364	/// 
365	/// The Simulation Context object provides a number of services implemented by the PMC, including
366	/// the ability to log messages. It is not used by this object.
367	///
368	/// # Arguments
369	/// * `context` - The simulation context of the object, which is specified through a CapeSimulationContext object
370	///
371	/// # Returns
372	/// * `Result` - A result object that indicates whether the operation was successful or not
373
374	fn set_simulation_context(&mut self,_:cape_open_1_2::CapeSimulationContext) -> Result<(),COBIAError> {
375		Ok(()) //not needed
376	}
377
378	/// Initialize the object
379	///
380	/// This object does not need any initialization, and the method always returns success. Initialization must be 
381	/// called by the PME on any CAPE-OPEN Primary PMC Object, after persistence (if the object is depersist) but 
382	/// before any other method is called (except setting the simulation context).
383	///
384	/// Any object that is successfully initialized must be terminated before it can be destroyed. If Initialize
385	/// returns an error, Terminate must not be called.
386	///
387	/// # Returns
388	/// * `Result` - A result object that indicates whether the operation was successful or not
389
390	fn initialize(&mut self) -> Result<(),COBIAError> {
391		Ok(()) //nothing to do
392	}
393
394	/// Terminate the object
395	///
396	/// During termination, and object must release all external references. For this object, 
397	/// the only extenal reference is the active material object. 
398	///
399	/// # Returns
400	/// * `Result` - A result object that indicates whether the operation was successful or not
401
402	fn terminate(&mut self) -> Result<(),COBIAError> {
403		//drop all external references
404		self.material=None;
405		Ok(())
406	}
407
408	/// Edit the object; this is the only time the object may show a modal dialog.
409	///
410	/// Editing may be invoked to alter the state of the object, or just to inspect the
411	/// object. Therefore the edit function must indicate whether the object has changed
412	/// so that the PME may re-obtain the information exposed by the object (supported
413	/// compounds, phases, ...) and invalidate any solution that involves calculations
414	/// by the object
415	///
416	/// # Arguments
417	/// * `window_id` - The window ID of the parent window
418	///
419	/// # Returns
420	/// * `Result` - A result object that indicates whether the operation was successful or not; 
421	///              contains modification state for a successful operation
422	fn edit(&mut self,_:CapeWindowId) -> Result<cape_open_1_2::CapeEditResult,COBIAError> {
423		Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED)) //no edit dialog
424	}
425}
426
427/// The ICapeThermoMaterialContext interface must be implemented by a property package to allow 
428/// setting the active material object on which all calculations are to be done. Calculation conditions
429/// are obtained from the active material object, and calculation results are written to the 
430/// active material object.
431
432impl cape_open_1_2::ICapeThermoMaterialContext for SaltWaterPropertyPackage {
433
434	/// Set the active material object
435	///
436	/// # Arguments
437	/// * `material` - The active material object
438	///
439	/// # Returns
440	/// * `Result` - A result object that indicates whether the operation was successful or not
441	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	/// Clear the active material object
447	///
448	/// # Returns
449	/// * `Result` - A result object that indicates whether the operation was successful or not
450	fn unset_material(&mut self) -> Result<(),COBIAError> {
451		self.material=None;
452		self.material_compound_indices.clear();
453		Ok(())
454	}
455}
456
457/// The ICapeThermoCompounds interface must be implemented by a property package to expose
458/// the supported list of compounds. The active list of compounds is that on the active
459/// material object
460impl cape_open_1_2::ICapeThermoCompounds for SaltWaterPropertyPackage {
461
462	/// Get one or more compound constant values for one or more compounds
463	///
464	/// # Arguments
465	/// * `props` - The list of compound properties to be obtained, which is specified through a CapeArrayStringIn object
466	/// * `comp_ids` - The list of compound IDs, which is specified through a CapeArrayStringIn object; if empty, values are to be returned for all compounds
467	/// * `contains_missing_values` - A boolean value that indicates whether the result contains missing values, which is returned through a CapeBoolean object
468	/// * `prop_vals` - The list of compound property values, which is returned through a CapeArrayValueOut object; for each property, the values for all compounds are returned
469	///
470	/// # Returns
471	/// * `Result` - A result object that indicates whether the operation was successful or not
472
473	fn get_compound_constant(&mut self,props:&CapeArrayStringIn,comp_ids:&CapeArrayStringIn,contains_missing_values:&mut CapeBoolean,prop_vals:&mut CapeArrayValueOut) -> Result<(),COBIAError> {
474		//check 
475		if self.constant_property_map.is_empty() {
476			//fill constant property map
477			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		//check comp_ids
486		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				//For more compounds, this should be a HashMap based lookup
493				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		//compile result
503		*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			//find in property map
508			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	/// Get the list of compounds supported by the package; water and sodium chloride for this package
526	///
527	/// # Arguments
528	/// * `comp_ids` - The list of compound IDs, which is returned through a CapeArrayStringOut object
529	/// * `formulae` - The list of compound formulae, which is returned through a CapeArrayStringOut object
530	/// * `names` - The list of compound names, which is returned through a CapeArrayStringOut object
531	/// * `boil_temps` - The list of compound boiling points, which is returned through a CapeArrayRealOut object
532	/// * `molwts` - The list of compound molecular weights, which is returned through a CapeArrayRealOut object
533	/// * `casnos` - The list of compound CAS registry numbers, which is returned through a CapeArrayStringOut object
534	///
535	/// # Returns
536	/// * `Result` - A result object that indicates whether the operation was successful or not
537
538	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	/// Get a list of supported compound constants
549	///
550	/// # Arguments
551	/// * `props` - The list of compound properties, which is returned through a CapeArrayStringOut object
552	///
553	/// # Returns
554	/// * `Result` - A result object that indicates whether the operation was successful or not
555	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	/// Get the number of compounds supported by this package
568	///
569	/// # Returns
570	/// * `Result` - A result object that indicates whether the operation was successful or not, 
571	///              containing the number of compounds in case of success
572	fn get_num_compounds(&mut self) -> Result<CapeInteger,COBIAError> {
573		Ok(2)
574	}
575
576	/// Calculate pressure dependent properties at specified pressure
577	/// 
578	/// This package does not support pressure dependent properties, and the method always fails.
579	///
580	/// # Returns
581	/// * `Result` - A result object that indicates whether the operation was successful or not, 
582	///              containing the number of compounds in case of success
583	fn get_pdependent_property(&mut self,_:&CapeArrayStringIn,_:CapeReal,_:&CapeArrayStringIn,_:&mut CapeBoolean,_:&mut CapeArrayRealOut) -> Result<(),COBIAError> {
584		//no pressure dependent property support
585		Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
586	}
587
588	/// Get the list of pressure dependent properties supported by this package
589	///
590	/// This package does not support pressure dependent properties, so returns an empty list.
591	///
592	/// # Arguments
593	/// * `compounds` - The list of compound IDs, which is specified through a CapeArrayStringIn object
594	/// * `props` - The list of pressure dependent properties, which is returned through a CapeArrayStringOut object
595	///
596	/// # Returns
597	/// * `Result` - A result object that indicates whether the operation was successful or not
598	fn get_pdependent_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
599		//no pressure dependent property support
600		let empty_list:[&str;0]=[];
601		props.put_array(&empty_list)?;
602		Ok(())
603	}
604
605	/// Calculate temperature dependent properties at specified temperature
606	///
607	/// This package does not support temperature dependent properties, and the method always fails.
608	///
609	/// # Returns
610	/// * `Result` - A result object that indicates whether the operation was successful or not, 
611	///              containing the number of compounds in case of success
612	fn get_tdependent_property(&mut self,_:&CapeArrayStringIn,_:CapeReal,_:&CapeArrayStringIn,_:&mut CapeBoolean,_:&mut CapeArrayRealOut) -> Result<(),COBIAError> {
613		//no temperature dependent property support
614		Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
615	}
616
617	/// Get the list of temperature dependent properties supported by this package
618	///
619	/// This package does not support temperature dependent properties, so returns an empty list.
620	///
621	/// # Arguments
622	/// * `props` - The list of temperature dependent properties, which is returned through a CapeArrayStringOut object
623	///
624	/// # Returns
625	/// * `Result` - A result object that indicates whether the operation was successful or not
626	fn get_tdependent_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
627		//no temperature dependent property support
628		let empty_list:[&str;0]=[];
629		props.put_array(&empty_list)?;
630		Ok(())
631	}
632}
633
634/// The ICapeThermoPhases interface must be implemented by a property package to expose
635/// the supported list of phases. 
636impl cape_open_1_2::ICapeThermoPhases for SaltWaterPropertyPackage {
637
638	/// Get the number of phases supported by this package. 
639	///
640	/// This package supports 1 phase.
641	///
642	/// # Returns
643	/// * `Result` - A result object that indicates whether the operation was successful or not,
644	///              and in case of success returns the number of phases supported by the package.
645    fn get_num_phases(&mut self) -> Result<CapeInteger,COBIAError> {
646        Ok(1) //only liquid
647    }
648
649	/// Get information on a phase.
650	///
651	/// # Arguments
652	/// * `phase_label` - The label of the phase, which is specified through a CapeStringIn object
653	/// * `phase_attribute` - The attribute of the phase, which is specified through a CapeStringIn object
654	/// * `value` - The value of the phase attribute, which is returned through a CapeValueOut object
655	///
656	/// # Returns
657	/// * `Result` - A result object that indicates whether the operation was successful or not
658    fn get_phase_info(&mut self,phase_label:&CapeStringIn,phase_attribute:&CapeStringIn,value:&mut CapeValueOut) -> Result<(),COBIAError> {
659        if self.liquid==*phase_label {
660			//for get_phase_info, performance is not critical. If performance is critical, one should
661			// not convert to string, but compare the CapeConstString directly, which is pre-encoded,
662			// or build a HashMap using CapeConstString as key
663			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	/// Get the list of phases supported by this package
675	/// 
676	/// This package supports only one phase: liquid. The liquid
677	/// phase exposes water as its key compound. As there is only one liquid phase, this is not
678	/// really necessary, but it is obvious in this context that the liquid phase is always
679	/// and acquous phase.
680	///
681	/// # Arguments
682	/// * `phase_labels` - The list of phase labels, which is returned through a CapeArrayStringOut object
683	/// * `state_of_aggregation` - The list of state of aggregation, which is returned through a CapeArrayStringOut object
684	/// * `key_compound_id` - The list of key compound IDs, which is returned through a CapeArrayStringOut object
685	///
686	/// # Returns
687	/// * `Result` - A result object that indicates whether the operation was successful or not
688    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
696/// The ICapeThermoPropertyRoutine interface must be implemented by a property package to allow
697/// a client to calculate single-phase and two-phase properties. 
698impl cape_open_1_2::ICapeThermoPropertyRoutine for SaltWaterPropertyPackage {
699
700	/// Calculate the fugacity coefficient of a compound in a phase
701	///
702	/// Not supported by this package.
703	///
704	/// # Returns
705	/// * `Result` - A result object that indicates whether the operation was successful or not
706    fn calc_and_get_ln_phi(&mut self,_:&CapeStringIn,_:CapeReal,_:CapeReal,_:&CapeArrayRealIn,_:CapeInteger,_:&mut CapeArrayRealOut,_:&mut CapeArrayRealOut,_:&mut CapeArrayRealOut,_:&mut CapeArrayRealOut) -> Result<(),COBIAError> {
707        //this package does not support fugacity coefficient calculations
708		Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
709    }
710
711	/// Calculate one or more single-phase properties for the specified phase
712	///
713	/// Obtains the calculation inputs from the active material object, and 
714	/// sets the calculation results on the active material object.
715	///
716	/// The underlying property calculations are performed by the `salt_water_calculator` module.
717	///
718	/// # Arguments
719	/// * `props` - The list of properties to be calculated, which is specified through a CapeArrayStringIn object
720	/// * `phase_label` - The label of the phase, which is specified through a CapeStringIn object
721	///
722	/// # Returns
723	/// * `Result` - A result object that indicates whether the operation was successful or not
724    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			//get temperature, pressure and composition (composition in mass units)
731			let material=self.material.as_ref().unwrap(); //cannot be None, see check_context_material
732			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	/// Calculate two-phase properties for the specified phase pair.
1088	///
1089	/// This package does not support two-phase properties, and the method always fails.
1090	///
1091	/// # Returns
1092	/// * `Result` - A result object that indicates whether the operation was successful or not
1093    fn calc_two_phase_prop(&mut self,_:&CapeArrayStringIn,_:&CapeArrayStringIn) -> Result<(),COBIAError> {
1094		//only one phase is defined
1095		Err(COBIAError::Code(COBIAERR_NOTIMPLEMENTED))
1096    }
1097
1098	/// Check whether a single-phase property calculation is supported for the specified phase.
1099	///
1100	/// This package supports a number of single-phase properties, which are defined in the
1101	/// `property_tables` module. 
1102	///
1103	/// # Arguments
1104	/// * `property` - The property to be checked, which is specified through a CapeStringIn object
1105	/// * `phase_label` - The label of the phase, which is specified through a CapeStringIn object
1106	///
1107	/// # Returns
1108	/// * `Result` - A result object that indicates whether the operation was successful or not, and if succesful, whether the property is supported.
1109    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	/// Check whether a two-phase property calculation is supported for the specified phase pair.
1121	///
1122	/// This package does not support two-phase properties, and the method always returns false.
1123	///
1124	/// # Returns
1125	/// * `Result` - A result object that indicates whether the operation was successful or not
1126    fn check_two_phase_prop_spec(&mut self,_:&CapeStringIn,_:&CapeArrayStringIn) -> Result<CapeBoolean,COBIAError> {
1127		//only one phase is defined
1128		Ok(false as CapeBoolean)
1129    }
1130
1131	/// Get a list of supported single-phase properties.
1132	///
1133	/// This package supports a number of single-phase properties, which are defined in the
1134	/// `property_tables` module. The list of supported properties is returned through the
1135	/// `props` argument.
1136	///
1137	/// # Arguments
1138	/// * `props` - The list of supported single-phase properties, which is returned through a CapeArrayStringOut object
1139	///
1140	/// # Returns
1141	/// * `Result` - A result object that indicates whether the operation was successful or not
1142    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	/// Get a list of supported two-phase properties.
1153	///
1154	/// This package does not support two-phase properties, and the method always returns an empty list.		
1155	/// # Arguments
1156	/// * `props` - The list of supported two-phase properties, which is returned through a CapeArrayStringOut object
1157	///
1158	/// # Returns
1159	/// * `Result` - A result object that indicates whether the operation was successful or not
1160    fn get_two_phase_prop_list(&mut self,props:&mut CapeArrayStringOut) -> Result<(),COBIAError> {
1161		//only one phase is defined (so alas, no surface tension)
1162		let empty_list:[&str;0]=[];
1163		props.put_array(&empty_list)?;
1164		Ok(())
1165    }
1166}
1167
1168/// The ICapeThermoPropertyRoutine interface must be implemented by a property package to allow
1169/// a client to calculate phase equilibria.
1170impl cape_open_1_2::ICapeThermoEquilibriumRoutine for SaltWaterPropertyPackage {
1171
1172	/// Calculate the phase equilibrium at specified conditions.
1173	///
1174	/// Given the specified conditions and overall composition, return the phase equilibrium that 
1175	/// matches those conditions. As this package only supports one phase, the resulting phase equilibrium
1176	/// is always a liquid phase at the specified conditions.
1177	///
1178	/// The underlying calculations are performed by the `salt_water_calculator` module.
1179	///
1180	/// # Arguments
1181	/// * `specification1` - The first specification for the phase equilibrium, which is specified through a CapeStringIn object
1182	/// * `specification2` - The second specification for the phase equilibrium, which is specified through a CapeStringIn object
1183	/// * `solution_type` - The type of solution to be used for the phase equilibrium calculation, which is specified through a CapeStringIn object; only the "Unspecified" solution type is supported.
1184	///
1185	/// # Returns
1186	/// * `Result` - A result object that indicates whether the operation was successful or not
1187    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(); //cannot be None, see check_context_material
1190		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		//check salinity range
1198		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			//outside of this range our enthalpy and entropy calculations are not valid
1210			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				//get pressure, enthalpy and salinity
1216				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				//calculate temperature to match enthaply
1221				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				//get pressure, entropy and salinity
1228				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				//calculate temperature to match enthaply
1243				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		//single phase at t and p, and overall composition
1250		if temperature.is_nan() {
1251			//temperature from MO
1252			material.get_overall_prop(&self.temperature,&self.empty_string,&mut self.scalar_property_value)?;
1253			temperature = self.scalar_property_value.value();
1254		} else {
1255			//put temperature on overall phase
1256			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			//pressure from MO
1261			material.get_overall_prop(&self.pressure,&self.empty_string,&mut self.scalar_property_value)?;
1262			pressure = self.scalar_property_value.value();
1263		} else {
1264			//put pressure on overall phase
1265			self.scalar_property_value.set(pressure);
1266			material.set_overall_prop(&self.pressure,&self.empty_string,&self.scalar_property_value)?;
1267		}
1268		//check that result is within operating region of the calculator - assume this is [0,120]C and [0,12]MPa
1269		// outside of these ranges, the enthapy and entropy calculations are not valid
1270		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		//set phase list on MO
1277		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		//set temperature
1283		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		//set pressure
1286		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		//set phase fraction
1289		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		//get overall composition
1292		material.get_overall_prop(&self.fraction,&self.mole,&mut self.property_value)?;
1293		//set phase composition
1294		material.set_single_phase_prop(&self.fraction,&self.phase_list[0],&self.mole,&self.property_value)?;
1295		//done
1296		Ok(())
1297    }
1298
1299	/// Check whether a particular phase equilibrium calculation is supported.
1300	///
1301	/// # Arguments
1302	/// * `specification1` - The first specification for the phase equilibrium, which is specified through a CapeStringIn object
1303	/// * `specification2` - The second specification for the phase equilibrium, which is specified through a CapeStringIn object
1304	/// * `solution_type` - The type of solution to be used for the phase equilibrium calculation, which is specified through a CapeStringIn object; only the "Unspecified" solution type is supported.
1305	///
1306	/// # Returns
1307	/// * `Result` - A result object that indicates whether the operation was successful or not, and if successful, whether the phase equilibrium calculation is supported.
1308    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(); //cannot be None, see check_context_material
1311		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); //only flashes that allow liquid are supported
1314		}
1315        match PhaseEquilibriumType::new(specification1,specification2,solution_type) {
1316			Ok(_) => Ok(true as CapeBoolean), //supported
1317			Err(_) => Ok(false as CapeBoolean), //not supported
1318		}
1319    }
1320}
1321
1322
1323/// The ICapeThermoPropertyRoutine interface can optionally be implemented by a property package to allow
1324/// a client to obtain values for universal constants
1325impl cape_open_1_2::ICapeThermoUniversalConstant for SaltWaterPropertyPackage {
1326
1327	/// Get the value of a universal constant.
1328	///
1329	/// for get_universal_constant, performance is not critical. If performance is critical, one should
1330	///  not convert to string, but compare the CapeConstString directly, which is pre-encoded,
1331	///  or build a HashMap using CapeConstString as key.
1332	///
1333	/// # Arguments
1334	/// * `constant_id` - The ID of the universal constant to be retrieved, which is specified through a CapeStringIn object
1335	/// * `constant_value` - The value of the universal constant, which is returned through a CapeValueOut object
1336	///
1337	/// # Returns
1338	/// * `Result` - A result object that indicates whether the operation was successful or not
1339    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	/// Get the list of universal constants supported by the property package.
1352	///
1353	/// # Arguments
1354	/// * `constant_id_list` - The list of supported universal constants, which is returned through a CapeArrayStringOut object
1355	///
1356	/// # Returns
1357	/// * `Result` - A result object that indicates whether the operation was successful or not
1358    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