Skip to content

Commit

Permalink
docs: small improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomSerg committed Jul 16, 2024
1 parent 207c997 commit 24c7bbf
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 85 deletions.
24 changes: 13 additions & 11 deletions cpmpy/expressions/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
Here is a list of standard python operators and what object (with what expr.name) it creates:
Comparisons:
**Comparisons**:
- x == y Comparison("==", x, y)
- x != y Comparison("!=", x, y)
Expand All @@ -20,9 +20,9 @@
- x > y Comparison(">", x, y)
- x >= y Comparison(">=", x, y)
Mathematical operators:
**Mathematical operators**:
- -x Operator("-", [x])
- x Operator("-", [x])
- x + y Operator("sum", [x,y])
- sum([x,y,z]) Operator("sum", [x,y,z])
- sum([c0*x, c1*y, c2*z]) Operator("wsum", [[c0,c1,c2],[x,y,z]])
Expand All @@ -32,7 +32,7 @@
- x % y Operator("mod", [x,y])
- x ** y Operator("pow", [x,y])
Logical operators:
**Logical operators**:
- x & y Operator("and", [x,y])
- x | y Operator("or", [x,y])
Expand All @@ -47,14 +47,16 @@
Apart from operator overloading, expressions implement two important functions:
- `is_bool()` which returns whether the __return type__ of the expression is Boolean.
If it does, the expression can be used as top-level constraint
or in logical operators.
- `is_bool()`
which returns whether the return type of the expression is Boolean.
If it does, the expression can be used as top-level constraint
or in logical operators.
- `value()` computes the value of this expression, by calling .value() on its
subexpressions and doing the appropriate computation
this is used to conveniently print variable values, objective values
and any other expression value (e.g. during debugging).
- `value()`
computes the value of this expression, by calling .value() on its
subexpressions and doing the appropriate computation
this is used to conveniently print variable values, objective values
and any other expression value (e.g. during debugging).
===============
List of classes
Expand Down
2 changes: 1 addition & 1 deletion cpmpy/solvers/exact.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def __init__(self, cpm_model=None, subsolver=None):
Arguments:
- cpm_model: Model(), a CPMpy Model() (optional)
- subsolver: None
- subsolver: None, not used
"""
if not self.supported():
raise Exception("Install 'exact' as a Python package to use this solver interface")
Expand Down
1 change: 1 addition & 0 deletions cpmpy/solvers/gurobi.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(self, cpm_model=None, subsolver=None):
Arguments:
- cpm_model: a CPMpy Model()
- subsolver: None, not used
"""
if not self.supported():
raise Exception(
Expand Down
2 changes: 1 addition & 1 deletion cpmpy/solvers/minizinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ def solveAll(self, display=None, time_limit=None, solution_limit=None, call_from
- time_limit: stop after this many seconds (default: None)
- solution_limit: stop after this many solutions (default: None)
- call_from_model: whether the method is called from a CPMpy Model instance or not
- any other keyword argument
- kwargs: any keyword argument, sets parameters of solver object, overwrites construction-time kwargs
Returns: number of solutions found
"""
Expand Down
2 changes: 1 addition & 1 deletion cpmpy/solvers/ortools.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def __init__(self, cpm_model=None, subsolver=None):
Arguments:
- cpm_model: Model(), a CPMpy Model() (optional)
- subsolver: None
- subsolver: None, not used
"""
if not self.supported():
raise Exception("Install the python 'ortools' package to use this solver interface")
Expand Down
6 changes: 2 additions & 4 deletions cpmpy/solvers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
"""
Utilities for handling solvers
Contains a static variable `builtin_solvers` that lists
CPMpy solvers (first one is the default solver by default)
=================
List of functions
=================
Expand Down Expand Up @@ -36,9 +33,10 @@ def param_combinations(all_params, remaining_keys=None, cur_params=None):
Recursively yield all combinations of param values
For example usage, see `examples/advanced/hyperparameter_search.py`
https://github.com/CPMpy/cpmpy/blob/master/examples/advanced/hyperparameter_search.py
- all_params is a dict of {key: list} items, e.g.:
{'val': [1,2], 'opt': [True,False]}
{'val': [1,2], 'opt': [True,False]}
- output is an generator over all {key:value} combinations
of the keys and values. For the example above:
Expand Down
25 changes: 15 additions & 10 deletions cpmpy/tools/dimacs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
##
## dimacs.py
##
"""
This file implements helper functions for exporting CPMpy models from and to DIMACS format.
DIMACS is a textual format to represent CNF problems.
The header of the file should be formatted as `p cnf <n_vars> <n_constraints>`
If the number of variables and constraints are not given, it is inferred by the parser.
Each remaining line of the file is formatted as a list of integers.
An integer represents a Boolean variable and a negative Boolean variable is represented using a `-` sign.
"""

import cpmpy as cp

from cpmpy.expressions.variables import _BoolVarImpl, NegBoolView
Expand All @@ -9,16 +24,6 @@

import re

"""
This file implements helper functions for exporting CPMpy models from and to DIMACS format.
DIMACS is a textual format to represent CNF problems.
The header of the file should be formatted as `p cnf <n_vars> <n_constraints>
If the number of variables and constraints are not given, it is inferred by the parser.
Each remaining line of the file is formatted as a list of integers.
An integer represents a Boolean variable and a negative Boolean variable is represented using a `-` sign.
"""


def write_dimacs(model, fname=None):
"""
Expand Down
2 changes: 1 addition & 1 deletion cpmpy/tools/explain/mcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
def mcs(soft, hard=[], solver="ortools"):
"""
Compute Minimal Correction Subset of unsatisfiable model.
Remvoving these contraints will result in a satisfiable model.
Removing these contraints will result in a satisfiable model.
Computes a subset of constraints which minimizes the total number of constraints to be removed
"""
return mcs_opt(soft, hard, 1, solver)
Expand Down
24 changes: 13 additions & 11 deletions cpmpy/tools/tune_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
Programming, Artificial Intelligence, and Operations Research. CPAIOR 2022. Lecture Notes in Computer Science,
vol 13292. Springer, Cham. https://doi.org/10.1007/978-3-031-08011-1_6
DOI: https://doi.org/10.1007/978-3-031-08011-1_6
Link to paper: https://rdcu.be/cQyWR
Link to original code of paper: https://github.com/ML-KULeuven/DeCaprio
- DOI: https://doi.org/10.1007/978-3-031-08011-1_6
- Link to paper: https://rdcu.be/cQyWR
- Link to original code of paper: https://github.com/ML-KULeuven/DeCaprio
This code currently only implements the author's 'Hamming' surrogate function.
The parameter tuner iteratively finds better hyperparameters close to the current best configuration during the search.
Expand All @@ -23,13 +23,15 @@
from ..solvers.solver_interface import ExitStatus

class ParameterTuner:
"""
Parameter tuner based on DeCaprio method [ref_to_decaprio]
"""

def __init__(self, solvername, model, all_params=None, defaults=None):
"""
Parameter tuner based on DeCaprio method [ref_to_decaprio]
:param: solvername: Name of solver to tune
:param: model: CPMpy model to tune parameters on
:param: all_params: optional, dictionary with parameter names and values to tune. If None, use predefined parameter set.
"""
:param solvername: Name of solver to tune
:param model: CPMpy model to tune parameters on
:param all_params: optional, dictionary with parameter names and values to tune. If None, use predefined parameter set.
"""
self.solvername = solvername
self.model = model
Expand All @@ -44,9 +46,9 @@ def __init__(self, solvername, model, all_params=None, defaults=None):

def tune(self, time_limit=None, max_tries=None, fix_params={}):
"""
:param: time_limit: Time budget to run tuner in seconds. Solver will be interrupted when time budget is exceeded
:param: max_tries: Maximum number of configurations to test
:param: fix_params: Non-default parameters to run solvers with.
:param time_limit: Time budget to run tuner in seconds. Solver will be interrupted when time budget is exceeded
:param max_tries: Maximum number of configurations to test
:param fix_params: Non-default parameters to run solvers with.
"""
if time_limit is not None:
start_time = time.time()
Expand Down
5 changes: 3 additions & 2 deletions docs/adding_solver.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ So for any solver you wish to add, chances are that most of the transformations

## Stateless transformation functions

Because CPMpy solver interfaces transform and post constraints *eagerly*, they can be used *incremental*, meaning that you can add some constraints, call `solve()` add some more constraints and solve again. If the underlying solver is also incremental, it will reuse knowledge of the previous solve call to speed up this solve call.
Because CPMpy's solver-interfaces transform and post constraints *eagerly*, they can be used *incremental*, meaning that you can add some constraints, call `solve()` add some more constraints and solve again. If the underlying solver is also incremental, it will reuse knowledge of the previous solve call to speed up this solve call.

The way that CPMpy succeeds to be an incremental modeling language, is by making all transformation functions *stateless*. Every transformation function is a python *function* that maps a (list of) CPMpy expressions to (a list of) equivalent CPMpy expressions. Transformations are not classes, they do not store state, they do not know (or care) what model a constraint belongs to. They take expressions as input and compute expressions as output. That means they can be called over and over again, and chained in any combination or order.

Expand Down Expand Up @@ -102,5 +102,6 @@ After posting the constraint, the answer of your solver is checked so you will b
## Tunable hyperparameters
CPMpy offers a tool for searching the best hyperparameter configuration for a given model on a solver (see [corresponding documentation](solver_parameters.md)).
Solver wanting to support this tool should add the following attributes to their solver interface: `tunable_params` and `default_params` (see [ortools](https://github.com/CPMpy/cpmpy/blob/11ae35b22357ad9b8d6f47317df2c236c3ef5997/cpmpy/solvers/ortools.py#L473) for an example).
Solvers wanting to support this tool should add the following attributes to their interface: `tunable_params` and `default_params` (see [OR-Tools](https://github.com/CPMpy/cpmpy/blob/11ae35b22357ad9b8d6f47317df2c236c3ef5997/cpmpy/solvers/ortools.py#L473) for an example).
3 changes: 2 additions & 1 deletion docs/how_to_debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ Either you have the same output, and it is not the solver's fault, or you have a

## Debugging a modeling error

You get an error when you create an expression? Then you are probably writing it wrongly. Check the documentation and the running examples for similar examples of what you wish to express.
You get an error when you create an expression? Then you are probably writing it wrongly. Check the documentation and the running examples for similar instances of what you wish to express.

Here are a few quirks in Python/CPMpy:
- when using `&` and `|`, make sure to always put the subexpressions in brackets. E.g. `(x == 1) & (y == 0)` instead of `x == 1 & y == 0`. The latter wont work, because Python will unfortunately think you meant `x == (1 & (y == 0))`.
- you can write `vars[other_var]` but you can't write `non_var_list[a_var]`. That is because the `vars` list knows CPMpy, and the `non_var_list` does not. Wrap it: `non_var_list = cpm_array(non_var_list)` first, or write `Element(non_var_list, a_var)` instead.
- only write `sum(v)` on lists, don't write it if `v` is a matrix or tensor, as you will get a list in response. Instead, use NumPy's `v.sum()` instead.
- when providing names for decision variables, make shure that they are unique. Many solvers depend on this uniqueness and you will encounter strange (and hard to debug) behaviour if you don't enforce this.

Try printing the expression `print(e)` or subexpressions, and check that the output matches what you wish to express. Decompose the expression and try printing the individual components and their piecewice composition to see what works and when it starts to break.

Expand Down
Loading

0 comments on commit 24c7bbf

Please sign in to comment.