• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 13628810750

03 Mar 2025 10:34AM UTC coverage: 87.238% (-1.4%) from 88.599%
13628810750

Pull #12814

github

web-flow
Merge 67d3e702d into 5184ca43d
Pull Request #12814: Light Cone Transpiler Pass

78 of 80 new or added lines in 2 files covered. (97.5%)

4037 existing lines in 175 files now uncovered.

75619 of 86681 relevant lines covered (87.24%)

332801.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.33
/qiskit/circuit/classical/expr/expr.py
1
# This code is part of Qiskit.
2
#
3
# (C) Copyright IBM 2023.
4
#
5
# This code is licensed under the Apache License, Version 2.0. You may
6
# obtain a copy of this license in the LICENSE.txt file in the root directory
7
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
#
9
# Any modifications or derivative works of this code must retain this
10
# copyright notice, and modified files need to carry a notice indicating
11
# that they have been altered from the originals.
12

13
"""Expression-tree nodes."""
14

15
# Given the nature of the tree representation and that there are helper functions associated with
16
# many of the classes whose arguments naturally share names with themselves, it's inconvenient to
17
# use synonyms everywhere.  This goes for the builtin 'type' as well.
18
# pylint: disable=redefined-builtin,redefined-outer-name
19

20
from __future__ import annotations
1✔
21

22
__all__ = [
1✔
23
    "Expr",
24
    "Var",
25
    "Value",
26
    "Cast",
27
    "Unary",
28
    "Binary",
29
]
30

31
import abc
1✔
32
import enum
1✔
33
import typing
1✔
34
import uuid
1✔
35

36
from .. import types
1✔
37

38
if typing.TYPE_CHECKING:
1✔
39
    import qiskit
×
40

41

42
_T_co = typing.TypeVar("_T_co", covariant=True)
1✔
43

44

45
# If adding nodes, remember to update `visitors.ExprVisitor` as well.
46

47

48
class Expr(abc.ABC):
1✔
49
    """Root base class of all nodes in the expression tree.  The base case should never be
50
    instantiated directly.
51

52
    This must not be subclassed by users; subclasses form the internal data of the representation of
53
    expressions, and it does not make sense to add more outside of Qiskit library code.
54

55
    All subclasses are responsible for setting their ``type`` attribute in their ``__init__``, and
56
    should not call the parent initializer."""
57

58
    __slots__ = ("type", "const")
1✔
59

60
    type: types.Type
1✔
61
    const: bool
1✔
62

63
    # Sentinel to prevent instantiation of the base class.
64
    @abc.abstractmethod
65
    def __init__(self):  # pragma: no cover
66
        pass
67

68
    def accept(
69
        self, visitor: qiskit.circuit.classical.expr.ExprVisitor[_T_co], /
70
    ) -> _T_co:  # pragma: no cover
71
        """Call the relevant ``visit_*`` method on the given :class:`ExprVisitor`.  The usual entry
72
        point for a simple visitor is to construct it, and then call :meth:`accept` on the root
73
        object to be visited.  For example::
74

75
            expr = ...
76
            visitor = MyVisitor()
77
            visitor.accept(expr)
78

79
        Subclasses of :class:`Expr` should override this to call the correct virtual method on the
80
        visitor.  This implements double dispatch with the visitor."""
81
        return visitor.visit_generic(self)
82

83

84
@typing.final
1✔
85
class Cast(Expr):
1✔
86
    """A cast from one type to another, implied by the use of an expression in a different
87
    context."""
88

89
    __slots__ = ("operand", "implicit")
1✔
90

91
    def __init__(self, operand: Expr, type: types.Type, implicit: bool = False):
1✔
92
        self.type = type
1✔
93
        self.const = operand.const
1✔
94
        self.operand = operand
1✔
95
        self.implicit = implicit
1✔
96

97
    def accept(self, visitor, /):
1✔
98
        return visitor.visit_cast(self)
1✔
99

100
    def __eq__(self, other):
1✔
101
        return (
1✔
102
            isinstance(other, Cast)
103
            and self.type == other.type
104
            and self.const == other.const
105
            and self.operand == other.operand
106
            and self.implicit == other.implicit
107
        )
