Coverage for backend/idaes_factory/idaes_factory_context.py: 96%

47 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-11-06 23:27 +0000

1from typing import Any 

2 

3from django.db import transaction 

4from django.db.models import QuerySet, Prefetch 

5 

6from common.models.idaes.flowsheet_schema import PropertyPackageType 

7from core.auxiliary.models import PropertyInfo, PropertySet 

8from core.auxiliary.models.PropertyValue import PropertyValue 

9from core.auxiliary.models.SolveState import SolveState 

10from flowsheetInternals.unitops.models.SimulationObject import SimulationObject 

11from . import queryset_lookup 

12from core.auxiliary.models.Scenario import Scenario 

13 

14 

15ParameterName = str 

16ParameterValue = float 

17LiveSolveParams = dict[ParameterName,ParameterValue] 

18""" 

19Parameters for solving. Used when solving live, to specify the values that each of the input columns in the multi-steady state simulation should be set to.  

20Key is the key of the column, should be one of the input columns used. 

21Value is a float value to set it to. 

22""" 

23 

24 

25 

26class IdaesFactoryContext: 

27 """ 

28 The IdaesFactoryContext class provides: 

29 

30 - Efficient retrieval of related objects from the 

31 database, for both serialisation and reloading  

32 of the flowsheet 

33 - A container for context variables such as time 

34 steps, property packages, etc. 

35 

36 It is used within idaes_factory to provide context 

37 through the various adapter classes. 

38 """ 

39 

40 

41 def __init__( 

42 self, 

43 flowsheet_id: int, 

44 time_steps: list[int] = [0], 

45 time_step_size: float = 1.0, 

46 require_variables_fixed: bool = False, 

47 solve_index: int | None = None, # If it's from multi-steady state, index to store values at 

48 scenario : Scenario = None, 

49 ) -> None: 

50 """Initialise the context with eager-loaded flowsheet data. 

51 

52 Args: 

53 flowsheet_id: Identifier of the flowsheet being prepared. 

54 time_steps: Time indices that adapters should serialise. 

55 require_variables_fixed: Whether adapters must enforce fixed variables. 

56 solve_index: Optional multi-steady-state solve index to bind. 

57 scenario: Scenario providing state and dynamics configuration. 

58 """ 

59 self.flowsheet_id = flowsheet_id 

60 self.simulation_objects: QuerySet[SimulationObject] = None 

61 

62 if solve_index is not None: 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true

63 self.solve_index = SolveState.objects.get(index=solve_index, scenario_id=scenario.id) 

64 else: 

65 self.solve_index = None 

66 

67 # context vars 

68 self.time_steps = time_steps 

69 self.time_step_size = time_step_size 

70 self.require_variables_fixed = require_variables_fixed 

71 self.property_packages: list[PropertyPackageType] = [] 

72 self.expression_values = {} 

73 self.scenario = scenario 

74 

75 self.load() 

76 

77 

78 def load(self): 

79 """Prefetch all simulation objects and related data for the flowsheet.""" 

80 # load all associated unit models, streams, property sets,  

81 # properties, ports, etc. into the context 

82 

83 objects_filter = { 

84 "flowsheet_id": self.flowsheet_id, 

85 "is_deleted": False 

86 } 

87 

88 # db calls: simulation_objects, 1 for each prefetch_related 

89 self.simulation_objects = SimulationObject.objects.filter( 

90 **objects_filter 

91 ).select_related( 

92 "properties", 

93 "recycleConnection", 

94 ).prefetch_related( 

95 Prefetch( 

96 "properties__ContainedProperties", 

97 queryset=PropertyInfo.objects 

98 .select_related("recycleConnection") 

99 .prefetch_related( 

100 Prefetch("values", queryset=PropertyValue.objects 

101 .select_related("controlManipulated", "controlSetPoint", "controlSetPoint__manipulated__property") 

102 .prefetch_related( 

103 "indexedItems", 

104 "controlSetPoint__manipulated__property__values" 

105 ) 

106 ) 

107 ) 

108 ), 

109 "ports", 

110 "connectedPorts", 

111 "propertyPackages", 

112 ) 

113 

114 def is_dynamic(self) -> bool: 

115 """Return whether the bound scenario has dynamics enabled.""" 

116 if self.scenario is not None: 

117 return self.scenario.enable_dynamics 

118 else: 

119 return False 

120 

121 # Updates the solve index so that the alread-loaded context can be used for multiple solves 

122 # (Useful for multi-steady state) 

123 def update_solve_index(self, index: int): 

124 """Rebind the context to a different solve index for multi-solves. 

125 

126 Args: 

127 index: Solve index associated with the current flowsheet. 

128 """ 

129 self.solve_index = SolveState.objects.get(index=index, flowsheet_id=self.flowsheet_id) 

130 

131 def get_simulation_object(self, obj_id: int) -> SimulationObject: 

132 """Return a simulation object by id using the prefetched queryset. 

133 

134 Args: 

135 obj_id: Primary key of the simulation object to retrieve. 

136 

137 Returns: 

138 The matching `SimulationObject` instance from the context cache. 

139 """ 

140 return queryset_lookup.get_simulation_object(self.simulation_objects, obj_id) 

141 

142 

143 def filter_object_type(self, include: set[str] = set()) -> list[SimulationObject]: 

144 """Filter cached simulation objects to the provided set of type keys. 

145 

146 Args: 

147 include: Unit operation type identifiers that should be returned. 

148 

149 Returns: 

150 List of `SimulationObject` instances matching the requested types. 

151 """ 

152 return queryset_lookup.filter_simulation_objects(self.simulation_objects, include) 

153 

154 

155 def exclude_object_type(self, exclude: set[str]) -> list[SimulationObject]: 

156 """Filter cached simulation objects by excluding specific type keys. 

157 

158 Args: 

159 exclude: Unit operation type identifiers that should be omitted. 

160 

161 Returns: 

162 List of `SimulationObject` instances not belonging to the excluded types. 

163 """ 

164 return queryset_lookup.exclude_simulation_objects(self.simulation_objects, exclude) 

165 

166 def get_property(self, property_set: PropertySet, key: str) -> PropertyInfo: 

167 """Retrieve a property from a property set by its key. 

168 

169 Args: 

170 property_set: Property set that contains the requested property. 

171 key: Identifier for the property within the set. 

172 

173 Returns: 

174 The `PropertyInfo` instance found in the given property set. 

175 """ 

176 return queryset_lookup.get_property(property_set, key) 

177 

178 def get_property_value(self, property: PropertyInfo, indexes: list[Any] | None = None) -> PropertyValue: 

179 """Retrieve a property value, optionally constrained by index selections. 

180 

181 Args: 

182 property: Property whose value object should be fetched. 

183 indexes: Optional list of indices to select a specific value entry. 

184 

185 Returns: 

186 The `PropertyValue` matching the property and index configuration. 

187 """ 

188 return queryset_lookup.get_value_object(property, indexes)