Skip to content

Commit

Permalink
fix: flow diagram generation
Browse files Browse the repository at this point in the history
The fix simplifies the diagram. Details below fuel and electricity
consumers will no longer available. It also assumes no changes over
time.
  • Loading branch information
jsolaas committed Nov 22, 2024
1 parent c463ba8 commit a4eddf0
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 560 deletions.
3 changes: 1 addition & 2 deletions src/ecalc_cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ def run(

if flow_diagram:
write_flow_diagram(
model_dto=model.dto,
result_options=model.result_options,
energy_model=model,
output_folder=output_folder,
name_prefix=name_prefix,
)
Expand Down
16 changes: 7 additions & 9 deletions src/ecalc_cli/io/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
from libecalc.application.graph_result import GraphResult
from libecalc.common.run_info import RunInfo
from libecalc.common.time_utils import resample_periods
from libecalc.dto import Asset, ResultOptions
from libecalc.infrastructure.file_utils import OutputFormat, get_result_output
from libecalc.presentation.exporter.configs.configs import LTPConfig, STPConfig
from libecalc.presentation.exporter.configs.formatter_config import PeriodFormatterConfig
from libecalc.presentation.exporter.exporter import Exporter
from libecalc.presentation.exporter.formatters.formatter import CSVFormatter
from libecalc.presentation.exporter.handlers.handler import MultiFileHandler
from libecalc.presentation.exporter.infrastructure import ExportableGraphResult
from libecalc.presentation.flow_diagram.EcalcModelMapper import EcalcModelMapper
from libecalc.presentation.flow_diagram.EcalcModelMapper import EnergyModelFlowDiagram
from libecalc.presentation.json_result.result import EcalcModelResult as EcalcModelResultDTO
from libecalc.presentation.yaml.model import YamlModel


def write_output(output: str, output_file: Path = None):
Expand Down Expand Up @@ -168,12 +168,11 @@ def export_tsv(
exporter.export(row_based_data)


def write_flow_diagram(model_dto: Asset, result_options: ResultOptions, output_folder: Path, name_prefix: str):
def write_flow_diagram(energy_model: YamlModel, output_folder: Path, name_prefix: str):
"""Write FDE diagram to file.
Args:
model_dto: eCalc model
result_options: Result options specifying start, end and frequency
energy_model: The yaml energy model
output_folder: Desired output location of FDE diagram
name_prefix: Name of FDE diagram file
Expand All @@ -183,10 +182,9 @@ def write_flow_diagram(model_dto: Asset, result_options: ResultOptions, output_f
EcalcCLIError: If a OSError occurs during the writing of diagram to file.
"""
flow_diagram = EcalcModelMapper.from_dto_to_fde(
ecalc_model=model_dto,
result_options=result_options,
)
flow_diagram = EnergyModelFlowDiagram(
energy_model=energy_model, model_period=energy_model.variables.period
).get_energy_flow_diagram()
flow_diagram_filename = f"{name_prefix}.flow-diagram.json" if name_prefix != "" else "flow-diagram.json"
flow_diagram_path = output_folder / flow_diagram_filename
try:
Expand Down
23 changes: 23 additions & 0 deletions src/libecalc/application/energy/energy_component.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import abc

from libecalc.application.energy.component_energy_context import ComponentEnergyContext
from libecalc.common.component_type import ComponentType
from libecalc.core.result import EcalcModelResult


Expand All @@ -15,5 +16,27 @@ class EnergyComponent(abc.ABC):
@abc.abstractmethod
def id(self) -> str: ...

@abc.abstractmethod
def get_component_process_type(self) -> ComponentType: ...

@abc.abstractmethod
def get_name(self) -> str: ...

@abc.abstractmethod
def is_provider(self) -> bool:
"""
Whether the energy component provides energy to other energy components.
"""
...

@abc.abstractmethod
def is_container(self) -> bool:
"""
Whether the energy component is a container for other energy components.
"""
...


class EvaluatableEnergyComponent(EnergyComponent, abc.ABC):
@abc.abstractmethod
def evaluate_energy_usage(self, energy_context: ComponentEnergyContext) -> EcalcModelResult: ...
4 changes: 2 additions & 2 deletions src/libecalc/application/energy/energy_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def get_regularity(self, component_id: str) -> dict[datetime, Expression]:
...

@abc.abstractmethod
def get_consumers(self, provider_id: str) -> list[EnergyComponent]:
def get_consumers(self, provider_id: str = None) -> list[EnergyComponent]:
"""
Get consumers of the given provider
Get consumers of the given provider. If no provider is given, assume top-level.
"""
...

Expand Down
4 changes: 3 additions & 1 deletion src/libecalc/dto/component_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
# TODO: Rename to energy graph, use composition instead of inheritance. Alternatively make YamlModel the EnergyGraph/EnergyModel and use Graph directly in YamlModel
# Currently it is practical to have the EnergyModel graph related functions here to deal with dto tests.
class ComponentGraph(Graph):
def get_consumers(self, provider_id: str) -> list[EnergyComponent]:
def get_consumers(self, provider_id: str = None) -> list[EnergyComponent]:
if provider_id is None:
provider_id = self.root
consumer_ids = self.get_successors(provider_id)
return [self.get_node(consumer_id) for consumer_id in consumer_ids]

Expand Down
123 changes: 111 additions & 12 deletions src/libecalc/dto/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from libecalc.application.energy.component_energy_context import ComponentEnergyContext
from libecalc.application.energy.emitter import Emitter
from libecalc.application.energy.energy_component import EnergyComponent
from libecalc.application.energy.energy_model import EnergyModel
from libecalc.common.component_type import ComponentType
from libecalc.common.consumption_type import ConsumptionType
Expand Down Expand Up @@ -131,7 +132,7 @@ def validate_fuel_exist(cls, fuel, info: ValidationInfo):
return fuel


class ElectricityConsumer(BaseConsumer):
class ElectricityConsumer(BaseConsumer, EnergyComponent):
component_type: Literal[
ComponentType.COMPRESSOR,
ComponentType.PUMP,
Expand All @@ -151,6 +152,18 @@ class ElectricityConsumer(BaseConsumer):
lambda data: check_model_energy_usage_type(data, EnergyUsageType.POWER)
)

def is_provider(self) -> bool:
return False

def is_container(self) -> bool:
return False

def get_component_process_type(self) -> ComponentType:
return self.component_type

def get_name(self) -> str:
return self.name

@field_validator("energy_usage_model", mode="before")
@classmethod
def check_energy_usage_model(cls, energy_usage_model):
Expand All @@ -162,7 +175,7 @@ def check_energy_usage_model(cls, energy_usage_model):
return energy_usage_model


class FuelConsumer(BaseConsumer, Emitter):
class FuelConsumer(BaseConsumer, Emitter, EnergyComponent):
component_type: Literal[
ComponentType.COMPRESSOR,
ComponentType.GENERIC,
Expand All @@ -177,6 +190,18 @@ class FuelConsumer(BaseConsumer, Emitter):
lambda data: check_model_energy_usage_type(data, EnergyUsageType.FUEL)
)

def is_provider(self) -> bool:
return False

def is_container(self) -> bool:
return False

def get_component_process_type(self) -> ComponentType:
return self.component_type

def get_name(self) -> str:
return self.name

def evaluate_emissions(
self,
energy_context: ComponentEnergyContext,
Expand Down Expand Up @@ -230,15 +255,39 @@ class PumpOperationalSettings(EcalcBaseModel):
fluid_density: Expression


class CompressorComponent(BaseConsumer):
class CompressorComponent(BaseConsumer, EnergyComponent):
component_type: Literal[ComponentType.COMPRESSOR] = ComponentType.COMPRESSOR
energy_usage_model: dict[Period, CompressorModel]

def is_provider(self) -> bool:
return False

def is_container(self) -> bool:
return False

def get_component_process_type(self) -> ComponentType:
return self.component_type

class PumpComponent(BaseConsumer):
def get_name(self) -> str:
return self.name


class PumpComponent(BaseConsumer, EnergyComponent):
component_type: Literal[ComponentType.PUMP] = ComponentType.PUMP
energy_usage_model: dict[Period, PumpModel]

def is_provider(self) -> bool:
return False

def is_container(self) -> bool:
return False

def get_component_process_type(self) -> ComponentType:
return self.component_type

def get_name(self) -> str:
return self.name


class Stream(EcalcBaseModel):
model_config = ConfigDict(populate_by_name=True)
Expand Down Expand Up @@ -294,19 +343,32 @@ class SystemComponentConditions(EcalcBaseModel):
crossover: list[Crossover]


class ConsumerSystem(BaseConsumer, Emitter):
class ConsumerSystem(BaseConsumer, Emitter, EnergyComponent):
component_type: Literal[ComponentType.CONSUMER_SYSTEM_V2] = Field(
ComponentType.CONSUMER_SYSTEM_V2,
title="TYPE",
description="The type of the component",
)

component_conditions: SystemComponentConditions
stream_conditions_priorities: Priorities[SystemStreamConditions]
consumers: Union[list[CompressorComponent], list[PumpComponent]]

def is_provider(self) -> bool:
return False

def is_container(self) -> bool:
return True

def is_fuel_consumer(self) -> bool:
return self.consumes == ConsumptionType.FUEL

def get_component_process_type(self) -> ComponentType:
return self.component_type

def get_name(self) -> str:
return self.name

def evaluate_emissions(
self,
energy_context: ComponentEnergyContext,
Expand Down Expand Up @@ -382,7 +444,7 @@ def evaluate_stream_conditions(
return dict(parsed_priorities)


class GeneratorSet(BaseEquipment, Emitter):
class GeneratorSet(BaseEquipment, Emitter, EnergyComponent):
component_type: Literal[ComponentType.GENERATOR_SET] = ComponentType.GENERATOR_SET
fuel: dict[Period, FuelType]
generator_set_model: dict[Period, GeneratorSetSampled]
Expand All @@ -401,6 +463,18 @@ class GeneratorSet(BaseEquipment, Emitter):
None, title="MAX_USAGE_FROM_SHORE", description="The peak load/effect that is expected for one hour, per year."
)

def is_provider(self) -> bool:
return True

def is_container(self) -> bool:
return False

def get_component_process_type(self) -> ComponentType:
return self.component_type

def get_name(self) -> str:
return self.name

def evaluate_emissions(
self,
energy_context: ComponentEnergyContext,
Expand Down Expand Up @@ -472,8 +546,9 @@ def get_graph(self) -> ComponentGraph:
return graph


class Installation(BaseComponent):
class Installation(BaseComponent, EnergyComponent):
component_type: Literal[ComponentType.INSTALLATION] = ComponentType.INSTALLATION

user_defined_category: Optional[InstallationUserDefinedCategoryType] = Field(default=None, validate_default=True)
hydrocarbon_export: dict[Period, Expression]
fuel_consumers: list[
Expand All @@ -484,6 +559,18 @@ class Installation(BaseComponent):
] = Field(default_factory=list)
venting_emitters: list[YamlVentingEmitter] = Field(default_factory=list)

def is_provider(self) -> bool:
return False

def is_container(self) -> bool:
return True

def get_component_process_type(self) -> ComponentType:
return self.component_type

def get_name(self) -> str:
return self.name

@property
def id(self) -> str:
return generate_id(self.name)
Expand All @@ -496,7 +583,7 @@ def id(self) -> str:

@field_validator("user_defined_category", mode="before")
def check_user_defined_category(cls, user_defined_category, info: ValidationInfo):
"""Provide which value and context to make it easier for user to correct wrt mandatory changes."""
# Provide which value and context to make it easier for user to correct wrt mandatory changes.
if user_defined_category is not None:
if user_defined_category not in list(InstallationUserDefinedCategoryType):
name_context_str = ""
Expand All @@ -512,7 +599,7 @@ def check_user_defined_category(cls, user_defined_category, info: ValidationInfo
@model_validator(mode="after")
def check_fuel_consumers_or_venting_emitters_exist(self):
try:
if self.fuel_consumers or self.venting_emitters or self.generator_sets:
if self.fuel_consumers or self.venting_emitters:
return self
except AttributeError:
raise ValueError(
Expand All @@ -534,15 +621,27 @@ def get_graph(self) -> ComponentGraph:
return graph


class Asset(Component):
class Asset(Component, EnergyComponent):
@property
def id(self):
return generate_id(self.name)

component_type: Literal[ComponentType.ASSET] = ComponentType.ASSET

name: ComponentNameStr

installations: list[Installation] = Field(default_factory=list)
component_type: Literal[ComponentType.ASSET] = ComponentType.ASSET

def is_provider(self) -> bool:
return False

def is_container(self) -> bool:
return True

def get_component_process_type(self) -> ComponentType:
return self.component_type

def get_name(self) -> str:
return self.name

@model_validator(mode="after")
def validate_unique_names(self):
Expand Down
Loading

0 comments on commit a4eddf0

Please sign in to comment.