Skip to content

Commit

Permalink
refactor: reference service interface
Browse files Browse the repository at this point in the history
Make a reference service to make it a bit easier to define interfaces for
the different types of models wer have.
  • Loading branch information
jsolaas committed Oct 22, 2024
1 parent 19ef808 commit d8f28ab
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 101 deletions.
7 changes: 0 additions & 7 deletions src/libecalc/common/errors/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,6 @@ def __init__(self, message: str):
super().__init__("Illegal state", message, error_type=EcalcErrorType.SERVER_ERROR)


class InvalidReferenceException(EcalcError):
"""The data provided is missing a required reference."""

def __init__(self, message: str):
super().__init__("Invalid reference", message, error_type=EcalcErrorType.CLIENT_ERROR)


class InvalidDateException(EcalcError): ...


Expand Down
24 changes: 24 additions & 0 deletions src/libecalc/presentation/yaml/domain/reference_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Iterable, Protocol

from libecalc.dto import CompressorModel, FuelType, GeneratorSetSampled, PumpModel, TabulatedData


class InvalidReferenceException(Exception):
def __init__(self, reference_type: str, reference: str, available_references: Iterable[str] = None):
if available_references is not None:
available_message = f"Available references: {', '.join(available_references)}"
else:
available_message = ""
super().__init__(f"Invalid {reference_type} reference '{reference}'. {available_message}")


class ReferenceService(Protocol):
def get_fuel_reference(self, reference: str) -> FuelType: ...

def get_generator_set_model(self, reference: str) -> GeneratorSetSampled: ...

def get_compressor_model(self, reference: str) -> CompressorModel: ...

def get_pump_model(self, reference: str) -> PumpModel: ...

