Coverage for backend/django/core/auxiliary/models/PropertyValue.py: 91%

87 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-03-26 20:57 +0000

1from django.db import models 

2from core.auxiliary.models.IndexedItem import IndexedItem 

3from core.managers import AccessControlManager 

4from core.auxiliary.models.ControlValue import ControlValue 

5from typing import TYPE_CHECKING 

6if TYPE_CHECKING: 

7 from core.auxiliary.models.PropertyInfo import PropertyInfo 

8 from core.auxiliary.models.PropertySet import PropertySet 

9 from core.auxiliary.models.ControlValue import ControlValue 

10 

11class PropertyValue(models.Model): 

12 flowsheet = models.ForeignKey("Flowsheet", on_delete=models.CASCADE, related_name="propertyValues") 

13 value = models.JSONField(null=True, blank=True) 

14 displayValue = models.JSONField(null=True, blank=True) 

15 enabled = models.BooleanField(default=True) 

16 formula = models.CharField(max_length=2048, null=True, blank=True, default=None) 

17 property: "PropertyInfo" = models.ForeignKey("PropertyInfo", on_delete=models.CASCADE, related_name="values", null=True) 

18 indexedItems = models.ManyToManyField(IndexedItem, related_name="propertyValues") 

19 created_at = models.DateTimeField(auto_now_add=True) 

20 

21 objects = AccessControlManager() 

22 controlManipulated: ControlValue | None 

23 controlSetPoint: ControlValue | None 

24 # Bulk update doesn't work with access control manager so we 

25 # sometimes need to just use the normal manager. 

26 _unsafe_objects = models.Manager() 

27 

28 class Meta: 

29 ordering = ['created_at'] 

30 

31 def enable(self, state = True) -> None: 

32 self.enabled = state 

33 self.save() 

34 

35 def get_index(self, index_set: str) -> list[IndexedItem]: 

36 """ 

37 Get the indexed item of the specified type for this property. 

38 """ 

39 return self.indexedItems.get(type=index_set) 

40 

41 def has_value(self) -> bool: 

42 return self.value not in [None, ""] 

43 

44 def get_indexed_items(self) -> list[IndexedItem]: 

45 """ 

46 Gets the list of indexed items for this property value, in order 

47 Note that this is kinda expensive if you're doing it for everything, 

48 as it looks up the unit op type every time. 

49 But it's helpful for testing. 

50 """ 

51 if self.property.is_custom_property(): 

52 return [] # no special indexes on custom properties as they don't exist in the schema 

53 indexes = list(self.indexedItems.all()) 

54 property_key = self.property.key 

55 properties = self.property.set.simulationObject.schema.properties.get(property_key, None) 

56 index_set_order = self.property.set.simulationObject.schema.properties[property_key].indexSets 

57 return sort_indexes(index_set_order, indexes) 

58 

59 def get_indexes(self) -> list[str]: 

60 """ 

61 Gets the list of indexes for this property value, in order. e.g "outlet_1 

62, Note that this is kinda expensive if you're doing it for everything, 

63 as it looks up the unit op type every time. 

64 But it's helpful for testing. 

65 """ 

66 return [index.key for index in self.get_indexed_items()] 

67 

68 def get_simulation_object(self): 

69 return self.property.set.simulationObject 

70 

71 def get_index_names(self) -> list[str]: 

72 return [index.displayName for index in self.get_indexed_items()] 

73 

74 def is_control_set_point(self) -> bool: 

75 return hasattr(self, "controlSetPoint") 

76 

77 def is_control_manipulated(self) -> bool: 

78 return hasattr(self, "controlManipulated") 

79 

80 def is_externally_controlled(self) -> bool: 

81 """ 

82 Returns true if the property is controlled or controlling an external property 

83 """ 

84 if not self.is_control_manipulated() and not self.is_control_set_point(): 

85 return False 

86 if self.is_control_manipulated(): 

87 # set point is from a different property set 

88 return self.controlManipulated.setPoint.property.set != self.property.set 

89 if self.is_control_set_point(): 89 ↛ 92line 89 didn't jump to line 92 because the condition on line 89 was always true

90 # manipulated property is from a different property set 

91 return self.controlSetPoint.manipulated.property.set != self.property.set 

