Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

41 store simulation data #211

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
12 changes: 12 additions & 0 deletions src/omotes_simulator_core/entities/assets/asset_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@

from pandas import DataFrame, concat

from omotes_simulator_core.solver.utils.fluid_properties import fluid_props
from omotes_simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
from omotes_simulator_core.solver.network.assets.base_asset import BaseAsset

from omotes_simulator_core.entities.assets.asset_defaults import (
PROPERTY_MASSFLOW,
PROPERTY_PRESSURE,
PROPERTY_TEMPERATURE,
PROPERTY_VOLUMEFLOW,
)


Expand Down Expand Up @@ -103,9 +105,19 @@ def write_standard_output(self) -> None:
PROPERTY_MASSFLOW: self.solver_asset.get_mass_flow_rate(i),
PROPERTY_PRESSURE: self.solver_asset.get_pressure(i),
PROPERTY_TEMPERATURE: self.solver_asset.get_temperature(i),
PROPERTY_VOLUMEFLOW: self.get_volume_flow_rate(i),
}
self.outputs[i].append(output_dict_temp)

def get_volume_flow_rate(self, i: int) -> float:
"""Calculates and returns the volume flow rate for the given port.

:param int i: The index of the port.
:return float: The volume flow rate.
"""
rho = fluid_props.get_density(self.solver_asset.get_temperature(i))
return self.solver_asset.get_mass_flow_rate(i) / rho

@abstractmethod
def write_to_output(self) -> None:
"""Placeholder to get data and store it in the asset."""
Expand Down
7 changes: 7 additions & 0 deletions src/omotes_simulator_core/entities/assets/asset_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class AtesDefaults:

# Default names
PROPERTY_HEAT_DEMAND = "heat_demand"
PROPERTY_HEAT_DEMAND_SET_POINT = "heat_demand_set_point"
PROPERTY_TEMPERATURE_SUPPLY = "temperature_supply"
PROPERTY_TEMPERATURE_RETURN = "temperature_return"
PROPERTY_TEMPERATURE = "temperature"
Expand All @@ -78,6 +79,7 @@ class AtesDefaults:
PROPERTY_PRESSURE = "pressure"
PROPERTY_MASSFLOW = "mass_flow"
PROPERTY_VOLUMEFLOW = "volume_flow"
PROPERTY_VELOCITY = "velocity"
PROPERTY_THERMAL_POWER = "thermal_power"
PROPERTY_VELOCITY_SUPPLY = "velocity_supply"
PROPERTY_VELOCITY_RETURN = "velocity_return"
Expand All @@ -86,6 +88,11 @@ class AtesDefaults:
PROPERTY_DIAMETER = "diameter"
PROPERTY_ROUGHNESS = "roughness"
PROPERTY_ALPHA_VALUE = "alpha_value"
PROPERTY_PRESSURE_LOSS = "pressure_loss"
PROPERTY_PRESSURE_LOSS_PER_LENGTH = "pressure_loss_per_length"
PROPERTY_HEAT_LOSS = "heat_loss"
PROPERTY_HEAT_SUPPLIED = "heat_supplied"
PROPERTY_HEAT_SUPPLY_SET_POINT = "heat_supply_set_point"

