Skip to content

Commit

Permalink
Merge pull request #1080 from oemof/revision/replace-constraint-tests
Browse files Browse the repository at this point in the history
Replaces lp diff constraint tests by granular unit tests for functionality.

Reasoning:
- The LP files depend on Pyomo internals, thus they change when Pyomo changes things.
- They include tests for stuff from multiple parts of the code. For example, if I change the constraint formulation for the Bus, every constraint test needs to be rewritten, not only the one for the Bus.
- They are hard to understand (for many) and failed tests are hard to fix. The latter is particularly because of the undefined order of lines, so that just looking at a diff will not tell the difference: Constraints might be in a different order but still the same.

These unit tests still use Pyomo but the results are independent from the back-end. They are rather easy to understand (rather trivial test examples), and even internal constraint formulations can be changed without breaking the test, so it really tests for the specified functionally not the implementation.
  • Loading branch information
p-snft authored Aug 20, 2024
2 parents 91c66bc + c8263ce commit 5706ca2
Show file tree
Hide file tree
Showing 179 changed files with 1,588 additions and 46,063 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tox_pytests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
run: tox

- name: Check test coverage
run: coverage report -m --fail-under=${{ matrix.vcs == 'bzr' && 89 || 90 }}
run: coverage report -m --fail-under=${{ matrix.vcs == 'bzr' && 84 || 85 }}

- name: Report to coveralls
run: coveralls
Expand Down
2 changes: 1 addition & 1 deletion src/oemof/solph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from . import processing
from . import views
from ._energy_system import EnergySystem
from ._energy_system import create_time_index
from ._groupings import GROUPINGS
from ._helpers import create_time_index
from ._models import Model
from ._options import Investment
from ._options import NonConvex
Expand Down
50 changes: 33 additions & 17 deletions src/oemof/solph/_console_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from oemof import solph


def check_oemof_installation(silent=False):
def _check_oemof_installation(solvers):
logging.disable(logging.CRITICAL)

date_time_index = pd.date_range("1/1/2012", periods=6, freq="h")
Expand Down Expand Up @@ -48,22 +48,38 @@ def check_oemof_installation(silent=False):
om = solph.Model(energysystem)

# check solvers
solver = dict()
for s in ["cbc", "glpk", "gurobi", "cplex"]:
solver_status = dict()
for s in solvers:
try:
om.solve(solver=s)
solver[s] = "working"
solver_status[s] = True
except Exception:
solver[s] = "not working"

if not silent:
print()
print("*****************************")
print("Solver installed with oemof:")
print()
for s, t in solver.items():
print("{0}: {1}".format(s, t))
print()
print("*****************************")
print("oemof successfully installed.")
print("*****************************")
solver_status[s] = False

return solver_status


def check_oemof_installation():

solvers_to_test = ["cbc", "glpk", "gurobi", "cplex"]

solver_status = _check_oemof_installation(solvers_to_test)

print_text = (
"***********************************\n"
"Solver installed with oemof.solph:\n"
"\n"
)
for solver, works in solver_status.items():
if works:
print_text += f"{solver}: installed and working\n"
else:
print_text += f"{solver}: not installed/ not working\n"
print_text += (
"\n"
"***********************************\n"
"oemof.solph successfully installed.\n"
"***********************************\n"
)

print(print_text)
70 changes: 0 additions & 70 deletions src/oemof/solph/_energy_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
"""

import calendar
import datetime
import warnings

import numpy as np
Expand Down Expand Up @@ -244,71 +242,3 @@ def get_period_duration(self, period):
- self.periods[period].min().year
+ 1
)


def create_time_index(
year: int = None,
interval: float = 1,
number: int = None,
start: datetime.datetime or datetime.date = None,
):
"""
Create a datetime index for one year.
Notes
-----
To create 8760 hourly intervals for a non leap year a datetime index with
8761 time points need to be created. So the number of time steps is always
the number of intervals plus one.
Parameters
----------
year : int, datetime
The year of the index. If number and start is set the year parameter is
ignored.
interval : float
The time interval in hours e.g. 0.5 for 30min or 2 for a two hour
interval (default: 1).
number : int
The number of time intervals. By default number is calculated to create
an index of one year. For a shorter or longer period the number of
intervals can be set by the user.
start : datetime.datetime or datetime.date
Optional start time. If start is not set, 00:00 of the first day of
the given year is the start time.
Examples
--------
>>> len(create_time_index(2014))
8761
>>> len(create_time_index(2012)) # leap year
8785
>>> len(create_time_index(2014, interval=0.5))
17521
>>> len(create_time_index(2014, interval=0.5, number=10))
11
>>> len(create_time_index(2014, number=10))
11
>>> str(create_time_index(2014, interval=0.5, number=10)[-1])
'2014-01-01 05:00:00'
>>> str(create_time_index(2014, interval=2, number=10)[-1])
'2014-01-01 20:00:00'
"""
if number is None:
if calendar.isleap(year):
hoy = 8784
else:
hoy = 8760
number = round(hoy / interval)
if start is None:
start = f"1/1/{year}"
try:
time_index = pd.date_range(
start, periods=number + 1, freq=f"{interval}h"
)
except ValueError:
# Pandas <2.2 compatibility
time_index = pd.date_range(
start, periods=number + 1, freq=f"{interval}H"
)
return time_index
76 changes: 76 additions & 0 deletions src/oemof/solph/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
"""

import calendar
import datetime
from warnings import warn

import pandas as pd
from oemof.tools import debugging


Expand Down Expand Up @@ -45,3 +48,76 @@ def warn_if_missing_attribute(obj, attribute):
msg.format(attribute, obj.label, type(obj)),
debugging.SuspiciousUsageWarning,
)


def create_time_index(
year: int = None,
interval: float = 1,
number: int = None,
start: datetime.date = None,
):
"""
Create a datetime index for one year.
Notes
-----
To create 8760 hourly intervals for a non leap year a datetime index with
8761 time points need to be created. So the number of time steps is always
the number of intervals plus one.
Parameters
----------
year : int, datetime
The year of the index.
Used to automatically set start and number for the specific year.
interval : float
The time interval in hours e.g. 0.5 for 30min or 2 for a two hour
interval (default: 1).
number : int
The number of time intervals. By default number is calculated to create
an index of one year. For a shorter or longer period the number of
intervals can be set by the user.
start : datetime.datetime or datetime.date
Optional start time. If start is not set, 00:00 of the first day of
the given year is the start time.
Examples
--------
>>> len(create_time_index(2014))
8761
>>> len(create_time_index(2012)) # leap year
8785
>>> len(create_time_index(2014, interval=0.5))
17521
>>> len(create_time_index(2014, interval=0.5, number=10))
11
>>> len(create_time_index(2014, number=10))
11
>>> str(create_time_index(2014, interval=0.5, number=10)[-1])
'2014-01-01 05:00:00'
>>> str(create_time_index(2014, interval=2, number=10)[-1])
'2014-01-01 20:00:00'
"""
if number is None:
if calendar.isleap(year):
hours_in_year = 8784
else:
hours_in_year = 8760
number = round(hours_in_year / interval)
if start is not None:
if year is not None:
raise ValueError(
"Arguments 'start' and 'year' are mutually exclusive."
)
else:
start = f"1/1/{year}"
try:
time_index = pd.date_range(
start, periods=number + 1, freq=f"{interval}h"
)
except ValueError:
# Pandas <2.2 compatibility
time_index = pd.date_range(
start, periods=number + 1, freq=f"{interval}H"
)
return time_index
Loading

0 comments on commit 5706ca2

Please sign in to comment.