qiskit/test/python/circuit/classical/test_expr_properties.py

150 lines
6.7 KiB
Python

# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
import copy
import pickle
import uuid
import ddt
from qiskit.circuit import ClassicalRegister, Clbit
from qiskit.circuit.classical import expr, types
from test import QiskitTestCase # pylint: disable=wrong-import-order
@ddt.ddt
class TestExprProperties(QiskitTestCase):
def test_bool_type_is_singleton(self):
"""The `Bool` type is meant (and used) as a Python singleton object for efficiency. It must
always be referentially equal to all other references to it."""
self.assertIs(types.Bool(), types.Bool())
self.assertIs(types.Bool(), copy.copy(types.Bool()))
self.assertIs(types.Bool(), copy.deepcopy(types.Bool()))
self.assertIs(types.Bool(), pickle.loads(pickle.dumps(types.Bool())))
@ddt.data(types.Bool(), types.Uint(8))
def test_types_can_be_cloned(self, obj):
"""Test that various ways of cloning a `Type` object are valid and produce equal output."""
self.assertEqual(obj, copy.copy(obj))
self.assertEqual(obj, copy.deepcopy(obj))
self.assertEqual(obj, pickle.loads(pickle.dumps(obj)))
@ddt.data(
expr.Var(ClassicalRegister(3, "c"), types.Uint(3)),
expr.Value(3, types.Uint(2)),
expr.Cast(expr.Value(1, types.Uint(8)), types.Bool()),
expr.Unary(expr.Unary.Op.LOGIC_NOT, expr.Value(False, types.Bool()), types.Bool()),
expr.Binary(
expr.Binary.Op.LOGIC_OR,
expr.Value(False, types.Bool()),
expr.Value(True, types.Bool()),
types.Bool(),
),
expr.Index(
expr.Var.new("a", types.Uint(3)),
expr.Binary(
expr.Binary.Op.SHIFT_LEFT,
expr.Binary(
expr.Binary.Op.SHIFT_RIGHT,
expr.Var.new("b", types.Uint(3)),
expr.Value(1, types.Uint(1)),
types.Uint(3),
),
expr.Value(1, types.Uint(1)),
types.Uint(3),
),
types.Bool(),
),
)
def test_expr_can_be_cloned(self, obj):
"""Test that various ways of cloning an `Expr` object are valid and produce equal output."""
self.assertEqual(obj, copy.copy(obj))
self.assertEqual(obj, copy.deepcopy(obj))
self.assertEqual(obj, pickle.loads(pickle.dumps(obj)))
def test_var_equality(self):
"""Test that various types of :class:`.expr.Var` equality work as expected both in equal and
unequal cases."""
var_a_bool = expr.Var.new("a", types.Bool())
self.assertEqual(var_a_bool, var_a_bool)
# Allocating a new variable should not compare equal, despite the name match. A semantic
# equality checker can choose to key these variables on only their names and types, if it
# knows that that check is valid within the semantic context.
self.assertNotEqual(var_a_bool, expr.Var.new("a", types.Bool()))
# Manually constructing the same object with the same UUID should cause it compare equal,
# though, for serialization ease.
self.assertEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="a"))
# This is a badly constructed variable because it's using a different type to refer to the
# same storage location (the UUID) as another variable. It is an IR error to generate this
# sort of thing, but we can't fully be responsible for that and a pass would need to go out
# of its way to do this incorrectly, but we can still ensure that the direct equality check
# would spot the error.
self.assertNotEqual(
var_a_bool, expr.Var(var_a_bool.var, types.Uint(8), name=var_a_bool.name)
)
# This is also badly constructed because it uses a different name to refer to the "same"
# storage location.
self.assertNotEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="b"))
# Obviously, two variables of different types and names should compare unequal.
self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Uint(8)))
# As should two variables of the same name but different storage locations and types.
self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("a", types.Uint(8)))
def test_var_uuid_clone(self):
"""Test that :class:`.expr.Var` instances that have an associated UUID and name roundtrip
through pickle and copy operations to produce values that compare equal."""
var_a_u8 = expr.Var.new("a", types.Uint(8))
self.assertEqual(var_a_u8, pickle.loads(pickle.dumps(var_a_u8)))
self.assertEqual(var_a_u8, copy.copy(var_a_u8))
self.assertEqual(var_a_u8, copy.deepcopy(var_a_u8))
def test_var_standalone(self):
"""Test that the ``Var.standalone`` property is set correctly."""
self.assertTrue(expr.Var.new("a", types.Bool()).standalone)
self.assertTrue(expr.Var.new("a", types.Uint(8)).standalone)
self.assertFalse(expr.Var(Clbit(), types.Bool()).standalone)
self.assertFalse(expr.Var(ClassicalRegister(8, "cr"), types.Uint(8)).standalone)
def test_var_hashable(self):
clbits = [Clbit(), Clbit()]
cregs = [ClassicalRegister(2, "cr1"), ClassicalRegister(2, "cr2")]
vars_ = [
expr.Var.new("a", types.Bool()),
expr.Var.new("b", types.Uint(16)),
expr.Var(clbits[0], types.Bool()),
expr.Var(clbits[1], types.Bool()),
expr.Var(cregs[0], types.Uint(2)),
expr.Var(cregs[1], types.Uint(2)),
]
duplicates = [
expr.Var(uuid.UUID(bytes=vars_[0].var.bytes), types.Bool(), name=vars_[0].name),
expr.Var(uuid.UUID(bytes=vars_[1].var.bytes), types.Uint(16), name=vars_[1].name),
expr.Var(clbits[0], types.Bool()),
expr.Var(clbits[1], types.Bool()),
expr.Var(cregs[0], types.Uint(2)),
expr.Var(cregs[1], types.Uint(2)),
]
# Smoke test.
self.assertEqual(vars_, duplicates)
# Actual test of hashability properties.
self.assertEqual(set(vars_ + duplicates), set(vars_))