Skip to main content

Library for x

Project description

El Arb: on-grid facility management

Profit-maximising control of inverters in a PV + inverter + battery + grid setup.

           ┌───────┐       
    ┌─x1──▶│ Grid  │──┐    
    │      └───────┘  │    
┌───────┐      ▲      │    
│ Solar │      │x3    │x4    
└───────┘      │      │    
    │      ┌───────┐  │    
    └─x2──▶│Battery│◀─┘    
           └───────┘       

Installation

Install using pip:

pip install elarb

Local development

make venv
source venv/bin/activate
make install_dev
make test

Example

Below is an example of using the optimiser over 24 hours starting with an empty battery. The scenario roughly corresponds to a small facility in Denmark for 36 hours in July. Since the optimiser requires a lot of parameters, e.g. for PV production, battery characteristics etc., the example is necessarily quite verbose.

import numpy as np

from elarb.optimiser import optimise, Instance
from elarb.models import SolarPanel, Battery, GridConnection, Inverter, Facility

# solar panels: 470Wp JinKO TigerNeo N-Type 60HL4 BF, 1.57 kWh / time, costs 1565 DKK
panel = SolarPanel(m2=0.75, depreciation_per_hour=0.006)
# Deye SUN-12K-SG04LP3-EU
inverter = Inverter(depreciation_per_hour=0.05, throughput_kWh=15.6, conversion_loss_pct=0.03)
# battery: Powerwall LBATTS Powerwall, 8.8 kwh
# bat_cost_dkk = 21000; bat_cycles = 6000; bat_depreciation = np.round(bat_cost_dkk / (8.8*bat_cycles), 3)
battery = Battery(depreciation_per_kWh=0.3, throughput_kWh=3.3, capacity_kWh=8.8, conversion_loss_pct=0.03)
# DK grid, udvidet stikledning
amp = 63
volt = 400
grid = GridConnection(throughput_kWh=amp * volt / 1000)

# Connect components in a facility
facility=Facility(
    panel=panel,
    battery=battery,
    inverter=inverter,
    grid=grid,
    n_panels=12,
    n_batteries=1,
    n_inverters=1,
)


spot_price = np.array([
    2.94, 2.73, 2.72, 3.01, 3.03, 3.44, 3.65, 3.82, 3.75, 3.61, 3.43,
    3.16, 3.16, 1.47, 1.47, 1.47, 1.47, 1.47, 3.03, 3.15, 3.07, 2.89,
    2.5 , 2.3 , 2.  , 1.76, 1.84, 2.17, 2.36, 2.89, 3.17, 3.38, 3.48,
    3.47, 3.47, 3.29])

pv_kwh = np.array([
    0.32, 0.27, 0.2 , 0.13, 0.09, 0.06, 0.02, 0.  , 0.  , 0.  , 0.  ,
    0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.02, 0.07, 0.16, 0.3 , 0.47,
    0.48, 0.46, 0.33, 0.34, 0.28, 0.23, 0.16, 0.1 , 0.04, 0.01, 0.  ,
    0.  , 0.  , 0.  ])
# https://radiuselnet.dk/elnetkunder/tariffer-og-netabonnement/
net_tariff = np.array([
    0.25, 0.25, 0.25, 0.25, 0.25, 0.66, 0.66, 0.66, 
    0.66, 0.25, 0.25, 0.25, 0.17, 0.17, 0.17, 0.17, 
    0.17, 0.17, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 
    0.25, 0.25, 0.25, 0.25, 0.25, 0.66, 0.66, 0.66, 
    0.66, 0.25, 0.25, 0.25, ])
spot_demand_kWh = np.zeros(36) + 9999
spot_supply_kWh = np.zeros(36) + 9999

instance = Instance(
    facility=facility,
    spot_price=spot_price,
    pv_dc_kWh_m2=pv_kwh,
    net_tariff=net_tariff,
    spot_demand_kWh=spot_demand_kWh,
    spot_supply_kWh=spot_supply_kWh,
    initial_soc=0.0
)

res = optimise(instance)

print()
print('Result')
print('- Profit:', np.round(res.value, 3))
print('- PV contribution:', np.round(res.x1_contrib, 3))
print('- X2 contribution:', np.round(res.x2_contrib, 3))
print('- X3 contribution:', np.round(res.x3_contrib, 3))
print('- X4 contribution:', np.round(res.x4_contrib, 3))
print('- Panel deprecation:', np.round(res.panel_depreciation, 3))
print('- Inverter deprecation:', np.round(res.inverter_depreciation, 3))
print('- Battery deprecation:', np.round(res.battery_depreciation, 3))
print('- Battery soc:', res.battery_soc_kWh.round(3))

"""
Result
- Profit: 108.738
- PV contribution: 56.369
- X2 contribution: 0.0
- X3 contribution: 79.023
- X4 contribution: -14.574
- Panel deprecation: 2.592
- Inverter deprecation: 1.8
- Battery deprecation: 7.688
- Battery soc: [0.    2.794 5.151 6.897 8.032 8.032 8.032 8.032 6.6   6.6   3.3   0.
 0.    0.    2.081 4.035 6.198 8.62  8.795 6.6   3.3   0.    0.    0.
 0.    2.881 5.849 8.294 8.785 8.8   8.8   8.8   8.8   8.8   5.5   2.2  ]
"""

Theory

There are four types of resources in the problem:

  • Solar panels
  • Inverter
  • Battery
  • Grid

We will assume that all resources are located all in the same grid region, e.g. DK1 or DK2.

The problem has the markov property in that the current state only depends on the previous state.

Graph of who can send kWh to who:

           ┌───────┐       
    ┌─x1──▶│ Grid  │──┐    
    │      └───────┘  │    
