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

Qiskit / qiskit / 13694788019

06 Mar 2025 08:53AM UTC coverage: 88.507% (+0.4%) from 88.089%
13694788019

Pull #13177

github

web-flow
Merge d9b744ab1 into f82689f83
Pull Request #13177: remove internal `add_control`

71839 of 81168 relevant lines covered (88.51%)

352105.06 hits per line

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

92.31
/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
    "Stretch",
26
    "Value",
27
    "Cast",
28
    "Unary",
29
    "Binary",
30
    "Index",
31
]
32

33
import abc
1✔
34
import enum
1✔
35
import typing
1✔
36
import uuid
1✔
37

38
from .. import types
1✔
39

40
if typing.TYPE_CHECKING:
1✔
41
    import qiskit
×
42

43

44
_T_co = typing.TypeVar("_T_co", covariant=True)
1✔
45

46

47
# If adding nodes, remember to update `visitors.ExprVisitor` as well.
48

49

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

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

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

60
    __slots__ = ("type", "const")
1✔
61

62
    type: types.Type
1✔
63
    const: bool
1✔
64

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

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

77
            expr = ...
78
            visitor = MyVisitor()
79
            visitor.accept(expr)
80

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

85

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

91
    __slots__ = ("operand", "implicit")
1✔
92

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

99
    def accept(self, visitor, /):
1✔
100
        return visitor.visit_cast(self)
1✔
101

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

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

114

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

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

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

127
    __slots__ = ("var", "name")
1✔
128

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

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

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

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

165
    def accept(self, visitor, /):
1✔
166
        return visitor.visit_var(self)
1✔
167

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

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

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

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

189
    def __getstate__(self):
1✔
190
        return (self.var, self.type, self.name)
1✔
191

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

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

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

207

208
@typing.final
1✔
209
class Stretch(Expr):
1✔
210
    """A stretch variable.
211

212
    In general, construction of stretch variables for use in programs should use :meth:`Stretch.new`
213
    or :meth:`.QuantumCircuit.add_stretch`.
214
    """
215

216
    __slots__ = (
1✔
217
        "var",
218
        "name",
219
    )
220

221
    var: uuid.UUID
1✔
222
    """A :class:`~uuid.UUID` to uniquely identify this stretch."""
1✔
223
    name: str
1✔
224
    """The name of the stretch variable."""
1✔
225

226
    def __init__(
1✔
227
        self,
228
        var: uuid.UUID,
229
        name: str,
230
    ):
231
        super().__setattr__("type", types.Duration())
1✔
232
        super().__setattr__("const", True)
1✔
233
        super().__setattr__("var", var)
1✔
234
        super().__setattr__("name", name)
1✔
235

236
    @classmethod
1✔
237
    def new(cls, name: str) -> typing.Self:
1✔
238
        """Generate a new named stretch variable."""
239
        return cls(uuid.uuid4(), name)
1✔
240

241
    def accept(self, visitor, /):
1✔
242
        return visitor.visit_stretch(self)
1✔
243

244
    def __setattr__(self, key, value):
1✔
245
        if hasattr(self, key):
×
246
            raise AttributeError(f"'Stretch' object attribute '{key}' is read-only")
×
247
        raise AttributeError(f"'Stretch' object has no attribute '{key}'")
×
248

249
    def __hash__(self):
1✔
250
        return hash((self.var, self.name))
1✔
251

252
    def __eq__(self, other):
1✔
253
        return isinstance(other, Stretch) and self.var == other.var and self.name == other.name
1✔
254

255
    def __repr__(self):
256
        return f"Stretch({self.var}, {self.name})"
257

258
    def __getstate__(self):
1✔
259
        return (self.var, self.name)
×
260

261
    def __setstate__(self, state):
1✔
262
        var, name = state
×
263
        super().__setattr__("type", types.Duration())
×
264
        super().__setattr__("const", True)
×
265
        super().__setattr__("var", var)
×
266
        super().__setattr__("name", name)
×
267

268
    def __copy__(self):
1✔
269
        # I am immutable...
270
        return self
×
271

272
    def __deepcopy__(self, memo):
