Skip to content

Commit 4594c1e

Browse files
committed
Bug fixes and improvements to list randomization
- Fix bug where randomized list would cause a TypeError when trying to add it for a set in the sparse solver. - Fix quality of results for sum to zero. This was badly affected by constructing lists one element at a time. - Split random list solvers into naive and progressive. Naive just randomizes and checks. Progressive tries to save state as it goes, saving as much as possible each time to minimize over-constraining the problem. - Add benchmarking vs pyvsc for random lists.
1 parent 2c3aeec commit 4594c1e

File tree

6 files changed

+503
-90
lines changed

6 files changed

+503
-90
lines changed

benchmarks/__main__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from benchmarks.pyvsc.basic import vsc_basic, cr_basic
1616
from benchmarks.pyvsc.in_keyword import vsc_in, cr_in, cr_in_order
1717
from benchmarks.pyvsc.ldinstr import vsc_ldinstr
18+
from benchmarks.pyvsc.randlist import vscRandListSumZero, \
19+
crRandListSumZero, vscRandListUnique, crRandListUnique
1820
from examples.ldinstr import ldInstr
1921

2022

@@ -113,6 +115,38 @@ def check(results):
113115
self.assertGreater(speedup, 10, "Performance has degraded!")
114116
self.run_benchmark(randobjs, 100, check)
115117

118+
def test_randlist_sumzero(self):
119+
'''
120+
Test random list example where the list must sum to zero.
121+
'''
122+
randobjs = {
123+
'vsc': vscRandListSumZero(),
124+
'cr': crRandListSumZero(Random(0)),
125+
}
126+
def check(results):
127+
self.assertGreater(results['cr'][1], results['vsc'][1])
128+
# This testcase is typically 20x faster, which may vary depending
129+
# on machine. Ensure it doesn't fall below 15x.
130+
speedup = results['cr'][1] / results['vsc'][1]
131+
self.assertGreater(speedup, 15, "Performance has degraded!")
132+
self.run_benchmark(randobjs, 100, check)
133+
134+
def test_randlist_unique(self):
135+
'''
136+
Test random list example where the list must be unique.
137+
'''
138+
randobjs = {
139+
'vsc': vscRandListUnique(),
140+
'cr': crRandListUnique(Random(0)),
141+
}
142+
def check(results):
143+
self.assertGreater(results['cr'][1], results['vsc'][1])
144+
# This testcase is typically 10-13x faster, which may vary depending
145+
# on machine. Ensure it doesn't fall below 10x.
146+
speedup = results['cr'][1] / results['vsc'][1]
147+
self.assertGreater(speedup, 10, "Performance has degraded!")
148+
self.run_benchmark(randobjs, 100, check)
149+
116150

117151
def parse_args():
118152
parser = ArgumentParser(description='Run unit tests for constrainedrandom library')

benchmarks/pyvsc/randlist.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# SPDX-License-Identifier: MIT
2+
# Copyright (c) 2023 Imagination Technologies Ltd. All Rights Reserved
3+
4+
'''
5+
Random list examples where constrainedrandom has previously struggled.
6+
'''
7+
8+
from constrainedrandom import RandObj
9+
from constrainedrandom.utils import unique
10+
import vsc
11+
12+
13+
@vsc.randobj
14+
class vscRandListSumZero(object):
15+
16+
def __init__(self):
17+
self.listvar = vsc.rand_list_t(vsc.int8_t(), 10)
18+
19+
@vsc.constraint
20+
def listvar_c(self):
21+
with vsc.foreach(self.listvar) as l:
22+
l >= -10
23+
l < 11
24+
25+
@vsc.constraint
26+
def listvar_sum_c(self):
27+
sum(self.listvar) == 0
28+
29+
30+
class crRandListSumZero(RandObj):
31+
32+
def __init__(self, *args):
33+
super().__init__(*args)
34+
self.add_rand_var('listvar', domain=range(-10, 11), length=10)
35+
def sum_0(listvar):
36+
return sum(listvar) == 0
37+
self.add_constraint(sum_0, ('listvar',))
38+
39+
40+
@vsc.randobj
41+
class vscRandListUnique(object):
42+
43+
def __init__(self):
44+
self.listvar = vsc.rand_list_t(vsc.uint8_t(), 10)
45+
46+
@vsc.constraint
47+
def listvar_c(self):
48+
with vsc.foreach(self.listvar) as l:
49+
l >= 0
50+
l < 10
51+
52+
@vsc.constraint
53+
def listvar_unique_c(self):
54+
vsc.unique(self.listvar)
55+
56+
57+
class crRandListUnique(RandObj):
58+
59+
def __init__(self, *args):
60+
super().__init__(*args)
61+
self.add_rand_var('listvar', domain=range(10), length=10, list_constraints=[unique])

constrainedrandom/internal/multivar.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,17 @@ def solve_groups(
196196
del problem._variables[var_name]
197197
problem.addVariable(var_name, (value,))
198198
else:
199-
solution_space = defaultdict(set)
199+
solution_space = defaultdict(list)
200200
for soln in solution_subset:
201201
for var_name, value in soln.items():
202-
solution_space[var_name].add(value)
202+
# List is ~2x slower than set for 'in',
203+
# but variables might be non-hashable.
204+
if value not in solution_space[var_name]:
205+
solution_space[var_name].append(value)
203206
for var_name, values in solution_space.items():
204207
if var_name in problem._variables:
205208
del problem._variables[var_name]
206-
problem.addVariable(var_name, list(values))
209+
problem.addVariable(var_name, values)
207210

208211
# Attempt to solve the group
209212
group_solutions = group_problem.solve(

0 commit comments

Comments
 (0)