Coverage for backend/core/auxiliary/viewsets/compound_conversions.py: 44%
99 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
1from compounds import CompoundDB
2from ..models.PropertySet import PropertySet
3from ..models.PropertyInfo import PropertyInfo
4from ..models.PropertyValue import PropertyValue
7def compound_db_to_molar_flow(name: str, value: float) -> float:
8 """
9 convert a value as mass flow to molar flow
10 (assumes converting kg/s to mol/s)
11 """
12 compound = CompoundDB.get_compound(name)
13 molecular_weight: float = compound["MolecularWeight"].value # g/mol
14 return value / molecular_weight * 1000
17def compound_db_to_mass_flow(name: str, value: float) -> float:
18 """
19 convert a value as molar flow to mass flow
20 (assumes converting mol/s to kg/s)
21 """
22 compound = CompoundDB.get_compound(name)
23 molecular_weight: float = compound["MolecularWeight"].value # g/mol
24 return value * molecular_weight / 1000
27def update_fraction_display_values(property_set: PropertySet) -> None:
28 """
29 Updates the display values of mass fractions based on the raw values
30 """
31 # Get the molar fraction property and its values
32 mole_frac_comp = property_set.get_property("mole_frac_comp")
33 property_values = mole_frac_comp.values.all()
35 # Calculate mass for each compound and track total mass
36 compound_masses = []
37 total_mass = 0.0
39 for prop in property_values:
40 # Get molar fraction (raw value)
41 molar_fraction = float(prop.value) if prop.value not in [None, ""] else 0.0
43 # Get molecular weight and calculate mass
44 compound_name = prop.get_index("compound").key
45 compound = CompoundDB.get_compound(compound_name)
46 molecular_weight = compound["MolecularWeight"].value # g/mol
48 # Calculate mass (molar fraction * molecular weight)
49 mass = molar_fraction * molecular_weight
50 compound_masses.append(mass)
51 total_mass += mass
53 # Calculate and set mass fractions as display values
54 if total_mass > 0:
55 for i, prop in enumerate(property_values):
56 # Calculate mass fraction
57 mass_fraction = compound_masses[i] / total_mass
58 # Set display value
59 prop.displayValue = str(mass_fraction)
61 # Update in database
62 PropertyValue.objects.bulk_update(property_values, ["displayValue"])
65def check_fully_defined(
66 property_set: PropertySet,
67 property_infos: list[PropertyInfo] | None = None,
68 exclude: PropertyInfo | None = None,
69 check_none_empty = False,
70 check_fraction_sum = False
71 ) -> bool:
72 """
73 Returns true if the properties in the given property sets are
74 fully defined (no read-write properties).
75 """
76 if property_infos is None:
77 property_infos = property_set.containedProperties.all()
79 for prop in property_infos:
80 if (
81 check_none_empty
82 and prop.key in ["flow_mol", "flow_mass", "flow_vol"]
83 and not prop.has_value()
84 ):
85 return False
86 if exclude is not None and prop.id == exclude.id: 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 continue
88 if any([
89 property_value.is_enabled() and
90 not property_value.has_value()
91 for property_value in prop.values.all()
92 ]):
93 return False
94 if (
95 check_fraction_sum
96 and property_set.compoundMode in ["MolarFraction", "MassFraction"]
97 ):
98 mole_frac_comp = property_set.get_property("mole_frac_comp")
99 return abs(
100 sum([
101 float(prop.displayValue) if prop.displayValue not in [None, ""] else 0
102 for prop in mole_frac_comp.values.all()
103 ]) - 1
104 ) <= 1e-3 # sum of fractions == 1
106 return True
109def serialize_to_current_mode(property_set: PropertySet, properties_schema: dict) -> None:
110 """
111 Serialize the composition property set to the current compound mode
112 (for GET requests). Adjusts the properties_schema in place
113 """
115 mole_frac_comp_schema = next(
116 (item for item in properties_schema if item["key"] == "mole_frac_comp"),
117 None
118 )
119 if mole_frac_comp_schema is None: 119 ↛ 120line 119 didn't jump to line 120 because the condition on line 119 was never true
120 return
122 if not check_fully_defined(property_set, check_none_empty=True): 122 ↛ 126line 122 didn't jump to line 126 because the condition on line 122 was always true
123 # stream is not fully defined, do not convert
124 return
126 def convert_to_mass_flow():
127 for key, value_data in mole_frac_comp_schema["values"].items():
128 # convert to mass flow
129 if value_data["value"] not in [None, ""]:
130 value_data["value"] = compound_db_to_mass_flow(key, float(value_data["value"]))
132 def convert_to_mass_fraction():
133 # sums for molar fractions and mass fractions should be equal
134 sum_molar_frac = 0
135 for value_data in mole_frac_comp_schema["values"].values():
136 sum_molar_frac += float(value_data["value"])
137 convert_to_mass_flow()
138 sum_mass_flow = 0
139 for value_data in mole_frac_comp_schema["values"].values():
140 sum_mass_flow += float(value_data["value"])
142 if sum_mass_flow == 0:
143 return
144 for value_data in mole_frac_comp_schema["values"].values():
145 value_data["value"] = float(value_data["value"]) / sum_mass_flow * sum_molar_frac
147 match property_set.compoundMode:
148 case "MolarFraction":
149 pass # already in molar fractions
150 case "MassFraction":
151 convert_to_mass_fraction()
154def convert_to_molar_fractions(property_set: PropertySet) -> None:
155 """
156 Converts the composition to molar fractions from raw values
157 """
158 mole_frac_comp = property_set.get_property("mole_frac_comp")
159 property_values = mole_frac_comp.values.all()
161 def molar_flows_to_fractions() -> None:
162 # convert from molar flows to molar fractions
163 sum_flows = sum([float(prop.value) for prop in property_values])
164 if sum_flows == 0: 164 ↛ 165line 164 didn't jump to line 165 because the condition on line 164 was never true
165 return
166 for prop in property_values:
167 prop.value = float(prop.value) / sum_flows
169 def mass_flows_to_molar_flows() -> None:
170 # convert from mass flows to molar flows
171 for prop in property_values:
172 if prop.displayValue in [None, ""]: 172 ↛ 173line 172 didn't jump to line 173 because the condition on line 172 was never true
173 prop.displayValue = prop.value
174 prop.value = compound_db_to_molar_flow(prop.get_index("compound").key, float(prop.displayValue))
176 match property_set.compoundMode:
177 case "MolarFraction": 177 ↛ 178line 177 didn't jump to line 178 because the pattern on line 177 never matched
178 return # already in molar fractions
179 case "MassFraction": 179 ↛ 184line 179 didn't jump to line 184 because the pattern on line 179 always matched
180 # assume total mass flow of 1
181 mass_flows_to_molar_flows()
182 molar_flows_to_fractions()
184 PropertyValue.objects.bulk_update(property_values, ["value", "displayValue"])
187def convert_to_raw_values(property_set: PropertySet) -> None:
188 """
189 Converts the composition to raw values from molar fractions
190 """
191 mole_frac_comp = property_set.get_property("mole_frac_comp")
192 property_values = mole_frac_comp.values.all()
193 for prop in property_values:
194 if prop.displayValue not in [None, ""]:
195 prop.value = float(prop.displayValue)
197 PropertyValue.objects.bulk_update(property_values, ["value", "displayValue"])