1✔
273
        # ... as are all my constituent parts.
274
        return self
×
275

276

277
@typing.final
1✔
278
class Value(Expr):
1✔
279
    """A single scalar value."""
280

281
    __slots__ = ("value",)
1✔
282

283
    def __init__(self, value: typing.Any, type: types.Type):
1✔
284
        self.type = type
1✔
285
        self.value = value
1✔
286
        self.const = True
1✔
287

288
    def accept(self, visitor, /):
1✔
289
        return visitor.visit_value(self)
1✔
290

291
    def __eq__(self, other):
1✔
292
        return isinstance(other, Value) and self.type == other.type and self.value == other.value
1✔
293

294
    def __repr__(self):
295
        return f"Value({self.value}, {self.type})"
296

297

298
@typing.final
1✔
299
class Unary(Expr):
1✔
300
    """A unary expression.
301

302
    Args:
303
        op: The opcode describing which operation is being done.
304
        operand: The operand of the operation.
305
        type: The resolved type of the result.
306
    """
307

308
    __slots__ = ("op", "operand")
1✔
309

310
    class Op(enum.Enum):
1✔
311
        """Enumeration of the opcodes for unary operations.
312

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

316
        The logical negation :data:`LOGIC_NOT` takes an input that is implicitly coerced to a
317
        Boolean, and returns a Boolean.
318
        """
319

320
        # If adding opcodes, remember to add helper constructor functions in `constructors.py`.
321
        # The opcode integers should be considered a public interface; they are used by
322
        # serialization formats that may transfer data between different versions of Qiskit.
323
        BIT_NOT = 1
1✔
324
        """Bitwise negation. ``~operand``."""
1✔
325
        LOGIC_NOT = 2
1✔
326
        """Logical negation. ``!operand``."""
1✔
327

328
        def __str__(self):
1✔
329
            return f"Unary.{super().__str__()}"
1✔
330

331
        def __repr__(self):
332
            return f"Unary.{super().__repr__()}"
333

334
    def __init__(self, op: Unary.Op, operand: Expr, type: types.Type):
1✔
335
        self.op = op
1✔
336
        self.operand = operand
1✔
337
        self.type = type
1✔
338
        self.const = operand.const
1✔
339

340
    def accept(self, visitor, /):
1✔
341
        return visitor.visit_unary(self)
1✔
342

343
    def __eq__(self, other):
1✔
344
        return (
1✔
345
            isinstance(other, Unary)
346
            and self.type == other.type
347
            and self.const == other.const
348
            and self.op is other.op
349
            and self.operand == other.operand
350
        )
351

352
    def __repr__(self):
353
        return f"Unary({self.op}, {self.operand}, {self.type})"
354

355

356
@typing.final
1✔
357
class Binary(Expr):
1✔
358
    """A binary expression.
359

360
    Args:
361
        op: The opcode describing which operation is being done.
362
        left: The left-hand operand.
363
        right: The right-hand operand.
364
        type: The resolved type of the result.
365
    """
366

367
    __slots__ = ("op", "left", "right")
1✔
368

369
    class Op(enum.Enum):
1✔
370
        """Enumeration of the opcodes for binary operations.
371

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

376
        The logical operations :data:`LOGIC_AND` and :data:`LOGIC_OR` first implicitly coerce their
377
        arguments to Booleans, and then apply the logical operation.  The resultant type is always
378
        Boolean.
379

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

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

389
        The binary arithmetic operators :data:`ADD`, :data:`SUB:, :data:`MUL`, and :data:`DIV`
390
        can be applied to two floats or two unsigned integers, which should be made to be of
391
        the same width during construction via a cast.
392
        The :data:`ADD`, :data:`SUB`, and :data:`DIV` operators can be applied on two durations
393
        yielding another duration, or a float in the case of :data:`DIV`. The :data:`MUL` operator
394
        can also be applied to a duration and a numeric type, yielding another duration. Finally,
395
        the :data:`DIV` operator can be used to divide a duration by a numeric type, yielding a
396
        duration.
397
        """
398

399
        # If adding opcodes, remember to add helper constructor functions in `constructors.py`
