diff --git a/comb_spec_searcher/comb_spec_searcher.py b/comb_spec_searcher/comb_spec_searcher.py index ec05ec275..8e36f8150 100644 --- a/comb_spec_searcher/comb_spec_searcher.py +++ b/comb_spec_searcher/comb_spec_searcher.py @@ -282,6 +282,14 @@ def add_rule( """ for comb_class, child_label in zip(rule.children, end_labels): if not rule.possibly_empty: + if self.debug: + if comb_class.is_empty(): + logger.debug( + "SANITY CHECK FAILURE.\n" + " The folowing combinatorial class " + "is set not empty but is empty!\n%s", + comb_class, + ) self.classdb.set_empty(child_label, empty=False) if self.symmetries and child_label not in self.symmetry_expanded: self._symmetry_expand(comb_class, child_label) @@ -551,7 +559,7 @@ def _auto_search_rules( logger.debug("Searching for specification.") if self.has_specification(): logger.info("Specification detected.") - return self.ruledb.get_specification_rules( + return self._get_specification_rules( smallest=smallest, minimization_time_limit=0.01 * (time.time() - auto_search_start), ) @@ -623,10 +631,15 @@ def get_specification( """ if not self.ruledb.has_specification(): raise SpecificationNotFound - kwargs = { - "minimization_time_limit": minimization_time_limit, - "smallest": smallest, - } - rules = self.ruledb.get_specification_rules(**kwargs) + rules = self._get_specification_rules( + smallest=smallest, minimization_time_limit=minimization_time_limit + ) logger.info("Creating a specification.") return CombinatorialSpecification(self.start_class, rules) + + def _get_specification_rules( + self, smallest: bool = False, minimization_time_limit: float = 10 + ) -> Iterator[AbstractRule]: + return self.ruledb.get_specification_rules( + smallest=smallest, minimization_time_limit=minimization_time_limit + ) diff --git a/comb_spec_searcher/combinatorial_class.py b/comb_spec_searcher/combinatorial_class.py index db8da0a7b..6c1bbc394 100644 --- a/comb_spec_searcher/combinatorial_class.py +++ b/comb_spec_searcher/combinatorial_class.py @@ -203,6 +203,25 @@ def minimum_size_of_object(self) -> int: "'minimum_size_of_object' must be implemented." ) + def min_size(self) -> int: + """ + This is only here for when you are using an is empty method which returns + False on sets which are empty. If you are using an is empty method which + is 100% accurate, you can ignore this method. + + Return the minimum size of the objects in the combinatorial class. + This is the method used by the Cartesian product constructor. + For productivity reasons, you must at least return 1, if this should be + greater than 0. + + By default, CartesianProductStrategy assumes that all children are + non-empty, and hence this check is redundant so it instead calls the + 'minimum_size_of_object' method. But, if you overwrite the + CartesianProductStrategy to allow for some children to be empty, then + the rules are reversible only if all children are non-empty. + """ + return self.minimum_size_of_object() + def to_bytes(self) -> bytes: """Return a compressed version of the class in the form of a 'bytes' object. If you are having memory issues then implement this function diff --git a/comb_spec_searcher/rule_db/forest.py b/comb_spec_searcher/rule_db/forest.py index b4f8c19eb..3cb5ae34e 100644 --- a/comb_spec_searcher/rule_db/forest.py +++ b/comb_spec_searcher/rule_db/forest.py @@ -689,6 +689,15 @@ def _add_empty_rule(self, ends: Iterable[int], rule: AbstractRule) -> None: Add empty rule for the children of the rule if needed. """ if not rule.possibly_empty: + if self.searcher.debug: + for child in rule.children: + if child.is_empty(): + logger.debug( + "SANITY CHECK FAILURE.\n" + " The folowing combinatorial class " + "assumed not empty but is empty!\n%s", + child, + ) return for label, comb_class in zip(ends, rule.children): if label not in self._already_empty and self.classdb.is_empty( diff --git a/comb_spec_searcher/strategies/constructor/cartesian.py b/comb_spec_searcher/strategies/constructor/cartesian.py index 15d273fcc..2b8ef8207 100644 --- a/comb_spec_searcher/strategies/constructor/cartesian.py +++ b/comb_spec_searcher/strategies/constructor/cartesian.py @@ -74,12 +74,9 @@ def __init__( for k in parent.extra_parameters: self.minimum_sizes[k] = parent.get_minimum_value(k) - self.min_child_sizes = tuple( - {"n": child.minimum_size_of_object()} for child in children - ) + self.min_child_sizes = tuple({"n": child.min_size()} for child in children) self.max_child_sizes = tuple( - {"n": child.minimum_size_of_object()} if child.is_atom() else {} - for child in children + {"n": child.min_size()} if child.is_atom() else {} for child in children ) self.parent_parameters = ("n",) + parent.extra_parameters @@ -397,7 +394,7 @@ def __init__( self._children_param_maps = CartesianProduct._build_children_param_map( parent, children, self.extra_parameters ) - self._min_sizes = tuple(child.minimum_size_of_object() for child in children) + self._min_sizes = tuple(child.min_size() for child in children) self._max_sizes = tuple( child.minimum_size_of_object() if child.is_atom() else None for child in children