92 return False 

93 

94 def is_enabled(self) -> bool: 

95 """ 

96 Returns true if the property is to be enabled in simulation: either a statevar or a controlled property 

97 """ 

98 return (self.enabled and not self.is_control_manipulated() ) or self.is_control_set_point() 

99 

100 

101 def add_control(self, prop: "PropertyValue") -> ControlValue: 

102 # This property is the set point controlling the manipulated property 

103 return ControlValue.create(setPoint=self, manipulated=prop, flowsheet=prop.flowsheet) 

104 

105 

106 def auto_replace(self): 

107 """ 

108 Automatically create control relationships as autocomplete option: 

109 e.g. 

110 - Mass Flow -> Molar Flow 

111 - Vapour Fraction -> Temperature 

112 

113 Logic: 

114 - Non-enabled property (self) becomes controlSetPoint 

115 - Enabled property becomes manipulated 

116 - No properties are enabled/disabled here 

117 """ 

118 property_info: PropertyInfo = self.property 

119 property_set: PropertySet = property_info.set 

120 simulation_object = property_set.simulationObject 

121 

122 control_mapping = {} 

123 

124 if simulation_object.objectType == "stream": 124 ↛ 141line 124 didn't jump to line 141 because the condition on line 124 was always true

125 control_mapping.update({ 

126 "flow_mass": "flow_mol", 

127 "vapor_frac": "temperature", 

128 "enth_mol": "temperature", 

129 "enth_mass": "temperature", 

130 "entr_mol": "pressure", 

131 "entr_mass": "pressure", 

132 # "total_energy_flow": "", 

133 # "flow_vol": "", 

134 # "mole_frac_phase_comp": "", 

135 # "efficiency_isentropic": "", // are unitops still streams?? 

136 # "deltaP": "work_mechanical", 

137 # "ratioP": "", 

138 # "work_mechanical": "", 

139 }) 

140 

141 setpoint_key = property_info.key 

142 manipulated_key = control_mapping.get(setpoint_key) 

143 if not manipulated_key: 

144 return 

145 

146 # find enabled property to manipulate 

147 manipulated_pvs = [ 

148 pv for prop_info in property_set.containedProperties.all() 

149 if prop_info.key == manipulated_key 

150 for pv in prop_info.values.all() 

151 if pv.enabled # only check the actual enabled flag 

152 ] 

153 

154 if not manipulated_pvs: 154 ↛ 155line 154 didn't jump to line 155 because the condition on line 154 was never true

155 return 

156 

157 manipulated_pv = manipulated_pvs[0] 

158 

159 if ControlValue.objects.filter(manipulated=manipulated_pv).exists(): 159 ↛ 160line 159 didn't jump to line 160 because the condition on line 159 was never true

160 return 

161 

162 # create the control relationship 

163 ControlValue.create( 

164 manipulated=manipulated_pv, 

165 setPoint=self, 

166 flowsheet=self.flowsheet 

167 ) 

168 

169PropertyValueIntermediate = PropertyValue.indexedItems.through 

170 

171 

172def sort_indexes(index_set_order : list[str], indexes : list[IndexedItem]) -> list[IndexedItem]: 

173 """ 

174 Sorts the list of indexed items based on the order defined in the config file. 

175 index_set_order: list[str], a list of index set types, the order in which the indexes should be sorted. e.g ["splitter_fraction","compound","phase"] 

176 indexes: list[IndexedItem], a list of indexed items to be sorted. e.g a list of indexed items attached to a PropertyValue 

177 """ 

178 item = [index.type for index in indexes] 

179 reordered_indexes = [None] * len(indexes) 

180 

181 for i in range(len(item)): 

182 if item[i] != index_set_order[i]: 

183 # Find the correct position for the current item in object_properties 

184 correct_position = index_set_order.index(item[i]) 

185 # Place the current index in the correct position 

186 reordered_indexes[correct_position] = indexes[i] 

187 else: 

188 # If the item is already in the correct position, keep it as is 

189 reordered_indexes[i] = indexes[i] 

190 

191 # Update the original indexes list with the reordered indexes 

192 for i in range(len(indexes)): 

193 indexes[i] = reordered_indexes[i] 

194 return indexes