-
Notifications
You must be signed in to change notification settings - Fork 140
Optimization #439
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
Optimization #439
Conversation
|
Is this ready for review? |
|
Not really, I would like to add some more tests and features, but if you have time for an early review it is most welcome! 😄 |
|
Interestingly, the tests trigger a deterministic segmentation fault on z3 with python3.6 for windows. I will try to reproduce locally. |
|
👋 Thanks for pushing this through! I will re-review this in the next few days. Since the PR description is almost 2yr old 👴 , could you make sure that the PR description captures the changes that were made? |
|
@marcogario wait before merging this. With @arrighi79 and @PatrickTrentin88 we are working on a more comprehensive and adherent refactoring of the optimization. I will use this branch as "master" for a series of PRs and then we'll merge this afterwards. |
marcogario
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very promising! 🎉
The PR is doing quite a bunch of things and I think it would better to split it in more manageable chunks.
- The optimization interface using "dumb" SMT solvers
- Integration with OptiMathsat
- Other changes (e.g., bug fixes in configuration, Z3 interpolants)
I am not particularly convinced that the dynamic loading of OptiMathsat is the best way to proceed. This adds a layer of indirection that needs to be resolved for basically every solver call. I wonder if this introduces a performance regression. Could you update the description of the PR explaining the rationale for this approach and alternatives that were considered?
| # Check if we are working on a version MathSAT supporting quantifier elimination | ||
| self._msat_lib = MSATLibLoader(self.__class__.__lib_name__) | ||
| if not hasattr(self._msat_lib, "MSAT_EXIST_ELIM_ALLSMT_FM"): | ||
| raise NotImplementedError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we still need this?
pysmt/solvers/optimizer.py
Outdated
| Interface for the optimization | ||
| """ | ||
|
|
||
| def optimize(self, cost_function, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this called minimize? optimize does not make it clear in which direction we are working (min/max).
pysmt/solvers/optimizer.py
Outdated
| def _setup(self): | ||
| raise NotImplementedError | ||
|
|
||
| def _cleanup(self, client_data): | ||
| raise NotImplementedError | ||
|
|
||
| def _pareto_setup(self, client_data): | ||
| raise NotImplementedError | ||
|
|
||
| def _pareto_cleanup(self, client_data): | ||
| raise NotImplementedError | ||
|
|
||
| def _pareto_check_progress(self, client_data, cost_functions, | ||
| costs_so_far, lts, les): | ||
| raise NotImplementedError | ||
|
|
||
| def _pareto_block_model(self, client_data, cost_functions, last_model, lts, les): | ||
| raise NotImplementedError | ||
|
|
||
| def _optimization_check_progress(self, client_data, cost_function, | ||
| cost_so_far, lt, le, strategy): | ||
| raise NotImplementedError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to document what an implementation of these methods would do?
pysmt/solvers/optimizer.py
Outdated
| feasible_solution_callback=None, | ||
| step_size=1, **kwargs): | ||
| """This function performs the optimization as described in | ||
| `Optimizer.optimize()`. However. two additional parameters are |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| `Optimizer.optimize()`. However. two additional parameters are | |
| `Optimizer.optimize()`. However. three additional parameters are |
pysmt/solvers/optimizer.py
Outdated
| the satisfiable models. | ||
| `feasible_solution_callback` is a function with a single | ||
| argument or None. If specified, the function will be called |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The argument can be None? If so
| argument or None. If specified, the function will be called | |
| argument (possibly None). If specified, the function will be called |
For now, we support only Z3 native minimization and a very naive optimization loop using solving under assumptions
For now, we support only Z3 native minimization and a very naive optimization loop using solving under assumptions
… factorization of out-of-solver optimizers
… optizimers, add test for MaxSMT, add warning for real optimization objectives in the algorithms
Add optimization tests
|
Ciao @marcogario, while @Framba-Luca fixes the tests and re-aligns the code with master, can you have a quick look and tell us if something is missing? I would like to merge this soon! |
marcogario
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've taken a very superficial look as I do not have much context on the change and this is quite a large PR.
It seems the test coverage is quite good, so I don't see a reason to delay merging these changes.
Maybe 2 things that we might add (as a follow-up is fine) are some words about this feature in the README and a dedicated example in the examples/ folder.
You have tests and you have quite a fair amount of explanation in optimizer.py about the various types of optimization problems. The goal of examples/ is to have that information in a format that is more "didactic", so people can quickly grasp the capabilities of pysmt and understand how to do something.
pysmt/optimization/goal.py
Outdated
| # check if gmpy2 is installed or not and decide which numeric classes are supported | ||
| accepted_integer_numbers_class = (int, ) | ||
| accepted_real_numbers_class = (float, Fraction) | ||
| try: | ||
| import gmpy2 | ||
| accepted_integer_numbers_class += (gmpy2.mpz, ) | ||
| accepted_real_numbers_class += (gmpy2.mpq, gmpy2.mpfr) | ||
| except ImportError: | ||
| pass | ||
| accepted_numbers_class = accepted_integer_numbers_class + accepted_real_numbers_class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a bit odd that you are running this half-way through the file. Pushing this to the top or having dedicated files per goal type would make this more obvious.
| from warnings import warn | ||
|
|
||
| from pysmt import git_version | ||
| from pysmt import __version__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit. This change could probably be its own PR.
| STR_LENGTH, STR_CONCAT, STR_CONTAINS, | ||
| STR_INDEXOF, STR_REPLACE, STR_SUBSTR, | ||
| STR_PREFIXOF, STR_SUFFIXOF, | ||
| STR_TO_INT, INT_TO_STR, | ||
| STR_CHARAT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume these were unused, otherwise CI would go 💥 , right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We checked, this is just a cleanup of the imports, the FNodel class only uses the STR_OPERATORS set
pysmt/optimization/goal.py
Outdated
| self.id = MaxSMTGoal._instance_id | ||
| MaxSMTGoal._instance_id = MaxSMTGoal._instance_id + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Why do you need this?
| """Returns a pair `(model, cost)` where `model` is an object | ||
| that obtained according to `goal` while satisfying all | ||
| the formulae asserted in the optimizer, while `cost` is the | ||
| objective function value for the model. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| """Returns a pair `(model, cost)` where `model` is an object | |
| that obtained according to `goal` while satisfying all | |
| the formulae asserted in the optimizer, while `cost` is the | |
| objective function value for the model. | |
| """Returns a pair `(model, cost)` where `model` is an object | |
| obtained according to `goal` while satisfying all | |
| the formulae asserted in the optimizer, while `cost` is the | |
| objective function value for the model. |
pysmt/optimization/optimizer.py
Outdated
| self.client_data = client_data | ||
|
|
||
| def linear_search_cut(self): | ||
| """must be called always""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is not really clear. Maybe the assumptions on how to use this object should be part of the class doctstring.
pysmt/optimization/optimsat.py
Outdated
| # TODO: | ||
| # - check msat does not instantiate any MSAT class directly (use virtual override) | ||
| # - is it possible to reintroduce file-level try-except for library import? | ||
| # - the "Not in Python's Path" message is wrong for MathSAT when only OptiMAthSAT | ||
| # is installed.. the current implementation must be revised. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean-up?
pysmt/optimization/optimsat.py
Outdated
| # TODO: support FP | ||
| # TODO: support signed/unsigned BV optimization |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean-up?
pysmt/test/smtlib/parser_utils.py
Outdated
| warnings.warn("Test (%s, %s) skipped because Boolector can't handle QF_UF." % (logic, smtfile)) | ||
| return | ||
| log = script.evaluate(solver) | ||
| print(log) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean-up?
| (QF_LIA, "smtlib2_load_objective_model.smt2", SAT, { | ||
| OptimizationTypes.BOXED: [Int(3), Int(30)], | ||
| OptimizationTypes.PARETO: [ # TODO weird. if pareto is tested before boxed in optimsat it works, otherwise it fails | ||
| (Int(x), Int(2*x + 20)) for x in range(3, 6) | ||
| ], | ||
| OptimizationTypes.LEXICOGRAPHIC: [Int(3), Int(26)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to create a follow-up issue for this?
pysmt/optimization/optimizer.py
Outdated
| if not goal.is_maxsmt_goal(): | ||
| term_type = goal.term().get_type() | ||
| # For bit-vectors, start lower and upper bounds to their width limit | ||
| if term_type.is_bv_type(): | ||
| # add 1 top upper bound because it is excluded | ||
| if goal.signed: | ||
| lower = -(2**(term_type.width-1)) | ||
| upper = (2**(term_type.width-1)) | ||
| else: | ||
| lower = 0 | ||
| upper = (2**term_type.width) + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why put this here and not in the constructor of OptSearchInterval?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note after sync: being that minimization has [lower, upper) and maximization has (lower, upper], here we have to check if we are minimizing or maximizing to understand if we have to add 1 to the upper (when maximizing) or remove 1 to the lower (when minimizing)
pysmt/optimization/optimizer.py
Outdated
| if formula is not None: | ||
| self.add_assertion(formula) | ||
| rt = self.solve() | ||
| # TODO for example in z3 the get_model must be called before the pop. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a design bug: after the pop() we are NOT guaranteed to have access to the model, so it would be better to get it before the pop() and return it to the caller (also in the pareto case)
pysmt/optimization/optimizer.py
Outdated
| add = 1 if self._obj.is_minimization_goal() else 0 | ||
| return (l + u + add) // 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- using a +1 is OK-ish for QF_LIA and QF_BV objectives, but not for QF_LRA (where we can simply use
/ 2instead of '// 2') - I think the general rule is: if I am minimizing, then at each step an optimal solution
sis s.t.lower < s <= upper. Instead, for maximization,lower <= s < upper. We have to document this. However, this means that if I havelower=-10andupper=-9in the case of maximization I would get apivot=-9which would make the algorithm loop. I think a simpler approach would be to compute the pivot as(l+u) // 2and then adjust it with a +1 or -1 if it is outside of the ranges above.
…ome edge cases. maxsmt on real with binary strategy could diverge
…on diverging for unbounded cases and use that method in tests that use the Real logic, avoiding non-endin tests in CI
pysmt/optimization/goal.py
Outdated
| # check if gmpy2 is installed or not and decide which numeric classes are supported | ||
| accepted_integer_numbers_class = (int, ) | ||
| accepted_real_numbers_class = (float, Fraction) | ||
| try: | ||
| import gmpy2 | ||
| accepted_integer_numbers_class += (gmpy2.mpz, ) | ||
| accepted_real_numbers_class += (gmpy2.mpq, gmpy2.mpfr) | ||
| except ImportError: | ||
| pass | ||
| accepted_numbers_class = accepted_integer_numbers_class + accepted_real_numbers_class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code could be removed, as we now ude the Real or Int constructors
| STR_LENGTH, STR_CONCAT, STR_CONTAINS, | ||
| STR_INDEXOF, STR_REPLACE, STR_SUBSTR, | ||
| STR_PREFIXOF, STR_SUFFIXOF, | ||
| STR_TO_INT, INT_TO_STR, | ||
| STR_CHARAT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We checked, this is just a cleanup of the imports, the FNodel class only uses the STR_OPERATORS set
|
|
||
| @clear_pending_pop | ||
| def optimize(self, goal, **kwargs): | ||
| msat_obj = self._assert_msat_goal(goal, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check that this works if called multiple times. (Add tests)
…s and add a way to destroy already asserted objectives, but it does not work
… Int and Real that already do the check in a unified way
…the incapability of removing an asserted goal. Also add some tests to check subsequent optimizations
Added initial support for Optimization Modulo Theory.
Currently, the only "real" optimizing solver we have is Z3, but the extension is designed to be general and extensible, on the same line as the solver factory.
Moreover, this PR introduces two mixins that can use any solver with an incremental or "solving under assumption" interface to perform a simple optimization loop on the SAT side. The technique is not complete and can diverge in general, but it is simple and also effective in some cases.