Skip to content

Commit 9f6515b

Browse files
committed
refactor: use time series flow rate for direct consumer function
1 parent 5198ddb commit 9f6515b

10 files changed

Lines changed: 246 additions & 147 deletions

File tree

src/libecalc/domain/infrastructure/energy_components/legacy_consumer/consumer_function/direct_expression_consumer_function.py

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,32 @@
33
from libecalc.common.energy_usage_type import EnergyUsageType
44
from libecalc.common.list.list_utils import array_to_list
55
from libecalc.common.units import Unit
6-
from libecalc.common.utils.rates import Rates, RateType
76
from libecalc.common.variables import ExpressionEvaluator
87
from libecalc.domain.infrastructure.energy_components.legacy_consumer.consumer_function import (
98
ConsumerFunction,
109
ConsumerFunctionResult,
1110
)
1211
from libecalc.domain.infrastructure.energy_components.legacy_consumer.consumer_function.utils import (
13-
apply_condition,
1412
apply_power_loss_factor,
15-
get_condition_from_expression,
1613
get_power_loss_factor_from_expression,
1714
)
1815
from libecalc.domain.process.core.results import EnergyFunctionGenericResult
1916
from libecalc.expression import Expression
17+
from libecalc.presentation.yaml.domain.expression_time_series_flow_rate import ExpressionTimeSeriesFlowRate
2018

2119

2220
class DirectExpressionConsumerFunction(ConsumerFunction):
2321
def __init__(
2422
self,
2523
energy_usage_type: EnergyUsageType,
2624
condition: Expression | None = None,
27-
fuel_rate: Expression | None = None,
28-
load: Expression | None = None,
25+
fuel_rate: ExpressionTimeSeriesFlowRate = None,
26+
load: ExpressionTimeSeriesFlowRate | None = None,
2927
power_loss_factor: Expression | None = None,
30-
consumption_rate_type: RateType = RateType.STREAM_DAY,
3128
):
32-
expression = fuel_rate if energy_usage_type == EnergyUsageType.FUEL.value else load
29+
self._energy_usage = fuel_rate if energy_usage_type == EnergyUsageType.FUEL.value else load
3330
power_loss_factor_expression = power_loss_factor
34-
assert isinstance(consumption_rate_type, RateType)
3531
self._energy_usage_type = energy_usage_type
36-
self._expression = expression
37-
self._convert_to_stream_day = consumption_rate_type == RateType.CALENDAR_DAY
3832
self._condition_expression = condition
3933
self._power_loss_factor_expression = power_loss_factor_expression
4034

@@ -66,28 +60,15 @@ def evaluate(
6660
expression_evaluator: ExpressionEvaluator,
6761
regularity: list[float],
6862
) -> ConsumerFunctionResult:
69-
energy_usage_expression_evaluated = expression_evaluator.evaluate(expression=self._expression) # type: ignore[arg-type]
70-
71-
# Do conditioning first - set rates to zero if conditions are not met
72-
condition = get_condition_from_expression(
73-
expression_evaluator=expression_evaluator,
74-
condition_expression=self._condition_expression,
75-
)
76-
77-
energy_usage = apply_condition(
78-
input_array=Rates.to_stream_day(
79-
calendar_day_rates=energy_usage_expression_evaluated,
80-
regularity=regularity,
81-
)
82-
if self._convert_to_stream_day
83-
else energy_usage_expression_evaluated,
84-
condition=condition,
85-
)
63+
energy_usage_raw = self._energy_usage.get_stream_day_values()
64+
energy_usage = array_to_list(np.asarray(energy_usage_raw))
65+
if energy_usage is None:
66+
energy_usage = []
8667

8768
energy_function_result = EnergyFunctionGenericResult(
88-
energy_usage=array_to_list(energy_usage), # type: ignore[arg-type]
69+
energy_usage=energy_usage,
8970
energy_usage_unit=self.energy_usage_unit,
90-
power=array_to_list(energy_usage) if self.is_electrical_consumer else None,
71+
power=energy_usage if self.is_electrical_consumer else None,
9172
power_unit=self.power_unit if self.is_electrical_consumer else None,
9273
)
9374

