Skip to content

Commit

Permalink
Merge pull request #99 from tum-ens/new_version
Browse files Browse the repository at this point in the history
v0.1.5
  • Loading branch information
TUM-Doepfert authored Oct 14, 2024
2 parents b1ecc38 + f784831 commit 2b2332a
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 34 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.idea

# Folders
00_docs/*
01_examples/*
02_config/*
03_input_data/*
04_scenarios/*
05_results/*

Expand Down
28 changes: 19 additions & 9 deletions hamlet/executor/markets/lem/lem.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,18 +254,18 @@ def __method_none(self, bids, offers, pricing_method):
"""Clears the market with no clearing method, i.e. only the retailer acts as trading partner"""

# Merge bids and offers on the energy_cumsum column
bids_offers = bids.join(offers, on=C_ENERGY_CUMSUM, how='outer')
bids_offers = pl.concat([bids, offers], how='diagonal')

# Sort the bids and offers by the energy_cumsum
bids_offers = bids_offers.sort(C_ENERGY_CUMSUM, descending=False)

# Remove all columns that end on _right
double_cols = [col for col in bids_offers.columns if col.endswith('_right')]
for col in double_cols:
orig_col = col.rsplit('_', 1)[0]
bids_offers = bids_offers.with_columns(pl.coalesce([orig_col, col]).alias(orig_col))
bids_offers = bids_offers.drop(double_cols)

# Fill the NaN values with the retailer
bids_offers = bids_offers.with_columns([
pl.when(pl.col(c.TC_ID_AGENT_IN).is_not_null()).then(pl.col(c.TC_ID_AGENT_IN))
.otherwise(pl.lit('retailer')).alias(c.TC_ID_AGENT_IN),
pl.when(pl.col(c.TC_ID_AGENT_OUT).is_not_null()).then(pl.col(c.TC_ID_AGENT_OUT))
.otherwise(pl.lit('retailer')).alias(c.TC_ID_AGENT_OUT),
])

# Create bids and offers table where retailer is the only trading partner
# Note: This currently works only for one retailer named 'retailer' in the future it needs to first obtain the
Expand All @@ -277,6 +277,10 @@ def __method_none(self, bids, offers, pricing_method):
bids = bids.fill_null(strategy='backward')
offers = offers.fill_null(strategy='backward')

# Filter rows that have the same agent id in the in and out column (cannot trade with themselves)
bids = bids.filter(pl.col(c.TC_ID_AGENT_IN) != pl.col(c.TC_ID_AGENT_OUT))
offers = offers.filter(pl.col(c.TC_ID_AGENT_IN) != pl.col(c.TC_ID_AGENT_OUT))

# Clear bids and offers
bids_cleared = bids.filter(pl.col(c.TC_PRICE_PU_IN) >= pl.col(c.TC_PRICE_PU_OUT))
offers_cleared = offers.filter(pl.col(c.TC_PRICE_PU_IN) >= pl.col(c.TC_PRICE_PU_OUT))
Expand All @@ -288,7 +292,6 @@ def __method_none(self, bids, offers, pricing_method):
# Create new dataframe with the cleared bids and offers
trades_cleared = pl.concat([bids_cleared, offers_cleared], how='vertical')


# Calculate the price and energy of the trades
trades_cleared = trades_cleared.with_columns(
(trades_cleared.select([c.TC_ENERGY_IN, c.TC_ENERGY_OUT]).min(axis=1).alias(c.TC_ENERGY)),
Expand Down Expand Up @@ -321,6 +324,7 @@ def __create_bids_offers(self, include_retailer=True):
retailer = retailer.with_columns(
[
pl.col(c.TC_TIMESTAMP).alias(c.TC_TIMESTEP),
pl.lit(self.bids_offers.select(pl.first(c.TC_TIMESTAMP))).alias(c.TC_TIMESTAMP),
pl.lit(None).alias(c.TC_ENERGY_TYPE),
# TODO: This can be removed once the energy type is added to the retailer table
]
Expand All @@ -332,6 +336,8 @@ def __create_bids_offers(self, include_retailer=True):

retailer = retailer.with_columns(
[
pl.col(c.TC_TIMESTAMP).cast(pl.Datetime(time_unit='ns', time_zone='UTC'), strict=False),
pl.col(c.TC_TIMESTEP).cast(pl.Datetime(time_unit='ns', time_zone='UTC'), strict=False),
pl.col(c.TC_REGION).cast(pl.Categorical, strict=False),
pl.col(c.TC_MARKET).cast(pl.Categorical, strict=False),
pl.col(c.TC_NAME).cast(pl.Categorical, strict=False),
Expand Down Expand Up @@ -691,6 +697,10 @@ def __apply_grid_levies(self, transactions):
# Concat the grids and levies
transactions = pl.concat([grid, levies], how='align')

# Set the timestamp column to the current timestamp
transactions = transactions.with_columns(pl.lit(self.tasks[c.TC_TIMESTAMP]).alias(c.TC_TIMESTAMP)
.cast(pl.Datetime(time_unit='ns', time_zone='UTC')))

return transactions

def __method_pda(self, bids, offers, pricing_method):
Expand Down
25 changes: 25 additions & 0 deletions hamlet/executor/utilities/controller/mpc/lincomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,31 @@ def __init__(self, name, **kwargs):
self.b2g = self.info['sizing']['b2g']
self.g2b = self.info['sizing']['g2b']

def define_constraints(self, model: Model) -> Model:
model = super().define_constraints(model)

# Add constraint that the battery can only charge from the grid if the b2g flag is set to true
model = self._constraint_b2g(model)

return model

def _constraint_b2g(self, model: Model) -> Model:
"""Adds the constraint that the battery can only charge from the grid if the b2g flag is set to true."""

# Define the variables
markets = self.info['markets']

# Define the constraint if b2g is disabled
if not self.b2g:
for market, energy in markets.items():
if energy == c.ET_ELECTRICITY: # Only electricity markets are considered
equation_disable_b2g = (model.variables[f'{self.name}_{self.comp_type}_mode'] -
model.variables[f'{market}_mode'] <= 0)
model.add_constraints(equation_disable_b2g, name=f'{self.name}_b2g_{market}',
coords=[self.timesteps])

return model


class Psh(SimpleStorage):

Expand Down
39 changes: 20 additions & 19 deletions hamlet/executor/utilities/controller/mpc/mpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __init__(self, **kwargs):
self.timesteps = pd.Index(range(len(self.timesteps)), name='timesteps')
# self.timesteps = self.n_steps # Use this line if the timesteps are not needed and the index is sufficient
# Reduce the socs to the current timestamp
self.socs = self.socs.filter(self.socs[c.TC_TIMESTAMP] == self.timestamp)
self.socs = self.socs.filter(self.socs[c.TC_TIMESTAMP] == self.timestamp + self.dt)

# Get the market types
# TODO: Still needs to be done and then adjusted in the market objects (right now the names are simply
Expand Down Expand Up @@ -125,19 +125,32 @@ def __init__(self, **kwargs):
c.P_HEAT_STORAGE: lincomps.HeatStorage,
}

# Create the plant objects
self.plant_objects = {}
self.create_plants()

# Create the market objects
self.market_objects = {}
self.create_markets()

# Create the plant objects
self.plant_objects = {}
self.create_plants()

# Define the model
self.define_variables()
self.define_constraints()
self.define_objective()

def create_markets(self):
""""""

# Define variables from the market results and a balancing variable for each energy type
for market in self.markets:
# Create market object
self.market_objects[f'{market}'] = lincomps.Market(name=market,
forecasts=self.forecasts,
timesteps=self.timesteps,
delta=self.dt)

return self.market_objects

def create_plants(self):
for plant_name, plant_data in self.plants.items():

Expand All @@ -163,23 +176,11 @@ def create_plants(self):
**plant_data,
socs=socs,
delta=self.dt,
timesteps=self.timesteps)
timesteps=self.timesteps,
markets=self.markets)

return self.plant_objects

def create_markets(self):
""""""

# Define variables from the market results and a balancing variable for each energy type
for market in self.markets:
# Create market object
self.market_objects[f'{market}'] = lincomps.Market(name=market,
forecasts=self.forecasts,
timesteps=self.timesteps,
delta=self.dt)

return self.market_objects

def define_variables(self):
# Define variables for each plant
for plant_name, plant in self.plant_objects.items():
Expand Down
11 changes: 6 additions & 5 deletions hamlet/executor/utilities/controller/rtc/lincomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ def define_variables(self, model, **kwargs):
def define_constraints(model):
return model

def define_electricity_variable(self, model, comp_type, lower, upper, integer=True) -> Model:
def define_electricity_variable(self, model, comp_type, lower, upper, integer=False) -> Model:
# Define the power variable
model.add_variables(name=f'{self.name}_{comp_type}_{c.ET_ELECTRICITY}', lower=lower, upper=upper, integer=integer)

return model

def define_heat_variable(self, model, comp_type, lower, upper, load_target=None, integer=True) -> Model:
def define_heat_variable(self, model, comp_type, lower, upper, load_target=None, integer=False) -> Model:
# Define the power variable
if load_target is None:
name = f'{self.name}_{comp_type}_{c.ET_HEAT}'
Expand All @@ -49,7 +49,7 @@ def define_heat_variable(self, model, comp_type, lower, upper, load_target=None,

return model

def define_cool_variable(self, model, comp_type, lower, upper, load_target=None, integer=True) -> Model:
def define_cool_variable(self, model, comp_type, lower, upper, load_target=None, integer=False) -> Model:
# Define the power variable
if load_target is None:
name = f'{self.name}_{comp_type}_{c.ET_COOLING}'
Expand All @@ -59,7 +59,7 @@ def define_cool_variable(self, model, comp_type, lower, upper, load_target=None,

return model

def define_h2_variable(self, model, comp_type, lower, upper, integer=True) -> Model:
def define_h2_variable(self, model, comp_type, lower, upper, integer=False) -> Model:
# Define the power variable
model.add_variables(name=f'{self.name}_{comp_type}_{c.ET_H2}', lower=lower, upper=upper, integer=integer)

Expand Down Expand Up @@ -398,7 +398,8 @@ def __init__(self, name, **kwargs):

# Kwargs variables
self.dt = kwargs['delta'].total_seconds() # time delta in seconds
self.soc = kwargs['socs'][f'{self.name}'][0] - self.energy # state of charge at timestep (energy)
self.soc = max(0, kwargs['socs'][f'{self.name}'][0] - self.energy) # state of charge at timestep (energy);
# must be greater than 0, overconsumption is assumed to be compensated elsewhere

# Calculate the energy needed to charge to full
self.energy_to_full = self.capacity - self.soc # energy needed to charge to full
Expand Down
2 changes: 1 addition & 1 deletion hamlet/executor/utilities/controller/rtc/rtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def create_plants(self):

# Retrieve the soc data for the plant (if applicable)
cols = [col for col in self.socs.columns if col.startswith(plant_name)]
socs = self.socs.select(cols)
socs = self.socs.filter(pl.col(c.TC_TIMESTAMP) == self.timestamp).select(cols)

# Get the plant class
plant_class = self.available_plants.get(plant_type)
Expand Down

0 comments on commit 2b2332a

Please sign in to comment.