Esempio n. 1
0
def test_export_when_remote_export_value_greater_than_local_export_value():
    """When there is some constant local demand, but enough capacity to satisfy this
    and also export at a higher value, we should still find a solution that maximises value.

    Note: this test currently fails to converge. Setting demand to zero works fine, 
    but any non-zero demand fails to converge.
    """
    energy_storage = create_battery()
    energy_storage.max_capacity = 4.0 * 48
    energy_storage.initial_state_of_charge = energy_storage.max_capacity
    energy_storage.discharging_power_limit = -20.0
    energy_system = create_energy_system(
        energy_storage,
        np.array([1.0] * 48),
        np.array([0.0] * 48),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF * 0.1)),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF * 0.2)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
    )
    optimiser = LocalEnergyOptimiser(30, N_INTERVALS, energy_system,
                                     OptimiserObjectiveSet.LocalModels)

    np.testing.assert_array_equal(optimiser.values("storage_discharge_demand"),
                                  np.ones(N_INTERVALS) * -2.0)
    np.testing.assert_array_equal(optimiser.values("storage_discharge_grid"),
                                  np.ones(N_INTERVALS) * -2.0)
    np.testing.assert_array_equal(optimiser.values("local_demand_transfer"),
                                  np.zeros(N_INTERVALS))
Esempio n. 2
0
def test_simple_arbitrage_for_cheaper_local_energy():
    """When the local energy import tariff is cheaper than the remote energy import tariff,
    the battery should charge off excess local generation.
    """
    energy_system = create_energy_system(
        create_battery(),
        np.array([0.0] * 24 + [5.0] * 24),
        np.array([-5.0] * 24 + [0.0] * 24),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
    )
    optimiser = LocalEnergyOptimiser(30, N_INTERVALS, energy_system,
                                     OptimiserObjectiveSet.LocalModels)
    storage_charge_total = optimiser.values("storage_charge_total")
    storage_discharge_total = optimiser.values("storage_discharge_total")
    for i in range(0, 24):
        assert storage_discharge_total[i] == 0.0
        assert storage_charge_total[i] == pytest.approx(
            energy_system.energy_storage.capacity / 24.0, 0.01)
    for i in range(24, 48):
        assert storage_discharge_total[i] == pytest.approx(
            -energy_system.energy_storage.capacity / 24.0, 0.01)
        assert storage_charge_total[i] == 0.0
Esempio n. 3
0
def test_cannot_remote_export_before_satisfying_local_demand():
    """Test that we cannot create an electrically infeasible solution where the battery
    is discharging into the remote grid (e.g. to take advantage of a high remote export value)
    when there is local demand that must be satisfied first
    """
    energy_storage = create_battery()
    energy_storage.max_capacity = 4.0 * 48
    energy_storage.initial_state_of_charge = energy_storage.max_capacity
    energy_storage.discharging_power_limit = -4.0
    energy_system = create_energy_system(
        energy_storage,
        np.array([2.0] * 48),
        np.array([0.0] * 48),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 3.0)),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 1.1)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
    )
    optimiser = LocalEnergyOptimiser(30, N_INTERVALS, energy_system,
                                     OptimiserObjectiveSet.LocalModels)

    np.testing.assert_array_equal(optimiser.values("local_net_import"),
                                  np.zeros(N_INTERVALS))
    np.testing.assert_array_equal(optimiser.values("local_net_export"),
                                  np.zeros(N_INTERVALS))
    np.testing.assert_array_equal(optimiser.values("storage_discharge_demand"),
                                  np.ones(N_INTERVALS) * -2.0)
    np.testing.assert_array_equal(optimiser.values("local_demand_transfer"),
                                  np.zeros(N_INTERVALS))
Esempio n. 4
0
def test_local_greedy_demand_optimisation():
    """Storage should preferentially discharge to meet demand earlier
    when tariffs are equal across time periods
    """
    energy_system = create_energy_system(
        create_battery(),
        np.array([0.0] * 24 + [5.0] * 24),
        np.array([-5.0] * 24 + [0.0] * 24),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(SIMPLE_FLAT_TARIFF * 1.5)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 3.0)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
    )
    optimiser = LocalEnergyOptimiser(
        30,
        N_INTERVALS,
        energy_system,
        OptimiserObjectiveSet.LocalModels +
        [OptimiserObjective.GreedyDemandDischarging],
    )

    storage_charge_generation = optimiser.values("storage_charge_generation")
    storage_discharge_demand = optimiser.values("storage_discharge_demand")
    for i in range(0, 24):
        assert storage_charge_generation[i] == pytest.approx(1.0 / 6.0, 3)
    for i in range(24, N_INTERVALS):
        assert storage_charge_generation[i] == 0.0

    for i in range(0, 24):
        assert storage_discharge_demand[i] == 0.0
    for i in range(24, 28):
        assert storage_discharge_demand[i] == -1.0
    for i in range(28, N_INTERVALS):
        assert storage_discharge_demand[i] == 0.0
