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

Logical flow #71

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
56a0643
adds comparison operators for technologies
samgdotson Dec 27, 2022
96bb241
adds tests for tech comparison operators
samgdotson Dec 27, 2022
f155423
adds logical flow to storage tech
samgdotson Feb 21, 2023
32b5e37
merge conflicts
samgdotson Mar 6, 2023
71fc46e
Merge branch 'tech_library' of github.com:samgdotson/osier into logic…
samgdotson Mar 6, 2023
6956545
merge conf
samgdotson Mar 6, 2023
78d8456
adds power behavior for technologies and tests
samgdotson Mar 6, 2023
011f117
updates the storage technology
samgdotson Mar 6, 2023
6799a38
Merge branch 'tech_library' of github.com:samgdotson/osier into logic…
samgdotson Mar 6, 2023
88e05bf
fixes battery methods
samgdotson Mar 6, 2023
f1bc572
fixes battery
samgdotson Mar 7, 2023
a45b156
adds tests for battery storage
samgdotson Mar 7, 2023
3e04ba7
adds functions to utils and a general model class
samgdotson Mar 7, 2023
8e6f0dc
updates solve in logic model
samgdotson Mar 7, 2023
35173b2
Merge branch 'debugging' of github.com:samgdotson/osier into logical_…
samgdotson Mar 7, 2023
1d4f852
adds empty results formatting method
samgdotson Mar 8, 2023
f34f3b2
minor changes
samgdotson Mar 28, 2023
1cd45ca
merge conf
samgdotson Oct 19, 2024
0b66cd0
Merge branch 'main' of github.com:arfc/osier into logical_flow
samgdotson Dec 14, 2024
8ae6cca
updates init
samgdotson Dec 14, 2024
3e0472f
adds hierarchical dispatch example
samgdotson Dec 14, 2024
a75a1ab
updates hierarchical dispatch notebook
samgdotson Dec 14, 2024
96d52d9
updates logic dispatch and example notebook
samgdotson Dec 15, 2024
41d7f76
updates logic flow and battery output
samgdotson Dec 15, 2024
74cd25c
adds test for logic model, issue with power_history attribute
samgdotson Dec 15, 2024
54a78eb
working logical flow model
samgdotson Dec 16, 2024
5388b02
updates dispatch notebook
samgdotson Dec 16, 2024
beb9fb9
adds parallelization tutorial
samgdotson Dec 18, 2024
0f44f53
updates dispatch tutorial with new model
samgdotson Dec 23, 2024
9cdc64c
removes empty cell in notebook
samgdotson Dec 23, 2024
de99762
removes notebook
samgdotson Dec 23, 2024
f211f42
autopep8
samgdotson Dec 23, 2024
f074eac
autopep8 again
samgdotson Dec 23, 2024
095b045
manual changes
samgdotson Dec 23, 2024
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
165 changes: 154 additions & 11 deletions docs/source/examples/dispatch_tutorial.ipynb

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions osier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@


from .technology import *
from .models.model import *
from .models.dispatch import *
from .models.logic_dispatch import *
from .models.capacity_expansion import *
from .models.deap_runner import *
from .utils import *
Expand Down
24 changes: 16 additions & 8 deletions osier/models/capacity_expansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
import functools
import time

from osier import DispatchModel
from osier import DispatchModel, LogicDispatchModel

from pymoo.core.problem import ElementwiseProblem

LARGE_NUMBER = 1e20

dispatch_models = {'lp': DispatchModel,
'hierarchical': LogicDispatchModel,
'logical': LogicDispatchModel,
'rule_based': LogicDispatchModel}


