Skip to content

Commit 65b0c4a

Browse files
committed
preserve the cache of solutions after computing the optimal constant blocks
1 parent 4e90280 commit 65b0c4a

File tree

1 file changed

+49
-21
lines changed

1 file changed

+49
-21
lines changed

src/sage/combinat/bijectionist.py

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
:widths: 30, 70
1616
:delim: |
1717
18-
:meth:`~Bijectionist.set_intertwining_relations` | Declare that the statistic intertwines with other maps.
19-
:meth:`~Bijectionist.set_constant_blocks` | Declare that the statistic is constant on some sets.
2018
:meth:`~Bijectionist.set_statistics` | Declare statistics that are preserved by the bijection.
2119
:meth:`~Bijectionist.set_value_restrictions` | Restrict the values of the statistic on an element.
20+
:meth:`~Bijectionist.set_constant_blocks` | Declare that the statistic is constant on some sets.
2221
:meth:`~Bijectionist.set_distributions` | Restrict the distribution of values of the statistic on some elements.
22+
:meth:`~Bijectionist.set_intertwining_relations` | Declare that the statistic intertwines with other maps.
23+
:meth:`~Bijectionist.set_pseudo_inverse_relation` | Declare that the statistic satisfies a certain relation.
24+
:meth:`~Bijectionist.set_homomesic` | Declare that the statistic is homomesic with respect to a given set partition.
25+
2326
2427
:meth:`~Bijectionist.statistics_table` | Print a table collecting information on the given statistics.
2528
:meth:`~Bijectionist.statistics_fibers` | Collect elements with the same statistics.
@@ -1168,7 +1171,7 @@ def set_distributions(self, *elements_distributions):
11681171
[([[]], [0]),
11691172
([[1]], [1]),
11701173
([[1, 2, 3]], [3]),
1171-
([[2, 1, 3]], [2]),
1174+
([[2, 3, 1]], [2]),
11721175
([[1, 2], [2, 1]], [1, 2])]
11731176
11741177
TESTS:
@@ -1694,7 +1697,10 @@ def merge_until_split():
16941697
while True:
16951698
solution = merge_until_split()
16961699
if solution is None:
1697-
self.set_constant_blocks(tmp_P)
1700+
self._P = tmp_P
1701+
# recreate the MILP
1702+
self._bmilp = _BijectionistMILP(self,
1703+
self._bmilp._solution_cache)
16981704
return
16991705

17001706
updated_multiple_preimages = defaultdict(list)
@@ -2040,7 +2046,7 @@ def minimal_subdistributions_blocks_iterator(self):
20402046
sage: bij.constant_blocks(optimal=True)
20412047
{{'a', 'b'}}
20422048
sage: list(bij.minimal_subdistributions_blocks_iterator())
2043-
[(['a', 'a', 'c', 'd', 'e'], [1, 1, 2, 2, 3])]
2049+
[(['b', 'b', 'c', 'd', 'e'], [1, 1, 2, 2, 3])]
20442050
20452051
An example with overlapping minimal subdistributions::
20462052
@@ -2561,14 +2567,19 @@ class is used to manage the MILP, add constraints, solve the
25612567
problem and check for uniqueness of solution values.
25622568
25632569
"""
2564-
def __init__(self, bijectionist: Bijectionist):
2570+
def __init__(self, bijectionist: Bijectionist, solutions=None):
25652571
r"""
25662572
Initialize the mixed integer linear program.
25672573
25682574
INPUT:
25692575
25702576
- ``bijectionist`` -- an instance of :class:`Bijectionist`.
25712577
2578+
- ``solutions`` (optional, default: ``None``) -- a list of
2579+
solutions of the problem, each provided as a dictionary
2580+
mapping `(a, z)` to a Boolean, such that at least one
2581+
element from each block of `P` appears as `a`.
2582+
25722583
.. TODO::
25732584
25742585
it might be cleaner not to pass the full bijectionist
@@ -2581,6 +2592,7 @@ def __init__(self, bijectionist: Bijectionist):
25812592
sage: from sage.combinat.bijectionist import _BijectionistMILP
25822593
sage: _BijectionistMILP(bij)
25832594
<sage.combinat.bijectionist._BijectionistMILP object at ...>
2595+
25842596
"""
25852597
# the attributes of the bijectionist class we actually use:
25862598
# _possible_block_values
@@ -2596,15 +2608,15 @@ def __init__(self, bijectionist: Bijectionist):
25962608

25972609
self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver)
25982610
self.milp.set_objective(None)
2599-
self._solution_cache = []
26002611
indices = [(p, z)
26012612
for p, tZ in bijectionist._possible_block_values.items()
26022613
for z in tZ]
26032614
self._x = self.milp.new_variable(binary=True, indices=indices)
26042615

2605-
for p in _disjoint_set_roots(bijectionist._P):
2606-
self.milp.add_constraint(sum(self._x[p, z]
2607-
for z in bijectionist._possible_block_values[p]) == 1,
2616+
tZ = bijectionist._possible_block_values
2617+
P = bijectionist._P
2618+
for p in _disjoint_set_roots(P):
2619+
self.milp.add_constraint(sum(self._x[p, z] for z in tZ[p]) == 1,
26082620
name=f"block {p}"[:50])
26092621
self.add_alpha_beta_constraints()
26102622
self.add_distribution_constraints()
@@ -2614,6 +2626,12 @@ def __init__(self, bijectionist: Bijectionist):
26142626
if get_verbose() >= 2:
26152627
self.show()
26162628

2629+
self._solution_cache = []
2630+
if solutions is not None:
2631+
for solution in solutions:
2632+
self._add_solution({(P.find(a), z): value
2633+
for (a, z), value in solution.items()})
2634+
26172635
def show(self, variables=True):
26182636
r"""
26192637
Print the constraints and variables of the MILP together
@@ -2744,8 +2762,9 @@ def solve(self, additional_constraints, solution_index=0):
27442762
return_indices=True))
27452763
try:
27462764
self.milp.solve()
2747-
self.last_solution = self.milp.get_values(self._x,
2748-
convert=bool, tolerance=0.1)
2765+
# moving this out of the try...finally block breaks SCIP
2766+
solution = self.milp.get_values(self._x,
2767+
convert=bool, tolerance=0.1)
27492768
finally:
27502769
b = self.milp.get_backend()
27512770
if hasattr(b, "_get_model"):
@@ -2754,16 +2773,23 @@ def solve(self, additional_constraints, solution_index=0):
27542773
m.freeTransform()
27552774
self.milp.remove_constraints(new_indices)
27562775

