Coverage for backend/idaes_factory/adapters/property_info_adapter.py: 81%
98 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 abc import ABC
2from common.models.idaes import PropertySchema, PropertiesSchema, PropertyValueSchema
3from core.auxiliary.models.PropertyInfo import PropertyInfo, check_is_except_last
4from core.auxiliary.models.PropertyValue import PropertyValue, sort_indexes
5from flowsheetInternals.unitops.models import SimulationObject
6from common.config_types import *
7from ..queryset_lookup import get_property, get_value_object
8from .convert_expression import convert_expression
9from ..idaes_factory_context import IdaesFactoryContext
10from flowsheetInternals.unitops.config.config_base import configuration
11from core.auxiliary.models.IndexedItem import IndexedItem
12from .property_value_adapter import serialise_property_value
14def serialise_property_info(ctx: IdaesFactoryContext, property_info: PropertyInfo,
15 is_tear: bool = False,
16 is_indexed: bool = True) -> PropertySchema:
17 """
18 A PropertyInfo object represents a single IndexedVar or ScalarVar in IDAES.
19 This method handles unpacking the indexes and putting it in the format that
20 idaes_service expects. see pump.json or any of the other idaes_service test files
21 as an example of what a property looks like.
22 """
24 property_values = property_info.values.all()
26 has_value = False
28 if ctx.scenario != None:
29 enable_rating = ctx.scenario.enable_rating
30 else:
31 enable_rating = False
33 data = []
35 for prop in property_values:
36 # get indexes
37 # ie. ["0", "benzene", "Vap"]
38 unsorted_indexes : list[IndexedItem] = list(prop.indexedItems.all())
40 # Get the object type and property key (e.g.: split_fraction)
41 property_key = property_info.key
42 index_set_order : list[str] = property_info.set.simulationObject.schema.properties[property_key].indexSets
44 # Sort the indexes so that they are in the order defined in the config file
45 # and continue serialization with the new indexes
46 indexes = [index.key for index in sort_indexes(index_set_order, unsorted_indexes)]
48 # get value data
49 # ie. {"id": 1, "value": 0.5, "controlled": int?, "guess": bool?, "constraint": str?} value_data = {
50 property_value = PropertyValueSchema(
51 id=prop.id,
52 discrete_indexes=indexes,
53 )
54 value = serialise_property_value(ctx, property_info, prop, is_indexed, is_tear=is_tear)
55 if value is not None:
56 has_value = True # used to determine if we should include the unit
57 if is_tear: 57 ↛ 59line 57 didn't jump to line 59 because the condition on line 57 was never true
58 # only pass value for tear variables
59 property_value.value = value
60 if (
61 prop.is_control_set_point()
62 and prop.is_externally_controlled()
63 ):
64 # this property is externally controlled, and will be included
65 # on the other side of the tear. So we need to set the id to -1
66 # to avoid clashing ids.
67 # We need to discard this one because this side is just a guess,
68 # while the other may be used for constraints which can be
69 # deactivated during optimization.
70 property_value.id = -1
71 else:
72 property_value.value = value
73 if (prop.is_control_set_point()):
74 if not enable_rating:
75 # get the id of the thing we're manipulating
76 property_value.controlled = prop.controlSetPoint.manipulated.id
77 else:
78 property_value.value = None
79 if (prop.is_control_manipulated()
80 and not enable_rating):
81 property_value.guess = True
83 if prop.formula not in [None, ""]:
84 property_value.constraint = convert_expression(prop.formula)
86 data.append(property_value)
88 property_schema = PropertySchema(data=data)
89 if has_value:
90 property_schema.unit = property_info.unit
92 return property_schema
96class ValueAdapter(ABC):
97 def serialise(self, ctx, model: SimulationObject):
98 pass
102# TODO: Deprecate this clase (just use the method directly.)
103class PropertyInfoAdapter(ValueAdapter):
104 """
105 Handles the serialisation and reloading of a single property
106 """
107 def __init__(
108 self,
109 is_indexed: bool = True,
110 ):
111 self._is_indexed = is_indexed
113 def serialise(self, ctx: IdaesFactoryContext, property_obj: PropertyInfo, is_tear: bool = False) -> PropertySchema:
114 """
115 - is_tear: bool, true if inlet side of a tear
116 """
117 return serialise_property_info(
118 ctx,
119 property_obj,
120 is_tear=is_tear,
121 is_indexed=self._is_indexed
122 )
126def is_group_enabled(model: SimulationObject, ctx: IdaesFactoryContext, prop_key: str) -> bool:
127 """
128 If the propertySetGroup that it's a part of is not enabled,
129 we don't need this property.
130 The main case for this is e.g heaters where "enable dynamics" can be
131 switched on or off.
132 """
133 # If the property is part of a group that is not enabled, don't serialise it
134 group = model.schema.properties[prop_key].propertySetGroup
135 group_schema = model.schema.propertySetGroups[group]
136 if group_schema.toggle:
137 toggle_property = ctx.get_property(model.properties, group_schema.toggle)
138 toggle_status = ctx.get_property_value(toggle_property).value
139 if toggle_status != True:
140 return False
141 # if it's the dynamics group, and it's not dynamic, don't serialise it
142 if group == "dynamics" and not ctx.is_dynamic(): 142 ↛ 143line 142 didn't jump to line 143 because the condition on line 142 was never true
143 return False
144 # Otherwise, it's enabled
145 return True
149class PropertyKeyAdapter(ValueAdapter):
150 """
151 Used to map one PropKey from a unit model to one property in an idaes
152 json schema.
153 """
154 def __init__(self,
155 prop_key: str,
156 is_indexed: bool = True,
157 has_unit: bool = True,
158 ):
159 """
160 prop_key: the key of the property in the Django unit model's PropertySet.
161 """
162 self._prop_key = prop_key
163 self._has_unit = has_unit # TODO: Replace with something that unwraps the unit if has_unit is False
164 self.property_adapter = PropertyInfoAdapter( is_indexed=is_indexed)
167 def serialise(self, ctx:IdaesFactoryContext, model: SimulationObject) -> PropertySchema:
169 if not is_group_enabled(model, ctx, self._prop_key):
170 # if the propertySetGroup is not enabled, don't serialise this property
171 return None
172 # get the property from the unit model
173 property_set = model.properties
174 prop = get_property(property_set, self._prop_key)
175 # serialise the property
176 return self.property_adapter.serialise(ctx, prop)
179class PropertyDictAdapter(ValueAdapter):
180 """
181 Handles running the relevant PropertyAdapter for each key in the schema.
182 """
184 def __init__(self, schema: dict[str, ValueAdapter]):
185 # convert schema to list if necessary and store in this object
186 self.schema = schema
189 def serialise(self, ctx:IdaesFactoryContext, model: SimulationObject) -> PropertiesSchema:
190 result = {}
191 for key, adapter in self.schema.items():
192 # get value from adapter
193 value: PropertySchema = adapter.serialise(ctx, model)
194 if value is not None:
195 result[key] = value
196 return result
199class SerialisePropertiesAdapter(ValueAdapter):
200 """
201 This class should replace PropertyDictAdapter and PropertyKeyAdapter.
202 """
204 def serialise(self, ctx: IdaesFactoryContext, model: SimulationObject) -> PropertiesSchema:
205 """
206 Serialise all properties in the model.
207 """
208 property_set = model.properties
210 result: PropertiesSchema = {}
212 prop_key: str
213 prop_schema: PropertyType
214 for prop_key,prop_schema in model.schema.properties.items():
215 # if the propertySetGroup is not enabled, don't serialise this property
216 if not is_group_enabled(model, ctx, prop_key):
217 continue
218 # if schema type is not number, skip it too
219 if prop_schema.type != "numeric":
220 continue
221 # get the property from the unit model
222 prop = get_property(property_set, prop_key)
223 # serialise the property
224 result[prop_key] = serialise_property_info(
225 ctx,
226 prop,
227 is_tear=False, # tears and streams are handled separately
228 is_indexed=prop_schema.hasTimeIndex
229 )
230 return result