Skip to content

Commit

Permalink
refactor: use Periods instead of datetime for temporal model dicts
Browse files Browse the repository at this point in the history
refactor: use periods in timeseries

refactor: use periods in simple models for delta profiles

refactor: change from timesteps to periods in results

chore: update tests to reflect change from timesteps to periods

refactor: change from timesteps to periods

chore: keep format of first columns in csv and tsv files

chore: datetime to periods

chore: make sure dictionary keys are periods not strings

chore: datetime to periods

chore: add specific end to test dtos

chore: update tests to run

chore: update snapshots

chore: update simple example

refactor: remove resample_time_steps

chore: add resample method to TimeSeriesVolumes and use it in queries

ECALC-1739
  • Loading branch information
olelod authored and jsolaas committed Oct 22, 2024
1 parent cdf3864 commit 4068795
Show file tree
Hide file tree
Showing 131 changed files with 106,358 additions and 33,575 deletions.
2 changes: 1 addition & 1 deletion examples/simple_yaml_model.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
"for identity, component in yaml_model.get_graph().nodes.items():\n",
" if identity in result.consumer_results:\n",
" component_result = result.consumer_results[identity].component_result\n",
" ds = pd.Series(component_result.energy_usage.values, index=component_result.energy_usage.timesteps)\n",
" ds = pd.Series(component_result.energy_usage.values, index=component_result.energy_usage.periods.start_dates)\n",
" _ = ds.plot(\n",
" xlabel=\"time\",\n",
" ylabel=component_result.energy_usage.unit,\n",
Expand Down
12 changes: 5 additions & 7 deletions src/ecalc_cli/io/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from ecalc_cli.errors import EcalcCLIError
from libecalc.application.graph_result import GraphResult
from libecalc.common.run_info import RunInfo
from libecalc.common.time_utils import resample_time_steps
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 TimeFormatterConfig
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
Expand Down Expand Up @@ -149,15 +149,13 @@ def export_tsv(
Returns:
"""
resampled_timevector = resample_time_steps(results.timesteps, frequency)[
:-1
] # last step is always added as a STOP, and does infer the end of the time vector
resampled_periods = resample_periods(results.periods, frequency)

prognosis_filter = config.filter(frequency=frequency)
result = prognosis_filter.filter(ExportableGraphResult(results), resampled_timevector)
result = prognosis_filter.filter(ExportableGraphResult(results), resampled_periods)

row_based_data: Dict[str, List[str]] = CSVFormatter(
separation_character="\t", index_formatters=TimeFormatterConfig.get_row_index_formatters()
separation_character="\t", index_formatters=PeriodFormatterConfig.get_row_index_formatters()
).format_groups(result)

exporter = Exporter()
Expand Down
49 changes: 25 additions & 24 deletions src/libecalc/application/energy_calculator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from collections import defaultdict
from datetime import datetime
from functools import reduce
from typing import Dict

Expand All @@ -12,6 +11,7 @@
from libecalc.common.priorities import PriorityID
from libecalc.common.priority_optimizer import PriorityOptimizer
from libecalc.common.temporal_model import TemporalModel
from libecalc.common.time_utils import Period
from libecalc.common.units import Unit
from libecalc.common.utils.rates import TimeSeriesInt, TimeSeriesString
from libecalc.common.variables import VariablesMap
Expand Down Expand Up @@ -43,8 +43,8 @@
)


def merge_results(results_per_timestep: Dict[datetime, EcalcModelResult]) -> EcalcModelResult:
return reduce(lambda acc, x: acc.merge(x), results_per_timestep.values())
def merge_results(results_per_period: Dict[Period, EcalcModelResult]) -> EcalcModelResult:
return reduce(lambda acc, x: acc.merge(x), results_per_period.values())


class EnergyCalculator:
Expand All @@ -70,8 +70,8 @@ def evaluate_energy_usage(self, variables_map: VariablesMap) -> Dict[str, EcalcM
consumes=component_dto.consumes,
energy_usage_model=TemporalModel(
{
start_time: EnergyModelMapper.from_dto_to_domain(model)
for start_time, model in component_dto.energy_usage_model.items()
period: EnergyModelMapper.from_dto_to_domain(model)
for period, model in component_dto.energy_usage_model.items()
}
),
)
Expand All @@ -82,13 +82,13 @@ def evaluate_energy_usage(self, variables_map: VariablesMap) -> Dict[str, EcalcM
name=component_dto.name,
temporal_generator_set_model=TemporalModel(
{
start_time: GeneratorModelSampled(
period: GeneratorModelSampled(
fuel_values=model.fuel_values,
power_values=model.power_values,
energy_usage_adjustment_constant=model.energy_usage_adjustment_constant,
energy_usage_adjustment_factor=model.energy_usage_adjustment_factor,
)
for start_time, model in component_dto.generator_set_model.items()
for period, model in component_dto.generator_set_model.items()
}
),
)
Expand All @@ -98,7 +98,7 @@ def evaluate_energy_usage(self, variables_map: VariablesMap) -> Dict[str, EcalcM
consumer_results[consumer_id].component_result.power.values
for consumer_id in self._graph.get_successors(component_dto.id)
],
timesteps=variables_map.time_vector,
periods=variables_map.periods,
)

consumer_results[component_dto.id] = EcalcModelResult(
Expand All @@ -115,32 +115,28 @@ def evaluate_energy_usage(self, variables_map: VariablesMap) -> Dict[str, EcalcM
)
optimizer = PriorityOptimizer()

results_per_timestep: Dict[str, Dict[datetime, ComponentResult]] = defaultdict(dict)
priorities_used = TimeSeriesString(
timesteps=[],
values=[],
unit=Unit.NONE,
)
for timestep in variables_map.time_vector:
consumers_for_timestep = [
results_per_period: Dict[str, Dict[Period, ComponentResult]] = defaultdict(dict)
priorities_used = []
for period in variables_map.periods:
consumers_for_period = [
create_consumer(
consumer=consumer,
timestep=timestep,
period=period,
)
for consumer in component_dto.consumers
]

consumer_system = ConsumerSystem(
id=component_dto.id,
consumers=consumers_for_timestep,
consumers=consumers_for_period,
component_conditions=component_dto.component_conditions,
)

def evaluator(priority: PriorityID):
stream_conditions_for_priority = evaluated_stream_conditions[priority]
stream_conditions_for_timestep = {
component_id: [
stream_condition.for_timestep(timestep) for stream_condition in stream_conditions
stream_condition.for_period(period) for stream_condition in stream_conditions
]
for component_id, stream_conditions in stream_conditions_for_priority.items()
}
Expand All @@ -150,23 +146,28 @@ def evaluator(priority: PriorityID):
priorities=list(evaluated_stream_conditions.keys()),
evaluator=evaluator,
)
priorities_used.append(timestep=timestep, value=optimizer_result.priority_used)
priorities_used.append(optimizer_result.priority_used)
for consumer_result in optimizer_result.priority_results:
results_per_timestep[consumer_result.id][timestep] = consumer_result
results_per_period[consumer_result.id][period] = consumer_result

priorities_used = TimeSeriesString(
periods=variables_map.periods,
values=priorities_used,
unit=Unit.NONE,
)
# merge consumer results
consumer_ids = [consumer.id for consumer in component_dto.consumers]
merged_consumer_results = []
for consumer_id in consumer_ids:
first_result, *rest_results = list(results_per_timestep[consumer_id].values())
first_result, *rest_results = list(results_per_period[consumer_id].values())
merged_consumer_results.append(first_result.merge(*rest_results))

# Convert to legacy compatible operational_settings_used
priorities_to_int_map = {
priority_name: index + 1 for index, priority_name in enumerate(evaluated_stream_conditions.keys())
}
operational_settings_used = TimeSeriesInt(
timesteps=priorities_used.timesteps,
periods=priorities_used.periods,
values=[priorities_to_int_map[priority_name] for priority_name in priorities_used.values],
unit=priorities_used.unit,
)
Expand Down Expand Up @@ -226,7 +227,7 @@ def evaluate_emissions(
for emission_name, emission_rate in emission_rates.items():
emission_result = EmissionResult(
name=emission_name,
timesteps=variables_map.time_vector,
periods=variables_map.get_periods(),
rate=emission_rate,
)
venting_emitter_results[emission_name] = emission_result
Expand Down
4 changes: 2 additions & 2 deletions src/libecalc/application/graph_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def get_energy_result(self, component_id: str) -> ComponentResult:
return self.consumer_results[component_id].component_result

@property
def timesteps(self):
return self.variables_map.time_vector
def periods(self):
return self.variables_map.periods

def get_emissions(self, component_id: str) -> Dict[str, EmissionResult]:
return self.emission_results[component_id]
Expand Down
21 changes: 10 additions & 11 deletions src/libecalc/common/list/list_utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from collections import defaultdict
from datetime import datetime
from typing import Any, Dict, List, Optional, Sequence, Union, cast

import numpy as np
from numpy import float64
from numpy.typing import NDArray

from libecalc.common.time_utils import Periods

"""
NOTE! A "list util" class is not the best, but maybe we should try to
expand a "prototype" class instead, e.g. create "our own static list class" where we extend
Expand Down Expand Up @@ -57,23 +58,21 @@ def group_data_by_value_at_index(index: int, row_based_data: List[List[Any]]) ->
return chart_grouped_by_index


def elementwise_sum(
*vectors: Sequence[Optional[float]], timesteps: Optional[List[datetime]] = None
) -> NDArray[np.float64]:
def elementwise_sum(*vectors: Sequence[Optional[float]], periods: Optional[Periods] = None) -> NDArray[np.float64]:
"""Sum up multiple vectors elementwise.
E.g. if we provide three lists [1,20], [2,10], [1,30], the result will be [1+2+1,20+10+30] = [4,60]
Args:
*vectors: Sequences to be summed up elementwise
timesteps: Optional list of timesteps used to initialize resulting array. If no timesteps are provided, the first vector is used
periods: Optional list of periods used to initialize resulting array. If no periods are provided, the first vector is used
Returns:
Numpy array where the elements of provided vectors are summed up elementwise
"""
if timesteps is not None:
result = np.full_like(timesteps, fill_value=0.0, dtype=float64)
if periods is not None:
result = np.full_like(periods.periods, fill_value=0.0, dtype=float64)
else:
result = np.full_like(vectors[0], fill_value=0.0, dtype=float64)

Expand All @@ -83,22 +82,22 @@ def elementwise_sum(


def elementwise_multiplication(
*vectors: Sequence[Optional[float]], timesteps: Optional[List[datetime]] = None
*vectors: Sequence[Optional[float]], periods: Optional[Periods] = None
) -> NDArray[np.float64]:
"""Multiply multiple vectors elementwise.
E.g. if we provide three lists [1,20], [2,10], [1,30], the result will be [1*2*1,20*10*30] = [2,6000]
Args:
*vectors: Sequences to be multiplied up elementwise
timesteps: Optional list of timesteps used to initialize resulting array. If no timesteps are provided, the first vector is used
periods: Optional list of periods used to initialize resulting array. If no periods are provided, the first vector is used
Returns:
Numpy array where the elements of provided vectors are multiplied up elementwise
"""
if timesteps is not None:
result = np.full_like(timesteps, fill_value=1.0, dtype=float64)
if periods is not None:
result = np.full_like(periods.periods, fill_value=1.0, dtype=float64)
else:
result = np.full_like(vectors[0], fill_value=1.0, dtype=float64)

Expand Down
30 changes: 13 additions & 17 deletions src/libecalc/common/stream_conditions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import operator
from datetime import datetime
from functools import reduce
from typing import List, Optional

from pydantic import BaseModel, ConfigDict

from libecalc.common.string.string_utils import generate_id, to_camel_case
from libecalc.common.time_utils import Period, Periods
from libecalc.common.utils.rates import TimeSeriesFloat, TimeSeriesStreamDayRate
from libecalc.domain.stream_conditions import (
Density,
Expand Down Expand Up @@ -52,7 +52,7 @@ def mix(self, *other_streams: "TimeSeriesStreamConditions") -> "TimeSeriesStream

target_pressure = self.pressure # Assuming 'self' decides the target pressure
if any(stream.pressure < target_pressure for stream in other_streams): # type: ignore
# TODO: return a warning object with the specific timesteps?
# TODO: return a warning object with the specific periods?
raise ValueError("Increasing pressure when mixing streams. That should not happen.")

return TimeSeriesStreamConditions(
Expand All @@ -63,34 +63,30 @@ def mix(self, *other_streams: "TimeSeriesStreamConditions") -> "TimeSeriesStream
fluid_density=self.fluid_density, # TODO: Check that they are equal? Or handle it?
)

def for_timestep(self, current_timestep: datetime) -> StreamConditions:
def for_period(self, period: Period) -> StreamConditions:
"""
For a given timestep, get the stream that is relevant for that timestep only.
Args:
current_timestep: the timestep must be a part of the global timevector
period: the period must be a part of the global periods
Returns: the stream that is relevant for the given timestep.
"""
return StreamConditions(
id=self.id,
name=self.name,
timestep=current_timestep,
rate=Rate(value=self.rate.for_timestep(current_timestep).values[0], unit=self.rate.unit)
period=period,
rate=Rate(value=self.rate.for_period(period).values[0], unit=self.rate.unit)
if self.rate is not None
else None,
pressure=Pressure(value=self.pressure.for_timestep(current_timestep).values[0], unit=self.pressure.unit)
pressure=Pressure(value=self.pressure.for_period(period).values[0], unit=self.pressure.unit)
if self.pressure is not None
else None,
density=Density(
value=self.fluid_density.for_timestep(current_timestep).values[0], unit=self.fluid_density.unit
)
density=Density(value=self.fluid_density.for_period(period).values[0], unit=self.fluid_density.unit)
if self.fluid_density is not None
else None,
temperature=Temperature(
value=self.temperature.for_timestep(current_timestep).values[0], unit=self.temperature.unit
)
temperature=Temperature(value=self.temperature.for_period(period).values[0], unit=self.temperature.unit)
if self.temperature is not None
else None,
)
Expand All @@ -101,24 +97,24 @@ def from_stream_condition(cls, stream_conditions: StreamConditions) -> "TimeSeri
id=stream_conditions.id,
name=stream_conditions.name,
rate=TimeSeriesStreamDayRate(
timesteps=[stream_conditions.timestep],
periods=Periods([stream_conditions.period]),
values=[stream_conditions.rate.value],
unit=stream_conditions.rate.unit,
),
pressure=TimeSeriesFloat(
timesteps=[stream_conditions.timestep],
periods=Periods([stream_conditions.period]),
values=[stream_conditions.pressure.value],
unit=stream_conditions.pressure.unit,
),
temperature=TimeSeriesFloat(
timesteps=[stream_conditions.timestep],
periods=Periods([stream_conditions.period]),
values=[stream_conditions.temperature.value],
unit=stream_conditions.temperature.unit,
)
if stream_conditions.temperature is not None
else None,
fluid_density=TimeSeriesFloat(
timesteps=[stream_conditions.timestep],
periods=Periods([stream_conditions.period]),
values=[stream_conditions.density.value],
unit=stream_conditions.density.unit,
)
Expand Down
7 changes: 4 additions & 3 deletions src/libecalc/common/tabular_time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing_extensions import Self

from libecalc.common.list.list_utils import transpose
from libecalc.common.time_utils import Periods
from libecalc.common.utils.rates import TimeSeries


Expand Down Expand Up @@ -50,9 +51,9 @@ def merge(cls, *objects_with_time_series: ObjectWithTimeSeries):
for other in others:
accumulated_value = merged_object.__getattribute__(key)
other_value = other.__getattribute__(key)
if key == "timesteps":
merged_timesteps = sorted(itertools.chain(accumulated_value, other_value))
merged_object.__setattr__(key, merged_timesteps)
if key == "periods":
merged_periods = sorted(itertools.chain(accumulated_value, other_value))
merged_object.__setattr__(key, Periods(merged_periods))
elif isinstance(value, TimeSeries):
merged_object.__setattr__(key, accumulated_value.merge(other_value))
elif isinstance(value, BaseModel):
Expand Down
Loading

0 comments on commit 4068795

Please sign in to comment.