Skip to content

Commit

Permalink
Merge branch 'master' into benchmarks_wv
Browse files Browse the repository at this point in the history
  • Loading branch information
Wout4 committed Nov 15, 2023
2 parents 956f48b + d5ca0a7 commit 6fb34e3
Show file tree
Hide file tree
Showing 20 changed files with 792 additions and 655 deletions.
1 change: 1 addition & 0 deletions .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
pip install python-sat
pip install z3-solver
pip install exact
pip install pysdd
- name: Test with pytest
run: |
python -m pytest tests/
Expand Down
17 changes: 13 additions & 4 deletions .readthedocs.yml → .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

version: 1
version: 2

build:
os: "ubuntu-22.04"
tools:
python: "3.7"

python:
version: 3.7
install:
- requirements: docs/requirements.txt

# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
builder: html
configuration: docs/conf.py
fail_on_warning: false

formats:
- pdf
- epub
2 changes: 1 addition & 1 deletion cpmpy/expressions/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ def get_bounds(self):
bounds = [lb1 * lb2, lb1 * ub2, ub1 * lb2, ub1 * ub2]
lowerbound, upperbound = min(bounds), max(bounds)
elif self.name == 'sum':
lbs, ubs = zip(*[get_bounds(x) for x in self.args])
lbs, ubs = get_bounds(self.args)
lowerbound, upperbound = sum(lbs), sum(ubs)
elif self.name == 'wsum':
weights, vars = self.args
Expand Down
31 changes: 14 additions & 17 deletions cpmpy/expressions/globalconstraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,28 +417,24 @@ class Cumulative(GlobalConstraint):
"""
def __init__(self, start, duration, end, demand, capacity):
assert is_any_list(start), "start should be a list"
start = flatlist(start)
assert is_any_list(duration), "duration should be a list"
duration = flatlist(duration)
for d in duration:
if get_bounds(d)[0]<0:
raise TypeError("durations should be non-negative")
assert is_any_list(end), "end should be a list"

start = flatlist(start)
duration = flatlist(duration)
end = flatlist(end)
assert len(start) == len(duration) == len(end), "Lists should be equal length"
assert len(start) == len(duration) == len(end), "Start, duration and end should have equal length"
n_jobs = len(start)

for lb in get_bounds(duration)[0]:
if lb < 0:
raise TypeError("Durations should be non-negative")

if is_any_list(demand):
demand = flatlist(demand)
assert len(demand) == len(start), "Shape of demand should match start, duration and end"
for d in demand:
if is_boolexpr(d):
raise TypeError("demands must be non-boolean: {}".format(d))
else:
if is_boolexpr(demand):
raise TypeError("demand must be non-boolean: {}".format(demand))
flatargs = flatlist([start, duration, end, demand, capacity])
if any(is_boolexpr(arg) for arg in flatargs):
raise TypeError("All input lists should contain only arithmetic arguments for Cumulative constraints: {}".format(flatargs))
assert len(demand) == n_jobs, "Demand should be supplied for each task or be single constant"
else: # constant demand
demand = [demand] * n_jobs

super(Cumulative, self).__init__("cumulative", [start, duration, end, demand, capacity])

Expand Down Expand Up @@ -468,7 +464,8 @@ def decompose(self):
demand_at_t += demand * ((start[job] <= t) & (t < end[job]))
else:
demand_at_t += demand[job] * ((start[job] <= t) & (t < end[job]))
cons += [capacity >= demand_at_t]

cons += [demand_at_t <= capacity]

return cons, []

Expand Down
6 changes: 6 additions & 0 deletions cpmpy/expressions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,15 @@ def get_bounds(expr):
returns appropriately rounded integers
"""

# import here to avoid circular import
from cpmpy.expressions.core import Expression
from cpmpy.expressions.variables import cpm_array

if isinstance(expr, Expression):
return expr.get_bounds()
elif is_any_list(expr):
lbs, ubs = zip(*[get_bounds(e) for e in expr])
return list(lbs), list(ubs) # return list as NDVarArray is covered above
else:
assert is_num(expr), f"All Expressions should have a get_bounds function, `{expr}`"
if is_bool(expr):
Expand Down
7 changes: 6 additions & 1 deletion cpmpy/expressions/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

import numpy as np
from .core import Expression, Operator
from .utils import is_num, is_int, flatlist, is_boolexpr, is_true_cst, is_false_cst
from .utils import is_num, is_int, flatlist, is_boolexpr, is_true_cst, is_false_cst, get_bounds


