From c5369f599a9bf1bd07f738c05b5e450582977879 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Sat, 12 Oct 2024 14:05:03 +0200 Subject: [PATCH 1/5] fix in get_scope when arguments are lists (e.g. in sum) --- pycona/utils.py | 50 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/pycona/utils.py b/pycona/utils.py index 1f82bcf..0ec0c59 100644 --- a/pycona/utils.py +++ b/pycona/utils.py @@ -9,6 +9,7 @@ import re from cpmpy.expressions.utils import all_pairs, is_any_list + class Objectives: """ A class to manage different objectives for query generation, find scope, and find constraint. @@ -54,6 +55,7 @@ def check_value(c): """ return bool(c.value()) + def get_con_subset(B, Y): """ Get the subset of constraints whose scope is a subset of Y. @@ -77,6 +79,7 @@ def get_kappa(B, Y): Y = frozenset(Y) return [c for c in B if frozenset(get_scope(c)).issubset(Y) and check_value(c) is False] + def get_lambda(B, Y): """ Get the subset of constraints whose scope is a subset of Y and are satisfied. @@ -88,6 +91,7 @@ def get_lambda(B, Y): Y = frozenset(Y) return [c for c in B if frozenset(get_scope(c)).issubset(Y) and check_value(c) is True] + def gen_pairwise(v1, v2): """ Generate pairwise constraints between two variables. @@ -98,6 +102,7 @@ def gen_pairwise(v1, v2): """ return [v1 == v2, v1 != v2, v1 < v2, v1 > v2] + def gen_pairwise_ineq(v1, v2): """ Generate pairwise inequality constraints between two variables. @@ -108,6 +113,7 @@ def gen_pairwise_ineq(v1, v2): """ return [v1 != v2] + def alldiff_binary(grid): """ Generate all different binary constraints for a grid. @@ -119,6 +125,7 @@ def alldiff_binary(grid): for c in gen_pairwise_ineq(v1, v2): yield c + def gen_scoped_cons(grid): """ Generate scoped constraints for a grid. @@ -145,6 +152,7 @@ def gen_scoped_cons(grid): for c in gen_pairwise_ineq(grid[i1, j1], grid[i2, j2]): yield c + def gen_all_cons(grid): """ Generate all pairwise constraints for a grid. @@ -156,6 +164,7 @@ def gen_all_cons(grid): for c in gen_pairwise(v1, v2): yield c + def get_scopes_vars(C): """ Get the set of variables involved in the scopes of constraints. @@ -165,6 +174,7 @@ def get_scopes_vars(C): """ return set([x for scope in [get_scope(c) for c in C] for x in scope]) + def get_scopes(C): """ Get the list of unique scopes of constraints. @@ -174,6 +184,7 @@ def get_scopes(C): """ return list(set([tuple(get_scope(c)) for c in C])) + def get_scope(constraint): """ Get the scope (variables) of a constraint. @@ -181,18 +192,18 @@ def get_scope(constraint): :param constraint: The constraint to get the scope of. :return: List of variables in the scope of the constraint. """ - if isinstance(constraint, _NumVarImpl): - return [constraint] - elif isinstance(constraint, Expression): - all_variables = [] - for argument in constraint.args: - if isinstance(argument, _NumVarImpl): - all_variables.append(argument) - else: - all_variables.extend(get_scope(argument)) - return all_variables - else: - return [] + + def collect_variables(item, variables): + if isinstance(item, _NumVarImpl): + variables.append(item) + elif isinstance(item, Expression) or is_any_list(item): + for arg in (item.args if isinstance(item, Expression) else item): + collect_variables(arg, variables) + + all_variables = [] + collect_variables(constraint, all_variables) + return all_variables + def get_constant(constraint): """ @@ -212,6 +223,7 @@ def get_constant(constraint): else: return [constraint] + def get_arity(constraint): """ Get the arity (number of variables) of a constraint. @@ -221,6 +233,7 @@ def get_arity(constraint): """ return len(get_scope(constraint)) + def get_min_arity(C): """ Get the minimum arity of a list of constraints. @@ -232,6 +245,7 @@ def get_min_arity(C): return min([get_arity(c) for c in C]) return 0 + def get_max_arity(C): """ Get the maximum arity of a list of constraints. @@ -243,6 +257,7 @@ def get_max_arity(C): return max([get_arity(c) for c in C]) return 0 + def get_relation(c, gamma): """ Get the relation index of a constraint in a given language. @@ -269,6 +284,7 @@ def get_relation(c, gamma): return -1 + def replace_variables(constraint, var_mapping): """ Replace the variables in a constraint using a dictionary mapping previous variables to new ones. @@ -306,6 +322,7 @@ def get_var_name(var): name = var.name.replace(name[0], '') return name + def get_var_ndims(var): """ Get the number of dimensions of a variable. @@ -318,6 +335,7 @@ def get_var_ndims(var): ndims = len(re.split(",", dims_str)) return ndims + def get_var_dims(var): """ Get the dimensions of a variable. @@ -331,6 +349,7 @@ def get_var_dims(var): dims = [int(dim) for dim in re.split(",", dims)] return dims + def get_divisors(n): """ Get the divisors of a number. @@ -344,6 +363,7 @@ def get_divisors(n): divisors.append(i) return divisors + def average_difference(values): """ Calculate the average difference between consecutive values in a list. @@ -375,6 +395,7 @@ def compute_sample_weights(Y): return sw + def get_variables_from_constraints(constraints): """ Get the list of variables involved in a list of constraints. @@ -382,6 +403,7 @@ def get_variables_from_constraints(constraints): :param constraints: List of constraints. :return: List of variables involved in the constraints. """ + def get_variables(expr): if isinstance(expr, _NumVarImpl): return [expr] @@ -403,6 +425,7 @@ def get_variables(expr): variable_list = sorted(variable_set, key=extract_nums) return variable_list + def combine_sets_distinct(set1, set2): """ Combine two sets into a set of distinct pairs. @@ -421,6 +444,7 @@ def combine_sets_distinct(set1, set2): result.add(tuple(sorted((a, b)))) return result + def unravel(lst, newlist): """ Recursively unravel nested lists, tuples, or arrays into a flat list. @@ -437,6 +461,7 @@ def unravel(lst, newlist): elif isinstance(e, (list, tuple, np.flatiter, np.ndarray)): unravel(e, newlist) + def get_combinations(lst, n): """ Get all combinations of a list of a given length. @@ -451,6 +476,7 @@ def get_combinations(lst, n): lst = newlist return list(combinations(lst, n)) + def restore_scope_values(scope, scope_values): """ Restore the original values of variables in a scope. From 65a6f1cb5d83cb06cb9aeb0a8816e20488e3d625 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Mon, 21 Oct 2024 09:33:22 +0200 Subject: [PATCH 2/5] get constants, same fix for lists --- pycona/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pycona/utils.py b/pycona/utils.py index 0ec0c59..c55f8a8 100644 --- a/pycona/utils.py +++ b/pycona/utils.py @@ -212,11 +212,12 @@ def get_constant(constraint): :param constraint: The constraint to get the constants of. :return: List of constants involved in the constraint. """ + if isinstance(constraint, _NumVarImpl): return [] - elif isinstance(constraint, Expression): + elif isinstance(constraint, Expression) or is_any_list(constraint): constants = [] - for argument in constraint.args: + for argument in (constraint.args if isinstance(constraint, Expression) else constraint): if not isinstance(argument, _NumVarImpl): constants.extend(get_constant(argument)) return constants From c8e6a71e91b110273e222d108e961597076a45f8 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Mon, 21 Oct 2024 09:39:07 +0200 Subject: [PATCH 3/5] just use cpmpy's get variables --- pycona/utils.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pycona/utils.py b/pycona/utils.py index c55f8a8..31922a6 100644 --- a/pycona/utils.py +++ b/pycona/utils.py @@ -4,6 +4,7 @@ import cpmpy as cp from cpmpy.expressions.core import Expression, Comparison, Operator from cpmpy.expressions.variables import NDVarArray, _NumVarImpl, NegBoolView +from cpmpy.transformations.get_variables import get_variables from sklearn.utils import class_weight import numpy as np import re @@ -192,17 +193,7 @@ def get_scope(constraint): :param constraint: The constraint to get the scope of. :return: List of variables in the scope of the constraint. """ - - def collect_variables(item, variables): - if isinstance(item, _NumVarImpl): - variables.append(item) - elif isinstance(item, Expression) or is_any_list(item): - for arg in (item.args if isinstance(item, Expression) else item): - collect_variables(arg, variables) - - all_variables = [] - collect_variables(constraint, all_variables) - return all_variables + return get_variables(constraint) def get_constant(constraint): From 5e1fca28719aababb4594b81653425a09d424f70 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Mon, 21 Oct 2024 09:44:36 +0200 Subject: [PATCH 4/5] consistent use of get_variables --- pycona/utils.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pycona/utils.py b/pycona/utils.py index 31922a6..08c6da9 100644 --- a/pycona/utils.py +++ b/pycona/utils.py @@ -396,17 +396,6 @@ def get_variables_from_constraints(constraints): :return: List of variables involved in the constraints. """ - def get_variables(expr): - if isinstance(expr, _NumVarImpl): - return [expr] - elif isinstance(expr, np.bool_): - return [] - elif isinstance(expr, np.int_) or isinstance(expr, int): - return [] - else: - # Recursively find variables in all arguments of the expression - return [var for argument in expr.args for var in get_variables(argument)] - # Create set to hold unique variables variable_set = set() for constraint in constraints: From 39b0c27673774c42467939da45f768ec0cc40725 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 27 Nov 2024 19:50:00 +0100 Subject: [PATCH 5/5] indeed, directly use get_scope --- pycona/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycona/utils.py b/pycona/utils.py index 5a0a17e..683fb66 100644 --- a/pycona/utils.py +++ b/pycona/utils.py @@ -410,7 +410,7 @@ def get_variables_from_constraints(constraints): # Create set to hold unique variables variable_set = set() for constraint in constraints: - variable_set.update(get_variables(constraint)) + variable_set.update(get_scope(constraint)) extract_nums = lambda s: list(map(int, s.name[s.name.index("[") + 1:s.name.index("]")].split(',')))