2757-
# veto the solution, by requiring that not all variables with
2758-
# value 1 have value 1 in the new MILP
2776+
self._add_solution(solution)
2777+
2778+
def _add_solution(self, solution):
2779+
r"""
2780+
Add the ``last_solution`` to the cache and an
2781+
appropriate constraint to the MILP.
2782+
2783+
"""
27592784
active_vars = [self._x[p, z]
27602785
for p in _disjoint_set_roots(self._bijectionist._P)
27612786
for z in self._bijectionist._possible_block_values[p]
2762-
if self.last_solution[(p, z)]]
2787+
if solution[(p, z)]]
27632788
self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1,
27642789
name="veto")
2790+
self._solution_cache.append(solution)
2791+
self.last_solution = solution
27652792

2766-
self._solution_cache.append(self.last_solution)
27672793

27682794
def _is_solution(self, constraint, values):
27692795
r"""
@@ -2833,9 +2859,11 @@ def solution(self, on_blocks):
28332859
{'a': 0, 'c': 0}
28342860
28352861
"""
2862+
P = self._bijectionist._P
2863+
tZ = self._bijectionist._possible_block_values
28362864
mapping = {} # A -> Z or P -> Z, a +-> s(a)
2837-
for p, block in self._bijectionist._P.root_to_elements_dict().items():
2838-
for z in self._bijectionist._possible_block_values[p]:
2865+
for p, block in P.root_to_elements_dict().items():
2866+
for z in tZ[p]:
28392867
if self.last_solution[p, z] == 1:
28402868
if on_blocks:
28412869
mapping[p] = z
@@ -3285,9 +3313,9 @@ def _non_copying_intersection(sets):
32853313
([[3, 1, 2]], [3])
32863314
([[4, 1, 2, 3]], [4])
32873315
([[5, 1, 2, 3, 4]], [5])
3288-
([[2, 1, 4, 5, 3], [2, 3, 5, 1, 4], [2, 4, 1, 5, 3], [2, 4, 5, 1, 3]], [2, 3, 3, 3])
3289-
([[2, 1, 5, 3, 4], [2, 5, 1, 3, 4], [3, 1, 5, 2, 4], [3, 5, 1, 2, 4]], [3, 3, 4, 4])
3290-
([[1, 3, 2, 5, 4], [1, 3, 5, 2, 4], [1, 4, 2, 5, 3], [1, 4, 5, 2, 3], [1, 4, 5, 3, 2], [1, 5, 4, 2, 3], [1, 5, 4, 3, 2]], [2, 2, 3, 3, 3, 3, 3])
3316+
([[2, 3, 1, 5, 4], [2, 4, 5, 3, 1], [2, 5, 4, 1, 3], [3, 4, 1, 5, 2]], [2, 3, 3, 3])
3317+
([[3, 1, 2, 5, 4], [4, 1, 2, 5, 3], [3, 5, 2, 1, 4], [4, 1, 5, 2, 3]], [3, 3, 4, 4])
3318+
([[2, 1, 3, 5, 4], [2, 4, 1, 3, 5], [2, 5, 3, 1, 4], [3, 4, 1, 2, 5], [3, 1, 5, 4, 2], [2, 5, 1, 4, 3], [2, 1, 5, 4, 3]], [2, 2, 3, 3, 3, 3, 3])
32913319
32923320
sage: l = list(bij.solutions_iterator()); len(l) # not tested -- (17 seconds with SCIP on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx)
32933321
504

0 commit comments

Comments
 (0)