# Static members
PIPE_DEFAULTS = PipeDefaults()
Expand Down
20 changes: 17 additions & 3 deletions src/omotes_simulator_core/entities/assets/demand_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
DEFAULT_PRESSURE,
DEFAULT_TEMPERATURE,
DEFAULT_TEMPERATURE_DIFFERENCE,
PROPERTY_HEAT_DEMAND,
PROPERTY_TEMPERATURE_RETURN,
PROPERTY_TEMPERATURE_SUPPLY,
PROPERTY_HEAT_DEMAND,
PROPERTY_HEAT_DEMAND_SET_POINT,
)
from omotes_simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
from omotes_simulator_core.entities.assets.utils import (
Expand All @@ -52,7 +53,7 @@ def __init__(self, asset_name: str, asset_id: str, port_ids: list[str]):
self.temperature_return_target = self.temperature_return
self.pressure_input = DEFAULT_PRESSURE
self.thermal_power_allocation = DEFAULT_POWER
self.mass_flowrate = 0
self.mass_flowrate = 0.0
self.solver_asset = ProductionAsset(name=self.name, _id=self.asset_id)
# Output list
self.output: list = []
Expand Down Expand Up @@ -99,4 +100,17 @@ def write_to_output(self) -> None:
The output list is a list of dictionaries, where each dictionary
represents the output of its asset for a specific timestep.
"""
pass
output_dict_temp = {
PROPERTY_HEAT_DEMAND_SET_POINT: -self.thermal_power_allocation,
PROPERTY_HEAT_DEMAND: self.get_actual_heat_supplied(),
}
self.outputs[1][-1].update(output_dict_temp)

def get_actual_heat_supplied(self) -> float:
"""Get the actual heat supplied by the asset.

:return float: The actual heat supplied by the asset [W].
"""
return (
self.solver_asset.get_internal_energy(1) - self.solver_asset.get_internal_energy(0)
) * self.solver_asset.get_mass_flow_rate(0)
44 changes: 41 additions & 3 deletions src/omotes_simulator_core/entities/assets/pipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
from omotes_simulator_core.entities.assets.asset_abstract import AssetAbstract
from omotes_simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
from omotes_simulator_core.solver.network.assets.solver_pipe import SolverPipe
from omotes_simulator_core.entities.assets.asset_defaults import (
PROPERTY_VELOCITY,
PROPERTY_PRESSURE_LOSS,
PROPERTY_PRESSURE_LOSS_PER_LENGTH,
PROPERTY_HEAT_LOSS,
)


class Pipe(AssetAbstract):
Expand Down Expand Up @@ -86,14 +92,13 @@ def __init__(
self.inner_diameter = inner_diameter
self.roughness = roughness
self.alpha_value = alpha_value
self.solver_asset = SolverPipe(
self.solver_asset: SolverPipe = SolverPipe(
name=self.name,
_id=self.asset_id,
length=self.length,
diameter=self.inner_diameter,
roughness=self.roughness,
)
self.output = []

def add_physical_data(self, esdl_asset: EsdlAssetObject) -> None:
"""Method to add physical data to the asset."""
Expand All @@ -111,4 +116,37 @@ def write_to_output(self) -> None:
The output list is a list of dictionaries, where each dictionary
represents the output of its asset for a specific timestep.
"""
pass
for i in range(len(self.connected_ports)):
output_dict_temp = {PROPERTY_VELOCITY: self.get_velocity(i)}
if i == 1: # only for the second connection point these properties are added
output_dict_temp.update(
{
PROPERTY_PRESSURE_LOSS: self.get_pressure_loss(),
PROPERTY_PRESSURE_LOSS_PER_LENGTH: self.get_pressure_loss_per_length(),
PROPERTY_HEAT_LOSS: self.get_heat_loss(),
}
)
self.outputs[i][-1].update(output_dict_temp)

def get_velocity(self, port: int) -> float:
"""Get the velocity of the fluid in the pipe at the given connection point.

:param int port: The port of the pipe for which to get the velocity.
:return: The velocity of the fluid in the pipe [m/s].
"""
return float(self.get_volume_flow_rate(port) / self.solver_asset.area)

def get_pressure_loss(self) -> float:
"""Get the pressure loss of the pipe."""
return self.solver_asset.get_pressure(1) - self.solver_asset.get_pressure(0)

def get_pressure_loss_per_length(self) -> float:
"""Get the pressure loss of the pipe per length."""
return self.get_pressure_loss() / self.length

def get_heat_loss(self) -> float:
"""Get the heat loss of the pipe.

The minus sign is added to make it a loss instead of supply.
"""
return -self.solver_asset.heat_supplied
24 changes: 22 additions & 2 deletions src/omotes_simulator_core/entities/assets/production_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
PROPERTY_SET_PRESSURE,
PROPERTY_TEMPERATURE_RETURN,
PROPERTY_TEMPERATURE_SUPPLY,
PROPERTY_HEAT_SUPPLIED,
PROPERTY_HEAT_SUPPLY_SET_POINT,
)
from omotes_simulator_core.entities.assets.esdl_asset_object import EsdlAssetObject
from omotes_simulator_core.entities.assets.utils import (
Expand Down Expand Up @@ -58,6 +60,9 @@ class ProductionCluster(AssetAbstract):
controlled_mass_flow: float | None
"""The controlled mass flow of the asset [kg/s]."""

heat_demand_set_point: float
"""The heat demand set point of the asset [W]."""

def __init__(self, asset_name: str, asset_id: str, port_ids: list[str]):
"""Initialize a ProductionCluster object.

Expand All @@ -73,6 +78,7 @@ def __init__(self, asset_name: str, asset_id: str, port_ids: list[str]):
self.temperature_return = DEFAULT_TEMPERATURE - DEFAULT_TEMPERATURE_DIFFERENCE
# DemandCluster pressure specifications
self.pressure_supply = DEFAULT_PRESSURE
self.heat_demand_set_point = 0.0
self.control_mass_flow = False
# Controlled mass flow
self.controlled_mass_flow = None
Expand Down Expand Up @@ -116,6 +122,7 @@ def _set_heat_demand(self, heat_demand: float) -> None:
The heat demand should be supplied in Watts.
"""
# Calculate the mass flow rate
self.heat_demand_set_point = heat_demand
self.controlled_mass_flow = heat_demand_and_temperature_to_mass_flow(
thermal_demand=heat_demand,
temperature_supply=self.temperature_supply,
Expand Down Expand Up @@ -195,10 +202,23 @@ def update(self) -> None:
to the value of the previous simulation.
"""

def get_actual_heat_supplied(self) -> float:
"""Get the actual heat supplied by the asset.

:return float: The actual heat supplied by the asset [W].
"""
return (
self.solver_asset.get_internal_energy(1) - self.solver_asset.get_internal_energy(0)
) * self.solver_asset.get_mass_flow_rate(0)

def write_to_output(self) -> None:
"""Placeholder to write the asset to the output.
"""Method to write the asset to the output.

The output list is a list of dictionaries, where each dictionary
represents the output of its asset for a specific timestep.
"""
pass
output_dict_temp = {
PROPERTY_HEAT_SUPPLY_SET_POINT: self.heat_demand_set_point,
PROPERTY_HEAT_SUPPLIED: self.get_actual_heat_supplied(),
}
self.outputs[1][-1].update(output_dict_temp)
16 changes: 16 additions & 0 deletions src/omotes_simulator_core/solver/network/assets/base_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,19 @@ def get_temperature(self, connection_point: int) -> float:
)
]
)

