From 481cc61606373bb19ac1130fdc9065e683c437da Mon Sep 17 00:00:00 2001 From: Christian Doh Dinga <cdohdinga@tudelft.nl> Date: Tue, 25 Jun 2024 16:36:59 +0200 Subject: [PATCH] add centralized optimization solution approach --- .../centralized_optimization.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/demoses_distibuted_optimization/centralized_optimization.py diff --git a/src/demoses_distibuted_optimization/centralized_optimization.py b/src/demoses_distibuted_optimization/centralized_optimization.py new file mode 100644 index 0000000..44eff32 --- /dev/null +++ b/src/demoses_distibuted_optimization/centralized_optimization.py @@ -0,0 +1,109 @@ +import yaml +import numpy as np +import pandas as pd +from typing import Dict +import pyomo.environ as pyo + + +def create_centralized_optimization_problem(data: Dict, ts: pd.DataFrame) -> pyo.ConcreteModel: + """Create optimization problem to solve the equilibrium problem in a centralized manner.""" + model = pyo.ConcreteModel(name='Optimization-problem') + + # Define sets + number_of_timesteps = data["General"]["nTimesteps"] + generator_list = data["Generators"].keys() + model.time = pyo.Set(initialize=list(range(number_of_timesteps)), name='timesteps') + model.generators = pyo.Set(initialize=list(generator_list), name='generators') + + # Generator-specific parameters + generator_cost_param_a_value = {gen: data['Generators'][gen]['a'] for gen in data['Generators']} + generator_cost_param_b_value = {gen: data['Generators'][gen]['b'] for gen in data['Generators']} + generator_capacity_value = {gen: data['Generators'][gen]['C'] for gen in data['Generators']} + model.generator_cost_param_a = pyo.Param(model.generators, name='generator_cost_param_a', initialize=generator_cost_param_a_value, mutable=False) + model.generator_cost_param_b = pyo.Param(model.generators, name='generator_cost_param_b', initialize=generator_cost_param_b_value, mutable=False) + model.generator_capacity = pyo.Param(model.generators, name='generator_capacity', initialize=generator_capacity_value, mutable=False) + + # Consumer-specific parameters + demand_timeseries = dict(enumerate(ts.loc[:,'LOAD'].values)) # combined demand from all consumers + model.demand_profile = pyo.Param(model.time, name='demand_profile', initialize=demand_timeseries, mutable=False) + + # For each consumer, we want to get their pv generation timeseries, then combined all together + consumer_list = data["Consumers"].keys() + total_consumers = data['General']['totConsumers'] + all_pv_profile = np.zeros(data["General"]["nTimesteps"]) + for con in consumer_list: + pv_availability_factor = ts.loc[:, data['Consumers'][con]['PV_AF']].values + pv_capacity = data['Consumers'][con]['PV_cap'] + share_of_agent = data['Consumers'][con]['Share'] + pv_profile = total_consumers * share_of_agent * pv_capacity * pv_availability_factor + try: + pv_profile.reshape(1, 24) + all_pv_profile = np.add(all_pv_profile, pv_profile) + except ValueError: + print("Error: trying to add np.zeros(24) array with a different shape array") + return 0 + + model.pv_profile = pyo.Param(model.time, name='demand_profile', initialize=dict(enumerate(all_pv_profile)), mutable=False) + + # Decision variables for generators + model.var_g = pyo.Var(model.generators, model.time, name='generation', domain=pyo.NonNegativeReals, initialize=0) + + # Define generator capacity limit constraint + def available_capacity_value(model, gen, t): + if 'AF' in data['Generators'][gen].keys(): + return model.generator_capacity[gen] * ts.loc[t, data['Generators'][gen]["AF"]] + else: + return model.generator_capacity[gen] + model.available_capacity = pyo.Param(model.generators, model.time, name='available_capacity', initialize=available_capacity_value, mutable=False) + + # Define objective function (OPEX are parametrized as a/2 * g**2 + b * g) + def generation_cost_rule(model): + return sum( + model.generator_cost_param_a[gen]/2 * model.var_g[gen, t]**2 + + model.generator_cost_param_b[gen] * model.var_g[gen, t] + for gen in model.generators for t in model.time + ) + model.objective_function = pyo.Objective(rule=generation_cost_rule, sense=pyo.minimize) + + # Define generator capacity limit constraint + def capacity_limit_rule(model, gen, t): + return model.var_g[gen, t] <= model.available_capacity[gen, t] + model.capacity_limit = pyo.Constraint(model.generators, model.time, rule=capacity_limit_rule) + + # Define energy balance constraint + def energy_balance_rule(model, t): + return (sum(model.var_g[gen, t] for gen in model.generators) >= model.demand_profile[t] - model.pv_profile[t]) + model.energy_balance = pyo.Constraint(model.time, rule=energy_balance_rule) + + return model + + +def solve_model(): + """Solve model and return results.""" + def read_config(config_file): + with open(config_file, 'r') as file: + config = yaml.safe_load(file) + return config + data = read_config('config.yaml') + ts = pd.read_csv('timeseries.csv', delimiter=';') + model = create_centralized_optimization_problem(data, ts) + solver = pyo.SolverFactory('gurobi') + model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT) + solver.solve(model) + print('############## Objective function value ###########') + model.objective_function.display() + print() + print('############## Generator output ###########') + model.var_g.display() + print() + dual_values = [] + for t in model.time: + dual_values.append(model.dual[model.energy_balance[t]]) + market_prices = pd.DataFrame(data={"market-prices": dual_values}, index=model.time) + market_prices.index.name = 'timesteps' + print('############## Market prices ###########') + print(market_prices) + + +if __name__ == "__main__": + solve_model() \ No newline at end of file -- GitLab