108

109
    def __repr__(self):
110
        return f"Cast({self.operand}, {self.type}, implicit={self.implicit})"
111

112

113
@typing.final
1✔
114
class Var(Expr):
1✔
115
    """A classical variable.
116

117
    These variables take two forms: a new-style variable that owns its storage location and has an
118
    associated name; and an old-style variable that wraps a :class:`.Clbit` or
119
    :class:`.ClassicalRegister` instance that is owned by some containing circuit.  In general,
120
    construction of variables for use in programs should use :meth:`Var.new` or
121
    :meth:`.QuantumCircuit.add_var`.
122

123
    Variables are immutable after construction, so they can be used as dictionary keys."""
124

125
    __slots__ = ("var", "name")
1✔
126

127
    var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID
1✔
128
    """A reference to the backing data storage of the :class:`Var` instance.  When lifting
1✔
129
    old-style :class:`.Clbit` or :class:`.ClassicalRegister` instances into a :class:`Var`,
130
    this is exactly the :class:`.Clbit` or :class:`.ClassicalRegister`.  If the variable is a
131
    new-style classical variable (one that owns its own storage separate to the old
132
    :class:`.Clbit`/:class:`.ClassicalRegister` model), this field will be a :class:`~uuid.UUID`
133
    to uniquely identify it."""
134
    name: str | None
1✔
135
    """The name of the variable.  This is required to exist if the backing :attr:`var` attribute
1✔
136
    is a :class:`~uuid.UUID`, i.e. if it is a new-style variable, and must be ``None`` if it is
137
    an old-style variable."""
138

139
    def __init__(
1✔
140
        self,
141
        var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID,
142
        type: types.Type,
143
        *,
144
        name: str | None = None,
145
    ):
146
        super().__setattr__("type", type)
1✔
147
        super().__setattr__("const", False)
1✔
148
        super().__setattr__("var", var)
1✔
149
        super().__setattr__("name", name)
1✔
150

151
    @classmethod
1✔
152
    def new(cls, name: str, type: types.Type) -> typing.Self:
1✔
153
        """Generate a new named variable that owns its own backing storage."""
154
        return cls(uuid.uuid4(), type, name=name)
1✔
155

156
    @property
1✔
157
    def standalone(self) -> bool:
1✔
158
        """Whether this :class:`Var` is a standalone variable that owns its storage
159
        location, if applicable. If false, this is a wrapper :class:`Var` around a
160
        pre-existing circuit object."""
161
        return isinstance(self.var, uuid.UUID)
1✔
162

163
    def accept(self, visitor, /):
1✔
164
        return visitor.visit_var(self)
1✔
165

166
    def __setattr__(self, key, value):
1✔
UNCOV
167
        if hasattr(self, key):
×
UNCOV
168
            raise AttributeError(f"'Var' object attribute '{key}' is read-only")
×
UNCOV
169
        raise AttributeError(f"'Var' object has no attribute '{key}'")
×
170

171
    def __hash__(self):
1✔
172
        return hash((self.type, self.var, self.name))
1✔
173

174
    def __eq__(self, other):
1✔
175
        return (
1✔
176
            isinstance(other, Var)
177
            and self.type == other.type
178
            and self.var == other.var
179
            and self.name == other.name
180
        )
181

182
    def __repr__(self):
183
        if self.name is None:
184
            return f"Var({self.var}, {self.type})"
185
        return f"Var({self.var}, {self.type}, name='{self.name}')"
186

187
    def __getstate__(self):
1✔
188
        return (self.var, self.type, self.name)
1✔
189

190
    def __setstate__(self, state):
1✔
191
        var, type, name = state
1✔
192
        super().__setattr__("type", type)
1✔
193
        super().__setattr__("const", False)
1✔
194
        super().__setattr__("var", var)
1✔
195
        super().__setattr__("name", name)
1✔
196

197
    def __copy__(self):
1✔
198
        # I am immutable...
199
        return self
1✔
200

201
    def __deepcopy__(self, memo):
1✔
202
        # ... as are all my constituent parts.
