1- from collections .abc import Iterable
1+ import asyncio
2+ from collections import Counter , OrderedDict
3+ from collections .abc import Collection , Iterable
24from itertools import product
35from os import path
46
5- from minizinc import Instance , Model , Solver
7+ from minizinc import Instance , Model , Solver , Status
68from ruamel .yaml import YAML
79from toolz .dicttoolz import merge
810
911base_dir = path .dirname (__file__ )
1012model_dir = path .join (base_dir , "model" )
1113
1214
15+ def to_hashable (obj ):
16+ if isinstance (obj , dict ):
17+ return frozenset ((to_hashable (k ), to_hashable (v )) for k , v in obj .items ())
18+ elif isinstance (obj , list ):
19+ return tuple (map (to_hashable , obj ))
20+ elif isinstance (obj , set ):
21+ return frozenset (map (to_hashable , obj ))
22+ else :
23+ return obj
24+
25+
26+ def check_uniqueness (solutions : Collection [dict ]):
27+ """
28+ Checks that each solution is unique.
29+ Throws an exception, if any duplicate found.
30+ """
31+
32+ hashable_solutions = list (map (to_hashable , solutions ))
33+ solution_counter = Counter (hashable_solutions )
34+ if solution_counter .total () != len (solution_counter ):
35+ print (f"duplicate solutions found: { solution_counter .total ()} vs { len (solution_counter )} " )
36+ for sol , count in solution_counter .most_common (5 ):
37+ if count >= 1 :
38+ print (f"{ count } solutions: { sol } " )
39+
40+ assert False
41+
42+
1343def solve_sm_links (
1444 anchor_epoch : int , number_of_epochs : int , number_of_links : int , number_of_solutions : int
1545):
@@ -79,14 +109,13 @@ def solve_block_cover(
79109 instance ["block_is_leaf" ] = block_is_leaf
80110
81111 assert number_of_solutions is not None
82- result = instance .solve (nr_solutions = number_of_solutions )
83112
84113 if anchor_epoch == 0 and not store_justified_epoch_equal_zero :
85114 return
86115
87- for s in result . solution :
116+ def extract_values ( s ) :
88117 max_block = s .max_block
89- yield {
118+ return {
90119 "block_epochs" : s .es [: max_block + 1 ],
91120 "parents" : s .parents [: max_block + 1 ],
92121 "previous_justifications" : s .prevs [: max_block + 1 ],
@@ -102,6 +131,22 @@ def solve_block_cover(
102131 },
103132 }
104133
134+ async def get_unique_solutions (number_of_solutions ):
135+ solution_map = OrderedDict ()
136+ async for res in instance .solutions (all_solutions = True ):
137+ if res .status == Status .SATISFIED :
138+ sol = extract_values (res .solution )
139+ key = to_hashable (sol )
140+ if key not in solution_map :
141+ solution_map [key ] = sol
142+ if len (solution_map ) >= number_of_solutions :
143+ break
144+ else :
145+ break
146+ return solution_map .values ()
147+
148+ yield from asyncio .run (get_unique_solutions (number_of_solutions ))
149+
105150
106151def generate_block_cover (params ):
107152 anchor_epoch = params ["anchor_epoch" ]
@@ -213,7 +258,7 @@ def generate_block_cover(params):
213258 "params" : [
214259 (
215260 {"anchor_epoch" : 0 , "number_of_epochs" : 6 , "number_of_links" : 4 },
216- {"number_of_blocks" : 16 , "max_children" : 2 , "number_of_solutions" : 5 },
261+ {"number_of_blocks" : 15 , "max_children" : 2 , "number_of_solutions" : 5 },
217262 ),
218263 (
219264 [{"sm_links" : [[0 , 1 ], [0 , 2 ], [2 , 3 ], [3 , 4 ]]}],
@@ -235,7 +280,7 @@ def generate_block_cover(params):
235280 "params" : [
236281 (
237282 {"anchor_epoch" : 0 , "number_of_epochs" : 6 , "number_of_links" : 4 },
238- {"number_of_blocks" : 16 , "max_children" : 2 , "number_of_solutions" : 5 },
283+ {"number_of_blocks" : 15 , "max_children" : 2 , "number_of_solutions" : 5 },
239284 ),
240285 (
241286 [{"sm_links" : [[0 , 1 ], [0 , 2 ], [2 , 3 ], [3 , 4 ]]}],
@@ -301,5 +346,7 @@ def generate_block_cover(params):
301346 assert False
302347 results = [merge (* sol ) for sol in product (* model_solutions )]
303348 solutions .extend (results )
349+
350+ check_uniqueness (solutions )
304351 with open (out_path , "w" ) as f :
305352 yaml .dump (solutions , f )
0 commit comments