class CapacityExpansion(ElementwiseProblem):
"""
Expand Down Expand Up @@ -98,6 +103,7 @@ def __init__(self,
allow_blackout=False,
verbosity=50,
solver='cbc',
model_engine='lp',
**kwargs):
self.technology_list = deepcopy(technology_list)
self.demand = demand
Expand All @@ -109,6 +115,7 @@ def __init__(self,
self.curtailment = curtailment
self.allow_blackout = allow_blackout
self.verbosity = verbosity
self.model_engine = model_engine.lower()
self.solver = solver

if isinstance(demand, unyt_array):
Expand Down Expand Up @@ -186,13 +193,14 @@ def _evaluate(self, x, out, *args, **kwargs):
- wind_gen \
- solar_gen

model = DispatchModel(technology_list=self.dispatchable_techs,
net_demand=net_demand,
power_units=self.power_units,
curtailment=self.curtailment,
allow_blackout=self.allow_blackout,
solver=self.solver,
verbosity=self.verbosity)
dispatch_model = dispatch_models[self.model_engine]
model = dispatch_model(technology_list=self.dispatchable_techs,
net_demand=net_demand,
power_units=self.power_units,
curtailment=self.curtailment,
allow_blackout=self.allow_blackout,
solver=self.solver,
verbosity=self.verbosity)
model.solve()

if model.results is not None:
Expand Down
6 changes: 3 additions & 3 deletions osier/models/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class DispatchModel():
Can be overridden by specifying a unit with the value.
solver : str
Indicates which solver to use. May require separate installation.
Accepts: ['cplex', 'cbc']. Other solvers will be added in the future.
Accepts: ['cplex', 'cbc', 'appsi_highs']. Other solvers will be added in the future.
lower_bound : float
The minimum amount of energy each technology can produce per time
period. Default is 0.0.
Expand Down Expand Up @@ -598,10 +598,10 @@ def solve(self, solver=None):
else:
optimizer = po.SolverFactory(self.solver)

results = optimizer.solve(self.model, tee=self.verbose)
try:
optimizer.solve(self.model, tee=self.verbose)
self.objective = self.model.objective()
except ValueError:
except (ValueError, RuntimeError):
if self.verbosity <= 30:
warnings.warn(
f"Infeasible or no solution. Objective set to {LARGE_NUMBER}")
Expand Down
100 changes: 100 additions & 0 deletions osier/models/logic_dispatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from osier import OsierModel
from osier.utils import get_tech_names
from copy import deepcopy
from unyt import unyt_array, MW
import pandas as pd
import numpy as np
import warnings

LARGE_NUMBER = 1e20


class LogicDispatchModel(OsierModel):

def __init__(self,
technology_list,
net_demand,
allow_blackout=False,
curtailment=True,
verbosity=50,
*args, **kwargs):
super().__init__(technology_list=technology_list,
net_demand=net_demand,
*args, **kwargs)
self.technology_list = technology_list
self.technology_list.sort()
self.original_order = get_tech_names(self.technology_list)
self.cost_history = np.zeros(len(net_demand))
self.covered_demand = None
self.objective = None
self.results = None
self.verbosity = verbosity
self.allow_blackout = allow_blackout
self.curtailment = curtailment

def _reset_all(self):
for t in self.technology_list:
t.reset_history()
return

def _format_results(self):
data = {}
for t in self.technology_list:
data[f"{t.technology_name}"] = unyt_array(
t.power_history).to_ndarray()
if t.technology_type == 'storage':
data[f"{t.technology_name}_level"] = unyt_array(
t.storage_history).to_ndarray()
data[f"{t.technology_name}_charge"] = unyt_array(
t.charge_history).to_ndarray()
data["Curtailment"] = np.array(
[v if v <= 0 else 0 for v in self.covered_demand])
data["Shortfall"] = np.array(
[v if v > 0 else 0 for v in self.covered_demand])
self.results = pd.DataFrame(data)
return

def _calculate_objective(self):
self.objective = sum(np.array(t.power_history).sum()
* t.variable_cost.to_value()
for t in self.technology_list)
return

def solve(self):
"""
This function executes the model solve with a rule-based approach.
Net demand is copied, then the technology histories are reset.
"""
self.covered_demand = self.net_demand.copy()
self._reset_all()
try:
for i, v in enumerate(self.covered_demand):
for t in self.technology_list:
power_out = t.power_output(v, time_delta=self.time_delta)
v -= power_out

self.covered_demand[i] = v
if not self.allow_blackout and (v > 0):
if self.verbosity <= 20:
print('solve failed -- unmet demand')
raise ValueError

if not self.curtailment and (v < 0):
if self.verbosity <= 20:
print(
('solve failed -- '
'too much overproduction '
'(no curtailment allowed)'))
raise ValueError

self._format_results()
self._calculate_objective()
except ValueError:
if self.verbosity <= 30:
warnings.warn(
(f"Infeasible or no solution."
f"Objective set to {LARGE_NUMBER}")
)
self.objective = LARGE_NUMBER

return
80 changes: 80 additions & 0 deletions osier/models/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import pandas as pd
import numpy as np
from unyt import unyt_array, hr, MW, kW, GW
import itertools as it
from osier import Technology
from osier.technology import _validate_quantity, _validate_unit
from osier.utils import synchronize_units
import warnings
import logging

_freq_opts = {'D': 'day',
'H': 'hour',
'S': 'second',
'T': 'minute'}


class OsierModel():
"""
A class for instantiating energy models in Osier.
"""

def __init__(self,
technology_list,
net_demand,
time_delta=None,
power_units=MW,
**kwargs):
self.net_demand = net_demand
self.time_delta = time_delta
self.results = None
self.objective = None

if isinstance(net_demand, unyt_array):
self.power_units = net_demand.units
elif isinstance(net_demand, (np.ndarray, list)):
self.power_units = power_units
self.net_demand = np.array(self.net_demand) * self.power_units
elif isinstance(net_demand, pd.core.series.Series):
self.power_units = power_units
self.net_demand = np.array(self.net_demand) * self.power_units
else:
self.power_units = power_units

self.technology_list = synchronize_units(
technology_list,
unit_power=self.power_units,
unit_time=self.time_delta.units)

@property
def time_delta(self):
return self._time_delta

@time_delta.setter
def time_delta(self, value):
if value:
valid_quantity = _validate_quantity(value, dimension='time')
self._time_delta = valid_quantity
else:
if isinstance(self.net_demand, pd.DataFrame):
try:
freq_list = list(self.net_demand.index.inferred_freq)
freq_key = freq_list[-1]
try:
value = float(freq_list[0])
except ValueError:
warnings.warn((f"Could not convert value "
f"{freq_list[0]} to float. "
"Setting to 1.0."),
UserWarning)
value = 1.0
self._time_delta = _validate_quantity(
f"{value} {_freq_opts[freq_key]}", dimension='time')
except KeyError:
warnings.warn(
(f"Could not infer time delta with freq {freq_key} "
"from pandas dataframe. Setting delta to 1 hour."),
UserWarning)
self._time_delta = 1 * hr
else:
self._time_delta = 1 * hr
2 changes: 1 addition & 1 deletion osier/tech_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
fuel_cost=0*to_MDOLLARS,
storage_duration=4,
efficiency=0.85,
initial_storage=0,
initial_storage=0.0*MW*hr,
capacity_credit=0.5,
lifecycle_co2_rate=3.3e-5*co2_eq_units,
land_intensity=0.0,
Expand Down
Loading
Loading