Skip to content

Commit

Permalink
Raise an exception when serializing CQMs with bad labels
Browse files Browse the repository at this point in the history
Closes #1358
  • Loading branch information
arcondello committed Sep 13, 2023
1 parent 85f5baf commit 9f84514
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 0 deletions.
10 changes: 10 additions & 0 deletions dimod/constrained/constrained.py
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,16 @@ def to_file(self, *,
# put everything in a constraints/label/ directory
lstr = json.dumps(serialize_variable(label))

# We want to disallow invalid filenames. We do this via a blacklist
# rather than a whitelist to be as permissive as possible. If we find enough
# other edge cases, we can switch in the future.
# Also, if we find that raising an error places an undue burden on users, we can
# switch to a scheme where invalid names are saved as files with generic directory
# labels.
if "/" in lstr:
# NULL actually passes fine because of the JSON dumps
raise ValueError("Cannot serialize constraint labels containing '/'")

with zf.open(f'constraints/{lstr}/lhs', "w", force_zip64=True) as fdst:
constraint.lhs._into_file(fdst)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Raise an exception when serializing constrained quadratic models with constraint
labels that are not legal file names.
See `#1358 <https://github.com/dwavesystems/dimod/issues/1358>`_.
28 changes: 28 additions & 0 deletions tests/test_constrained.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,34 @@ def test_unused_variable(self):
self.assertEqual(new.lower_bound(v), cqm.lower_bound(v))
self.assertEqual(new.upper_bound(v), cqm.upper_bound(v))

def test_unusual_constraint_labels(self):
x, y = dimod.Binaries("xy")

with self.subTest("/"):
cqm = dimod.CQM()
cqm.add_constraint(x + y <= 5, label="hello/world")
with self.assertRaises(ValueError):
cqm.to_file()

# NULL actually works because it's passed through JSON
with self.subTest("NULL"):
cqm = dimod.CQM()
cqm.add_constraint(x + y <= 5, label="hello\0world")
with cqm.to_file() as f:
new = dimod.CQM.from_file(f)

self.assertEqual(list(new.constraints), ["hello\0world"])

# a few other potentially tricky characters, not exhaustive
for c in ";,\\>|😜+-&":
with self.subTest(c):
cqm = dimod.CQM()
cqm.add_constraint(x + y <= 5, label=f"hello{c}world")
with cqm.to_file() as f:
new = dimod.CQM.from_file(f)

self.assertEqual(list(new.constraints), [f"hello{c}world"])


class TestSetObjective(unittest.TestCase):
def test_bqm(self):
Expand Down

0 comments on commit 9f84514

Please sign in to comment.