ahuora-builder 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ahuora_builder-0.1.0/.gitignore +6 -0
- ahuora_builder-0.1.0/PKG-INFO +14 -0
- ahuora_builder-0.1.0/README.md +0 -0
- ahuora_builder-0.1.0/pyproject.toml +34 -0
- ahuora_builder-0.1.0/src/ahuora_builder/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/arc_manager.py +33 -0
- ahuora_builder-0.1.0/src/ahuora_builder/build_state.py +57 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/PIDController.py +494 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/PySMOModel.py +178 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/SimpleEffectivenessHX_DH.py +727 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/add_initial_dynamics.py +35 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_compressor.py +107 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_cooler.py +33 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_heat_exchanger.py +183 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_heat_exchanger_1d.py +258 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_heater.py +41 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_pressure_changer.py +34 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_pump.py +107 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_separator.py +371 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_tank.py +133 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_turbine.py +132 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_valve.py +300 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/custom_variable.py +29 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/direct_steam_injection.py +371 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/acBus.py +280 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/ac_property_package.py +279 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/battery.py +170 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/bus.py +182 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/energy_mixer.py +195 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/energy_splitter.py +228 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/grid.py +173 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/hydro.py +169 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/link.py +137 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/load.py +155 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/mainDistributionBoard.py +257 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/power_property_package.py +253 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/solar.py +176 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/storage.py +230 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/storage_wrapper +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/tests/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/tests/test_bus.py +44 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/tests/test_energy_mixer.py +46 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/tests/test_mdb.py +49 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/transformer.py +187 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/transformer_property_package.py +267 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/transmissionLine.py +228 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/energy/wind.py +206 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/hda_ideal_VLE.py +1341 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/hda_reaction.py +182 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/heat_exchanger_1d_wrapper.py +31 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/integration_block.py +106 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/inverted.py +81 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/performance_curves.py +1 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/reactions/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/reactions/hda_stoich.py +10 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/simple_separator.py +680 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/tests/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/tests/test_SimpleEffectivenessHX_DH.py +91 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/tests/test_custom_tank.py +70 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/tests/test_direct_steam_injection.py +41 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/tests/test_simple_separator.py +46 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/tests/test_waterpipe.py +46 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/desuperheater.py +624 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/header.py +889 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/simple_heat_pump.py +567 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/steam_header.py +353 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/steam_user.py +944 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/temp.py +349 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_desuperheater.py +142 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_header.py +998 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_ntu_hx.py +129 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_simple_heat_pump.py +120 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_steam_header.py +703 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_steam_user.py +277 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_waterpipe.py +36 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/tests/test_willans_turbine.py +253 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/thermal_utility_systems/willans_turbine.py +804 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/translator.py +129 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/updated_pressure_changer.py +1404 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/valve_wrapper.py +38 -0
- ahuora_builder-0.1.0/src/ahuora_builder/custom/water_tank_with_units.py +456 -0
- ahuora_builder-0.1.0/src/ahuora_builder/diagnostics/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/diagnostics/infeasibilities.py +40 -0
- ahuora_builder-0.1.0/src/ahuora_builder/diagnostics/tests/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/diagnostics/tests/test_infeasibilities.py +28 -0
- ahuora_builder-0.1.0/src/ahuora_builder/flowsheet_manager.py +542 -0
- ahuora_builder-0.1.0/src/ahuora_builder/flowsheet_manager_type.py +20 -0
- ahuora_builder-0.1.0/src/ahuora_builder/generate_python_file.py +440 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/BlockContext.py +84 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/adapter.py +355 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/adapter_library.py +549 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/adapter_methods.py +80 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/expression_parsing.py +105 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/load_unit_model.py +147 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/slice_manipulation.py +7 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/tests/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/tests/test_expression_parsing.py +15 -0
- ahuora_builder-0.1.0/src/ahuora_builder/methods/units_handler.py +129 -0
- ahuora_builder-0.1.0/src/ahuora_builder/ml_wizard.py +101 -0
- ahuora_builder-0.1.0/src/ahuora_builder/port_manager.py +20 -0
- ahuora_builder-0.1.0/src/ahuora_builder/properties_manager.py +44 -0
- ahuora_builder-0.1.0/src/ahuora_builder/property_package_manager.py +78 -0
- ahuora_builder-0.1.0/src/ahuora_builder/solver.py +38 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tear_manager.py +98 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_generate_python_file/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_generate_python_file/configurations/compressor_generated.py +63 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_generate_python_file/configurations/heat_exchanger_generated.py +70 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_generate_python_file/configurations/pump_generated.py +84 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_generate_python_file/configurations/recycle_generated.py +73 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_generate_python_file/test_generate_python_file.py +108 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/__init__.py +0 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/BT_PR.json +59 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/BT_PR_solved.json +59 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/bus.json +99 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/bus_solved.json +50 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/compound_separator.json +377 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/compound_separator_solved.json +374 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/compressor.json +38 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/compressor_solved.json +68 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/constraints.json +44 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/constraints_solved.json +59 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/control.json +39 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/control_solved.json +68 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/dynamic_tank.json +733 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/dynamic_tank_solved.json +846 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/elimination.json +39 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/elimination_solved.json +68 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/expressions.json +68 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/expressions_solved.json +104 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/header.json +1192 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/header_solved.json +761 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/heat_exchanger.json +63 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/heat_exchanger_solved.json +104 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/heat_pump.json +137 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/heat_pump_solved.json +104 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/machine_learning.json +2156 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/machine_learning_solved.json +266 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/mass_flow_tear.json +77 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/mass_flow_tear_solved.json +68 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/milk_heater.json +521 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/milk_heater_solved.json +311 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/mixer.json +44 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/mixer_solved.json +86 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/optimization.json +62 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/optimization_solved.json +59 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/propane_heat_pump.json +167 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/propane_heat_pump_solved.json +158 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/propane_recycle.json +141 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/propane_recycle_solved.json +104 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/pump.json +64 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/pump_solved.json +59 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/pump_unit_conversions.json +63 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/recycle.json +49 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/recycle_solved.json +50 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/sb_vapor_frac.json +29 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/sb_vapor_frac_solved.json +29 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/solar.json +67 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/solar_solved.json +50 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/vapor_frac_target.json +67 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/configurations/vapor_frac_target_solved.json +68 -0
- ahuora_builder-0.1.0/src/ahuora_builder/tests/test_solver/test_solve_models.py +250 -0
- ahuora_builder-0.1.0/src/ahuora_builder/timing.py +65 -0
- ahuora_builder-0.1.0/src/ahuora_builder/types/__init__.py +1 -0
- ahuora_builder-0.1.0/src/ahuora_builder/unit_model_manager.py +48 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ahuora-builder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Load a Ahuora .JSON file into a pyomo model.
|
|
5
|
+
Requires-Python: >=3.14
|
|
6
|
+
Requires-Dist: ahuora-builder-types
|
|
7
|
+
Requires-Dist: ahuora-compounds
|
|
8
|
+
Requires-Dist: common
|
|
9
|
+
Requires-Dist: coverage==7.11.3
|
|
10
|
+
Requires-Dist: idaes-pse==2.8.0
|
|
11
|
+
Requires-Dist: pyomo==6.9.2
|
|
12
|
+
Requires-Dist: scikit-learn==1.7.2
|
|
13
|
+
Requires-Dist: scipy==1.16.3
|
|
14
|
+
Requires-Dist: typing-validation==1.2.4
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ahuora-builder"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Load a Ahuora .JSON file into a pyomo model."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"ahuora-compounds",
|
|
9
|
+
"ahuora-builder-types",
|
|
10
|
+
"common",
|
|
11
|
+
"coverage==7.11.3",
|
|
12
|
+
"idaes-pse==2.8.0",
|
|
13
|
+
"pyomo==6.9.2",
|
|
14
|
+
"scikit-learn==1.7.2",
|
|
15
|
+
"scipy==1.16.3",
|
|
16
|
+
"typing-validation==1.2.4",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.uv.sources]
|
|
20
|
+
ahuora-compounds = { git = "https://github.com/waikato-ahuora-smart-energy-systems/PropertyPackages.git", rev = "v0.0.29" }
|
|
21
|
+
ahuora-builder-types = { workspace = true }
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["hatchling"]
|
|
26
|
+
build-backend = "hatchling.build"
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=9.0.1",
|
|
31
|
+
"pytest-test-groups>=1.2.1",
|
|
32
|
+
"uvicorn[standard]==0.34.3",
|
|
33
|
+
]
|
|
34
|
+
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pyomo.network import Arc
|
|
2
|
+
from .flowsheet_manager_type import FlowsheetManager
|
|
3
|
+
from ahuora_builder_types import PortId
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ArcManager:
|
|
7
|
+
|
|
8
|
+
def __init__(self, flowsheet_manager: FlowsheetManager):
|
|
9
|
+
"""
|
|
10
|
+
Initializes the arc manager
|
|
11
|
+
"""
|
|
12
|
+
self._flowsheet_manager = flowsheet_manager
|
|
13
|
+
|
|
14
|
+
def load(self):
|
|
15
|
+
"""
|
|
16
|
+
Loads arcs from the schema and adds them to the flowsheet
|
|
17
|
+
"""
|
|
18
|
+
schema = self._flowsheet_manager.schema
|
|
19
|
+
|
|
20
|
+
for arc_schema in schema.arcs:
|
|
21
|
+
self.add_arc(arc_schema.source, arc_schema.destination)
|
|
22
|
+
|
|
23
|
+
def add_arc(self, from_port_id: PortId, to_port_id: PortId):
|
|
24
|
+
"""
|
|
25
|
+
Adds an arc between two ports
|
|
26
|
+
"""
|
|
27
|
+
port_manager = self._flowsheet_manager.ports
|
|
28
|
+
from_port = port_manager.get_port(from_port_id)
|
|
29
|
+
to_port = port_manager.get_port(to_port_id)
|
|
30
|
+
arc = Arc(source=from_port, destination=to_port)
|
|
31
|
+
self._flowsheet_manager.model.fs.add_component(
|
|
32
|
+
f"arc_{from_port_id}_{to_port_id}", arc
|
|
33
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from idaes.core import FlowsheetBlock
|
|
3
|
+
|
|
4
|
+
from ahuora_builder_types.flowsheet_schema import PropertyPackageType
|
|
5
|
+
from ahuora_builder_types.payloads import BuildStateRequestSchema
|
|
6
|
+
from ahuora_builder_types.unit_model_schema import SolvedPropertyValueSchema
|
|
7
|
+
from .property_package_manager import create_property_package
|
|
8
|
+
from .methods.adapter import fix_block, serialize_properties_map, deactivate_fixed_guesses
|
|
9
|
+
from .methods.BlockContext import BlockContext
|
|
10
|
+
from pyomo.environ import ConcreteModel
|
|
11
|
+
from .properties_manager import PropertiesManager
|
|
12
|
+
from pyomo.environ import Block, assert_optimal_termination, SolverFactory
|
|
13
|
+
from pyomo.core.base.constraint import ScalarConstraint
|
|
14
|
+
from .flowsheet_manager import build_flowsheet
|
|
15
|
+
from idaes.core.util.model_statistics import degrees_of_freedom, number_unused_variables, number_activated_equalities, number_unfixed_variables, number_unfixed_variables_in_activated_equalities
|
|
16
|
+
from idaes.core.util.model_diagnostics import DiagnosticsToolbox
|
|
17
|
+
|
|
18
|
+
def solve_state_block(schema: BuildStateRequestSchema) -> list[SolvedPropertyValueSchema]:
|
|
19
|
+
m, sb = build_state(schema.property_package)
|
|
20
|
+
# if there's only one compound in the property package, remove the mole_frac_comp (as it's over specified)
|
|
21
|
+
if len(schema.property_package.compounds) == 1:
|
|
22
|
+
del schema.properties["mole_frac_comp"]
|
|
23
|
+
|
|
24
|
+
block_ctx = BlockContext(m.fs)
|
|
25
|
+
fix_block(sb, schema.properties, m.fs, block_ctx)
|
|
26
|
+
|
|
27
|
+
# If the degrees of freedom are not zero, don't try solve.
|
|
28
|
+
# However, the degree of freedom logic ignores any variables that aren't actually used. So if temperature
|
|
29
|
+
# and pressure are both not specified, and there are no constraints for them either, it decides that
|
|
30
|
+
# they are not part of the solution, and says there's 0 degrees of freedom.
|
|
31
|
+
# so instead, we actually check there are unfixed variables that are not in activated equalities.
|
|
32
|
+
# TODO: Check why adding and degrees_of_freedom(sb) == 0 makes pr fail (python manage.py test core.auxiliary.tests.test_Compounds)
|
|
33
|
+
if number_unfixed_variables(sb) - number_unfixed_variables_in_activated_equalities(sb) != 0:
|
|
34
|
+
return [] # This means no properties are returned, so the backend won't update anything.
|
|
35
|
+
block_ctx.apply_elimination()
|
|
36
|
+
|
|
37
|
+
# initialise the state block, which will perform a solve
|
|
38
|
+
sb.initialize(outlvl=1)
|
|
39
|
+
deactivate_fixed_guesses(m.fs.guess_vars)
|
|
40
|
+
|
|
41
|
+
return serialize_properties_map(m.fs)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_state(schema: PropertyPackageType) -> Any: # PropertyPackageSchema
|
|
45
|
+
m = build_flowsheet(dynamic=False)
|
|
46
|
+
# create the property package and state block
|
|
47
|
+
property_package = create_property_package(schema, m)
|
|
48
|
+
state_block = property_package.build_state_block(m.fs.time, defined_state=True)
|
|
49
|
+
m.fs.add_component(f"PP_{schema.id}_state", state_block)
|
|
50
|
+
|
|
51
|
+
return m, state_block
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_state_vars(schema: PropertyPackageType) -> Any:
|
|
55
|
+
_, state_block = build_state(schema)
|
|
56
|
+
|
|
57
|
+
return state_block[0].define_state_vars()
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
from idaes.models.control.controller import PIDControllerData, ControllerType, ControllerMVBoundType, ControllerAntiwindupType, smooth_bound, smooth_heaviside
|
|
2
|
+
from pyomo.environ import Var
|
|
3
|
+
from idaes.core import UnitModelBlockData, declare_process_block_class
|
|
4
|
+
from pyomo.common.config import ConfigValue, In, Bool
|
|
5
|
+
import pyomo.environ as pyo
|
|
6
|
+
from idaes.core.util.exceptions import ConfigurationError
|
|
7
|
+
import pyomo.dae as pyodae
|
|
8
|
+
from idaes.core.util import scaling as iscale
|
|
9
|
+
import functools
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@declare_process_block_class(
|
|
13
|
+
"PIDController2",
|
|
14
|
+
doc="PID controller model block. To use this the model must be dynamic.",
|
|
15
|
+
)
|
|
16
|
+
class PIDController2Data(UnitModelBlockData):
|
|
17
|
+
"""
|
|
18
|
+
PID controller class.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
CONFIG = UnitModelBlockData.CONFIG()
|
|
22
|
+
CONFIG.declare(
|
|
23
|
+
"mv_bound_type",
|
|
24
|
+
ConfigValue(
|
|
25
|
+
default=ControllerMVBoundType.NONE,
|
|
26
|
+
domain=In(
|
|
27
|
+
[
|
|
28
|
+
ControllerMVBoundType.NONE,
|
|
29
|
+
ControllerMVBoundType.SMOOTH_BOUND,
|
|
30
|
+
ControllerMVBoundType.LOGISTIC,
|
|
31
|
+
]
|
|
32
|
+
),
|
|
33
|
+
description="Type of bounds to apply to the manipulated variable (mv)).",
|
|
34
|
+
doc=(
|
|
35
|
+
"""Type of bounds to apply to the manipulated variable output. If,
|
|
36
|
+
bounds are applied, the model parameters **mv_lb** and **mv_ub** set the bounds.
|
|
37
|
+
The **default** is ControllerMVBoundType.NONE. See the controller documentation
|
|
38
|
+
for details on the mathematical formulation. The options are:
|
|
39
|
+
**ControllerMVBoundType.NONE** no bounds, **ControllerMVBoundType.SMOOTH_BOUND**
|
|
40
|
+
smoothed mv = min(max(mv_unbound, ub), lb), and **ControllerMVBoundType.LOGISTIC**
|
|
41
|
+
logistic function to enforce bounds.
|
|
42
|
+
"""
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
CONFIG.declare(
|
|
47
|
+
"calculate_initial_integral",
|
|
48
|
+
ConfigValue(
|
|
49
|
+
default=True,
|
|
50
|
+
domain=Bool,
|
|
51
|
+
description="Calculate the initial integral term value if True",
|
|
52
|
+
doc="Calculate the initial integral term value if True",
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
CONFIG.declare(
|
|
56
|
+
"controller_type",
|
|
57
|
+
ConfigValue(
|
|
58
|
+
default=ControllerType.PI,
|
|
59
|
+
domain=In(
|
|
60
|
+
[
|
|
61
|
+
ControllerType.P,
|
|
62
|
+
ControllerType.PI,
|
|
63
|
+
ControllerType.PD,
|
|
64
|
+
ControllerType.PID,
|
|
65
|
+
]
|
|
66
|
+
),
|
|
67
|
+
description="Control type",
|
|
68
|
+
doc="""Controller type. The **default** = ControllerType.PI and the
|
|
69
|
+
options are: **ControllerType.P** Proportional, **ControllerType.PI**
|
|
70
|
+
proportional and integral, **ControllerType.PD** proportional and derivative, and
|
|
71
|
+
**ControllerType.PID** proportional, integral, and derivative
|
|
72
|
+
""",
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
CONFIG.declare(
|
|
76
|
+
"antiwindup_type",
|
|
77
|
+
ConfigValue(
|
|
78
|
+
default=ControllerAntiwindupType.NONE,
|
|
79
|
+
domain=In(
|
|
80
|
+
[
|
|
81
|
+
ControllerAntiwindupType.NONE,
|
|
82
|
+
ControllerAntiwindupType.CONDITIONAL_INTEGRATION,
|
|
83
|
+
ControllerAntiwindupType.BACK_CALCULATION,
|
|
84
|
+
]
|
|
85
|
+
),
|
|
86
|
+
description="Type of antiwindup technique to use.",
|
|
87
|
+
doc=(
|
|
88
|
+
"""Type of antiwindup technique to use. Options are **ControllerAntiwindupType.NONE**,
|
|
89
|
+
**ControllerAntiwindupType.CONDITIONAL_INTEGRATION**, and **ControllerAntiwindupType.BACK_CALCULATION**.
|
|
90
|
+
See the controller documentation for details on the mathematical formulation.
|
|
91
|
+
"""
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
CONFIG.declare(
|
|
96
|
+
"derivative_on_error",
|
|
97
|
+
ConfigValue(
|
|
98
|
+
default=False,
|
|
99
|
+
domain=Bool,
|
|
100
|
+
description="Whether basing derivative action on process var or error",
|
|
101
|
+
doc="""Naive implementations of derivative action can cause large spikes in
|
|
102
|
+
control when the setpoint is changed. One solution is to use the (negative)
|
|
103
|
+
derivative of the process variable to calculate derivative action instead
|
|
104
|
+
of using the derivative of setpoint error. If **True**, use the derivative of
|
|
105
|
+
setpoint error to calculate derivative action. If **False** (default), use the
|
|
106
|
+
(negative) derivative of the process variable instead.
|
|
107
|
+
""",
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def build(self):
|
|
112
|
+
"""
|
|
113
|
+
Build the PID block
|
|
114
|
+
"""
|
|
115
|
+
super().build()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if self.config.dynamic is False:
|
|
119
|
+
raise ConfigurationError(
|
|
120
|
+
"PIDControllers work only with dynamic flowsheets."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
We want to create the controlled and manipulated variables internally, and then the user can use constraints to set them later on.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
self.process_var = Var(
|
|
128
|
+
self.flowsheet().time,
|
|
129
|
+
initialize=0.0,
|
|
130
|
+
doc="Setpoint for the PID controller",
|
|
131
|
+
)
|
|
132
|
+
self.manipulated_var = Var(
|
|
133
|
+
self.flowsheet().time,
|
|
134
|
+
initialize=0.0,
|
|
135
|
+
doc="Manipulated variable for the PID controller",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Shorter pointers to time set information
|
|
139
|
+
time_set = self.flowsheet().time
|
|
140
|
+
time_units = self.flowsheet().time_units
|
|
141
|
+
if time_units is None:
|
|
142
|
+
time_units = pyo.units.dimensionless
|
|
143
|
+
t0 = time_set.first()
|
|
144
|
+
|
|
145
|
+
# Type Check
|
|
146
|
+
if not issubclass(self.process_var[t0].ctype, (pyo.Var, pyo.Expression)):
|
|
147
|
+
raise TypeError(
|
|
148
|
+
f"process_var must reference a Var or Expression not {self.process_var[t0].ctype}"
|
|
149
|
+
)
|
|
150
|
+
if not issubclass(self.manipulated_var[t0].ctype, pyo.Var):
|
|
151
|
+
raise TypeError(
|
|
152
|
+
f"manipulated_var must reference a Var not {self.manipulated_var[t0].ctype}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if not self.config.antiwindup_type == ControllerAntiwindupType.NONE:
|
|
156
|
+
if not self.config.controller_type in [
|
|
157
|
+
ControllerType.PI,
|
|
158
|
+
ControllerType.PID,
|
|
159
|
+
]:
|
|
160
|
+
raise ConfigurationError(
|
|
161
|
+
"User specified antiwindup method for controller without integral action."
|
|
162
|
+
)
|
|
163
|
+
if self.config.mv_bound_type == ControllerMVBoundType.NONE:
|
|
164
|
+
raise ConfigurationError(
|
|
165
|
+
"User specified antiwindup method for unbounded MV."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Get the appropriate units for various controller variables
|
|
169
|
+
mv_units = pyo.units.get_units(self.manipulated_var[t0])
|
|
170
|
+
pv_units = pyo.units.get_units(self.process_var[t0])
|
|
171
|
+
if mv_units is None:
|
|
172
|
+
mv_units = pyo.units.dimensionless
|
|
173
|
+
if pv_units is None:
|
|
174
|
+
pv_units = pyo.units.dimensionless
|
|
175
|
+
gain_p_units = mv_units / pv_units
|
|
176
|
+
|
|
177
|
+
if not self.config.mv_bound_type == ControllerMVBoundType.NONE:
|
|
178
|
+
# Parameters
|
|
179
|
+
self.mv_lb = pyo.Param(
|
|
180
|
+
mutable=True,
|
|
181
|
+
initialize=0.05,
|
|
182
|
+
doc="Controller output lower bound",
|
|
183
|
+
units=mv_units,
|
|
184
|
+
)
|
|
185
|
+
self.mv_ub = pyo.Param(
|
|
186
|
+
mutable=True,
|
|
187
|
+
initialize=1,
|
|
188
|
+
doc="Controller output upper bound",
|
|
189
|
+
units=mv_units,
|
|
190
|
+
)
|
|
191
|
+
if self.config.mv_bound_type == ControllerMVBoundType.SMOOTH_BOUND:
|
|
192
|
+
self.smooth_eps = pyo.Param(
|
|
193
|
+
mutable=True,
|
|
194
|
+
initialize=1e-4,
|
|
195
|
+
doc="Smoothing parameter for controller output limits when the bound"
|
|
196
|
+
" type is SMOOTH_BOUND",
|
|
197
|
+
units=mv_units,
|
|
198
|
+
)
|
|
199
|
+
elif self.config.mv_bound_type == ControllerMVBoundType.LOGISTIC:
|
|
200
|
+
self.logistic_bound_k = pyo.Param(
|
|
201
|
+
mutable=True,
|
|
202
|
+
initialize=4,
|
|
203
|
+
doc="Smoothing parameter for controller output limits when the bound"
|
|
204
|
+
" type is LOGISTIC",
|
|
205
|
+
units=pyo.units.dimensionless,
|
|
206
|
+
)
|
|
207
|
+
if (
|
|
208
|
+
self.config.antiwindup_type
|
|
209
|
+
== ControllerAntiwindupType.CONDITIONAL_INTEGRATION
|
|
210
|
+
):
|
|
211
|
+
self.conditional_integration_k = pyo.Param(
|
|
212
|
+
mutable=True,
|
|
213
|
+
initialize=200,
|
|
214
|
+
doc="Parameter governing steepness of transition between integrating and not integrating."
|
|
215
|
+
"A larger value means a steeper transition.",
|
|
216
|
+
units=pyo.units.dimensionless,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Variable for basic controller settings may change with time.
|
|
220
|
+
self.setpoint = pyo.Var(
|
|
221
|
+
time_set, initialize=0.5, doc="Setpoint", units=pv_units
|
|
222
|
+
)
|
|
223
|
+
self.gain_p = pyo.Var(
|
|
224
|
+
time_set,
|
|
225
|
+
initialize=0.1,
|
|
226
|
+
doc="Gain for proportional part",
|
|
227
|
+
units=gain_p_units,
|
|
228
|
+
)
|
|
229
|
+
if self.config.controller_type in [ControllerType.PI, ControllerType.PID]:
|
|
230
|
+
self.gain_i = pyo.Var(
|
|
231
|
+
time_set,
|
|
232
|
+
initialize=0.1,
|
|
233
|
+
doc="Gain for integral part",
|
|
234
|
+
units=gain_p_units / time_units,
|
|
235
|
+
)
|
|
236
|
+
if self.config.controller_type in [ControllerType.PD, ControllerType.PID]:
|
|
237
|
+
self.gain_d = pyo.Var(
|
|
238
|
+
time_set,
|
|
239
|
+
initialize=0.01,
|
|
240
|
+
doc="Gain for derivative part",
|
|
241
|
+
units=gain_p_units * time_units,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if self.config.antiwindup_type == ControllerAntiwindupType.BACK_CALCULATION:
|
|
245
|
+
self.gain_b = pyo.Var(
|
|
246
|
+
time_set,
|
|
247
|
+
initialize=0.1,
|
|
248
|
+
doc="Gain for back calculation antiwindup",
|
|
249
|
+
units=1 / time_units,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
self.mv_ref = pyo.Var(
|
|
253
|
+
time_set,
|
|
254
|
+
initialize=0.5,
|
|
255
|
+
doc="Controller bias",
|
|
256
|
+
units=mv_units,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Error expression or variable (variable required for derivative term)
|
|
260
|
+
if (
|
|
261
|
+
self.config.controller_type in [ControllerType.PD, ControllerType.PID]
|
|
262
|
+
and self.config.derivative_on_error
|
|
263
|
+
):
|
|
264
|
+
self.error = pyo.Var(
|
|
265
|
+
time_set, initialize=0, doc="Error variable", units=pv_units
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
@self.Constraint(time_set, doc="Error constraint")
|
|
269
|
+
def error_eqn(b, t):
|
|
270
|
+
return b.error[t] == b.setpoint[t] - b.process_var[t]
|
|
271
|
+
|
|
272
|
+
self.derivative_term = pyodae.DerivativeVar(
|
|
273
|
+
self.error,
|
|
274
|
+
wrt=self.flowsheet().time,
|
|
275
|
+
initialize=0,
|
|
276
|
+
units=pv_units / time_units,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
else:
|
|
280
|
+
|
|
281
|
+
@self.Expression(time_set, doc="Error expression")
|
|
282
|
+
def error(b, t):
|
|
283
|
+
return b.setpoint[t] - b.process_var[t]
|
|
284
|
+
|
|
285
|
+
if (
|
|
286
|
+
self.config.controller_type in [ControllerType.PD, ControllerType.PID]
|
|
287
|
+
and not self.config.derivative_on_error
|
|
288
|
+
):
|
|
289
|
+
# Need to create a Var because process_var might be an Expression
|
|
290
|
+
self.negative_pv = pyo.Var(
|
|
291
|
+
time_set,
|
|
292
|
+
initialize=0,
|
|
293
|
+
doc="Negative of process variable",
|
|
294
|
+
units=pv_units,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@self.Constraint(time_set, doc="Negative process variable equation")
|
|
298
|
+
def negative_pv_eqn(b, t):
|
|
299
|
+
return b.negative_pv[t] == -b.process_var[t]
|
|
300
|
+
|
|
301
|
+
self.derivative_term = pyodae.DerivativeVar(
|
|
302
|
+
self.negative_pv,
|
|
303
|
+
wrt=self.flowsheet().time,
|
|
304
|
+
initialize=0,
|
|
305
|
+
units=pv_units / time_units,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# integral term written de_i(t)/dt = e(t)
|
|
309
|
+
if self.config.controller_type in [ControllerType.PI, ControllerType.PID]:
|
|
310
|
+
self.mv_integral_component = pyo.Var(
|
|
311
|
+
time_set,
|
|
312
|
+
initialize=0,
|
|
313
|
+
doc="Integral contribution to control action",
|
|
314
|
+
units=mv_units,
|
|
315
|
+
)
|
|
316
|
+
self.mv_integral_component_dot = pyodae.DerivativeVar(
|
|
317
|
+
self.mv_integral_component,
|
|
318
|
+
wrt=time_set,
|
|
319
|
+
initialize=0,
|
|
320
|
+
units=mv_units / time_units,
|
|
321
|
+
doc="Rate of change of integral contribution to control action",
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if self.config.calculate_initial_integral:
|
|
325
|
+
|
|
326
|
+
@self.Constraint(doc="Calculate initial e_i based on output")
|
|
327
|
+
def initial_integral_error_eqn(b):
|
|
328
|
+
if self.config.controller_type == ControllerType.PI:
|
|
329
|
+
return b.mv_integral_component[t0] == (
|
|
330
|
+
b.manipulated_var[t0]
|
|
331
|
+
- b.mv_ref[t0]
|
|
332
|
+
- b.gain_p[t0] * b.error[t0]
|
|
333
|
+
)
|
|
334
|
+
return b.mv_integral_component[t0] == (
|
|
335
|
+
b.manipulated_var[t0]
|
|
336
|
+
- b.mv_ref[t0]
|
|
337
|
+
- b.gain_p[t0] * b.error[t0]
|
|
338
|
+
- b.gain_d[t0] * b.derivative_term[t0]
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
@self.Expression(time_set, doc="Unbounded output for manipulated variable")
|
|
342
|
+
def mv_unbounded(b, t):
|
|
343
|
+
if self.config.controller_type == ControllerType.PID:
|
|
344
|
+
return (
|
|
345
|
+
b.mv_ref[t]
|
|
346
|
+
+ b.gain_p[t] * b.error[t]
|
|
347
|
+
+ b.mv_integral_component[t]
|
|
348
|
+
+ b.gain_d[t] * b.derivative_term[t]
|
|
349
|
+
)
|
|
350
|
+
elif self.config.controller_type == ControllerType.PI:
|
|
351
|
+
return (
|
|
352
|
+
b.mv_ref[t] + b.gain_p[t] * b.error[t] + b.mv_integral_component[t]
|
|
353
|
+
)
|
|
354
|
+
elif self.config.controller_type == ControllerType.PD:
|
|
355
|
+
return (
|
|
356
|
+
b.mv_ref[t]
|
|
357
|
+
+ b.gain_p[t] * b.error[t]
|
|
358
|
+
+ b.gain_d[t] * b.derivative_term[t]
|
|
359
|
+
)
|
|
360
|
+
elif self.config.controller_type == ControllerType.P:
|
|
361
|
+
return b.mv_ref[t] + b.gain_p[t] * b.error[t]
|
|
362
|
+
else:
|
|
363
|
+
raise ConfigurationError(
|
|
364
|
+
f"{self.config.controller_type} is not a valid PID controller type"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
@self.Constraint(time_set, doc="Bounded output of manipulated variable")
|
|
368
|
+
def mv_eqn(b, t):
|
|
369
|
+
if self.config.mv_bound_type == ControllerMVBoundType.SMOOTH_BOUND:
|
|
370
|
+
return b.manipulated_var[t] == smooth_bound(
|
|
371
|
+
b.mv_unbounded[t], lb=b.mv_lb, ub=b.mv_ub, eps=b.smooth_eps
|
|
372
|
+
)
|
|
373
|
+
elif self.config.mv_bound_type == ControllerMVBoundType.LOGISTIC:
|
|
374
|
+
return (
|
|
375
|
+
(b.manipulated_var[t] - b.mv_lb)
|
|
376
|
+
* (
|
|
377
|
+
1
|
|
378
|
+
+ pyo.exp(
|
|
379
|
+
-b.logistic_bound_k
|
|
380
|
+
* (b.mv_unbounded[t] - (b.mv_lb + b.mv_ub) / 2)
|
|
381
|
+
/ (b.mv_ub - b.mv_lb)
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
) == b.mv_ub - b.mv_lb
|
|
385
|
+
return b.manipulated_var[t] == b.mv_unbounded[t]
|
|
386
|
+
|
|
387
|
+
# deactivate the time 0 mv_eqn instead of skip, should be fine since
|
|
388
|
+
# first time step always exists.
|
|
389
|
+
if self.config.calculate_initial_integral:
|
|
390
|
+
self.mv_eqn[t0].deactivate()
|
|
391
|
+
|
|
392
|
+
if self.config.controller_type in [ControllerType.PI, ControllerType.PID]:
|
|
393
|
+
|
|
394
|
+
@self.Constraint(time_set, doc="de_i(t)/dt = e(t)")
|
|
395
|
+
def mv_integration_eqn(b, t):
|
|
396
|
+
if (
|
|
397
|
+
self.config.antiwindup_type
|
|
398
|
+
== ControllerAntiwindupType.CONDITIONAL_INTEGRATION
|
|
399
|
+
):
|
|
400
|
+
# This expression is not sensitive to whether the "right" or "wrong" bound is active for a given
|
|
401
|
+
# expression of error.
|
|
402
|
+
return b.mv_integral_component_dot[t] == b.gain_i[t] * b.error[
|
|
403
|
+
t
|
|
404
|
+
] * (
|
|
405
|
+
smooth_heaviside(
|
|
406
|
+
(b.mv_unbounded[t] - b.mv_lb) / (b.mv_ub - b.mv_lb),
|
|
407
|
+
b.conditional_integration_k,
|
|
408
|
+
)
|
|
409
|
+
# 1
|
|
410
|
+
- smooth_heaviside(
|
|
411
|
+
(b.mv_unbounded[t] - b.mv_ub) / (b.mv_ub - b.mv_lb),
|
|
412
|
+
b.conditional_integration_k,
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
elif (
|
|
416
|
+
self.config.antiwindup_type
|
|
417
|
+
== ControllerAntiwindupType.BACK_CALCULATION
|
|
418
|
+
):
|
|
419
|
+
return b.mv_integral_component_dot[t] == b.gain_i[t] * b.error[
|
|
420
|
+
t
|
|
421
|
+
] + b.gain_b[t] * (b.manipulated_var[t] - b.mv_unbounded[t])
|
|
422
|
+
else:
|
|
423
|
+
return b.mv_integral_component_dot[t] == b.gain_i[t] * b.error[t]
|
|
424
|
+
|
|
425
|
+
self.mv_integration_eqn[t0].deactivate()
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# TODO: Initial values.
|
|
429
|
+
# For now, mv_integral_component is 0
|
|
430
|
+
self.mv_integral_component[t0].fix(0)
|
|
431
|
+
|
|
432
|
+
def calculate_scaling_factors(self):
|
|
433
|
+
super().calculate_scaling_factors()
|
|
434
|
+
gsf = iscale.get_scaling_factor
|
|
435
|
+
ssf = functools.partial(iscale.set_scaling_factor, overwrite=False)
|
|
436
|
+
cst = functools.partial(iscale.constraint_scaling_transform, overwrite=False)
|
|
437
|
+
|
|
438
|
+
# orig_pv = self.config.process_var
|
|
439
|
+
# orig_mv = self.config.manipulated_var
|
|
440
|
+
time_set = self.flowsheet().time
|
|
441
|
+
t0 = time_set.first()
|
|
442
|
+
|
|
443
|
+
sf_pv = iscale.get_scaling_factor(self.config.process_var[t0])
|
|
444
|
+
if sf_pv is None:
|
|
445
|
+
sf_pv = iscale.get_scaling_factor(self.process_var[t0], default=1)
|
|
446
|
+
sf_mv = iscale.get_scaling_factor(self.config.manipulated_var[t0])
|
|
447
|
+
if sf_mv is None:
|
|
448
|
+
sf_mv = iscale.get_scaling_factor(self.manipulated_var[t0], default=1)
|
|
449
|
+
|
|
450
|
+
# Don't like calling scaling laterally like this, but we need scaling factors for the pv and mv
|
|
451
|
+
# Except this causes a StackOverflow with flowsheet-level PVs or MVs---put this on ice for now
|
|
452
|
+
# if sf_pv is None:
|
|
453
|
+
# try:
|
|
454
|
+
# iscale.calculate_scaling_factors(self.config.process_var.parent_block())
|
|
455
|
+
# except RecursionError:
|
|
456
|
+
# raise ConfigurationError(
|
|
457
|
+
# f"Circular scaling dependency detected in Controller {self.name}. The only way this should be "
|
|
458
|
+
# "able to happen is if a loop of controllers exists manipulating each others setpoints without "
|
|
459
|
+
# "terminating in an actual process variable."
|
|
460
|
+
# )
|
|
461
|
+
# if sf_mv is None:
|
|
462
|
+
# try:
|
|
463
|
+
# iscale.calculate_scaling_factors(self.config.manipulated_var.parent_block())
|
|
464
|
+
# except RecursionError:
|
|
465
|
+
# raise ConfigurationError(
|
|
466
|
+
# f"Circular scaling dependency detected in Controller {self.name}. The only way this should be "
|
|
467
|
+
# "able to happen is if a loop of controllers exists manipulating each others setpoints without "
|
|
468
|
+
# "terminating in an actual process variable."
|
|
469
|
+
# )
|
|
470
|
+
|
|
471
|
+
if self.config.calculate_initial_integral:
|
|
472
|
+
sf_mv = gsf(self.manipulated_var[t0], default=1, warning=True)
|
|
473
|
+
cst(self.initial_integral_error_eqn, sf_mv)
|
|
474
|
+
|
|
475
|
+
for t in time_set:
|
|
476
|
+
sf_pv = gsf(self.process_var[t], default=1, warning=True)
|
|
477
|
+
sf_mv = gsf(self.manipulated_var[t], default=1, warning=True)
|
|
478
|
+
|
|
479
|
+
ssf(self.setpoint[t], sf_pv)
|
|
480
|
+
ssf(self.mv_ref[t], sf_mv)
|
|
481
|
+
cst(self.mv_eqn[t], sf_mv)
|
|
482
|
+
|
|
483
|
+
if self.config.controller_type in [ControllerType.PD, ControllerType.PID]:
|
|
484
|
+
if self.config.derivative_on_error:
|
|
485
|
+
ssf(self.error[t], sf_pv)
|
|
486
|
+
cst(self.error_eqn[t], sf_pv)
|
|
487
|
+
else:
|
|
488
|
+
ssf(self.negative_pv[t], sf_pv)
|
|
489
|
+
cst(self.negative_pv_eqn[t], sf_pv)
|
|
490
|
+
|
|
491
|
+
if self.config.controller_type in [ControllerType.PI, ControllerType.PID]:
|
|
492
|
+
ssf(self.mv_integral_component[t], sf_mv)
|
|
493
|
+
|
|
494
|
+
cst(self.mv_integration_eqn[t], sf_pv)
|