def get_tabulated_model(self, reference: str) -> TabulatedData: ...
35 changes: 11 additions & 24 deletions src/libecalc/presentation/yaml/mappers/component_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@
from libecalc.common.consumer_type import ConsumerType
from libecalc.common.consumption_type import ConsumptionType
from libecalc.common.energy_model_type import EnergyModelType
from libecalc.common.errors.exceptions import InvalidReferenceException
from libecalc.common.logger import logger
from libecalc.common.time_utils import Period, define_time_model_for_period
from libecalc.dto import ConsumerFunction, FuelType
from libecalc.dto.components import Asset, Consumer, ElectricityConsumer, FuelConsumer, GeneratorSet, Installation
from libecalc.dto.utils.validators import convert_expression
from libecalc.expression import Expression
from libecalc.presentation.yaml.domain.reference_service import InvalidReferenceException, ReferenceService
from libecalc.presentation.yaml.mappers.consumer_function_mapper import (
ConsumerFunctionMapper,
)
from libecalc.presentation.yaml.mappers.utils import resolve_reference
from libecalc.presentation.yaml.validation_errors import (
DataValidationError,
DtoValidationError,
)
from libecalc.presentation.yaml.yaml_entities import References
from libecalc.presentation.yaml.yaml_models.yaml_model import YamlValidator
from libecalc.presentation.yaml.yaml_types.components.legacy.yaml_electricity_consumer import YamlElectricityConsumer
from libecalc.presentation.yaml.yaml_types.components.legacy.yaml_fuel_consumer import YamlFuelConsumer
Expand Down Expand Up @@ -65,7 +63,7 @@ def _get_component_type(energy_usage_models: Dict[datetime, ConsumerFunction]) -
def _resolve_fuel(
consumer_fuel: Union[Optional[str], Dict],
default_fuel: Optional[str],
references: References,
references: ReferenceService,
target_period: Period,
) -> Optional[Dict[datetime, FuelType]]:
fuel = consumer_fuel or default_fuel # Use parent fuel only if not specified on this consumer
Expand All @@ -77,18 +75,15 @@ def _resolve_fuel(

temporal_fuel_model = {}
for start_time, fuel in time_adjusted_fuel.items():
resolved_fuel = resolve_reference(
fuel,
references=references.fuel_types,
)
resolved_fuel = references.get_fuel_reference(fuel)

temporal_fuel_model[start_time] = resolved_fuel

return temporal_fuel_model


class ConsumerMapper:
def __init__(self, references: References, target_period: Period):
def __init__(self, references: ReferenceService, target_period: Period):
self.__references = references
self._target_period = target_period
self.__energy_usage_model_mapper = ConsumerFunctionMapper(references=references, target_period=target_period)
Expand Down Expand Up @@ -116,7 +111,7 @@ def from_yaml_to_dto(
except InvalidReferenceException as e:
raise DataValidationError(
data=data.model_dump(),
message=f"Fuel '{consumer_fuel or default_fuel}' does not exist",
message=str(e),
error_key="fuel",
) from e

Expand All @@ -132,13 +127,8 @@ def from_yaml_to_dto(
except ValidationError as e:
raise DtoValidationError(data=data.model_dump(), validation_error=e) from e

energy_usage_model = resolve_reference(
data.energy_usage_model,
references=self.__references.models,
) # TODO: This is never a reference? So resolve_reference always return the same value, remove

try:
energy_usage_model = self.__energy_usage_model_mapper.from_yaml_to_dto(energy_usage_model)
energy_usage_model = self.__energy_usage_model_mapper.from_yaml_to_dto(data.energy_usage_model)
except ValidationError as e:
raise DtoValidationError(data=data.model_dump(), validation_error=e) from e

Expand Down Expand Up @@ -174,7 +164,7 @@ def from_yaml_to_dto(


class GeneratorSetMapper:
def __init__(self, references: References, target_period: Period):
def __init__(self, references: ReferenceService, target_period: Period):
self.__references = references
self._target_period = target_period
self.__consumer_mapper = ConsumerMapper(references=references, target_period=target_period)
Expand All @@ -197,11 +187,8 @@ def from_yaml_to_dto(
data.electricity2fuel, target_period=self._target_period
)
generator_set_model = {
start_time: resolve_reference(
model_timestep,
references=self.__references.models,
)
for start_time, model_timestep in generator_set_model_data.items()
start_time: self.__references.get_generator_set_model(model_reference)
for start_time, model_reference in generator_set_model_data.items()
}

# When consumers was created directly in the return dto.GeneratorSet - function,
Expand Down Expand Up @@ -237,7 +224,7 @@ def from_yaml_to_dto(


class InstallationMapper:
def __init__(self, references: References, target_period: Period):
def __init__(self, references: ReferenceService, target_period: Period):
self.__references = references
self._target_period = target_period
self.__generator_set_mapper = GeneratorSetMapper(references=references, target_period=target_period)
Expand Down Expand Up @@ -290,7 +277,7 @@ def from_yaml_to_dto(self, data: YamlInstallation) -> Installation:
class EcalcModelMapper:
def __init__(
self,
references: References,
references: ReferenceService,
target_period: Period,
):
self.__references = references
Expand Down
54 changes: 16 additions & 38 deletions src/libecalc/presentation/yaml/mappers/consumer_function_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@
VariableSpeedCompressorTrainMultipleStreamsAndPressures,
)
from libecalc.expression import Expression
from libecalc.presentation.yaml.mappers.utils import (
resolve_reference,
)
from libecalc.presentation.yaml.yaml_entities import References
from libecalc.presentation.yaml.domain.reference_service import ReferenceService
from libecalc.presentation.yaml.yaml_keywords import EcalcYamlKeywords
from libecalc.presentation.yaml.yaml_types.components.legacy.energy_usage_model import (
YamlElectricityEnergyUsageModel,
Expand Down Expand Up @@ -94,15 +91,12 @@ def _get_compressor_train_energy_usage_type(


def _compressor_system_mapper(
energy_usage_model: YamlEnergyUsageModelCompressorSystem, references: References = None
energy_usage_model: YamlEnergyUsageModelCompressorSystem, references: ReferenceService
) -> CompressorSystemConsumerFunction:
compressors = []
compressor_power_usage_type = set()
for compressor in energy_usage_model.compressors:
compressor_train = resolve_reference(
value=compressor.compressor_model,
references=references.models,
)
compressor_train = references.get_compressor_model(compressor.compressor_model)

compressors.append(
CompressorSystemCompressor(
Expand Down Expand Up @@ -145,18 +139,15 @@ def _compressor_system_mapper(


def _pump_system_mapper(
energy_usage_model: YamlEnergyUsageModelPumpSystem, references: References = None
energy_usage_model: YamlEnergyUsageModelPumpSystem, references: ReferenceService
) -> PumpSystemConsumerFunction:
"""Remove references from pump system and map yaml to DTO
:param energy_usage_model: dict representing PumpSystem
:return:
"""
pumps = []
for pump in energy_usage_model.pumps:
pump_model = resolve_reference(
pump.chart,
references=references.models,
)
pump_model = references.get_pump_model(pump.chart)
pumps.append(PumpSystemPump(name=pump.name, pump_model=pump_model))

return PumpSystemConsumerFunction(
Expand All @@ -182,7 +173,7 @@ def _pump_system_mapper(


def _direct_mapper(
energy_usage_model: YamlEnergyUsageModelDirect, references: References = None
energy_usage_model: YamlEnergyUsageModelDirect, references: ReferenceService
) -> DirectConsumerFunction:
"""Change type to match DTOs, then pass the dict on to DTO to automatically create the correct DTO.
:param energy_usage_model:
Expand All @@ -200,12 +191,9 @@ def _direct_mapper(


def _tabulated_mapper(
energy_usage_model: YamlEnergyUsageModelTabulated, references: References = None
energy_usage_model: YamlEnergyUsageModelTabulated, references: ReferenceService
) -> TabulatedConsumerFunction:
energy_model = resolve_reference(
energy_usage_model.energy_function,
references.models,
)
energy_model = references.get_tabulated_model(energy_usage_model.energy_function)
return TabulatedConsumerFunction(
energy_usage_type=EnergyUsageType.POWER
if EnergyUsageType.POWER.value in energy_model.headers
Expand All @@ -223,11 +211,8 @@ def _tabulated_mapper(
)


def _pump_mapper(energy_usage_model: YamlEnergyUsageModelPump, references: References = None) -> PumpConsumerFunction:
energy_model = resolve_reference(
energy_usage_model.energy_function,
references=references.models,
)
def _pump_mapper(energy_usage_model: YamlEnergyUsageModelPump, references: ReferenceService) -> PumpConsumerFunction:
energy_model = references.get_pump_model(energy_usage_model.energy_function)
return PumpConsumerFunction(
power_loss_factor=energy_usage_model.power_loss_factor,
condition=_map_condition(energy_usage_model),
Expand All @@ -240,12 +225,9 @@ def _pump_mapper(energy_usage_model: YamlEnergyUsageModelPump, references: Refer


def _variable_speed_compressor_train_multiple_streams_and_pressures_mapper(
energy_usage_model: YamlEnergyUsageModelCompressorTrainMultipleStreams, references: References = None
energy_usage_model: YamlEnergyUsageModelCompressorTrainMultipleStreams, references: ReferenceService
) -> CompressorConsumerFunction:
compressor_train_model = resolve_reference(
energy_usage_model.compressor_train_model,
references=references.models,
)
compressor_train_model = references.get_compressor_model(energy_usage_model.compressor_train_model)
rates_per_stream = [
Expression.setup_from_expression(value=rate_expression)
for rate_expression in energy_usage_model.rate_per_stream
Expand Down Expand Up @@ -293,13 +275,9 @@ def _variable_speed_compressor_train_multiple_streams_and_pressures_mapper(


def _compressor_mapper(
energy_usage_model: YamlEnergyUsageModelCompressor, references: References = None
energy_usage_model: YamlEnergyUsageModelCompressor, references: ReferenceService
) -> CompressorConsumerFunction:
energy_model = resolve_reference(
energy_usage_model.energy_function,
references=references.models,
)

energy_model = references.get_compressor_model(energy_usage_model.energy_function)
compressor_train_energy_usage_type = _get_compressor_train_energy_usage_type(compressor_train=energy_model)

return CompressorConsumerFunction(
Expand All @@ -325,14 +303,14 @@ def _compressor_mapper(


class ConsumerFunctionMapper:
def __init__(self, references: References, target_period: Period):
def __init__(self, references: ReferenceService, target_period: Period):
self.__references = references
self._target_period = target_period

@staticmethod
def create_model(
model: Union[YamlFuelEnergyUsageModel, YamlElectricityEnergyUsageModel],
references: References = None,
references: ReferenceService,
):
model_creator = _consumer_function_mapper.get(model.type)
if model_creator is None:
Expand Down
3 changes: 2 additions & 1 deletion src/libecalc/presentation/yaml/mappers/create_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from libecalc.common.logger import logger
from libecalc.common.string.string_utils import get_duplicates
from libecalc.dto import EnergyModel
from libecalc.presentation.yaml.domain.reference_service import ReferenceService
from libecalc.presentation.yaml.mappers.facility_input import FacilityInputMapper
from libecalc.presentation.yaml.mappers.fuel_and_emission_mapper import FuelMapper
from libecalc.presentation.yaml.mappers.model import ModelMapper
Expand All @@ -14,7 +15,7 @@
from libecalc.presentation.yaml.yaml_types.models import YamlConsumerModel


def create_references(configuration: YamlValidator, resources: Resources) -> References:
def create_references(configuration: YamlValidator, resources: Resources) -> ReferenceService:
"""Create references-lookup used throughout the yaml.
:param resources: list of resources containing data for the FILE reference in facility input
Expand Down
3 changes: 2 additions & 1 deletion src/libecalc/presentation/yaml/mappers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pandas as pd

from libecalc.common.errors.exceptions import HeaderNotFoundException, InvalidReferenceException
from libecalc.common.errors.exceptions import HeaderNotFoundException
from libecalc.common.logger import logger
from libecalc.common.units import Unit
from libecalc.dto.types import (
Expand All @@ -12,6 +12,7 @@
ChartPolytropicHeadUnit,
ChartRateUnit,
)
from libecalc.presentation.yaml.domain.reference_service import InvalidReferenceException
from libecalc.presentation.yaml.resource import Resource
from libecalc.presentation.yaml.validation_errors import (
ResourceValidationError,
Expand Down
28 changes: 23 additions & 5 deletions src/libecalc/presentation/yaml/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@

from typing_extensions import Self, deprecated

from libecalc.common.time_utils import Frequency
from libecalc.common.time_utils import Frequency, Period
from libecalc.common.variables import VariablesMap
from libecalc.dto import ResultOptions
from libecalc.dto.component_graph import ComponentGraph
from libecalc.presentation.yaml.configuration_service import ConfigurationService
from libecalc.presentation.yaml.domain.reference_service import ReferenceService
from libecalc.presentation.yaml.domain.time_series_collections import TimeSeriesCollections
from libecalc.presentation.yaml.mappers.component_mapper import EcalcModelMapper
from libecalc.presentation.yaml.mappers.create_references import create_references
from libecalc.presentation.yaml.mappers.variables_mapper import map_yaml_to_variables
from libecalc.presentation.yaml.mappers.variables_mapper.get_global_time_vector import get_global_time_vector
from libecalc.presentation.yaml.model_validation_exception import ModelValidationException
from libecalc.presentation.yaml.parse_input import map_yaml_to_dto
from libecalc.presentation.yaml.resource_service import ResourceService
from libecalc.presentation.yaml.validation_errors import DtoValidationError
from libecalc.presentation.yaml.yaml_models.yaml_model import YamlValidator
Expand All @@ -24,6 +26,8 @@
YamlModelValidationContextNames,
)

DEFAULT_START_TIME = datetime(1900, 1, 1)


class YamlModel:
"""
Expand Down Expand Up @@ -52,13 +56,27 @@ def __init__(

self._is_validated = False

def _get_reference_service(self) -> ReferenceService:
return create_references(self._configuration, self.resources)

@cached_property
@deprecated(
"Avoid using the dto objects directly, we want to remove them. get_graph() might be useful instead, although the nodes will change."
)
def dto(self):
self.validate_for_run()
return map_yaml_to_dto(configuration=self._configuration, resources=self.resources)
model_mapper = EcalcModelMapper(
references=self._get_reference_service(),
target_period=self.period,
)
return model_mapper.from_yaml_to_dto(configuration=self._configuration)

@property
def period(self) -> Period:
return Period(
start=self.start or DEFAULT_START_TIME,
end=self.end or datetime.max,
)

@property
def start(self) -> Optional[datetime]:
Expand All @@ -74,8 +92,8 @@ def _get_time_series_collections(self) -> TimeSeriesCollections:
def _get_time_vector(self):
return get_global_time_vector(
time_series_time_vector=self._get_time_series_collections().get_time_vector(),
start=self.start,
end=self.end,
start=self._configuration.start,
end=self._configuration.end,
frequency=self._output_frequency,
additional_dates=self._configuration.dates,
)
Expand Down
Loading

0 comments on commit d8f28ab

Please sign in to comment.