400
        # The opcode integers should be considered a public interface; they are used by
401
        # serialization formats that may transfer data between different versions of Qiskit.
402
        BIT_AND = 1
1✔
403
        """Bitwise "and". ``lhs & rhs``."""
1✔
404
        BIT_OR = 2
1✔
405
        """Bitwise "or". ``lhs | rhs``."""
1✔
406
        BIT_XOR = 3
1✔
407
        """Bitwise "exclusive or". ``lhs ^ rhs``."""
1✔
408
        LOGIC_AND = 4
1✔
409
        """Logical "and". ``lhs && rhs``."""
1✔
410
        LOGIC_OR = 5
1✔
411
        """Logical "or". ``lhs || rhs``."""
1✔
412
        EQUAL = 6
1✔
413
        """Numeric equality. ``lhs == rhs``."""
1✔
414
        NOT_EQUAL = 7
1✔
415
        """Numeric inequality. ``lhs != rhs``."""
1✔
416
        LESS = 8
1✔
417
        """Numeric less than. ``lhs < rhs``."""
1✔
418
        LESS_EQUAL = 9
1✔
419
        """Numeric less than or equal to. ``lhs <= rhs``"""
1✔
420
        GREATER = 10
1✔
421
        """Numeric greater than. ``lhs > rhs``."""
1✔
422
        GREATER_EQUAL = 11
1✔
423
        """Numeric greater than or equal to. ``lhs >= rhs``."""
1✔
424
        SHIFT_LEFT = 12
1✔
425
        """Zero-padding bitshift to the left.  ``lhs << rhs``."""
1✔
426
        SHIFT_RIGHT = 13
1✔
427
        """Zero-padding bitshift to the right.  ``lhs >> rhs``."""
1✔
428
        ADD = 14
1✔
429
        """Addition. ``lhs + rhs``."""
1✔
430
        SUB = 15
1✔
431
        """Subtraction. ``lhs - rhs``."""
1✔
432
        MUL = 16
1✔
433
        """Multiplication. ``lhs * rhs``."""
1✔
434
        DIV = 17
1✔
435
        """Division. ``lhs / rhs``."""
1✔
436

437
        def __str__(self):
1✔
438
            return f"Binary.{super().__str__()}"
1✔
439

440
        def __repr__(self):
441
            return f"Binary.{super().__repr__()}"
442

443
    def __init__(self, op: Binary.Op, left: Expr, right: Expr, type: types.Type):
1✔
444
        self.op = op
1✔
445
        self.left = left
1✔
446
        self.right = right
1✔
447
        self.type = type
1✔
448
        self.const = left.const and right.const
1✔
449

450
    def accept(self, visitor, /):
1✔
451
        return visitor.visit_binary(self)
1✔
452

453
    def __eq__(self, other):
1✔
454
        return (
1✔
455
            isinstance(other, Binary)
456
            and self.type == other.type
457
            and self.const == other.const
458
            and self.op is other.op
459
            and self.left == other.left
460
            and self.right == other.right
461
        )
462

463
    def __repr__(self):
464
        return f"Binary({self.op}, {self.left}, {self.right}, {self.type})"
465

466

467
@typing.final
1✔
468
class Index(Expr):
1✔
469
    """An indexing expression.
470

471
    Args:
472
        target: The object being indexed.
473
        index: The expression doing the indexing.
474
        type: The resolved type of the result.
475
    """
476

477
    __slots__ = ("target", "index")
1✔
478

479
    def __init__(self, target: Expr, index: Expr, type: types.Type):
1✔
480
        self.target = target
1✔
481
        self.index = index
1✔
482
        self.type = type
1✔
483
        self.const = target.const and index.const
1✔
484

485
    def accept(self, visitor, /):
1✔
486
        return visitor.visit_index(self)
1✔
487

488
    def __eq__(self, other):
1✔
489
        return (
1✔
490
            isinstance(other, Index)
491
            and self.type == other.type
492
            and self.const == other.const
493
            and self.target == other.target
494
            and self.index == other.index
495
        )
496

497
    def __repr__(self):
498
        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