diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 1f1836206..9641ed573 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -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/ diff --git a/.readthedocs.yml b/.readthedocs.yaml similarity index 61% rename from .readthedocs.yml rename to .readthedocs.yaml index c3491de3c..80fe372c9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yaml @@ -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 \ No newline at end of file + builder: html + configuration: docs/conf.py + fail_on_warning: false + +formats: + - pdf + - epub \ No newline at end of file diff --git a/cpmpy/expressions/core.py b/cpmpy/expressions/core.py index 709d00811..1b210e680 100644 --- a/cpmpy/expressions/core.py +++ b/cpmpy/expressions/core.py @@ -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 diff --git a/cpmpy/expressions/globalconstraints.py b/cpmpy/expressions/globalconstraints.py index db4cc3ff6..5ed2be859 100644 --- a/cpmpy/expressions/globalconstraints.py +++ b/cpmpy/expressions/globalconstraints.py @@ -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]) @@ -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, [] diff --git a/cpmpy/expressions/utils.py b/cpmpy/expressions/utils.py index 9cded85ca..9b0310773 100644 --- a/cpmpy/expressions/utils.py +++ b/cpmpy/expressions/utils.py @@ -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): diff --git a/cpmpy/expressions/variables.py b/cpmpy/expressions/variables.py index b8a0f88d3..31c1ab33b 100644 --- a/cpmpy/expressions/variables.py +++ b/cpmpy/expressions/variables.py @@ -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): @@ -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): diff --git a/cpmpy/solvers/minizinc.py b/cpmpy/solvers/minizinc.py index 8cc24ff5d..d7356e32e 100644 --- a/cpmpy/solvers/minizinc.py +++ b/cpmpy/solvers/minizinc.py @@ -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 diff --git a/cpmpy/solvers/ortools.py b/cpmpy/solvers/ortools.py index ef5e785ab..9fb8a4049 100644 --- a/cpmpy/solvers/ortools.py +++ b/cpmpy/solvers/ortools.py @@ -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...) @@ -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": diff --git a/cpmpy/solvers/pysdd.py b/cpmpy/solvers/pysdd.py index 79e15d262..c378df627 100644 --- a/cpmpy/solvers/pysdd.py +++ b/cpmpy/solvers/pysdd.py @@ -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 diff --git a/docs/api/expressions.rst b/docs/api/expressions.rst index 81bee1a3f..4ea9d467a 100644 --- a/docs/api/expressions.rst +++ b/docs/api/expressions.rst @@ -3,5 +3,4 @@ Expressions (:mod:`cpmpy.expressions`) .. automodule:: cpmpy.expressions :members: - :undoc-members: :inherited-members: diff --git a/docs/conf.py b/docs/conf.py index 83d1bbfe5..55b91cf13 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 @@ -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'] @@ -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, diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 000000000..48631c4f5 --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1 @@ +sphinx==5.3.0 \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt index 5b924499b..bfbbd40c7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,7 @@ numpy>=1.17.0 m2r2>=0.2.7 -sphinx-automodapi \ No newline at end of file +sphinx-automodapi +sphinx_rtd_theme +sphinx==5.3.0 +sphinx_rtd_theme==1.1.1 +readthedocs-sphinx-search==0.1.1 \ No newline at end of file diff --git a/examples/chess-recognition.py b/examples/chess-recognition.py new file mode 100644 index 000000000..c5ed220da --- /dev/null +++ b/examples/chess-recognition.py @@ -0,0 +1,201 @@ +#!/usr/bin/python3 +""" +Example of evaluating a chess position in CPMpy + +Evaluating a chess position by getting the probabilities of which piece it is and the places the pieces are placed +""" +import time + +from cpmpy import * +import numpy as np +from enum import Enum +from functools import total_ordering + + +# Replace numerals by letters for easier interpretation +def replace_value(x): + return dict.get(x, x) + + +# calculate the softmax of a vector +def softmax(vector): + e = np.exp(vector) + return e / e.sum() + + +piece_probabilities = np.array([[-1.1558e+01, -1.8940e-01, -1.0785e+01, -8.4097e+00, -1.3034e+01, -8.7986e+00, + -2.6694e+00, 1.2009e+01, -3.5726e+00, -9.1412e-01, -2.0600e+00, -5.9005e-01], + [-1.4162e+01, -1.7926e+01, -1.4494e+01, -2.1050e+00, -1.6337e+01, -1.7570e+01, + -5.1813e+00, -1.4064e+01, -5.9986e+00, 1.4458e+01, -9.9950e+00, -1.4928e+01], + [-1.2823e+01, -1.6611e+01, -1.3248e+01, -2.6784e+00, -1.3868e+01, -1.7606e+01, + -4.4053e+00, -1.1934e+01, -5.1489e+00, 1.2227e+01, -7.1553e+00, -1.3355e+01], + [-1.5258e+00, -1.4117e+01, -1.5366e+01, -9.6915e+00, -1.2290e+01, -1.5584e+01, + 1.5208e+01, -4.8226e+00, -6.0717e+00, 1.6145e-02, -4.0168e+00, -6.2375e+00], + [-1.2323e+01, -1.5209e+01, -1.1199e+01, -2.0871e+00, -1.3257e+01, -1.5116e+01, + -4.8906e+00, -1.1009e+01, -2.9934e+00, 1.1106e+01, -7.4524e+00, -1.1324e+01], + [-1.0075e+01, -1.2352e+01, -5.5208e+00, 1.5060e+01, -1.4162e+01, -1.3731e+01, + -1.8131e+01, -2.2809e+01, -2.0523e+01, -9.2885e+00, -2.2656e+01, -2.3837e+01], + [-1.1237e+00, -1.5327e+01, -1.6454e+01, -1.0157e+01, -1.2902e+01, -1.6999e+01, + 1.6460e+01, -5.9819e+00, -6.6868e+00, 3.4977e-01, -4.5292e+00, -7.6471e+00], + [-5.8989e+00, -8.2629e+00, -6.0951e+00, -2.0709e+00, -6.8444e+00, -6.9492e+00, + -1.4980e+00, -6.4349e+00, -2.8802e+00, 6.5927e+00, -5.2023e+00, -5.9768e+00], + [-1.5690e+01, -1.9345e+01, -1.4696e+01, -9.7584e-01, -1.6753e+01, -1.8871e+01, + -7.1721e+00, -1.5371e+01, -5.9698e+00, 1.4855e+01, -1.0443e+01, -1.5127e+01], + [-1.5975e+01, -1.9709e+01, -1.6952e+01, -1.3250e+00, -1.7329e+01, -1.8364e+01, + -6.4735e+00, -1.5004e+01, -7.9697e+00, 1.5087e+01, -1.0322e+01, -1.4676e+01], + [-9.6248e+00, -1.3684e+01, -8.5521e+00, 1.4719e+01, -1.1945e+01, -1.3185e+01, + -1.6002e+01, -2.2853e+01, -1.9399e+01, -6.8938e+00, -1.9636e+01, -2.1333e+01], + [-2.8619e+00, -5.1141e+00, 1.3954e+01, 9.9080e-01, -5.9906e+00, -2.2784e+00, + -1.1103e+01, -1.2708e+01, -8.1810e-01, -6.1267e+00, -1.4330e+01, -1.3472e+01], + [-1.1058e+01, -1.4450e+01, -1.0945e+01, -1.8521e+00, -1.2169e+01, -1.3680e+01, + -4.4671e+00, -1.1790e+01, -5.1938e+00, 1.1196e+01, -8.3586e+00, -1.1404e+01], + [-7.1482e+00, -1.1688e+01, -6.5647e+00, 1.1144e+01, -9.7372e+00, -1.1413e+01, + -1.0610e+01, -1.6807e+01, -1.3919e+01, -5.2683e+00, -1.4328e+01, -1.5948e+01], + [-3.4779e+00, -6.1333e+00, -1.9338e+00, 7.6642e+00, -5.6737e+00, -4.8410e+00, + -8.8620e+00, -1.1995e+01, -8.9921e+00, -4.4965e+00, -1.1039e+01, -1.0286e+01], + [-5.4471e+00, -7.3961e+00, -4.0091e+00, 1.0008e+01, -8.2364e+00, -7.7552e+00, + -1.1562e+01, -1.4871e+01, -1.2803e+01, -4.9956e+00, -1.4799e+01, -1.4024e+01], + [-1.0975e+01, -1.4695e+01, -7.0197e+00, 1.6814e+01, -1.4073e+01, -1.4442e+01, + -1.8432e+01, -2.4743e+01, -2.0148e+01, -6.8818e+00, -2.2039e+01, -2.4336e+01], + [-1.8632e+00, 1.4883e+00, -2.7764e+00, -1.0044e+00, 1.1251e+01, -2.1246e+00, + -9.2420e+00, -1.0971e+01, -8.4337e+00, -4.7349e+00, -1.9218e+00, -9.4412e+00], + [-5.1858e+00, 1.4478e+01, -6.0291e+00, -2.7641e+00, -3.3264e+00, -3.5799e+00, + -1.1613e+01, 6.5402e-01, -1.1856e+01, -6.3279e+00, -1.2178e+01, -1.0819e+01], + [-8.3045e+00, -1.1004e+01, -5.4839e+00, 1.2478e+01, -1.1314e+01, -1.0851e+01, + -1.3917e+01, -1.9016e+01, -1.5132e+01, -3.3467e+00, -1.7661e+01, -1.8405e+01], + [-1.0578e+01, -9.6113e+00, -1.0476e+01, -6.9775e+00, -3.4868e+00, -9.2788e+00, + 2.2917e-01, 1.6189e+00, -1.9423e+00, 9.8658e-01, 8.7442e+00, 1.9664e+00], + [1.7821e+00, 2.5520e+00, 2.3929e+00, -3.7030e-01, -3.3090e-01, 1.9798e-01, -6.8749e+00, + -6.4092e+00, -5.7582e+00, -5.6637e+00, -8.7920e+00, -7.5211e+00]]) + +# make them actual probabilities +for i in range(len(piece_probabilities)): + piece_probabilities[i] = softmax(piece_probabilities[i]) + +occupancy_in_board = np.array([[False, False, False, False, False, False, True, False], + [True, False, False, False, False, False, False, False], + [False, True, False, False, True, True, False, False], + [False, True, True, False, True, False, True, True], + [True, False, True, True, True, False, False, False], + [False, False, False, True, False, True, False, True], + [False, False, True, False, True, False, True, False], + [True, False, False, False, True, False, False, False]]) + +# Variable to represent the whole board +board = intvar(0, 12, (8, 8), "board") + +# take the log of the NN output values +logprobs = np.log(piece_probabilities) + +# Convert log probabilities to ints using a desired precision +PRECISION = 1e-4 +lprobs = -logprobs / PRECISION + +# convert to cpm_array to be able to use it in the objective function +lprobs = cpm_array(np.int64(lprobs)) + +# Assign values to pieces +b, k, n, p, q, r = 0, 1, 2, 3, 4, 5 # Black pieces +B, K, N, P, Q, R = 6, 7, 8, 9, 10, 11 # White pieces +E = 12 # Empty square + +# Lowercases are black pieces, uppercases white pieces, and E is empty square +dict = {0: 'b', 1: 'k', 2: 'n', 3: 'p', 4: 'q', 5: 'r', 6: 'B', 7: 'K', 8: 'N', 9: 'P', 10: 'Q', 11: 'R', 12: 'E'} + +# The constraints that a chess board has to follow +model = Model( + # Both colors have only one king + Count(board, k) == 1, + Count(board, K) == 1, + # Both colors have maximum 16 pieces + (sum(board[l, c] < 6 for l in range(0, 8) for c in range(0, 8)) <= 16), + (sum(((board[l, c] >= 6) & (board[l, c] < 12)) for l in range(0, 8) for c in range(0, 8)) <= 16), + # Both colors have maximum 8 pawns + Count(board, p) <= 8, + Count(board, P) <= 8, + # Pawns can't be on the edge of the board + (Count(board[0], p) == 0), + (Count(board[0], P) == 0), + (Count(board[7], p) == 0), + (Count(board[7], P) == 0), + # The number of pieces of each piece type without promotion + ((Count(board, p) == 8) | (Count(board, P) == 8)).implies( + (Count(board, b) <= 2) & + (Count(board, r) <= 2) & + (Count(board, n) <= 2) & + (Count(board, q) <= 1) + ), + ((Count(board, P) == 8) | (Count(board, p) == 8)).implies( + (Count(board, B) <= 2) & + (Count(board, R) <= 2) & + (Count(board, N) <= 2) & + (Count(board, Q) <= 1) + ), + # Bishops can't have moved if the pawns are still in starting position + ((board[1, 1] == p) & (board[1, 3] == p) & ( + Count(board, p) == 8) & (Count(board, b) == 2) + ).implies(board[0, 2] == b), + ((board[6, 1] == P) & (board[6, 3] == P) & ( + Count(board, P) == 8) & (Count(board, B) == 2) + ).implies(board[7, 2] == B), + ((board[1, 4] == p) & (board[1, 6] == p) & ( + Count(board, b) == 2) & (Count(board, p) == 8) + ).implies(board[0, 5] == b), + ((board[6, 4] == P) & (board[6, 6] == P) & ( + Count(board, P) == 8) & (Count(board, B) == 2) + ).implies(board[7, 5] == B), + # If bishop isn't able to get out and pawns are still in starting position, rook is stuck + (((board[6, 4] == P) & (board[6, 6] == P) & (board[6, 7] == P) & (board[7, 5] == B) & ( + Count(board, P) == 8) & (Count(board, R) == 2) ).implies( + Xor([board[7, 6] == R, board[7, 7] == R]))), + (((board[6, 1] == P) & (board[6, 3] == P) & (board[6, 0] == P) & (board[7, 2] == B) & ( + Count(board, P) == 8) & (Count(board, R) == 2) ).implies( + Xor([board[7, 1] == R, board[7, 0] == R]))), + (((board[1, 1] == p) & (board[1, 3] == p) & (board[1, 0] == p) & (board[0, 2] == b) & ( + Count(board, p) == 8) & (Count(board, r) == 2) ).implies( + Xor([board[0, 1] == r, board[0, 0] == r]))), + (((board[1, 4] == p) & (board[1, 6] == p) & (board[1, 7] == p) & (board[0, 5] == b) & ( + Count(board, p) == 8) & (Count(board, r) == 2) ).implies( + Xor([board[0, 6] == r, board[0, 7] == r]))), + # If both bishops are stuck and pawns are in starting position, king and queen are also in starting position + (((board[1, 1] == p) & (board[1, 2] == p) & (board[1, 3] == p) & (board[1, 4] == p) & (board[1, 5] == p) & ( + board[1, 6] == p)).implies((board[0, 3] == q) & (board[0, 4] == k))), + (((board[6, 1] == P) & (board[6, 2] == P) & (board[6, 3] == P) & (board[6, 4] == P) & (board[6, 5] == P) & ( + board[6, 6] == P)).implies((board[7, 3] == Q) & (board[7, 4] == K))), + # If the pawns in column 2 or 7 didn't move, bishop of own color can't be in the corners of the board + (board[6, 1] == P).implies(~((board[7, 0] == B))), + (board[6, 6] == P).implies(~((board[7, 7] == B))), + (board[1, 6] == p).implies(~((board[0, 7] == b))), + (board[1, 1] == p).implies(~((board[0, 0] == b))), + # If the pawns in column 2 or 7 didn't move, bishop of other color can't be in the corners of the board + # except when promoted + ((board[6, 1] == P) & (board[7, 0] == B)).implies(Count(board, p) <= 7), + ((board[6, 6] == P) & (board[7, 7] == B)).implies(Count(board, p) <= 7), + ((board[1, 6] == p) & (board[0, 7] == b)).implies(Count(board, P) <= 7), + ((board[1, 1] == p) & (board[0, 0] == b)).implies(Count(board, P) <= 7), +) + +# Constraints for the empty squares +model += board[occupancy_in_board == False] == E + +# Maximize the probabilities of the pieces to get the most likely board +obj = sum(lp[v] for lp, v in zip(lprobs, board[occupancy_in_board])) + +model.minimize(obj) + +start = time.time() +sat = model.solve() +end = time.time() + +print(f"{end-start} seconds needed to solve") +# Solve the model +if sat: + print(board.value()) + + v_replace_value = np.vectorize(replace_value) + board_string = v_replace_value(board.value()) + + print(board_string) + +else: + print("no model found") diff --git a/examples/nonogram_ortools.ipynb b/examples/nonogram_ortools.ipynb index 58ae96ae6..3134824cd 100644 --- a/examples/nonogram_ortools.ipynb +++ b/examples/nonogram_ortools.ipynb @@ -95,43 +95,17 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, + "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\t []\t []\t [6]\t [5]\t [5]\t [5]\t [6]\t [11]\t [10, 1]\t [10]\t [10]\t [17]\t [21]\t [1, 14, 1]\t [15]\t [14]\t [7, 1]\t [7, 2]\t [5, 1]\t [5, 1]\t [5, 1]\t [5]\t [4]\t []\t []\n", - "[]\n", - "[]\n", - "[]\n", - "[10]\n", - "[2, 9]\n", - "[12]\n", - "[12]\n", - "[12]\n", - "[7]\n", - "[10]\n", - "[1, 6]\n", - "[1, 7]\n", - "[2, 10]\n", - "[3, 9, 1]\n", - "[14]\n", - "[14]\n", - "[13]\n", - "[11]\n", - "[9]\n", - "[7]\n", - "[3, 2]\n", - "[2, 1]\n", - "[1, 1]\n", - "[2, 2]\n", - "[]\n" - ] + "data": { + "image/png": "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px/74IMP1NHRoWuvvdbFZgAAAOfGlTNcAwMDOnz4cHq/p6dH3d3dKikpUWVlpe69916tWbNG8+fP1/z587VmzRoVFhYqEom4URcAAGBKXBm4Dhw4oCVLlqT3m5ubJUmNjY3avHmz/uEf/kHvvfeevva1r+mdd97R1VdfrSeffFJFRUVu1AUAAJgSVwau2tpa2bY95vM+n0+tra1qbW3NXikAAABDPHcNFwAAQL5h4AIAADCMgQsAAMAwz92Hy2mWZcmyLEfzzvzp1UxTuXTNrVy60jWXuprKpStdTeVmkuWzx7t6PYclk0kFg0HFYjEVFha6XQcAAOSZoaEhRSIRJRIJFRcXj7s2789whcPhCV+ETFiWpXg8rlAoJL/f79lMU7l0za1cutI1l7qayqUrXU3l9vf3T3pt3g9cfr/f0TfMZC5d6Woql650zaWupnLpSlenczPJ4aJ5AAAAwxi4AAAADGPgAgAAMIyBCwAAwDDjA1c0GtVVV12loqIilZaW6uabb9ahQ4cmPO6f//mfdemll6qgoEALFizQT37yE9NVAQAAjDA+cHV0dKipqUn79u1TPB7XqVOnFA6HNTg4OOYx69evV0tLi1pbW/Xqq6/qH//xH9XU1KRf/vKXpusCAAA4zvhtIXbt2jVif9OmTSotLVVXV5duuOGGUY/56U9/qrvuuktf/vKXJUl/+Zd/qX379umhhx5SfX296coAAACOyvp9uBKJhCSppKRkzDWpVEof/ehHRzxWUFCg559/XpZljXrfi1QqpVQqld5PJpMONQYAAJiarF40b9u2mpubdf311+uyyy4bc92yZcv0L//yL+rq6pJt2zpw4IA2btwoy7L0xz/+cdRjotGogsFgequoqDD1PwMAACAjWR24Vq1apZdeeklbtmwZd90DDzyguro6XXPNNfL7/brpppu0cuVKSdJHPvKRUY9paWlRIpFIb729vU7XBwAAOCdZG7hWr16tnTt3as+ePZozZ864awsKCrRx40YNDQ3pyJEjOnr0qKqqqlRUVKQLLrhg1GMCgYCKi4tHbAAAAF5g/Bou27a1evVqbd++XXv37tW8efMmfazf708PZ0888YQ+//nP67zzuHUYAADILcYHrqamJsViMe3YsUNFRUXq6+uTJAWDQRUUFIx6zGuvvabnn39eV199td555x09+uijeuWVV/T4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AAC4YkpnuGzbnnDNkSNHVFtbm/Fxt9xyi2655ZZzrQYAAOAZjlw0P56Ojg51dnaa/jUAAACeZXzg6unpMf0rAAAAPM3RO80DAADgbMbPcLnNsixZluVo3pk/vZppKpeuuZVLV7rmUldTuXSlq6ncTLJ89mSuYM9ByWRSwWBQsVhMhYWFbtcBAAB5ZmhoSJFIRIlEQsXFxeOuzfszXOFweMIXIROWZSkejysUCsnv93s201QuXXMrl650zaWupnLpSldTuf39/ZNem/cDl9/vd/QNM5lLV7qayqUrXXOpq6lcutLV6dxMcrhoHgAAwDAGLgAAAMMYuAAAAAxj4AIAADAso4Fr/fr1uvzyy1VcXKzi4mItXrxYv/nNb8Y95p//+Z916aWXqqCgQAsWLNBPfvKTCX/PPffco0996lMKBAK64oorMqkIAADgORl9S3HOnDl68MEHdckll0iSHn/8cd1000168cUXtWjRorPWr1+/Xi0tLfrxj3+sq666Ss8//7z+5m/+Rn/xF3+h+vr6MX+Pbdu68847tX//fr300ksZ/k8CAADwlowGrj8fkr773e9q/fr12rdv36gD109/+lPddddd+vKXvyxJ+su//Evt27dPDz300LgD1/e+9z1J0ltvvcXABQAAct4534fr9OnT+vnPf67BwUEtXrx41DWpVEof/ehHRzxWUFCg559/XpZlOXp/jVQqpVQqld5PJpOOZQMAAExFxhfNv/zyy/rYxz6mQCCgv/3bv9X27du1cOHCUdcuW7ZM//Iv/6Kuri7Ztq0DBw5o48aNsixLf/zjH6dc/kzRaFTBYDC9VVRUOJoPAABwrjIeuBYsWKDu7m7t27dPf/d3f6fGxkb99re/HXXtAw88oLq6Ol1zzTXy+/266aabtHLlSknSRz7ykSkV/3MtLS1KJBLprbe319F8AACAc5XxwDV9+nRdcskluvLKKxWNRlVdXa1169aNuragoEAbN27U0NCQjhw5oqNHj6qqqkpFRUW64IILplz+TIFAIP3tyQ83AAAAL5jyv6Vo2/aIa6dG4/f7NWfOHEnSE088oc9//vM67zxuAQYAAP5zyGjg+ta3vqW6ujpVVFTo5MmTeuKJJ7R3717t2rVr1PWvvfaann/+eV199dV655139Oijj+qVV17R448/Pu7vOXz4sAYGBtTX16f33ntP3d3d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01QuXXMrl650zaWupnLpSldTuf39/ZNem/cDl9/vd/QNM5lLV7qayqUrXXOpq6lcutLV6dxMcrhoHgAAwDAGLgAAAMMYuAAAAAzz7MB18uRJ3XvvvZo7d64KCgp07bXX6oUXXnC7FgAAQMY8O3B99atfVTwe109/+lO9/PLLCofDWrp0qd588023qwEAAGTEkwPXe++9p1/84hd6+OGHdcMNN+iSSy5Ra2ur5s2bp/Xr17tdDwAAICOeHLhOnTql06dP66Mf/eiIxwsKCvTss8+61AoAAODceHLgKioq0uLFi/Xf//t/17Fjx3T69Gn97Gc/0/79+3X8+PFRj0mlUkomkyM2AAAAL/DkwCVJP/3pT2Xbti666CIFAgF973vfUyQS0Uc+8pFR10ejUQWDwfRWUVGR5cYAAACj8+zAdfHFF6ujo0MDAwPq7e3V888/L8uyNG/evFHXt7S0KJFIpLfe3t4sNwYAABid5/9pnxkzZmjGjBl65513tHv3bj388MOjrgsEAgoEAlluBwAAMDHPDly7d++WbdtasGCBDh8+rPvuu08LFizQHXfc4XY1AACAjHj2I8VEIqGmpiZ94hOf0O23367rr79eTz75pJF/yBIAAMAkz57hWrFihVasWOF2DQAAgCnz7BkuAACAfMHABQAAYBgDFwAAgGEMXAAAAIZ59qJ5p1iWJcuyHM0786dXM03l0jW3culK11zqaiqXrnQ1lZtJls+2bdux3+whyWRSwWBQsVhMhYWFbtcBAAB5ZmhoSJFIRIlEQsXFxeOuzfszXOFweMIXIROWZSkejysUCjl2TzATmaZy6ZpbuXSlay51NZVLV7qayu3v75/02rwfuPx+v5GbpZrIpStdTeXSla651NVULl3p6nRuJjlcNA8AAGAYAxcAAIBhDFwAAACGeXbgqqqqks/nO2trampyuxoAAEBGPHvR/AsvvKDTp0+n91955RWFQiHdeuutLrYCAADInGcHrgsvvHDE/oMPPqiLL75YN954o0uNAAAAzo1nB64zffDBB/rZz36m5uZm+Xy+UdekUimlUqn0fjKZzFY9AACAcXn2Gq4ztbe3691339XKlSvHXBONRhUMBtNbRUVF9goCAACMIycGrg0bNqiurk7l5eVjrmlpaVEikUhvvb29WWwIAAAwNs9/pPjGG2/oqaee0rZt28ZdFwgE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Pp+6u7sdybVtW62trSovL1dBQYFqa2v16quvupLrVtc//zPx4fY//+f/HDMzGo3qqquuUlFRkUpLS3XzzTfr0KFDI9ac63uWSXcnMlauXHnW//ZrrrnG0W533XWXfD6f1q5dm3H/qqqqUd+fpqamjLNMZkq59fdWLnU9UzQalc/n07333utobq5g4MoR77zzjq677jr5/X795je/0W9/+1s98sgjOv/886eUu2jRIh0/fjy9vfzyy470NZFrItOJ13VwcFDV1dVqa2sb8/nrrrtODz74YEbdJsp9+OGH9eijj6qtrU0vvPCCysrKFAqF0v+OaDZz3ep65p+H48ePa+PGjfL5fPrSl740ZmZHR4eampq0b98+xeNxnTp1SuFwWIODgyN+77m8Z5l0dyrjr/7qr0a8Br/+9a8d69be3q79+/ervLw84+6S9MILL4zoFo/HJUm33nrrOeWZyvxQrvy9ZSrXVFfpP963xx57TJdffrljmTnHRk74xje+YV9//fWOZn7nO9+xq6urHc00lWuqq9OvqyR7+/btoz7X09NjS7JffPHFKecODw/bZWVl9oMPPph+7P3337eDwaD9wx/+0NXcbHUdzU033WR/5jOfmXSmbdv2iRMnbEl2R0fHWc9N5T0702S6n0tGY2OjfdNNNzmea9u2/fvf/96+6KKL7FdeecWeO3eu/U//9E9T+j22bdv33HOPffHFF9vDw8NTznI6M5f+3sqlrrZt2ydPnrTnz59vx+Nx+8Ybb7TvueceI7/H6zjDlSN27typK6+8UrfeeqtKS0tVU1OjH//4x1POff3111VeXq558+bptttu0+9+9zsH2prJNZFp6nU1raenR319fQqHw+nHAoGAbrzxRj333HOeyjXV9c/94Q9/0P/6X/9LX/nKVzI6LpFISJJKSkoc65JNe/fuVWlpqT7+8Y/rb/7mb3TixIkpZw4PD6uhoUH33XefFi1a5EBL6YMPPtDPfvYz3XnnnfL5fJ7MzJW/t0zlmura1NSk5cuXa+nSpY7k5SoGrhzxu9/9TuvXr9f8+fO1e/du/e3f/q3uvvtu/eQnPznnzKuvvlo/+clPtHv3bv34xz9WX1+frr32WvX390+pq4lcU11NvK7Z0NfXJ0maNWvWiMdnzZqVfs4ruaa6/rnHH39cRUVF+uIXvzjpY2zbVnNzs66//npd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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -141,13 +115,19 @@ "# url = \"https://pbs.twimg.com/profile_images/711593363624095747/lJV3XX-H_400x400.jpg\" # Alan Turing\n", "\n", "row_rules, col_rules = picture_to_nonogram_constraints(url)\n", - "# TODO, THIS IS NOT A PROPER VISUALISATION...\n", - "# IDEALLY IT WOULD BE IN A TABLE or so...\n", - "for cr in col_rules:\n", - " print(\"\\t\",cr, end=\"\")\n", - "print(\"\")\n", - "for rr in row_rules:\n", - " print(rr)" + "\n", + "# visualise the constraints over the grid\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.ticker import AutoMinorLocator\n", + "fig, ax = plt.subplots(1,1,figsize=(7,7))\n", + "ax.imshow([[0 for x in range(len(col_rules))] for y in range(len(row_rules))],cmap=\"binary\")\n", + "ax.set_xticks(range(len(col_rules)), [\"\\n\".join([str(i) for i in r]) for r in col_rules])\n", + "ax.set_yticks(range(len(row_rules)), [\",\".join([str(i) for i in r]) for r in row_rules])\n", + "ax.grid(which='minor')\n", + "ax.minorticks_on()\n", + "ax.tick_params(which='minor', bottom=False, left=False)\n", + "ax.xaxis.set_minor_locator(AutoMinorLocator(2))\n", + "ax.yaxis.set_minor_locator(AutoMinorLocator(2)) # we show minor gridlines between the constraints, so that they allign with the blocks to be colored in." ] }, { @@ -265,23 +245,19 @@ { "cell_type": "code", "execution_count": 7, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, + "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/var/folders/b_/fbfx8w992m9_7pm37zz2vmg00000gn/T/ipykernel_69469/2290791997.py:10: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.\n", + "C:\\Users\\wout\\AppData\\Local\\Temp\\ipykernel_18252\\34177285.py:9: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.\n", " fig.show()\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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27aqq6s1c+ZMnXPOOUOaq6OjQ2PHju3x2NixY9XR0dHnMX6/X5ZlxTafzzekDAAAAImSsMK1ePFivf7663Ff8juRL19esW2730suNTU1CofDsa29vT0hOQAAAIYqId+luGTJEm3evFmBQEDjx48f8nx5eXnHnc06cODAcWe9vsjtdsvtdg/5tQEAABJtSGe4bNvW4sWLtWHDBr344ouaOHFiQkJNnz5djY2NPR57/vnnNWPGjITMDwAAkExDOsNVVVWldevWadOmTcrOzo6dlbIsS1lZWX0eFwwGJUmdnZ06ePCggsGgRo4cqcmTJ0uSbrvtNl166aW67777dOWVV2rTpk164YUX9PLLLw8lLgAAgCOGtCxEX/dUrV27VgsXLpQk1dbWqr6+Xm1tbf0eN2HChB5j/uu//kv/8R//offee09nnHGGfv7zn+uqq66KOxvLQpjDx6vNSKe/ASDd8L4FEwbSNYZ0hiueP+C2tjYVFxcP+Lirr75aV1999WCjAQAApIyE3DTfn+bmZgUCAdMvAwAAkLKMF65QKGT6JQAAAFJaQleaBwAAwPEoXAAAAIYZv6SI4cfUp+nS6VNEfKIQMPNvln9bGK44wwUAAGAYhQsAAMAwChcAAIBhFC4AAADDKFwAAACGUbgAAAAMG1Dhevjhh3XeeecpJydHOTk5mj59up599tl+j/nVr36ls88+W1lZWSosLNRjjz12wte57bbb9M1vflNut1vnn3/+QCICAACknAGtwzV+/HgtX75cZ555piTp0Ucf1ZVXXqnXXntNU6ZMOW78ww8/rJqaGv32t7/VtGnT9Oqrr+r73/++vv71r6u8vLzP17FtWzfccIO2b9+u119/fYD/SQAAAKnFZQ9x5TqPx6MHHnhAN95443HPzZgxQ5dccokeeOCB2GNLly5VS0uLXn755RPOXVtbq4aGBgWDwQHnikQisixL4XBYOTk5Az4+2Vjsj4VPgXSTTgufptP7C9LHQLrGoFeaP3bsmJ566il1dXVp+vTpvY6JRqM6+eSTezyWlZWlV199VUeOHFFmZuZgX77X14pGo7H9SCSSsLkBAACGYsA3zb/xxhv62te+JrfbrR/84AfauHGjJk+e3OvYOXPm6He/+5127twp27bV0tKiNWvW6MiRI/rwww+HHP6L/H6/LMuKbT6fL6HzAwAADNaAC1dh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\n", "text/plain": [ "
" ] @@ -291,7 +267,6 @@ } ], "source": [ - "import matplotlib.pyplot as plt\n", "fig, ax = plt.subplots(1,1,figsize=(7,7))\n", "\n", "ax.imshow(board.value(),cmap=\"binary\")\n", @@ -361,9 +336,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.9.13" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/examples/room_assignment.ipynb b/examples/room_assignment.ipynb index 40aa61ae5..97efb931a 100644 --- a/examples/room_assignment.ipynb +++ b/examples/room_assignment.ipynb @@ -49,73 +49,8 @@ "outputs": [ { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
startendroom
02021-08-072021-08-162.0
12021-07-072021-07-131.0
22021-07-242021-08-011.0
32021-08-272021-09-050.0
42021-08-132021-09-03NaN
\n", - "
" - ], - "text/plain": [ - " start end room\n", - "0 2021-08-07 2021-08-16 2.0\n", - "1 2021-07-07 2021-07-13 1.0\n", - "2 2021-07-24 2021-08-01 1.0\n", - "3 2021-08-27 2021-09-05 0.0\n", - "4 2021-08-13 2021-09-03 NaN" - ] + "text/plain": " start end room\n0 2021-08-07 2021-08-16 2.0\n1 2021-07-07 2021-07-13 1.0\n2 2021-07-24 2021-08-01 1.0\n3 2021-08-27 2021-09-05 0.0\n4 2021-08-13 2021-09-03 NaN", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
startendroom
02021-08-072021-08-162.0
12021-07-072021-07-131.0
22021-07-242021-08-011.0
32021-08-272021-09-050.0
42021-08-132021-09-03NaN
\n
" }, "execution_count": 3, "metadata": {}, @@ -179,7 +114,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "True ExitStatus.OPTIMAL (0.022478 seconds)\n" + "True ExitStatus.OPTIMAL (0.06577480000000001 seconds)\n" ] } ], @@ -207,29 +142,7 @@ "outputs": [ { "data": { - "text/html": [ - " \n", - " " - ] + "text/html": " \n " }, "metadata": {}, "output_type": "display_data" @@ -237,9 +150,6 @@ { "data": { "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, "data": [ { "alignmentgroup": "True", @@ -336,46 +246,45 @@ "orientation": "h", "showlegend": false, "textposition": "auto", - "type": "bar", "x": [ - 777600000, - 518400000, - 691200000, - 777600000, - 1814400000, - 518400000, - 604800000, - 604800000, - 518400000, - 604800000, - 518400000, - 604800000, - 432000000, - 604800000, - 777600000, - 1123200000, - 432000000, - 518400000, - 604800000, - 432000000, - 604800000, - 172800000, - 604800000, - 604800000, - 1209600000, - 432000000, - 950400000, - 432000000, - 518400000, - 950400000, - 604800000, - 950400000, - 432000000, - 864000000, - 432000000, - 518400000, - 691200000, - 604800000 + 7.776E8, + 5.184E8, + 6.912E8, + 7.776E8, + 1.8144E9, + 5.184E8, + 6.048E8, + 6.048E8, + 5.184E8, + 6.048E8, + 5.184E8, + 6.048E8, + 4.32E8, + 6.048E8, + 7.776E8, + 1.1232E9, + 4.32E8, + 5.184E8, + 6.048E8, + 4.32E8, + 6.048E8, + 1.728E8, + 6.048E8, + 6.048E8, + 1.2096E9, + 4.32E8, + 9.504E8, + 4.32E8, + 5.184E8, + 9.504E8, + 6.048E8, + 9.504E8, + 4.32E8, + 8.64E8, + 4.32E8, + 5.184E8, + 6.912E8, + 6.048E8 ], "xaxis": "x", "y": [ @@ -418,143 +327,83 @@ 0, 0 ], - "yaxis": "y" + "yaxis": "y", + "type": "bar" } ], "layout": { - "barmode": "overlay", - "coloraxis": { - "colorbar": { - "title": { - "text": "index" - } - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "legend": { - "tracegroupgap": 0 - }, - "margin": { - "t": 60 - }, "template": { "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ + "histogram2dcontour": [ { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" + "type": "histogram2dcontour", + "colorbar": { + "outlinewidth": 0, + "ticks": "" }, - "type": "carpet" + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ] } ], "choropleth": [ { + "type": "choropleth", "colorbar": { "outlinewidth": 0, "ticks": "" - }, - "type": "choropleth" + } } ], - "contour": [ + "histogram2d": [ { + "type": "histogram2d", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -590,31 +439,22 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" + ] } ], "heatmap": [ { + "type": "heatmap", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -650,22 +490,22 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] - ], - "type": "heatmap" + ] } ], "heatmapgl": [ { + "type": "heatmapgl", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -701,34 +541,31 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] - ], - "type": "heatmapgl" + ] } ], - "histogram": [ + "contourcarpet": [ { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" + "type": "contourcarpet", + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } } ], - "histogram2d": [ + "contour": [ { + "type": "contour", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -764,22 +601,22 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] - ], - "type": "histogram2d" + ] } ], - "histogram2dcontour": [ + "surface": [ { + "type": "surface", "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -815,37 +652,19 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] - ], - "type": "histogram2dcontour" + ] } ], "mesh3d": [ { + "type": "mesh3d", "colorbar": { "outlinewidth": 0, "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" + } } ], "scatter": [ @@ -858,149 +677,162 @@ "type": "scatter" } ], - "scatter3d": [ + "parcoords": [ { + "type": "parcoords", "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } - }, + } + } + ], + "scatterpolargl": [ + { + "type": "scatterpolargl", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - }, - "type": "scatter3d" + } } ], - "scattercarpet": [ + "bar": [ { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 } }, - "type": "scattercarpet" + "type": "bar" } ], "scattergeo": [ { + "type": "scattergeo", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - }, - "type": "scattergeo" + } } ], - "scattergl": [ + "scatterpolar": [ { + "type": "scatterpolar", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - }, - "type": "scattergl" + } } ], - "scattermapbox": [ + "histogram": [ { "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 } }, - "type": "scattermapbox" + "type": "histogram" } ], - "scatterpolar": [ + "scattergl": [ { + "type": "scattergl", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - }, - "type": "scatterpolar" + } } ], - "scatterpolargl": [ + "scatter3d": [ { - "marker": { + "type": "scatter3d", + "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, - "type": "scatterpolargl" + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + } + } + ], + "scattermapbox": [ + { + "type": "scattermapbox", + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + } } ], "scatterternary": [ { + "type": "scatterternary", "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } - }, - "type": "scatterternary" + } } ], - "surface": [ + "scattercarpet": [ { - "colorbar": { - "outlinewidth": 0, - "ticks": "" + "type": "scattercarpet", + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + } + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" } ], "table": [ @@ -1023,15 +855,84 @@ }, "type": "table" } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } ] }, "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, "autotypenumbers": "strict", + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "hovermode": "closest", + "hoverlabel": { + "align": "left" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "bgcolor": "#E5ECF6", + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "ternary": { + "bgcolor": "#E5ECF6", + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, "coloraxis": { "colorbar": { "outlinewidth": 0, @@ -1039,55 +940,51 @@ } }, "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], + "sequential": [ [ - 0.1, - "#c51b7d" + 0.0, + "#0d0887" ], [ - 0.2, - "#de77ae" + 0.1111111111111111, + "#46039f" ], [ - 0.3, - "#f1b6da" + 0.2222222222222222, + "#7201a8" ], [ - 0.4, - "#fde0ef" + 0.3333333333333333, + "#9c179e" ], [ - 0.5, - "#f7f7f7" + 0.4444444444444444, + "#bd3786" ], [ - 0.6, - "#e6f5d0" + 0.5555555555555556, + "#d8576b" ], [ - 0.7, - "#b8e186" + 0.6666666666666666, + "#ed7953" ], [ - 0.8, - "#7fbc41" + 0.7777777777777778, + "#fb9f3a" ], [ - 0.9, - "#4d9221" + 0.8888888888888888, + "#fdca26" ], [ - 1, - "#276419" + 1.0, + "#f0f921" ] ], - "sequential": [ + "sequentialminus": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -1123,125 +1020,106 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], - "sequentialminus": [ + "diverging": [ [ 0, - "#0d0887" + "#8e0152" ], [ - 0.1111111111111111, - "#46039f" + 0.1, + "#c51b7d" ], [ - 0.2222222222222222, - "#7201a8" + 0.2, + "#de77ae" ], [ - 0.3333333333333333, - "#9c179e" + 0.3, + "#f1b6da" ], [ - 0.4444444444444444, - "#bd3786" + 0.4, + "#fde0ef" ], [ - 0.5555555555555556, - "#d8576b" + 0.5, + "#f7f7f7" ], [ - 0.6666666666666666, - "#ed7953" + 0.6, + "#e6f5d0" ], [ - 0.7777777777777778, - "#fb9f3a" + 0.7, + "#b8e186" ], [ - 0.8888888888888888, - "#fdca26" + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" ], [ 1, - "#f0f921" + "#276419" ] ] }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "#E5ECF6", - "showlakes": true, - "showland": true, - "subunitcolor": "white" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" + "xaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "automargin": true, + "zerolinewidth": 2 }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" + "yaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 }, - "bgcolor": "#E5ECF6", - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } + "zerolinecolor": "white", + "automargin": true, + "zerolinewidth": 2 }, "scene": { "xaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", - "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", - "zerolinecolor": "white" + "zerolinecolor": "white", + "gridwidth": 2 }, "yaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", - "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", - "zerolinecolor": "white" + "zerolinecolor": "white", + "gridwidth": 2 }, "zaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", - "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", - "zerolinecolor": "white" + "zerolinecolor": "white", + "gridwidth": 2 } }, "shapedefaults": { @@ -1249,98 +1127,107 @@ "color": "#2a3f5f" } }, - "ternary": { - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "geo": { + "bgcolor": "white", + "landcolor": "#E5ECF6", + "subunitcolor": "white", + "showland": true, + "showlakes": true, + "lakecolor": "white" }, "title": { "x": 0.05 }, - "xaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 + "mapbox": { + "style": "light" } } }, "xaxis": { "anchor": "y", "domain": [ - 0, - 1 + 0.0, + 1.0 ], "type": "date" }, "yaxis": { "anchor": "x", "domain": [ - 0, - 1 + 0.0, + 1.0 ], "title": { "text": "Room Number" } - } + }, + "coloraxis": { + "colorbar": { + "title": { + "text": "index" + } + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ] + }, + "legend": { + "tracegroupgap": 0 + }, + "margin": { + "t": 60 + }, + "barmode": "overlay" + }, + "config": { + "plotlyServerURL": "https://plot.ly" } }, - "text/html": [ - "
" - ] + "text/html": "
" }, "metadata": {}, "output_type": "display_data" @@ -1353,6 +1240,7 @@ "df['Room Number'] = requestvars.value() # value in the solution\n", "df['Room Number'].fillna(0, inplace=True)\n", "fig = px.timeline(df, x_start=\"start\", x_end=\"end\", y=\"Room Number\", color=df.index)\n", + "fig.to_html()\n", "fig.show()" ] } diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 9ce252bd4..895d675bf 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -15,7 +15,7 @@ "gurobi": {}, "minizinc": {"circuit"}, "pysat": {"circuit", "element","min","max","allequal","alldifferent","cumulative"}, - "pysdd": {"circuit", "element","min","max","allequal","alldifferent","cumulative"}, + "pysdd": {"circuit", "element","min","max","allequal","alldifferent","cumulative",'xor'}, "exact": {}, } diff --git a/tests/test_expressions.py b/tests/test_expressions.py index 0b51133dd..8921aa5ea 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -6,6 +6,7 @@ from cpmpy.expressions import * from cpmpy.expressions.variables import NDVarArray from cpmpy.expressions.core import Operator, Expression +from cpmpy.expressions.utils import get_bounds class TestComparison(unittest.TestCase): def test_comps(self): @@ -444,6 +445,27 @@ def test_incomplete_func(self): self.assertTrue(m.solve(solver="z3")) self.assertTrue(cons.value()) + + def test_list(self): + + # cpm_array + iv = cp.intvar(0,10,shape=3) + lbs, ubs = iv.get_bounds() + self.assertListEqual([0,0,0], lbs.tolist()) + self.assertListEqual([10,10,10], ubs.tolist()) + # list + iv = [cp.intvar(0,10) for _ in range(3)] + lbs, ubs = get_bounds(iv) + self.assertListEqual([0, 0, 0], lbs) + self.assertListEqual([10, 10, 10], ubs) + # nested list + exprs = [intvar(0,1), [intvar(2,3), intvar(4,5)], [intvar(5,6)]] + lbs, ubs = get_bounds(exprs) + self.assertListEqual([0,[2,4],[5]], lbs) + self.assertListEqual([1,[3,5],[6]], ubs) + + + def test_not_operator(self): p = boolvar() q = boolvar() @@ -466,10 +488,10 @@ def test_not_operator(self): def test_description(self): a,b = cp.boolvar(name="a"), cp.boolvar(name="b") - cons = a ^ b + cons = a | b cons.set_description("either a or b should be true, but not both") - self.assertEqual(repr(cons), "a xor b") + self.assertEqual(repr(cons), "(a) or (b)") self.assertEqual(str(cons), "either a or b should be true, but not both") # ensure nothing goes wrong due to calling __str__ on a constraint with a custom description @@ -480,19 +502,19 @@ def test_description(self): self.assertTrue(cp.Model(cons).solve(solver=solver)) ## test extra attributes of set_description - cons = a ^ b + cons = a | b cons.set_description("either a or b should be true, but not both", override_print=False) - self.assertEqual(repr(cons), "a xor b") - self.assertEqual(str(cons), "a xor b") + self.assertEqual(repr(cons), "(a) or (b)") + self.assertEqual(str(cons), "(a) or (b)") - cons = a ^ b + cons = a | b cons.set_description("either a or b should be true, but not both", full_print=True) - self.assertEqual(repr(cons), "a xor b") - self.assertEqual(str(cons), "either a or b should be true, but not both -- a xor b") + self.assertEqual(repr(cons), "(a) or (b)") + self.assertEqual(str(cons), "either a or b should be true, but not both -- (a) or (b)") diff --git a/tests/test_globalconstraints.py b/tests/test_globalconstraints.py index fc237f575..f6386b9ea 100644 --- a/tests/test_globalconstraints.py +++ b/tests/test_globalconstraints.py @@ -1,8 +1,12 @@ import copy import unittest + +import pytest + import cpmpy as cp from cpmpy.expressions.globalfunctions import GlobalFunction from cpmpy.exceptions import TypeError +from cpmpy.solvers import CPM_minizinc class TestGlobal(unittest.TestCase): @@ -368,6 +372,36 @@ def test_cumulative_single_demand(self): m += cp.Cumulative(start, duration, end, demand, capacity) self.assertTrue(m.solve()) + def test_cumulative_decomposition_capacity(self): + import numpy as np + + # before merging #435 there was an issue with capacity constraint + start = cp.intvar(0, 10, 4, "start") + duration = [1, 2, 2, 1] + end = cp.intvar(0, 10, shape=4, name="end") + demand = 10 # tasks cannot be scheduled + capacity = np.int64(5) # bug only happened with numpy ints + cons = cp.Cumulative(start, duration, end, demand, capacity) + self.assertFalse(cp.Model(cons).solve()) # this worked fine + # also test decomposition + self.assertFalse(cp.Model(cons.decompose()).solve()) # capacity was not taken into account and this failed + + @pytest.mark.skipif(not CPM_minizinc.supported(), + reason="Minizinc not installed") + def test_cumulative_single_demand(self): + start = cp.intvar(0, 10, name="start") + dur = 5 + end = cp.intvar(0, 10, name="end") + demand = 2 + capacity = 10 + + m = cp.Model() + m += cp.Cumulative([start], [dur], [end], [demand], capacity) + + self.assertTrue(m.solve(solver="ortools")) + self.assertTrue(m.solve(solver="minizinc")) + + def test_cumulative_no_np(self): start = cp.intvar(0, 10, 4, "start") duration = (1, 2, 2, 1) # smt weird such as a tuple diff --git a/tests/test_solvers_solhint.py b/tests/test_solvers_solhint.py index 00dbeac61..15a6521af 100644 --- a/tests/test_solvers_solhint.py +++ b/tests/test_solvers_solhint.py @@ -10,7 +10,7 @@ class TestSolutionHinting(unittest.TestCase): def test_hints(self): a,b = cp.boolvar(shape=2) - model = cp.Model(a ^ b) + model = cp.Model(a | b) for n, solver_class in cp.SolverLookup.base_solvers(): if not solver_class.supported(): @@ -29,7 +29,7 @@ def test_hints(self): self.assertEqual(a.value(), False) self.assertEqual(b.value(), True) - slv.solution_hint([a,b], [True,True]) + slv.solution_hint([a,b], [False,False]) self.assertTrue(slv.solve(**args)) # should also work with an UNSAT hint slv.solution_hint([a,[b]], [[[False]], True]) # check nested lists