Skip to content

Commit f784831

Browse files
committed
Merge branch 'develop' into 'master'
v0.1.5 See merge request tum-ens/HAMLET!136
2 parents b1ecc38 + e1051c0 commit f784831

6 files changed

Lines changed: 75 additions & 34 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
.idea
22

33
# Folders
4+
00_docs/*
5+
01_examples/*
6+
02_config/*
7+
03_input_data/*
48
04_scenarios/*
59
05_results/*
610

hamlet/executor/markets/lem/lem.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -254,18 +254,18 @@ def __method_none(self, bids, offers, pricing_method):
254254
"""Clears the market with no clearing method, i.e. only the retailer acts as trading partner"""
255255

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

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

262-
# Remove all columns that end on _right
263-
double_cols = [col for col in bids_offers.columns if col.endswith('_right')]
264-
for col in double_cols:
265-
orig_col = col.rsplit('_', 1)[0]
266-
bids_offers = bids_offers.with_columns(pl.coalesce([orig_col, col]).alias(orig_col))
267-
bids_offers = bids_offers.drop(double_cols)
268-
262+
# Fill the NaN values with the retailer
263+
bids_offers = bids_offers.with_columns([
264+
pl.when(pl.col(c.TC_ID_AGENT_IN).is_not_null()).then(pl.col(c.TC_ID_AGENT_IN))
265+
.otherwise(pl.lit('retailer')).alias(c.TC_ID_AGENT_IN),
266+
pl.when(pl.col(c.TC_ID_AGENT_OUT).is_not_null()).then(pl.col(c.TC_ID_AGENT_OUT))
267+
.otherwise(pl.lit('retailer')).alias(c.TC_ID_AGENT_OUT),
268+
])
269269

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

280+
# Filter rows that have the same agent id in the in and out column (cannot trade with themselves)
281+
bids = bids.filter(pl.col(c.TC_ID_AGENT_IN) != pl.col(c.TC_ID_AGENT_OUT))
282+
offers = offers.filter(pl.col(c.TC_ID_AGENT_IN) != pl.col(c.TC_ID_AGENT_OUT))
283+
280284
# Clear bids and offers
281285
bids_cleared = bids.filter(pl.col(c.TC_PRICE_PU_IN) >= pl.col(c.TC_PRICE_PU_OUT))
282286
offers_cleared = offers.filter(pl.col(c.TC_PRICE_PU_IN) >= pl.col(c.TC_PRICE_PU_OUT))
@@ -288,7 +292,6 @@ def __method_none(self, bids, offers, pricing_method):
288292
# Create new dataframe with the cleared bids and offers
289293
trades_cleared = pl.concat([bids_cleared, offers_cleared], how='vertical')
290294

291-
292295
# Calculate the price and energy of the trades
293296
trades_cleared = trades_cleared.with_columns(
294297
(trades_cleared.select([c.TC_ENERGY_IN, c.TC_ENERGY_OUT]).min(axis=1).alias(c.TC_ENERGY)),
@@ -321,6 +324,7 @@ def __create_bids_offers(self, include_retailer=True):
321324
retailer = retailer.with_columns(
322325
[
323326
pl.col(c.TC_TIMESTAMP).alias(c.TC_TIMESTEP),
327+
pl.lit(self.bids_offers.select(pl.first(c.TC_TIMESTAMP))).alias(c.TC_TIMESTAMP),
324328
pl.lit(None).alias(c.TC_ENERGY_TYPE),
325329
# TODO: This can be removed once the energy type is added to the retailer table
326330
]
@@ -332,6 +336,8 @@ def __create_bids_offers(self, include_retailer=True):
332336

333337
retailer = retailer.with_columns(
334338
[
339+
pl.col(c.TC_TIMESTAMP).cast(pl.Datetime(time_unit='ns', time_zone='UTC'), strict=False),
340+
pl.col(c.TC_TIMESTEP).cast(pl.Datetime(time_unit='ns', time_zone='UTC'), strict=False),
335341
pl.col(c.TC_REGION).cast(pl.Categorical, strict=False),
336342
pl.col(c.TC_MARKET).cast(pl.Categorical, strict=False),
337343
pl.col(c.TC_NAME).cast(pl.Categorical, strict=False),
@@ -691,6 +697,10 @@ def __apply_grid_levies(self, transactions):
691697
# Concat the grids and levies
692698
transactions = pl.concat([grid, levies], how='align')
693699

700+
# Set the timestamp column to the current timestamp
701+
transactions = transactions.with_columns(pl.lit(self.tasks[c.TC_TIMESTAMP]).alias(c.TC_TIMESTAMP)
702+
.cast(pl.Datetime(time_unit='ns', time_zone='UTC')))
703+
694704
return transactions
695705

696706
def __method_pda(self, bids, offers, pricing_method):

hamlet/executor/utilities/controller/mpc/lincomps.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,31 @@ def __init__(self, name, **kwargs):
941941
self.b2g = self.info['sizing']['b2g']
942942
self.g2b = self.info['sizing']['g2b']
943943

944+
def define_constraints(self, model: Model) -> Model:
945+
model = super().define_constraints(model)
946+
947+
# Add constraint that the battery can only charge from the grid if the b2g flag is set to true
948+
model = self._constraint_b2g(model)
949+
950+
return model
951+
952+
def _constraint_b2g(self, model: Model) -> Model:
953+
"""Adds the constraint that the battery can only charge from the grid if the b2g flag is set to true."""
954+
955+
# Define the variables
956+
markets = self.info['markets']
957+
958+
# Define the constraint if b2g is disabled
959+
if not self.b2g:
960+
for market, energy in markets.items():
961+
if energy == c.ET_ELECTRICITY: # Only electricity markets are considered
962+
equation_disable_b2g = (model.variables[f'{self.name}_{self.comp_type}_mode'] -
963+
model.variables[f'{market}_mode'] <= 0)
964+
model.add_constraints(equation_disable_b2g, name=f'{self.name}_b2g_{market}',
965+
coords=[self.timesteps])
966+
967+
return model
968+
944969

945970
class Psh(SimpleStorage):
946971

hamlet/executor/utilities/controller/mpc/mpc.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def __init__(self, **kwargs):
9595
self.timesteps = pd.Index(range(len(self.timesteps)), name='timesteps')
9696
# self.timesteps = self.n_steps # Use this line if the timesteps are not needed and the index is sufficient
9797
# Reduce the socs to the current timestamp
98-
self.socs = self.socs.filter(self.socs[c.TC_TIMESTAMP] == self.timestamp)
98+
self.socs = self.socs.filter(self.socs[c.TC_TIMESTAMP] == self.timestamp + self.dt)
9999

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

128-
# Create the plant objects
129-
self.plant_objects = {}
130-
self.create_plants()
131-
132128
# Create the market objects
133129
self.market_objects = {}
134130
self.create_markets()
135131

132+
# Create the plant objects
133+
self.plant_objects = {}
134+
self.create_plants()
135+
136136
# Define the model
137137
self.define_variables()
138138
self.define_constraints()
139139
self.define_objective()
140140

141+
def create_markets(self):
142+
""""""
143+
144+
# Define variables from the market results and a balancing variable for each energy type
145+
for market in self.markets:
146+
# Create market object
147+
self.market_objects[f'{market}'] = lincomps.Market(name=market,
148+
forecasts=self.forecasts,
149+
timesteps=self.timesteps,
150+
delta=self.dt)
151+
152+
return self.market_objects
153+
141154
def create_plants(self):
142155
for plant_name, plant_data in self.plants.items():
143156

@@ -163,23 +176,11 @@ def create_plants(self):
163176
**plant_data,
164177
socs=socs,
165178
delta=self.dt,
166-
timesteps=self.timesteps)
179+
timesteps=self.timesteps,
180+
markets=self.markets)
167181

168182
return self.plant_objects
169183

170-
def create_markets(self):
171-
""""""
172-
173-
# Define variables from the market results and a balancing variable for each energy type
174-
for market in self.markets:
175-
# Create market object
176-
self.market_objects[f'{market}'] = lincomps.Market(name=market,
177-
forecasts=self.forecasts,
178-
timesteps=self.timesteps,
179-
delta=self.dt)
180-
181-
return self.market_objects
182-
183184
def define_variables(self):
184185
# Define variables for each plant
185186
for plant_name, plant in self.plant_objects.items():

hamlet/executor/utilities/controller/rtc/lincomps.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ def define_variables(self, model, **kwargs):
3333
def define_constraints(model):
3434
return model
3535

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

4040
return model
4141

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

5050
return model
5151

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

6060
return model
6161

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

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

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

403404
# Calculate the energy needed to charge to full
404405
self.energy_to_full = self.capacity - self.soc # energy needed to charge to full

hamlet/executor/utilities/controller/rtc/rtc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def create_plants(self):
181181

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

186186
# Get the plant class
187187
plant_class = self.available_plants.get(plant_type)

0 commit comments

Comments
 (0)