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

1from compounds import CompoundDB 

2from ..models.PropertySet import PropertySet 

3from ..models.PropertyInfo import PropertyInfo 

4from ..models.PropertyValue import PropertyValue 

5 

6 

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 

15 

16 

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 

25 

26 

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() 

34 

35 # Calculate mass for each compound and track total mass 

36 compound_masses = [] 

37 total_mass = 0.0 

38 

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 

42 

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 

47 

48 # Calculate mass (molar fraction * molecular weight) 

49 mass = molar_fraction * molecular_weight 

50 compound_masses.append(mass) 

51 total_mass += mass 

52 

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) 

60 

61 # Update in database 

62 PropertyValue.objects.bulk_update(property_values, ["displayValue"]) 

63 

64 

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() 

78 

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 

105 

106 return True 

107 

108 

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 """ 

114 

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 

121 

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 

125 

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"])) 

131 

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"]) 

141 

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 

146 

147 match property_set.compoundMode: 

148 case "MolarFraction": 

149 pass # already in molar fractions 

150 case "MassFraction": 

151 convert_to_mass_fraction() 

152 

153 

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() 

160 

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 

168 

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)) 

175 

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() 

183 

184 PropertyValue.objects.bulk_update(property_values, ["value", "displayValue"]) 

185 

186 

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) 

196 

197 PropertyValue.objects.bulk_update(property_values, ["value", "displayValue"])