203
        return self
1✔
204

205

206
@typing.final
1✔
207
class Value(Expr):
1✔
208
    """A single scalar value."""
209

210
    __slots__ = ("value",)
1✔
211

212
    def __init__(self, value: typing.Any, type: types.Type):
1✔
213
        self.type = type
1✔
214
        self.value = value
1✔
215
        self.const = True
1✔
216

217
    def accept(self, visitor, /):
1✔
218
        return visitor.visit_value(self)
1✔
219

220
    def __eq__(self, other):
1✔
221
        return isinstance(other, Value) and self.type == other.type and self.value == other.value
1✔
222

223
    def __repr__(self):
224
        return f"Value({self.value}, {self.type})"
225

226

227
@typing.final
1✔
228
class Unary(Expr):
1✔
229
    """A unary expression.
230

231
    Args:
232
        op: The opcode describing which operation is being done.
233
        operand: The operand of the operation.
234
        type: The resolved type of the result.
235
    """
236

237
    __slots__ = ("op", "operand")
1✔
238

239
    class Op(enum.Enum):
1✔
240
        """Enumeration of the opcodes for unary operations.
241

242
        The bitwise negation :data:`BIT_NOT` takes a single bit or an unsigned integer of known
243
        width, and returns a value of the same type.
244

245
        The logical negation :data:`LOGIC_NOT` takes an input that is implicitly coerced to a
246
        Boolean, and returns a Boolean.
247
        """
248

249
        # If adding opcodes, remember to add helper constructor functions in `constructors.py`.
250
        # The opcode integers should be considered a public interface; they are used by
251
        # serialization formats that may transfer data between different versions of Qiskit.
252
        BIT_NOT = 1
1✔
253
        """Bitwise negation. ``~operand``."""
1✔
254
        LOGIC_NOT = 2
1✔
255
        """Logical negation. ``!operand``."""
1✔
256

257
        def __str__(self):
1✔
258
            return f"Unary.{super().__str__()}"
1✔
259

260
        def __repr__(self):
261
            return f"Unary.{super().__repr__()}"
262

263
    def __init__(self, op: Unary.Op, operand: Expr, type: types.Type):
1✔
264
        self.op = op
1✔
265
        self.operand = operand
1✔
266
        self.type = type
1✔
267
        self.const = operand.const
1✔
268

269
    def accept(self, visitor, /):
1✔
270
        return visitor.visit_unary(self)
1✔
271

272
    def __eq__(self, other):
1✔
273
        return (
1✔
274
            isinstance(other, Unary)
275
            and self.type == other.type
276
            and self.const == other.const
277
            and self.op is other.op
278
            and self.operand == other.operand
279
        )
280

281
    def __repr__(self):
282
        return f"Unary({self.op}, {self.operand}, {self.type})"
283

284

285
@typing.final
1✔
286
class Binary(Expr):
1✔
287
    """A binary expression.
288

289
    Args:
290
        op: The opcode describing which operation is being done.
291
        left: The left-hand operand.
292
        right: The right-hand operand.
293
        type: The resolved type of the result.
294
    """
295

296
    __slots__ = ("op", "left", "right")
1✔
297

298
    class Op(enum.Enum):
1✔
299
        """Enumeration of the opcodes for binary operations.
300

301
        The bitwise operations :data:`BIT_AND`, :data:`BIT_OR` and :data:`BIT_XOR` apply to two
302
        operands of the same type, which must be a single bit or an unsigned integer of fixed width.
303
        The resultant type is the same as the two input types.
304

305
        The logical operations :data:`LOGIC_AND` and :data:`LOGIC_OR` first implicitly coerce their
306
        arguments to Booleans, and then apply the logical operation.  The resultant type is always
307
        Boolean.
308

309
        The binary mathematical relations :data:`EQUAL`, :data:`NOT_EQUAL`, :data:`LESS`,
310
        :data:`LESS_EQUAL`, :data:`GREATER` and :data:`GREATER_EQUAL` take unsigned integers
311
        (with an implicit cast to make them the same width), and return a Boolean.
312

313
        The bitshift operations :data:`SHIFT_LEFT` and :data:`SHIFT_RIGHT` can take bit-like
314
        container types (e.g. unsigned integers) as the left operand, and any integer type as the
315
        right-hand operand.  In all cases, the output bit width is the same as the input, and zeros
316
        fill in the "exposed" spaces.
317
        """