@@ -104,7 +85,7 @@ def evaluate(
10485
# but negative fuel rates are always invalid.
10586

10687
if self.is_fuel_consumer:
107-
is_valid[energy_usage < 0] = False
88+
is_valid[np.asarray(energy_usage) < 0] = False
10889

10990
consumer_function_result = ConsumerFunctionResult(
11091
periods=expression_evaluator.get_periods(),

src/libecalc/presentation/yaml/domain/expression_time_series_flow_rate.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44

55
from libecalc.common.time_utils import Period
6-
from libecalc.common.utils.rates import Rates
6+
from libecalc.common.utils.rates import Rates, RateType
77
from libecalc.domain.infrastructure.energy_components.legacy_consumer.consumer_function.utils import (
88
apply_condition,
99
get_condition_from_expression,
@@ -18,20 +18,23 @@ class ExpressionTimeSeriesFlowRate(TimeSeriesFlowRate):
1818
"""
1919
Provides flow rate values by evaluating a time series expression.
2020
21-
This class assumes that the input time series expression yields flow rates per calendar day.
22-
- Stream day values are derived by converting calendar day rates using the specified regularity,
23-
and then applying an optional condition expression to filter or modify the results.
24-
21+
This class supports configurable rate types via the `consumption_rate_type` parameter.
22+
- If `consumption_rate_type` is set to calendar day (default), flow rates are converted to stream day rates using the specified regularity.
23+
- If set to stream day, rates are used as-is.
24+
- An optional condition expression can be applied to filter or modify the results.
2525
"""
2626

2727
def __init__(
2828
self,
2929
time_series_expression: TimeSeriesExpression,
3030
regularity: Regularity,
3131
condition_expression: Expression | dict[Period, Expression] | None = None,
32+
consumption_rate_type: RateType | None = RateType.CALENDAR_DAY,
3233
):
3334
self._time_series_expression = time_series_expression
3435
self._regularity = regularity
36+
assert isinstance(consumption_rate_type, RateType)
37+
self._consumption_rate_type = consumption_rate_type
3538
self.condition = get_condition_from_expression(
3639
expression_evaluator=self._time_series_expression.expression_evaluator,
3740
condition_expression=condition_expression,
@@ -46,12 +49,19 @@ def get_stream_day_values(self) -> Sequence[float]:
4649
"""
4750

4851
# if regularity is 0 for a calendar day rate, set stream day rate to 0 for that step
49-
calendar_day_rate = self._time_series_expression.get_evaluated_expressions()
50-
stream_day_rate = apply_condition(
51-
input_array=Rates.to_stream_day(
52-
calendar_day_rates=np.asarray(calendar_day_rate, dtype=np.float64),
52+
rate = self._time_series_expression.get_evaluated_expressions()
53+
rate_array = np.asarray(rate, dtype=np.float64)
54+
55+
if self._consumption_rate_type == RateType.CALENDAR_DAY:
56+
rate_array = Rates.to_stream_day(
57+
calendar_day_rates=rate_array,
5358
regularity=self._regularity.get_values,
54-
),
59+
)
60+
61+
# If already stream_day, no conversion needed
62+
stream_day_rate = apply_condition(
63+
input_array=rate_array,
5564
condition=self.condition,
5665
)
66+
5767
return stream_day_rate

src/libecalc/presentation/yaml/mappers/consumer_function_mapper.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,33 +200,51 @@ def __init__(
200200
self._period_subsets[period] = (period_regularity, period_evaluator)
201201

202202
def _map_direct(
203-
self, model: YamlEnergyUsageModelDirectFuel | YamlEnergyUsageModelDirectElectricity, consumes: ConsumptionType
203+
self,
204+
model: YamlEnergyUsageModelDirectFuel | YamlEnergyUsageModelDirectElectricity,
205+
consumes: ConsumptionType,
206+
period: Period,
204207
) -> DirectExpressionConsumerFunction:
208+
period_regularity, period_evaluator = self._period_subsets[period]
205209
condition = convert_expression(_map_condition(model))
206210
consumption_rate_type = RateType((model.consumption_rate_type or ConsumptionRateType.STREAM_DAY).value)
207211
power_loss_factor = convert_expression(model.power_loss_factor)
208212
if isinstance(model, YamlEnergyUsageModelDirectFuel):
209213
if consumes != ConsumptionType.FUEL:
210214
raise InvalidConsumptionType(actual=ConsumptionType.FUEL, expected=consumes)
215+
fuel_rate_expression = TimeSeriesExpression(
216+
expressions=model.fuel_rate, expression_evaluator=period_evaluator
217+
)
218+
fuel_rate = ExpressionTimeSeriesFlowRate(
219+
time_series_expression=fuel_rate_expression,
220+
regularity=period_regularity,
221+
condition_expression=condition,
222+
consumption_rate_type=consumption_rate_type,
223+
)
211224
return DirectExpressionConsumerFunction(
212225
energy_usage_type=EnergyUsageType.FUEL,
213-
fuel_rate=convert_expression(model.fuel_rate), # type: ignore[arg-type]
226+
fuel_rate=fuel_rate,
214227
condition=condition, # type: ignore[arg-type]
215228
power_loss_factor=power_loss_factor, # type: ignore[arg-type]
216-
consumption_rate_type=consumption_rate_type,
217229
)
218230
else:
219231
assert isinstance(model, YamlEnergyUsageModelDirectElectricity)
220232

221233
if consumes != ConsumptionType.ELECTRICITY:
222234
raise InvalidConsumptionType(actual=ConsumptionType.ELECTRICITY, expected=consumes)
223235

236+
load_expression = TimeSeriesExpression(expressions=model.load, expression_evaluator=period_evaluator)
237+
load = ExpressionTimeSeriesFlowRate(
238+
time_series_expression=load_expression,
239+
regularity=period_regularity,
240+
condition_expression=condition,
241+
consumption_rate_type=consumption_rate_type,
242+
)
224243
return DirectExpressionConsumerFunction(
225244
energy_usage_type=EnergyUsageType.POWER,
226-
load=convert_expression(model.load), # type: ignore[arg-type]
245+
load=load,
227246
condition=condition, # type: ignore[arg-type]
228247
power_loss_factor=power_loss_factor, # type: ignore[arg-type]
229-
consumption_rate_type=consumption_rate_type,
230248
)
231249

232250
def _map_tabular(self, model: YamlEnergyUsageModelTabulated, consumes: ConsumptionType) -> TabularConsumerFunction:
@@ -543,7 +561,7 @@ def from_yaml_to_dto(
543561
for period, model in self._time_adjusted_model.items():
544562
try:
545563
if isinstance(model, YamlEnergyUsageModelDirectElectricity | YamlEnergyUsageModelDirectFuel):
546-
mapped_model = self._map_direct(model=model, consumes=consumes)
564+
mapped_model = self._map_direct(model=model, consumes=consumes, period=period)
547565
elif isinstance(model, YamlEnergyUsageModelCompressor):
548566
mapped_model = self._map_compressor(model, consumes=consumes)
549567
elif isinstance(model, YamlEnergyUsageModelPump):

tests/libecalc/conftest.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,59 @@
22

33
from libecalc.common.energy_usage_type import EnergyUsageType
44
from libecalc.common.utils.rates import RateType
5+
from libecalc.common.variables import ExpressionEvaluator
56
from libecalc.domain.infrastructure.energy_components.legacy_consumer.consumer_function.direct_expression_consumer_function import (
67
DirectExpressionConsumerFunction,
78
)
8-
from libecalc.expression import Expression
9+
from libecalc.domain.regularity import Regularity
10+
from libecalc.expression.expression import ExpressionType
11+
from libecalc.presentation.yaml.domain.expression_time_series_flow_rate import ExpressionTimeSeriesFlowRate
12+
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
913

1014

1115
@pytest.fixture
12-
def direct_expression_model_factory():
16+
def regularity_factory():
17+
def create_regularity(expression_evaluator: ExpressionEvaluator, regularity_value: ExpressionType = 1):
18+
regularity = Regularity(
19+
expression_input=regularity_value,
20+
expression_evaluator=expression_evaluator,
21+
target_period=expression_evaluator.get_period(),
22+
)
23+
return regularity
24+
25+
return create_regularity
26+
27+
28+
@pytest.fixture
29+
def direct_expression_model_factory(regularity_factory):
1330
def create_direct_expression_model(
14-
expression: Expression,
31+
expression: ExpressionType,
1532
energy_usage_type: EnergyUsageType,
33+
expression_evaluator: ExpressionEvaluator,
1634
consumption_rate_type: RateType = RateType.STREAM_DAY,
35+
regularity: Regularity | None = None,
1736
):
37+
if regularity is None:
38+
regularity = regularity_factory(expression_evaluator)
39+
40+
usage_expression = TimeSeriesExpression(expressions=expression, expression_evaluator=expression_evaluator)
41+
usage = ExpressionTimeSeriesFlowRate(
42+
time_series_expression=usage_expression, regularity=regularity, consumption_rate_type=consumption_rate_type
43+
)
44+
1845
if energy_usage_type == EnergyUsageType.POWER:
1946
return DirectExpressionConsumerFunction(
2047
energy_usage_type=energy_usage_type,
2148
condition=None,
22-
load=expression,
49+
load=usage,
2350
power_loss_factor=None,
24-
consumption_rate_type=consumption_rate_type,
2551
)
2652
else:
2753
return DirectExpressionConsumerFunction(
2854
energy_usage_type=energy_usage_type,
2955
condition=None,
30-
fuel_rate=expression,
56+
fuel_rate=usage,
3157
power_loss_factor=None,
32-
consumption_rate_type=consumption_rate_type,
3358
)
3459

3560
return create_direct_expression_model

tests/libecalc/core/consumers/conftest.py

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,43 +60,38 @@ def create_tabulated_fuel_consumer(
6060
@pytest.fixture
6161
def electricity_consumer_factory(direct_expression_model_factory):
6262
def _create_electricity_consumer(
63-
expression_evaluator: ExpressionEvaluator, energy_usage_model: TemporalModel = None
63+
expression_evaluator: ExpressionEvaluator, energy_usage_model: TemporalModel = None, values: list[float] = None
6464
) -> ElectricityConsumer:
65+
regularity = Regularity(
66+
expression_evaluator=expression_evaluator,
67+
target_period=expression_evaluator.get_period(),
68+
expression_input=1,
69+
)
6570
if energy_usage_model is None:
71+
periods = expression_evaluator.get_periods().periods
72+
if values is None:
73+
values = [0.0] * len(periods)
74+
else:
75+
# Pad or truncate values to match periods
76+
values = (values + [0.0] * len(periods))[: len(periods)]
77+
6678
energy_usage_model = TemporalModel(
6779
{
68-
Period(datetime(2020, 1, 1), datetime(2021, 1, 1)): direct_expression_model_factory(
69-
expression=Expression.setup_from_expression(value=1),
70-
energy_usage_type=EnergyUsageType.POWER,
71-
consumption_rate_type=RateType.STREAM_DAY,
72-
),
73-
Period(
74-
datetime(2021, 1, 1), datetime(2022, 1, 1)
75-
): direct_expression_model_factory( # Run above capacity
76-
expression=Expression.setup_from_expression(value=2),
80+
period: direct_expression_model_factory(
81+
expression=value,
7782
energy_usage_type=EnergyUsageType.POWER,
7883
consumption_rate_type=RateType.STREAM_DAY,
79-
),
80-
Period(
81-
datetime(2022, 1, 1), datetime(2023, 1, 1)
82-
): direct_expression_model_factory( # Run above capacity
83-
expression=Expression.setup_from_expression(value=10),
84-
energy_usage_type=EnergyUsageType.POWER,
85-
consumption_rate_type=RateType.STREAM_DAY,
86-
),
87-
Period(datetime(2023, 1, 1)): direct_expression_model_factory( # Ensure we handle 0 load as well.
88-
expression=Expression.setup_from_expression(value=0),
89-
energy_usage_type=EnergyUsageType.POWER,
90-
consumption_rate_type=RateType.STREAM_DAY,
91-
),
84+
expression_evaluator=expression_evaluator.get_subset(
85+
*period.get_period_indices(expression_evaluator.get_periods())
86+
),
87+
regularity=regularity.get_subset(
88+
*period.get_period_indices(expression_evaluator.get_periods())
89+
),
90+
)
91+
for period, value in zip(periods, values)
9292
}
9393
)
9494

95-
regularity = Regularity(
96-
expression_evaluator=expression_evaluator,
97-
target_period=expression_evaluator.get_period(),
98-
expression_input=1,
99-
)
10095
return ElectricityConsumer(
10196
id=uuid4(),
10297
path_id=PathID("direct_consumer"),

0 commit comments

Comments
 (0)