From 9504c83beb330d72b6c578abef8c1e2fab72e460 Mon Sep 17 00:00:00 2001 From: Thomas Falch Johansen Date: Fri, 15 Sep 2023 15:59:59 +0200 Subject: [PATCH] tests: add tests for crossover calculation Exceeding flow can be handled with crossover in consumer system. The function that calculated it was hard to understand and verify. Adding tests shows that this is not working correctly (commented tests). Hopefully this makes it easier to understand and to fix the crossover function to work correctly --- .../core/consumers/consumer_system.py | 21 +++++++ .../tests/core/consumers/test_crossover.py | 56 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/ecalc/libraries/libecalc/common/tests/core/consumers/test_crossover.py diff --git a/src/ecalc/libraries/libecalc/common/libecalc/core/consumers/consumer_system.py b/src/ecalc/libraries/libecalc/common/libecalc/core/consumers/consumer_system.py index 7525af545c..d387705a9c 100644 --- a/src/ecalc/libraries/libecalc/common/libecalc/core/consumers/consumer_system.py +++ b/src/ecalc/libraries/libecalc/common/libecalc/core/consumers/consumer_system.py @@ -279,8 +279,29 @@ def _topologically_sort_consumers_by_crossover(crossover: List[int], consumers: def _get_crossover_rates( max_rate: List[float], rates: List[List[float]] ) -> Tuple[NDArray[np.float64], List[List[float]]]: + """ + This function is run over a single consumer only, and is normally run in a for loop + across all "dependent" consumers in the consumer system, such as here, in a consumer system. + + + Args: + max_rate: a list of max rates for one consumer, for each relevant timestep + rates: list of list of the rates being sent in to this consumer. Usually this will have an outer list of length 1, since the consumer + will only have 1 incoming rate. However, due to potential incoming crossover rate, and due to multistream outer length may be > 1. Length of the outer list is + 1 (standard incoming stream) at index 0, + nr of crossover streams + nr of additional incoming streams (multi stream) + + Returns: 1. the additional crossover rate that is required if the total incoming + rate exceeds the max rate capacity for the consumer in question. + 2. the rates within capacity of the given consumer + """ + + # Get total rate across all input rates for this consumer (incl already crossovers from other consumers, if any) total_rate = np.sum(rates, axis=0) + + # If we exceed total rate, we need to calculate exceeding rate, and return that as (potential) crossover rate to + # another consumer crossover_rate = np.where(total_rate > max_rate, total_rate - max_rate, 0) + rates_within_capacity = [] left_over_crossover_rate = crossover_rate.copy() for rate in reversed(rates): diff --git a/src/ecalc/libraries/libecalc/common/tests/core/consumers/test_crossover.py b/src/ecalc/libraries/libecalc/common/tests/core/consumers/test_crossover.py new file mode 100644 index 0000000000..5b0e0b6b12 --- /dev/null +++ b/src/ecalc/libraries/libecalc/common/tests/core/consumers/test_crossover.py @@ -0,0 +1,56 @@ +import numpy as np +import pytest +from libecalc.core.consumers.consumer_system import ConsumerSystem + + +class TestCrossover: + parameterized_crossover_rates = [ + ([], [], [], []), # All empty + ([4, 4], [[2, 2], [2, 2]], [0, 0], [[2, 2], [2, 2]]), # All rates within capacity + ([4, 4], [[3, 3], [1, 1]], [0, 0], [[3, 3], [1, 1]]), # All rates within capacity + ( # Exceeds capacity, cross over required + [4, 4], # 4 for both timesteps + [[4, 4], [1, 1]], # 4 in rate + 1 crossover, 1 exceeding + [1, 1], # 1 exceeding + [[4, 4], [0, 0]], # we have capacity for first stream, but not 2nd stream + ), + ( # Exceeds capacity, cross over required + [4, 4], # 4 for both timesteps + [[5, 5]], # 4 in rate + [1, 1], # 1 exceeding + [[4, 4]], + ), + # ( # FAILS: Exceeds capacity, cross over required - fails as well..correct? leads to expected_rates_within_capacity = [[6, 6], [0, 0]] + # max_rate = [4, 4] # 4 for both timesteps + # rates = [[5, 5], [2, 2]] # 4 in rate + # expected_crossover_rate = [3, 3] # 1 exceeding + # expected_rates_within_capacity = [[4, 4], [0, 0]] + # ), + # ( # FAILS: Exceeds capacity, cross over required - bug? leads to expected_rates_within_capacity = [[6, 6], [0, 0]] + # max_rate = [4, 4] # 4 for both timesteps + # rates = [[5, 5], [0, 0]] # 4 in rate + # expected_crossover_rate = [1, 1] # 1 exceeding + # expected_rates_within_capacity = [[4, 4], [0, 0]] + # ), + ( # Under capacity + [4, 4], # 4 for both timesteps + [[2, 2], [1, 1]], # 3 in rate, below 4 + [0, 0], + [[2, 2], [1, 1]], + ), + ] + + @pytest.mark.parametrize( + "max_rate, rates, expected_crossover_rate, expected_rates_within_capacity", + parameterized_crossover_rates, + ) + def test_get_crossover_rates( + self, + max_rate, + rates, + expected_crossover_rate, + expected_rates_within_capacity, + ): + crossover_rate, rates_within_capacity = ConsumerSystem._get_crossover_rates(max_rate, rates) + assert np.array_equal(crossover_rate, expected_crossover_rate) + assert np.array_equal(rates_within_capacity, expected_rates_within_capacity)