def get_internal_energy(self, connection_point: int) -> float:
"""Method to get the internal energy of a connection point.

:param int connection_point: The connection point for which to get the internal energy.
:return: The internal energy of the connection point.
"""
return float(
self.prev_sol[
self.get_index_matrix(
property_name="internal_energy",
connection_point=connection_point,
use_relative_indexing=True,
)
]
)
83 changes: 83 additions & 0 deletions unit_test/entities/test_demand_cluster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Copyright (c) 2024. Deltares & TNO
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""Test demand cluster."""

import unittest
from unittest.mock import patch
from omotes_simulator_core.entities.assets.demand_cluster import DemandCluster


class TestDemandCluster(unittest.TestCase):
"""Test demand cluster."""

def setUp(self) -> None:
"""Set up test case."""
# Create a production cluster object
self.heat_demand = DemandCluster(
asset_name="production_cluster",
asset_id="production_cluster_id",
port_ids=["test1", "test2"],
)

def test_demand_cluster_create(self):
"""Evaluate the creation of a demand cluster object."""
# Arrange

# Act
demand_cluster = DemandCluster(
asset_name="demand_cluster",
asset_id="demand_cluster_id",
port_ids=["test1", "test2"],
)

# Assert
self.assertEqual(demand_cluster.name, "demand_cluster")
self.assertEqual(demand_cluster.asset_id, "demand_cluster_id")
self.assertEqual(demand_cluster.connected_ports, ["test1", "test2"])
self.assertEqual(demand_cluster._internal_diameter, 1.2)
self.assertEqual(demand_cluster.temperature_supply, 300)
self.assertEqual(demand_cluster.temperature_return, 300 - 30)
self.assertEqual(demand_cluster.temperature_return_target, 270)
self.assertEqual(demand_cluster.pressure_input, 1e6)
self.assertEqual(demand_cluster.thermal_power_allocation, 500000.0)
self.assertEqual(demand_cluster.mass_flowrate, 0.0)
self.assertEqual(demand_cluster.solver_asset.name, "demand_cluster")
self.assertEqual(demand_cluster.output, [])

def test_get_actual_heat_supplied(self):
"""Evaluate the get_actual_heat_supplied method.""" # noqa: D202

# Arrange
def get_internal_energy(_, i: int):
if i == 0:
return 1.0e6
if i == 1:
return 2.0e6

def get_mass_flow_rate(_, i: int):
return 0.5

with patch(
"omotes_simulator_core.solver.network.assets.base_asset.BaseAsset.get_internal_energy",
get_internal_energy,
), patch(
"omotes_simulator_core.solver.network.assets.base_asset.BaseAsset.get_mass_flow_rate",
get_mass_flow_rate,
):
# Act
actual_heat_supplied = self.heat_demand.get_actual_heat_supplied()
# Assert
self.assertEqual(actual_heat_supplied, 0.5 * 1e6)
Loading
Loading