Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
- Provide access to `capacity` (maximum number of elements) in `MovingWindow`.
- Methods to retrieve oldest and newest timestamp of valid samples are added to both.


- Now when printing `FormulaEngine` for debugging purposes the the formula will be shown in infix notation, which should be easier to read.

- The CI now runs cross-arch tests on `arm64` architectures.

- The `min` and `max` functions in the `FormulaEngine` are now public. Note that the same functions have been public in the builder.

## Bug Fixes

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def __truediv__(
"""
return self._higher_order_builder(self, self._create_method) / other # type: ignore

def _max(
def max(
self, other: _GenericEngine | _GenericHigherOrderBuilder | QuantityT
) -> _GenericHigherOrderBuilder:
"""Return a formula engine that outputs the maximum of `self` and `other`.
Expand All @@ -180,7 +180,7 @@ def _max(
"""
return self._higher_order_builder(self, self._create_method).max(other) # type: ignore

def _min(
def min(
self, other: _GenericEngine | _GenericHigherOrderBuilder | QuantityT
) -> _GenericHigherOrderBuilder:
"""Return a formula engine that outputs the minimum of `self` and `other`.
Expand Down
64 changes: 57 additions & 7 deletions tests/timeseries/_formula_engine/test_formula_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,68 @@ async def test_formula_composition_missing_bat(self, mocker: MockerFixture) -> N
assert count == 10

async def test_formula_composition_min_max(self, mocker: MockerFixture) -> None:
"""Test the composition of formulas with min/max values."""
"""Test the composition of formulas with the min and max."""
mockgrid = MockMicrogrid(grid_meter=True)
mockgrid.add_chps(1)
await mockgrid.start(mocker)

logical_meter = microgrid.logical_meter()
engine_min = logical_meter.grid_power._min( # pylint: disable=protected-access
Power.zero()
).build("grid_power_min")
engine_min = logical_meter.grid_power.min(logical_meter.chp_power).build(
"grid_power_min"
)
engine_min_rx = engine_min.new_receiver()
engine_max = logical_meter.grid_power.max(logical_meter.chp_power).build(
"grid_power_max"
)
engine_max_rx = engine_max.new_receiver()

await mockgrid.mock_resampler.send_meter_power([100.0, 200.0])

# Test min
min_pow = await engine_min_rx.receive()
assert (
min_pow and min_pow.value and min_pow.value.isclose(Power.from_watts(100.0))
)

# Test max
max_pow = await engine_max_rx.receive()
assert (
max_pow and max_pow.value and max_pow.value.isclose(Power.from_watts(200.0))
)

await mockgrid.mock_resampler.send_meter_power([-100.0, -200.0])

# Test min
min_pow = await engine_min_rx.receive()
assert (
min_pow
and min_pow.value
and min_pow.value.isclose(Power.from_watts(-200.0))
)

# Test max
max_pow = await engine_max_rx.receive()
assert (
max_pow
and max_pow.value
and max_pow.value.isclose(Power.from_watts(-100.0))
)

await engine_min._stop() # pylint: disable=protected-access
await mockgrid.cleanup()
await logical_meter.stop()

async def test_formula_composition_min_max_const(
self, mocker: MockerFixture
) -> None:
"""Test the compositing formulas and constants with the min and max functions."""
mockgrid = MockMicrogrid(grid_meter=True)
await mockgrid.start(mocker)

logical_meter = microgrid.logical_meter()
engine_min = logical_meter.grid_power.min(Power.zero()).build("grid_power_min")
engine_min_rx = engine_min.new_receiver()
engine_max = logical_meter.grid_power._max( # pylint: disable=protected-access
Power.zero()
).build("grid_power_max")
engine_max = logical_meter.grid_power.max(Power.zero()).build("grid_power_max")
engine_max_rx = engine_max.new_receiver()

await mockgrid.mock_resampler.send_meter_power([100.0])
Expand Down
72 changes: 72 additions & 0 deletions tests/timeseries/test_formula_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,78 @@ async def test_simple(self) -> None:
],
)

async def test_min_max(self) -> None:
"""Test min and max functions in combination."""
await self.run_test(
3,
lambda c2, c4, c5: c2.min(c4).max(c5),
[
([4.0, 6.0, 5.0], 5.0),
],
)

async def test_max(self) -> None:
"""Test the max function."""
await self.run_test(
3,
lambda c2, c4, c5: c2 * c4.max(c5),
[
([10.0, 12.0, 15.0], 150.0),
],
)
await self.run_test(
3,
lambda c2, c4, c5: (c2 * c4).max(c5),
[
([10.0, 12.0, 15.0], 120.0),
],
)
await self.run_test(
3,
lambda c2, c4, c5: (c2 + c4).max(c5),
[
([10.0, 12.0, 15.0], 22.0),
],
)
await self.run_test(
3,
lambda c2, c4, c5: c2 + c4.max(c5),
[
([10.0, 12.0, 15.0], 25.0),
],
)

async def test_min(self) -> None:
"""Test the min function."""
await self.run_test(
3,
lambda c2, c4, c5: (c2 * c4).min(c5),
[
([10.0, 12.0, 15.0], 15.0),
],
)
await self.run_test(
3,
lambda c2, c4, c5: c2 * c4.min(c5),
[
([10.0, 12.0, 15.0], 120.0),
],
)
await self.run_test(
3,
lambda c2, c4, c5: (c2 + c4).min(c5),
[
([10.0, 2.0, 15.0], 12.0),
],
)
await self.run_test(
3,
lambda c2, c4, c5: c2 + c4.min(c5),
[
([10.0, 12.0, 15.0], 22.0),
],
)

async def test_compound(self) -> None:
"""Test compound formulas."""
await self.run_test(
Expand Down