318

319
        # If adding opcodes, remember to add helper constructor functions in `constructors.py`
320
        # The opcode integers should be considered a public interface; they are used by
321
        # serialization formats that may transfer data between different versions of Qiskit.
322
        BIT_AND = 1
1✔
323
        """Bitwise "and". ``lhs & rhs``."""
1✔
324
        BIT_OR = 2
1✔
325
        """Bitwise "or". ``lhs | rhs``."""
1✔
326
        BIT_XOR = 3
1✔
327
        """Bitwise "exclusive or". ``lhs ^ rhs``."""
1✔
328
        LOGIC_AND = 4
1✔
329
        """Logical "and". ``lhs && rhs``."""
1✔
330
        LOGIC_OR = 5
1✔
331
        """Logical "or". ``lhs || rhs``."""
1✔
332
        EQUAL = 6
1✔
333
        """Numeric equality. ``lhs == rhs``."""
1✔
334
        NOT_EQUAL = 7
1✔
335
        """Numeric inequality. ``lhs != rhs``."""
1✔
336
        LESS = 8
1✔
337
        """Numeric less than. ``lhs < rhs``."""
1✔
338
        LESS_EQUAL = 9
1✔
339
        """Numeric less than or equal to. ``lhs <= rhs``"""
1✔
340
        GREATER = 10
1✔
341
        """Numeric greater than. ``lhs > rhs``."""
1✔
342
        GREATER_EQUAL = 11
1✔
343
        """Numeric greater than or equal to. ``lhs >= rhs``."""
1✔
344
        SHIFT_LEFT = 12
1✔
345
        """Zero-padding bitshift to the left.  ``lhs << rhs``."""
1✔
346
        SHIFT_RIGHT = 13
1✔
347
        """Zero-padding bitshift to the right.  ``lhs >> rhs``."""
1✔
348

349
        def __str__(self):
1✔
350
            return f"Binary.{super().__str__()}"
1✔
351

352
        def __repr__(self):
353
            return f"Binary.{super().__repr__()}"
354

355
    def __init__(self, op: Binary.Op, left: Expr, right: Expr, type: types.Type):
1✔
356
        self.op = op
1✔
357
        self.left = left
1✔
358
        self.right = right
1✔
359
        self.type = type
1✔
360
        self.const = left.const and right.const
1✔
361

362
    def accept(self, visitor, /):
1✔
363
        return visitor.visit_binary(self)
1✔
364

365
    def __eq__(self, other):
1✔
366
        return (
1✔
367
            isinstance(other, Binary)
368
            and self.type == other.type
369
            and self.const == other.const
370
            and self.op is other.op
371
            and self.left == other.left
372
            and self.right == other.right
373
        )
374

375
    def __repr__(self):
376
        return f"Binary({self.op}, {self.left}, {self.right}, {self.type})"
377

378

379
@typing.final
1✔
380
class Index(Expr):
1✔
381
    """An indexing expression.
382

383
    Args:
384
        target: The object being indexed.
385
        index: The expression doing the indexing.
386
        type: The resolved type of the result.
387
    """
388

389
    __slots__ = ("target", "index")
1✔
390

391
    def __init__(self, target: Expr, index: Expr, type: types.Type):
1✔
392
        self.target = target
1✔
393
        self.index = index
1✔
394
        self.type = type
1✔
395
        self.const = target.const and index.const
1✔
396

397
    def accept(self, visitor, /):
1✔
398
        return visitor.visit_index(self)
1✔
399

400
    def __eq__(self, other):
1✔
401
        return (
1✔
402
            isinstance(other, Index)
403
            and self.type == other.type
404
            and self.const == other.const
405
            and self.target == other.target
406
            and self.index == other.index
407
        )
408

409
    def __repr__(self):
410
        return f"Index({self.target}, {self.index}, {self.type})"
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc