Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added variables to obj function, improved tests #25

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions examples/one-producer/run_mts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
DATAPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'data_mts')
OUTPUTPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'results/mts_eco_easy/')
'results/mts_eco/')
REGRESSION = 'regression.csv' # regression coefficients for thermal capacity and heat losses
TIMESERIES = 'timeseries.csv' # timeseries for heat scaling
PLOTS = True # save plots of the district
Expand Down Expand Up @@ -110,6 +110,9 @@ def main(filepath, outputpath, plots=True, solver='gurobi', mode='forced'):


if __name__ == '__main__':
main(filepath=os.path.join(DATAPATH), outputpath=os.path.join(OUTPUTPATH),
plots=PLOTS, solver=SOLVER, mode='economic')
main(filepath=os.path.join(DATAPATH),
outputpath=os.path.join(OUTPUTPATH),
plots=PLOTS,
solver=SOLVER,
mode='forced')
print(f'Finished {OUTPUTPATH}')
10 changes: 8 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""Pytest configuration file."""


def pytest_addoption(parser):
parser.addoption("--solver", action="store", default="scip",
help="Define the solver to use for the optimization. Default is cbc.")
parser.addoption(
"--solver",
action="store",
default="scip",
help="Define the solver to use for the optimization. Default is cbc.")
1 change: 1 addition & 0 deletions tests/hydraulics.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def test_regression():
# heat loss regression
r_heat_loss = precalc.regression_heat_losses(
settings, thermal_capacity=r_thermal_cap)

# assert that each params item is within 1% of the expected value
assert r_heat_loss['a'] == approx(4.348000e-07, rel=0.01)
assert r_heat_loss['b'] == approx(0.02189, rel=0.01)
Expand Down
13 changes: 9 additions & 4 deletions tests/mts_easy.py → tests/mts.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"""Test the multiple timestep optimization model."""

import os

import pyomo.environ as pyo
import pandas as pd
from pytest import approx

import topotherm as tt


def read_regression(path, i):
"""Read the regression coefficients for the thermal capacity and heat
losses from csv file.
Expand All @@ -24,7 +28,7 @@ def read_regression(path, i):
return r_thermal_cap, r_heat_loss


def test_mtseasy_forced(request):
def test_mts_forced(request):
"""Main function to run the optimization"""
solver_name = request.config.getoption("--solver")
assert solver_name in ["scip", "gurobi", "cbc"], f"Unsupported solver: {solver_name}"
Expand Down Expand Up @@ -62,10 +66,10 @@ def test_mtseasy_forced(request):
assert result.solver.status == pyo.SolverStatus.ok
# assert that the objective value is less than 2% away from the expected
# value
assert (abs(pyo.value(model.obj)) - 4.6259e+06) < 0.02 * 4.6259e+06
assert abs(pyo.value(model.obj)) == approx(519676.4358105995, rel=0.02)


def test_mtseasy_eco(request):
def test_mts_eco(request):
"""Main function to run the optimization"""
solver_name = request.config.getoption("--solver")
assert solver_name in ["scip", "gurobi", "cbc"], f"Unsupported solver: {solver_name}"
Expand Down Expand Up @@ -100,4 +104,5 @@ def test_mtseasy_eco(request):

result = opt.solve(model, tee=True)
assert result.solver.status == pyo.SolverStatus.ok
assert (abs(pyo.value(model.obj)) - 4.01854e+04) < 0.02 * 4.01854e+04
assert pyo.value(model.obj) == approx(-39212.8646266408, rel=0.02)
# assert (abs(pyo.value(model.obj)) - 4.01854e+04) < 0.02 * 4.01854e+04
4 changes: 2 additions & 2 deletions tests/sts.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_sts_forced(request):
assert result.solver.status == pyo.SolverStatus.ok
# assert that the objective value is less than 2% away from the expected
# value
assert abs(pyo.value(model.obj)) == approx(519676.4358105995, rel=0.02)
assert pyo.value(model.obj) == approx(519676.4358105995, rel=0.02)


def test_sts_eco(request):
Expand Down Expand Up @@ -101,5 +101,5 @@ def test_sts_eco(request):

result = opt.solve(model, tee=True)
assert result.solver.status == pyo.SolverStatus.ok
assert abs(pyo.value(model.obj)) == approx(4.01854e+04, rel=0.02)
assert pyo.value(model.obj) == approx(-4.01854e+04, rel=0.02)
# assert (abs(pyo.value(model.obj)) - 4.01854e+04) < 0.02 * 4.01854e+04
1 change: 1 addition & 0 deletions tests/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from pytest import approx


def test_import():
import topotherm

Expand Down
80 changes: 44 additions & 36 deletions topotherm/multiple_timestep.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

The module contains the following functions:
* annuity: Calculate the annuity factor
* model: Create the optimization model for the multiple time steps operation
* model: Create the optimization model for the multiple time steps
operation
"""
import numpy as np
import pyomo.environ as pyo
Expand Down Expand Up @@ -260,56 +261,63 @@ def connection_to_consumer_fcd(m, d, j, t):
rule=connection_to_consumer_fcd,
doc=msg_)

if optimization_mode == "forced":
def connection_to_consumer_built_fcd(m, j):
return m.lambda_b[j] >= 1
mdl.cons_connection_forced = pyo.Constraint(
mdl.forced_edges, rule=connection_to_consumer_built_fcd,
mdl.forced_edges,
rule=connection_to_consumer_built_fcd,
doc='The house connection has to be built to satisfy the demand')

if optimization_mode == "forced":
def total_energy_conservation(m, t):
return sum(m.P_source[k, t] for k in m.set_n_p) \
- sum(m.P['ij', 'in', k, t] - m.P['ij', 'out', k, t] for k in m.set_n_i) \
- sum(m.P['ji', 'in', k, t] - m.P['ji', 'out', k, t] for k in m.set_n_i) \
- sum(matrices['q_c'][k, t] for k in m.set_n_c) == 0
mdl.cons_total_energy_cons = pyo.Constraint(mdl.set_t, rule=total_energy_conservation,
doc='Total energy conservation')

def objective_function(m):
fuel = sum(
sum(m.P_source[k, t]
mdl.cons_total_energy_cons = pyo.Constraint(
mdl.set_t,
rule=total_energy_conservation,
doc='Total energy conservation')

mdl.revenue = pyo.Var(doc='Revenue', domain=pyo.NegativeReals)
mdl.revenue_constr = pyo.Constraint(
expr=mdl.revenue == sum(
sum(mdl.lambda_b[j] * matrices['flh_consumer'][k, t]
* matrices['q_c'][k, t] for k, j in mdl.consumer_edges)
for t in mdl.set_t) * economics.heat_price * (-1),
doc='Revenue constraint')

mdl.opex_source = pyo.Var(doc='OPEX Source', domain=pyo.PositiveReals)
mdl.opex_source_constr = pyo.Constraint(
expr=mdl.opex_source == sum(
sum(mdl.P_source[k, t]
* economics.source_price[k]
* matrices['flh_source'][k, t]
for k in m.set_n_p)
for t in mdl.set_t)

pipes = sum(
(
(m.P_cap[k] * regression_inst['a']
+ regression_inst['b'] * m.lambda_b[k]
) * annuity(economics.pipes_c_irr, economics.pipes_lifetime) * matrices['l_i'][k]
) for k in m.set_n_i
)

source = sum(m.P_source_inst[k]
for k in mdl.set_n_p)
for t in mdl.set_t),
doc='OPEX Source constraint')

mdl.capex_pipes = pyo.Var(doc='CAPEX Pipe', domain=pyo.PositiveReals)
pipes = sum(
((mdl.P_cap[k] * regression_inst['a']
+ regression_inst['b'] * mdl.lambda_b[k])
* annuity(economics.pipes_c_irr, economics.pipes_lifetime)
* matrices['l_i'][k]) for k in mdl.set_n_i)

mdl.capex_pipe_constr = pyo.Constraint(
expr=mdl.capex_pipes == pipes,
doc='CAPEX Pipe constraint')

mdl.capex_source = pyo.Var(doc='CAPEX Source', domain=pyo.PositiveReals)
mdl.capex_source_constr = pyo.Constraint(
expr=mdl.capex_source == sum(mdl.P_source_inst[k]
* economics.source_c_inv[k]
* annuity(economics.source_c_irr[k],
economics.source_lifetime[k])
for k in m.set_n_p)

if optimization_mode == "economic":
revenue = sum(
sum(m.lambda_b[j]
* matrices['flh_consumer'][k, t]
* matrices['q_c'][k, t]
for k, j in mdl.consumer_edges)
for t in mdl.set_t) * economics.heat_price * (-1)
else:
revenue = 0

return fuel + pipes + source + revenue
for k in mdl.set_n_p),
doc='CAPEX Source constraint')

mdl.obj = pyo.Objective(rule=objective_function, doc='Objective function')
mdl.obj = pyo.Objective(
expr=mdl.capex_source + mdl.capex_pipes + mdl.opex_source + mdl.revenue,
doc='Objective function')

return mdl
21 changes: 10 additions & 11 deletions topotherm/single_timestep.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def total_energy_cons(m, t):
mdl.set_t, rule=total_energy_cons,
doc='Total energy conservation')

mdl.revenue = pyo.Var(doc='Revenue')
mdl.revenue = pyo.Var(doc='Revenue', domain=pyo.NegativeReals)
mdl.revenue_constr = pyo.Constraint(
expr=mdl.revenue == (
sum(
Expand All @@ -267,7 +267,7 @@ def total_energy_cons(m, t):
for t in mdl.set_t) * economics.heat_price * (-1)),
doc='Revenue constraint')

mdl.opex_source = pyo.Var(doc='OPEX Source')
mdl.opex_source = pyo.Var(doc='OPEX Source', domain=pyo.PositiveReals)
mdl.opex_source_constr = pyo.Constraint(
expr=mdl.opex_source == sum(
sum(mdl.P_source[k, t]
Expand All @@ -277,7 +277,7 @@ def total_energy_cons(m, t):
for t in mdl.set_t),
doc='OPEX Source constraint')

mdl.capex_pipes = pyo.Var(doc='CAPEX Pipe')
mdl.capex_pipes = pyo.Var(doc='CAPEX Pipe', domain=pyo.PositiveReals)

# CAREFUL HARDCODED FOR 0 TIME STEPS
def pipes_fix(k):
Expand All @@ -289,19 +289,18 @@ def pipes_var(k):
* (mdl.lambda_['ij', k] + mdl.lambda_['ji', k]))

mdl.capex_pipe_constr = pyo.Constraint(
expr=mdl.capex_pipes == (sum(
expr=mdl.capex_pipes == sum(
(pipes_fix(k) + pipes_var(k)) * matrices['l_i'][k]
for k in mdl.set_n_i)
* annuity(economics.pipes_c_irr, economics.pipes_lifetime)),
* annuity(economics.pipes_c_irr, economics.pipes_lifetime),
doc='CAPEX Pipe constraint')

mdl.capex_source = pyo.Var(doc='CAPEX Source')
mdl.capex_source = pyo.Var(doc='CAPEX Source', domain=pyo.PositiveReals)
mdl.capex_source_constr = pyo.Constraint(
expr=mdl.capex_source == sum(mdl.P_source_inst[k]
* economics.source_c_inv[k]
* annuity(economics.source_c_irr[k],
economics.source_lifetime[k])
for k in mdl.set_n_p),
expr=mdl.capex_source == sum(
mdl.P_source_inst[k] * economics.source_c_inv[k]
* annuity(economics.source_c_irr[k], economics.source_lifetime[k])
for k in mdl.set_n_p),
doc='CAPEX Source constraint')

mdl.obj = pyo.Objective(
Expand Down
Loading