Coverage for backend/django/core/auxiliary/viewsets/compound_conversions.py: 71%
129 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-13 02:47 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-13 02:47 +0000
1from ahuora_compounds import CompoundDB
2from ..models.PropertySet import PropertySet
3from ..models.PropertyInfo import PropertyInfo
4from ..models.PropertyValue import PropertyValue
5from flowsheetInternals.unitops.config.objects.stream_config import property_combinations
8def compound_db_to_molar_flow(name: str, value: float) -> float:
9 """
10 convert a value as mass flow to molar flow
11 (assumes converting kg/s to mol/s)
12 """
13 compound = CompoundDB.get_compound(name)
14 molecular_weight: float = compound.MolecularWeight.value # g/mol
15 return value / molecular_weight * 1000
18def compound_db_to_mass_flow(name: str, value: float) -> float:
19 """
20 convert a value as molar flow to mass flow
21 (assumes converting mol/s to kg/s)
22 """
23 compound = CompoundDB.get_compound(name)
24 molecular_weight: float = compound.MolecularWeight.value # g/mol
25 return value * molecular_weight / 1000
28def update_fraction_display_values(property_set: PropertySet) -> None:
29 """
30 Updates the display values of mass fractions based on the raw values
31 """
32 # Get the molar fraction property and its values
33 mole_frac_comp = property_set.get_property("mole_frac_comp")
34 property_values = mole_frac_comp.values.all()
36 # Calculate mass for each compound and track total mass
37 compound_masses = []
38 total_mass = 0.0
40 for prop in property_values:
41 # Get molar fraction (raw value)
42 molar_fraction = float(prop.value) if prop.value not in [None, ""] else 0.0
44 # Get molecular weight and calculate mass
45 compound_name = prop.get_index("compound").key
46 compound = CompoundDB.get_compound(compound_name)
47 molecular_weight = compound.MolecularWeight.value # g/mol
49 # Calculate mass (molar fraction * molecular weight)
50 mass = molar_fraction * molecular_weight
51 compound_masses.append(mass)
52 total_mass += mass
54 # Calculate and set mass fractions as display values
55 if total_mass > 0:
56 for i, prop in enumerate(property_values):
57 # Calculate mass fraction
58 mass_fraction = compound_masses[i] / total_mass
59 # Set display value
60 prop.displayValue = str(mass_fraction)
62 # Update in database
63 PropertyValue.objects.bulk_update(property_values, ["displayValue"])
66def check_fully_defined(
67 property_set: PropertySet,
68 property_infos: list[PropertyInfo] | None = None,
69 exclude: PropertyInfo | None = None,
70 check_none_empty = False,
71 check_fraction_sum = False
72 ) -> bool:
73 """
74 Returns true if the properties in the given property sets are
75 fully defined (no read-write properties).
76 """
77 if property_infos is None:
78 property_infos = property_set.containedProperties.all()
80 for prop in property_infos:
81 if (
82 check_none_empty
83 and prop.key in ["flow_mol", "flow_mass", "flow_vol"]
84 and not prop.has_value()
85 ):
86 return False
87 if exclude is not None and prop.id == exclude.id: 87 ↛ 88line 87 didn't jump to line 88 because the condition on line 87 was never true
88 continue
89 if any([
90 property_value.is_enabled() and
91 not property_value.has_value()
92 for property_value in prop.values.all()
93 ]):
94 return False
95 if (
96 check_fraction_sum
97 and property_set.compoundMode in ["MolarFraction", "MassFraction"]
98 ):
99 mole_frac_comp = property_set.get_property("mole_frac_comp")
100 return abs(
101 sum([
102 (
103 float(prop.displayValue)
104 if prop.displayValue not in [None, ""]
105 else float(prop.value)
106 if prop.value not in [None, ""]
107 else 0
108 )
109 for prop in mole_frac_comp.values.all()
110 ]) - 1
111 ) <= 1e-3 # sum of fractions == 1
113 return True
116def serialize_to_current_mode(property_set: PropertySet, properties_schema: dict) -> None:
117 """
118 Serialize the composition property set to the current compound mode
119 (for GET requests). Adjusts the properties_schema in place
120 """
121 def apply_display_value(property_key: str) -> None:
122 property_schema = next(
123 (item for item in properties_schema if item["key"] == property_key),
124 None,
125 )
126 if property_schema is None: 126 ↛ 127line 126 didn't jump to line 127 because the condition on line 126 was never true
127 return
129 for value_data in property_schema["values"]:
130 if value_data["displayValue"] not in [None, ""]:
131 value_data["value"] = value_data["displayValue"]
133 mole_frac_comp_schema = next(
134 (item for item in properties_schema if item["key"] == "mole_frac_comp"),
135 None
136 )
137 if mole_frac_comp_schema is None: 137 ↛ 138line 137 didn't jump to line 138 because the condition on line 137 was never true
138 return
140 if property_set.compoundMode == "MassFraction":
141 apply_display_value("flow_mass")
143 if not stream_has_build_state_inputs(property_set):
144 # stream is not fully defined, do not convert
145 return
147 def iter_value_entries():
148 values = mole_frac_comp_schema["values"]
149 if isinstance(values, dict): 149 ↛ 150line 149 didn't jump to line 150 because the condition on line 149 was never true
150 for key, value_data in values.items():
151 yield key, value_data
152 return
154 for value_data in values:
155 yield value_data["indexedSets"][0], value_data
157 def convert_to_mass_flow():
158 for key, value_data in iter_value_entries():
159 # convert to mass flow
160 if value_data["value"] not in [None, ""]: 160 ↛ 158line 160 didn't jump to line 158 because the condition on line 160 was always true
161 value_data["value"] = compound_db_to_mass_flow(key, float(value_data["value"]))
163 def convert_to_mass_fraction():
164 # sums for molar fractions and mass fractions should be equal
165 sum_molar_frac = 0
166 for _, value_data in iter_value_entries():
167 sum_molar_frac += float(value_data["value"])
168 convert_to_mass_flow()
169 sum_mass_flow = 0
170 for _, value_data in iter_value_entries():
171 sum_mass_flow += float(value_data["value"])
173 if sum_mass_flow == 0: 173 ↛ 174line 173 didn't jump to line 174 because the condition on line 173 was never true
174 return
175 for _, value_data in iter_value_entries():
176 value_data["value"] = float(value_data["value"]) / sum_mass_flow * sum_molar_frac
178 match property_set.compoundMode:
179 case "MolarFraction": 179 ↛ 180line 179 didn't jump to line 180 because the pattern on line 179 never matched
180 pass # already in molar fractions
181 case "MassFraction": 181 ↛ exitline 181 didn't return from function 'serialize_to_current_mode' because the pattern on line 181 always matched
182 convert_to_mass_fraction()
185def convert_to_molar_fractions(property_set: PropertySet) -> None:
186 """
187 Converts the composition to molar fractions from raw values
188 """
189 mole_frac_comp = property_set.get_property("mole_frac_comp")
190 property_values = mole_frac_comp.values.all()
192 def molar_flows_to_fractions() -> None:
193 # convert from molar flows to molar fractions
194 sum_flows = sum([float(prop.value) for prop in property_values])
195 if sum_flows == 0: 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 return
197 for prop in property_values:
198 prop.value = float(prop.value) / sum_flows
200 def mass_flows_to_molar_flows() -> None:
201 # convert from mass flows to molar flows
202 for prop in property_values:
203 if prop.displayValue in [None, ""]:
204 prop.displayValue = prop.value
205 prop.value = compound_db_to_molar_flow(prop.get_index("compound").key, float(prop.displayValue))
207 match property_set.compoundMode:
208 case "MolarFraction": 208 ↛ 209line 208 didn't jump to line 209 because the pattern on line 208 never matched
209 return # already in molar fractions
210 case "MassFraction": 210 ↛ 215line 210 didn't jump to line 215 because the pattern on line 210 always matched
211 # assume total mass flow of 1
212 mass_flows_to_molar_flows()
213 molar_flows_to_fractions()
215 PropertyValue.objects.bulk_update(property_values, ["value", "displayValue"])
218def convert_to_raw_values(property_set: PropertySet) -> None:
219 """
220 Converts the composition to raw values from molar fractions
221 """
222 mole_frac_comp = property_set.get_property("mole_frac_comp")
223 property_values = mole_frac_comp.values.all()
224 for prop in property_values:
225 if prop.displayValue not in [None, ""]:
226 prop.value = float(prop.displayValue)
228 PropertyValue.objects.bulk_update(property_values, ["value", "displayValue"])
231def stream_has_build_state_inputs(property_set: PropertySet) -> bool:
232 """Return whether a stream has enough inputs for an async build-state."""
233 try:
234 mole_frac_comp = property_set.get_property("mole_frac_comp")
235 except ValueError:
236 return False
238 has_complete_composition = check_fully_defined(
239 property_set,
240 [mole_frac_comp],
241 check_fraction_sum=True,
242 )
243 if not has_complete_composition:
244 return False
246 for property_pair in property_combinations:
247 if all(property_set.get_property(key).has_value() for key in property_pair):
248 return True
250 return False