┌───────┐      ▲      │    
│ Solar │      │x3    │x4    
└───────┘      │      │    
    │      ┌───────┐  │    
    └─x2──▶│Battery│◀─┘    
           └───────┘       

Problem formulation

think about how/if to include conversion loss in formula. Can it be modelled as a "tax" on x3?

Problem formulation:

$$ \begin{align*} \underset{x}{\mathrm{argmax}} \quad \Sigma_{t \in T} & \quad x1(t) \cdot p_{grid}(t) - x1(t) \cdot p_{solar}(t) \ & + \quad x2(t) \cdot p_{battery}(t) - x2(t) \cdot p_{solar}(t) \ & + \quad x3(t) \cdot p_{grid}(t) - x3(t) \cdot p_{battery}(t) \ & + \quad x4(t) \cdot p_{battery}(t) - x4(t) \cdot p_{grid}(t) \ \text{s.t.} & \ & \quad x1(t) + x3(t) \leq d_{grid}(t) \ & \quad x2(t) + x4(t) \leq d_{battery}(t) \ & \quad x1(t) + x2(t) \leq s_{solar}(t) \ & \quad x3(t) \leq s_{battery}(t) \ & \quad x4(t) \leq s_{grid}(t) \ & \quad x1(t) + x4(t) \leq d_{grid}(t) \ & \quad max \lbrace init_{battery} + \Sigma_{t=0}^{i} x2(t)+x4(t)-x3(t) \rbrace \leq C_{battery}, \forall i \in \lbrack 1, T \rbrack \ & x1(t) \in \mathbb{Z}^+, x2(t) \in \mathbb{Z}^+, x3(t) \in \mathbb{Z}^+, x4(t) \in \mathbb{Z}^+, \quad \forall t \ \end{align*} $$

Tips:

  • We can use cvxpy's cumsum along with max for capacity constraint! The sum over time (x2+x4-x3).

Notes:

  • We can assume that $d_{grid} = \infty, \forall t$, but the constraint is included anyway
  • Instead of $d_{battery}(t)$ we should model that the net amount sold to battery in previous time (i.e. all t' < t) plus the initial charge cannot exceed the capacity, for all t.
  • Time t is discretised into buckets of one hour and capital T denotes the last time bucket
  • Consult the tables below for all variable and constraint descriptions
  • Maybe we need a higher $p_{battery}$ when SoC < 10%, because higher depreciation
  • Maybe we need an initial SoC for the battery, in order to model the capacity constraint correctly

Variables

Decision

Variable Description
x1(t) ∈ Z+ kWh to buy from of solar and sell to grid at time t
x2(t) ∈ Z+ kWh to buy from solar and sell to battery at time t
x3(t) ∈ Z+ kWh to buy from battery and sell to grid at time t
x4(t) ∈ Z+ kWh to buy from grid and sell to battery at time t

Prices

We assume that the price is the same regardless of whether you buy or sell. This can easily be generalised to different prices for buying and selling.

Variable Description
p_solar(t) ∈ Z+ The price of buying 1 kWh from the solar panel at time t, which depends on the depreciation of the panel per kWh produced
p_grid(t) ∈ Z+ The price to buy/sell 1 kWh from/to the grid, at time t, which equals the spot price in the region
p_battery(t) ∈ Z+ The price to buy/sell 1 kWh from the battery, which equals the approximate depreciation of the battery per kWh stored

Supply

Variable Description
s_solar(t) ∈ Z+ The amount of kWh produced by the solar panel at time t, which depends, e.g., on how much the sun shines at time t
s_grid(t) ∈ Z+ The amount of kWh available to buy from the grid at time t in the given region
s_battery(t) ∈ Z+ The amount of kWh available to buy from the battery at time t, which depends on the current SoC, the discharge speed and the conversion loss

Demand

Variable Description
d_grid(t) ∈ Z+ The amount of kWh that can be sold to the grid at time t
d_battery(t) ∈ Z+ The amount of kWh that can be sold to the battery at time t, which depends on the current SoC, the charge speed and the conversion loss

Capacity

Variable Description
init_battery ∈ Z+ The initial state of charge (SoC) of the battery in kWh
C_battery The capacity of the battery in kWh

Constraints

Expression Description
x1(t) + x3(t) ≤ d_grid(t) the amount to sell to the grid cannot exceed the demand of the grid (theoretical)
x2(t) + x4(t) ≤ d_battery(t) the amount to sell to the battery cannot exceed the demand (i.e. capability to charge) of the battery
x1(t) + x2(t) ≤ s_solar(t) The amount sold to the grid plus the battery cannot exceed the kWh produced by solar panels
x3(t) ≤ s_battery(t) the amound to buy from battery cannot exceed the supply of the battery
x4(t) ≤ s_grid(t) the amound to buy from grid cannot exceed the supply of the grid
x1(t) + x4(t) ≤ d_grid(t) (???) Note: mistake here? The amound to buy from the solar panel and the grid cannot exceed the demand of the battery
max (init_battery + Σ_t=0^i (x2(t)+x4(t)-x3(t)) ≤ C_battery, ∀ i ∈ [1, T] Capacity constraint of battery. Compute the cumulative sum of input/output of battery (x2, x3, x4) up to t for all t, which must be below capacity for all t

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

elarb-0.1.0-py3-none-any.whl (8.1 kB view details)

Uploaded Python 3

File details

Details for the file elarb-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: elarb-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.2

File hashes

Hashes for elarb-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 edd6f1d4cb442c3594f0a3a626fde1a68a41cc9842f770cdba895c9b48c2484d
MD5 9ef3a2ece2558b35d4111b3a23ed05c0
BLAKE2b-256 6b5d4712fd3755c84220f584e0c5eaa7ab9b1db7c38a49ba0148e471f9ad3d94

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page