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
« prev ^ index » next coverage.py v7.10.7, created at 2026-02-12 01:47 +0000
1from django.db import models
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
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)
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()
29 class Meta:
30 ordering = ['created_at']
32 def enable(self, state = True) -> None:
33 self.enabled = state
34 self.save()
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)
42 def has_value(self) -> bool:
43 return self.value not in [None, ""]
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)
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()]
69 def get_index_names(self) -> list[str]:
70 return [index.displayName for index in self.get_indexed_items()]
72 def is_control_set_point(self) -> bool:
73 return hasattr(self, "controlSetPoint")
75 def is_control_manipulated(self) -> bool:
76 return hasattr(self, "controlManipulated")
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
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()
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)
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
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
120 control_mapping = {}
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 })
139 setpoint_key = property_info.key
140 manipulated_key = control_mapping.get(setpoint_key)
141 if not manipulated_key:
142 return
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 ]
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
155 manipulated_pv = manipulated_pvs[0]
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
160 # create the control relationship
161 ControlValue.create(
162 manipulated=manipulated_pv,
163 setPoint=self,
164 flowsheet=self.flowsheet
165 )
167PropertyValueIntermediate = PropertyValue.indexedItems.through
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)
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]
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