Smart components exampleΒΆ
Here follows a heavily commented example of how to use the smart component prototype, the FourQ
import krangpower as kp
import numpy as np
# In this example, we will simulate a battery with a toy logic that decides what to do according to its state and
# a price signal.
# -------------------------------------
# CONSTANTS
# -------------------------------------
PRICEPERIOD = 48
um = kp.UM
# -------------------------------------
# PRICE DEFINITION
# -------------------------------------
# we define a simple sinusoidal price with 12 hour period.
def price(t_hour):
return np.sin(t_hour/PRICEPERIOD/2/np.pi) + np.random.uniform(-0.7, 0.7)
# -------------------------------------
# BATTERY SMART LOGIC DEFINITION
# -------------------------------------
# we have to define the logic we want for the battery into a DecisionModel inherited class.
class BatteryManager(kp.DecisionModel):
def __init__(self, capacity=10*um.kWh, init_soc=5*um.kWh, pwr=3*um.kW, aggressiveness=2):
self.avg_price = 0
self.aggro = aggressiveness
self.nsamples = 0
self.capacity = capacity
self.SOC = init_soc
self.pwr_limit = pwr
self.money_gained = 0
self.pf = 0.985
def decide_pq(self, oek, mynode): # you have to override "decide_pq".
# the logic implemented here decides power according to how low is the price with respect to the recorded
# average and how much the battery is far from being filled at half capacity
# get the price
hour = oek.brain.Solution.DblHour() # returns the simulated hour
el_price = price(hour.magnitude) # this is the sell/buy price
# update the average
self.avg_price = (self.avg_price * self.nsamples + el_price) / (self.nsamples + 1)
self.nsamples += 1
# calculate conveniency and hunger (both are in [-1,1])
conveniency = self.avg_price - el_price
hunger = (self.capacity/2 - self.SOC)/(self.capacity/2)
# calculate the power as the sum of conveniency and hunger, clipped to the battery power limits. The logic
# operates in GENERATOR convention: positive power is produced, negative power is absorbed.
newpower = np.clip(
- (conveniency + hunger) * self.aggro,
- self.pwr_limit.magnitude,
self.pwr_limit.magnitude
)
energy_exchanged = newpower * um.kW * oek.get('stepsize')['stepsize']
# don't forget to update your SOC!
self.SOC += -energy_exchanged # negative energy exchanged CHARGES the battery
self.SOC = np.clip(self.SOC, 0*um.kWh, self.capacity)
# we update the gain/expense
self.money_gained += (energy_exchanged.to('kWh').magnitude * el_price)
# you must give back P, Q
return newpower * um.kW, 0.01 * newpower * um.kW
# -------------------------------------
# CREATING THE CIRCUIT
# -------------------------------------
# creating an appropriate voltage source. We want to simulate a 400V, 50Hz grid.
vs = kp.Vsource(basekv=0.4, frequency=50.0, Isc1=5)
# instantiating the circuit
circuit = kp.Krang('market_example', voltage_source=vs)
# we add a couple lines
circuit['sourcebus', 'bus_1'] << kp.Line(length=50 * um.m).aka('line_1')
circuit['bus_1', 'bus_2'] << kp.Line(length=50 * um.m).aka('line_2')
# definition and addition of our "smart battery" to bus_2
mylogic = BatteryManager()
circuit['bus_2', ] << kp.FourQ(kV=0.4).aka('controlled_battery') * mylogic
# setting the simulation step at 1 hr, tot sim time at three days
circuit.set(stepsize=1 * um.hr, number=72)
# -------------------------------------
# DEFINING WHAT RESULTS TO RECORD
# -------------------------------------
# in order to get interesting results, you have to define functions that take the Krang as argument and calculate
# interesting stuff. You can also refer to other objects in the namespace.
def my_power(oek):
pwr = sum(oek['controlled_battery'].Powers()[0][0:3]).magnitude
# we have to call magnitude, because they're all
# Pint Quantities!
return np.real(pwr)
def my_soc(_oek):
return (mylogic.SOC / mylogic.capacity).magnitude
def voltage_at_main(oek):
return (np.sum(np.abs((oek['sourcebus'].Voltages()[0:3]))) / 3).magnitude
# -------------------------------------
# SOLVE AND DISPLAY
# -------------------------------------
# evalsolve solves the prescribed steps of simulation and at each step evaluates the functions you pass to it.
pwr_hist = circuit.evalsolve(my_power, my_soc, voltage_at_main, as_df=False)
# we get a dict whose keys are the function names and whose values are lists of the results returned by the functions.
# print(pwr_hist)
# we display how many money units we gained with our wise battery operation.
print('\nBalance:')
print(mylogic.money_gained)