def BoolVar(shape=1, name=None):
Expand Down Expand Up @@ -594,6 +594,11 @@ def all(self, axis=None, out=None):
# return the NDVarArray that contains the all() constraints
return out


def get_bounds(self):
lbs, ubs = zip(*[get_bounds(e) for e in self])
return cpm_array(lbs), cpm_array(ubs)

# VECTORIZED master function (delegate)
def _vectorized(self, other, attr):
if not isinstance(other, Iterable):
Expand Down
8 changes: 7 additions & 1 deletion cpmpy/solvers/minizinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,13 @@ def zero_based(array):
elif expr.name == "cumulative":
start, dur, end, _, _ = expr.args
self += [s + d == e for s,d,e in zip(start,dur,end)]
return "cumulative({},{},{},{})".format(args_str[0], args_str[1], args_str[3], args_str[4])
if len(start) == 1:
assert len(start) == 1
format_str = "cumulative([{}],[{}],[{}],{})"
else:
format_str = "cumulative({},{},{},{})"

return format_str.format(args_str[0], args_str[1], args_str[3], args_str[4])

elif expr.name == 'ite':
cond, tr, fal = expr.args
Expand Down
4 changes: 1 addition & 3 deletions cpmpy/solvers/ortools.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def solve(self, time_limit=None, assumptions=None, solution_callback=None, **kwa
You can use any of these parameters as keyword argument to `solve()` and they will
be forwarded to the solver. Examples include:
- num_search_workers=8 number of parallel workers (default: 1)
- num_search_workers=8 number of parallel workers (default: 8)
- log_search_progress=True to log the search process to stdout (default: False)
- cp_model_presolve=False to disable presolve (default: True, almost always beneficial)
- cp_model_probing_level=0 to disable probing (default: 2, also valid: 1, maybe 3, etc...)
Expand Down Expand Up @@ -466,8 +466,6 @@ def _post_constraint(self, cpm_expr, reifiable=False):
return self.ort_model.AddAllowedAssignments(array, table)
elif cpm_expr.name == "cumulative":
start, dur, end, demand, cap = self.solver_vars(cpm_expr.args)
if is_num(demand):
demand = [demand] * len(start)
intervals = [self.ort_model.NewIntervalVar(s,d,e,f"interval_{s}-{d}-{e}") for s,d,e in zip(start,dur,end)]
return self.ort_model.AddCumulative(intervals, demand, cap)
elif cpm_expr.name == "circuit":
Expand Down
2 changes: 1 addition & 1 deletion cpmpy/solvers/pysdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def transform(self, cpm_expr):
"""
# works on list of nested expressions
cpm_cons = toplevel_list(cpm_expr)
cpm_cons = decompose_in_tree(cpm_cons)
cpm_cons = decompose_in_tree(cpm_cons,supported={'xor'}, supported_reified={'xor'}) #keep unsupported xor for error message purposes.
cpm_cons = simplify_boolean(cpm_cons) # for cleaning (BE >= 0) and such
return cpm_cons

Expand Down
1 change: 0 additions & 1 deletion docs/api/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ Expressions (:mod:`cpmpy.expressions`)

.. automodule:: cpmpy.expressions
:members:
:undoc-members:
:inherited-members:
17 changes: 4 additions & 13 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
'sphinx.ext.autosummary',
'sphinx.ext.mathjax',
'sphinx.ext.viewcode',
'm2r2',
'sphinx_rtd_theme',
'sphinx_automodapi.automodapi',
'm2r2'
'sphinx_automodapi.smart_resolver'
]

numpydoc_show_class_members = False
Expand All @@ -64,17 +66,6 @@
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']


html_theme_options = {
'logo': 'cpmpy.png',
'github_user': 'tias',
'github_repo': 'cppy',
'github_button': True,
'github_type': 'star',
'sidebar_width': '152px',
'body_text_align': 'justify'
}


# Autodoc settings
autodoc_default_flags = ['members', 'special-members']

Expand All @@ -83,7 +74,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
# html_theme = 'alabaster'
html_theme = "sphinx_rtd_theme"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sphinx==5.3.0
6 changes: 5 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
numpy>=1.17.0
m2r2>=0.2.7
sphinx-automodapi
sphinx-automodapi
sphinx_rtd_theme
sphinx==5.3.0
sphinx_rtd_theme==1.1.1
readthedocs-sphinx-search==0.1.1
Loading

0 comments on commit 6fb34e3

Please sign in to comment.