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

85 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-02-12 01:47 +0000

1from django.db import models 

2 

3from core.auxiliary.models.IndexedItem import IndexedItem 

4from core.managers import AccessControlManager 

5from core.auxiliary.models.ControlValue import ControlValue 

6from typing import TYPE_CHECKING 

7if TYPE_CHECKING: 

8 from core.auxiliary.models.PropertyInfo import PropertyInfo 

9 from core.auxiliary.models.PropertySet import PropertySet 

10 from core.auxiliary.models.ControlValue import ControlValue 

11 

12class PropertyValue(models.Model): 

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

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

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

16 enabled = models.BooleanField(default=True) 

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

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

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

20 created_at = models.DateTimeField(auto_now_add=True) 

21 

22 objects = AccessControlManager() 

23 controlManipulated: ControlValue | None 

24 controlSetPoint: ControlValue | None 

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

26 # sometimes need to just use the normal manager. 

27 _unsafe_objects = models.Manager() 

28 

29 class Meta: 

30 ordering = ['created_at'] 

31 

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

33 self.enabled = state 

34 self.save() 

35 

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

37 """ 

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

39 """ 

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

41 

42 def has_value(self) -> bool: 

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

44 

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

46 """ 

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

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

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

50 But it's helpful for testing. 

51 """ 

52 if self.property.is_custom_property(): 

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

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

55 property_key = self.property.key 

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

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

58 return sort_indexes(index_set_order, indexes) 

59 

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

61 """ 

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

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

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

65 But it's helpful for testing. 

66 """ 

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

68 

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

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

71 

72 def is_control_set_point(self) -> bool: 

73 return hasattr(self, "controlSetPoint") 

74 

75 def is_control_manipulated(self) -> bool: 

76 return hasattr(self, "controlManipulated") 

77 

78 def is_externally_controlled(self) -> bool: 

79 """ 

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

81 """ 

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

83 return False 

84 if self.is_control_manipulated(): 

85 # set point is from a different property set 

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

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

88 # manipulated property is from a different property set 

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

90 return False 

91 

92 def is_enabled(self) -> bool: 

93 """ 

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

95 """ 

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

97 

98 

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

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

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

102 

103 

104 def auto_replace(self): 

105 """ 

106 Automatically create control relationships as autocomplete option: 

107 e.g. 

108 - Mass Flow -> Molar Flow 

109 - Vapour Fraction -> Temperature 

110 

111 Logic: 

112 - Non-enabled property (self) becomes controlSetPoint 

113 - Enabled property becomes manipulated 

114 - No properties are enabled/disabled here 

115 """ 

116 property_info: PropertyInfo = self.property 

117 property_set: PropertySet = property_info.set 

118 simulation_object = property_set.simulationObject 

119 

120 control_mapping = {} 

121 

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

123 control_mapping.update({ 

124 "flow_mass": "flow_mol", 

125 "vapor_frac": "temperature", 

126 "enth_mol": "temperature", 

127 "enth_mass": "temperature", 

128 "entr_mol": "pressure", 

129 "entr_mass": "pressure", 

130 # "total_energy_flow": "", 

131 # "flow_vol": "", 

132 # "mole_frac_phase_comp": "", 

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

134 # "deltaP": "work_mechanical", 

135 # "ratioP": "", 

136 # "work_mechanical": "", 

137 }) 

138 

139 setpoint_key = property_info.key 

140 manipulated_key = control_mapping.get(setpoint_key) 

141 if not manipulated_key: 

142 return 

143 

144 # find enabled property to manipulate 

145 manipulated_pvs = [ 

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

147 if prop_info.key == manipulated_key 

148 for pv in prop_info.values.all() 

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

150 ] 

151 

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

153 return 

154 

155 manipulated_pv = manipulated_pvs[0] 

156 

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

158 return 

159 

160 # create the control relationship 

161 ControlValue.create( 

162 manipulated=manipulated_pv, 

163 setPoint=self, 

164 flowsheet=self.flowsheet 

165 ) 

166 

167PropertyValueIntermediate = PropertyValue.indexedItems.through 

168 

169 

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

171 """ 

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

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

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

175 """ 

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

177 reordered_indexes = [None] * len(indexes) 

178 

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

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

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

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

183 # Place the current index in the correct position 

184 reordered_indexes[correct_position] = indexes[i] 

185 else: 

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

187 reordered_indexes[i] = indexes[i] 

188 

189 # Update the original indexes list with the reordered indexes 

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

191 indexes[i] = reordered_indexes[i] 

192 return indexes