Skip to content

Commit 2fab6f4

Browse files
committed
Raise an exception when serializing CQMs with bad labels
Closes #1358
1 parent 85f5baf commit 2fab6f4

File tree

3 files changed

+45
-0
lines changed

3 files changed

+45
-0
lines changed

dimod/constrained/constrained.py

+10
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,16 @@ def to_file(self, *,
17851785
# put everything in a constraints/label/ directory
17861786
lstr = json.dumps(serialize_variable(label))
17871787

1788+
# We want to disallow invalid filenames. We do this via a blacklist
1789+
# rather than a whitelist to be as permissive as possible. If we find enough
1790+
# other edge cases, we can switch in the future.
1791+
# Also, if we find that raising an error places an undue burden on users, we can
1792+
# switch to a scheme where invalid names are saved as files with generic directory
1793+
# labels.
1794+
if "/" in lstr:
1795+
# NULL actually passes fine because of the JSON dumps
1796+
raise ValueError("Cannot serialize constraint labels containing '/'")
1797+
17881798
with zf.open(f'constraints/{lstr}/lhs', "w", force_zip64=True) as fdst:
17891799
constraint.lhs._into_file(fdst)
17901800

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
Raise an exception when serializing constrained quadratic models with constraint
5+
labels containing ``"/"``. Previously they would be serialized but would subsequently
6+
break deserialization.
7+
See `#1358 <https://github.com/dwavesystems/dimod/issues/1358>`_.

tests/test_constrained.py

+28
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,34 @@ def test_unused_variable(self):
13341334
self.assertEqual(new.lower_bound(v), cqm.lower_bound(v))
13351335
self.assertEqual(new.upper_bound(v), cqm.upper_bound(v))
13361336

1337+
def test_unusual_constraint_labels(self):
1338+
x, y = dimod.Binaries("xy")
1339+
1340+
with self.subTest("/"):
1341+
cqm = dimod.CQM()
1342+
cqm.add_constraint(x + y <= 5, label="hello/world")
1343+
with self.assertRaises(ValueError):
1344+
cqm.to_file()
1345+
1346+
# NULL actually works because it's passed through JSON
1347+
with self.subTest("NULL"):
1348+
cqm = dimod.CQM()
1349+
cqm.add_constraint(x + y <= 5, label="hello\0world")
1350+
with cqm.to_file() as f:
1351+
new = dimod.CQM.from_file(f)
1352+
1353+
self.assertEqual(list(new.constraints), ["hello\0world"])
1354+
1355+
# a few other potentially tricky characters, not exhaustive
1356+
for c in ";,\\>|😜+-&":
1357+
with self.subTest(c):
1358+
cqm = dimod.CQM()
1359+
cqm.add_constraint(x + y <= 5, label=f"hello{c}world")
1360+
with cqm.to_file() as f:
1361+
new = dimod.CQM.from_file(f)
1362+
1363+
self.assertEqual(list(new.constraints), [f"hello{c}world"])
1364+
13371365

13381366
class TestSetObjective(unittest.TestCase):
13391367
def test_bqm(self):

0 commit comments

Comments
 (0)