Esempio n. 5
0
def test_no_action_for_equal_export_import_price():
    """Given a simple tariff structure where transport costs are zero, and import
    tariffs are twice export tariffs, with no distinction between local and remote tariffs,
    the battery should not charge at all.
    """
    energy_system = create_energy_system(
        create_battery(),
        np.array([0.0] * 24 + [5.0] * 24),
        np.array([-5.0] * 24 + [0.0] * 24),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
    )
    optimiser = LocalEnergyOptimiser(30, N_INTERVALS, energy_system,
                                     OptimiserObjectiveSet.LocalModels)

    np.testing.assert_array_equal(optimiser.values("storage_state_of_charge"),
                                  np.zeros(N_INTERVALS))
Esempio n. 6
0
def test_reasonable_local_optimisation_convergence(demand, generation):
    """This is a fairly straight-forward optimisation problem with randomly generated
    demand and generation. This tests that the optimisation, given a flat set of tariffs,
    will have:
    - battery starting and ending empty
    - local demand transfer always equal to the difference between demand and generation
    - only exporting or importing at each interval
    - only charging or discharging in each interval
    - given current tariffs, the battery should never export to the grid

    Args:
        demand (np.ndarray): Array of (non-negative) energy demand for intervals
        generation (np.ndarray): Array of (non-positive) energy generation values for intervals
    """
    energy_system = create_energy_system(
        create_battery(),
        demand,
        generation,
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 2.0)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
    )
    optimiser = LocalEnergyOptimiser(30, N_INTERVALS, energy_system,
                                     OptimiserObjectiveSet.LocalModels)
    local_net_import = optimiser.values("local_net_import")
    local_net_export = optimiser.values("local_net_export")
    storage_discharge_demand = optimiser.values("storage_discharge_demand")
    storage_discharge_grid = optimiser.values("storage_discharge_grid")
    storage_state_of_charge = optimiser.values("storage_state_of_charge")
    local_demand_transfer = optimiser.values("local_demand_transfer")

    for i in range(N_INTERVALS):
        assert local_net_import[i] >= 0.0
        assert local_net_export[i] <= 0.0
        assert local_net_import[i] == 0.0 or local_net_export[i] == 0.0
        # Convoluted way to say that the discharge to meet demand is always
        # less than the max possible, within some tolerance
        assert storage_discharge_demand[i] - (max(
            0, demand[i] + generation[i])) <= 0.0
        assert storage_discharge_demand[i] <= 0.0
        assert storage_discharge_grid[i] == 0.0

    assert storage_state_of_charge[-1] == 0.0
Esempio n. 7
0
def test_cannot_remote_import_before_satisfying_local_generation():
    """Test that we cannot create an electrically infeasible solution where the battery
    is charging remotely from the grid while there is excess generation.

    This test requires the two electrical feasibility charge constraints to be active
    in order to pass.
    """
    energy_storage = create_battery()
    energy_storage.max_capacity = 4.0 * 48
    energy_storage.discharging_power_limit = -4.0
    energy_system = create_energy_system(
        energy_storage,
        np.array([0.0] * 48),
        np.array([-2.0] * 48),
        dict(enumerate(SIMPLE_FLAT_TARIFF)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 3.0)),
        dict(enumerate(-SIMPLE_FLAT_TARIFF * 1.0)),
        dict(enumerate(SIMPLE_FLAT_TARIFF / 3.0)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
        dict(enumerate(ZERO_TARIFF)),
    )
    optimiser = LocalEnergyOptimiser(
        30, N_INTERVALS, energy_system,
        OptimiserObjectiveSet.LocalModelsThirdParty)

    np.testing.assert_array_equal(optimiser.values("local_net_import"),
                                  np.zeros(N_INTERVALS))
    np.testing.assert_array_equal(optimiser.values("local_net_export"),
                                  np.ones(N_INTERVALS) * -2.0)
    np.testing.assert_array_equal(
        optimiser.values("storage_charge_generation"), np.zeros(N_INTERVALS))
    np.testing.assert_array_equal(optimiser.values("storage_charge_grid"),
                                  np.zeros(N_INTERVALS))
    np.testing.assert_array_equal(optimiser.values("storage_state_of_charge"),
                                  np.zeros(N_INTERVALS))
local_tariff.add_remote_transport_tariff_profile_import(rt_import_tariff_dct)
energy_system.add_demand(load)
energy_system.add_generation(pv)
energy_system.add_local_tariff(local_tariff)


# Dispatch capabilities will be added in a future version
'''dispatch = DispatchRequest()
req = [[4, 7, 12], [(0, 5000), (0, 15000), (0, 28000)]]
req = [[4], [(0, 15000)]]
dispatch.add_dispatch_request_linear_ramp(req)
energy_system.add_dispatch(dispatch)'''

# Invoke the optimiser and optimise
local_energy_models = True
optimiser = LocalEnergyOptimiser(15, 96, energy_system, OptimiserObjectiveSet.LocalModelsThirdParty + OptimiserObjectiveSet.LocalPeakOptimisation)




############################ Analyse the Optimisation ########################################
storage_energy_delta = optimiser.values('storage_charge_grid') +\
                       optimiser.values('storage_charge_generation') +\
                       optimiser.values('storage_discharge_demand') +\
                       optimiser.values('storage_discharge_grid')



colors = sns.color_palette()
hrs = np.arange(0, len(test_load)) / 4
fig = plt.figure(figsize=(14, 7))