Coverage for /home/runner/work/tket/tket/pytket/pytket/circuit/logic_exp.py: 90%
555 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-02 12:44 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-02 12:44 +0000
1# Copyright Quantinuum
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Classes and functions for constructing logical
16expressions over Bit and BitRegister."""
18import contextlib
19from collections.abc import Iterable, Iterator, Sequence
20from dataclasses import dataclass
21from enum import Enum
22from typing import Any, ClassVar, TypeVar, Union, cast
24from pytket.circuit import Bit, BitRegister
26T = TypeVar("T")
29def filter_by_type(seq: Iterable, var_type: type[T]) -> Iterator[tuple[int, T]]:
30 """Return enumeration of seq, with only elements of type var_type."""
31 return filter(lambda x: isinstance(x[1], var_type), enumerate(seq))
34class BitWiseOp(Enum):
35 """Enum for operations on Bit."""
37 AND = "&"
38 OR = "|"
39 XOR = "^"
40 EQ = "=="
41 NEQ = "!="
42 NOT = "~"
43 ZERO = "0"
44 ONE = "1"
47class RegWiseOp(Enum):
48 """Enum for operations on BitRegister."""
50 AND = "&"
51 OR = "|"
52 XOR = "^"
53 EQ = "=="
54 NEQ = "!="
55 LT = "<"
56 GT = ">"
57 LEQ = "<="
58 GEQ = ">="
59 ADD = "+"
60 SUB = "-"
61 MUL = "*"
62 DIV = "/"
63 POW = "**"
64 LSH = "<<"
65 RSH = ">>"
66 NOT = "~"
67 NEG = "-" # noqa: PIE796
70Ops = Union[BitWiseOp, RegWiseOp] # all op enum types # noqa: UP007
73Constant = int # constants in expression
74Variable = Union[Bit, BitRegister] # variables in expression # noqa: UP007
75ArgType = Union[
76 "LogicExp", Bit | BitRegister, Constant
77] # all possible arguments in expression
80@dataclass(init=False)
81class LogicExp:
82 """Logical expressions over Bit or BitRegister.
83 Encoded as a tree of expressions"""
85 op: Ops # enum for operation encoded by this node
86 args: list[ArgType] # arguments of operation
87 # class level dictionary mapping enum to class
88 op_cls_dict: ClassVar[dict[Ops, type["LogicExp"]]] = {}
90 @classmethod
91 def factory(cls, op: Ops) -> type["LogicExp"]: # noqa: PLR0911, PLR0912
92 """Return matching operation class for enum."""
93 # RegNeg cannot be initialised this way as "-" clashes with SUB
94 if op == BitWiseOp.AND:
95 return BitAnd
96 if op == BitWiseOp.OR:
97 return BitOr
98 if op == BitWiseOp.XOR:
99 return BitXor
100 if op == BitWiseOp.NOT:
101 return BitNot
102 if op == BitWiseOp.EQ:
103 return BitEq
104 if op == BitWiseOp.NEQ:
105 return BitNeq
106 if op == BitWiseOp.ZERO:
107 return BitZero
108 if op == BitWiseOp.ONE:
109 return BitOne
110 if op == RegWiseOp.AND:
111 return RegAnd
112 if op == RegWiseOp.OR:
113 return RegOr
114 if op == RegWiseOp.XOR:
115 return RegXor
116 if op == RegWiseOp.ADD:
117 return RegAdd
118 if op == RegWiseOp.SUB:
119 return RegSub
120 if op == RegWiseOp.MUL:
121 return RegMul
122 if op == RegWiseOp.DIV:
123 return RegDiv
124 if op == RegWiseOp.POW:
125 return RegPow
126 if op == RegWiseOp.LSH:
127 return RegLsh
128 if op == RegWiseOp.RSH:
129 return RegRsh
130 if op == RegWiseOp.EQ:
131 return RegEq
132 if op == RegWiseOp.NEQ:
133 return RegNeq
134 if op == RegWiseOp.LT:
135 return RegLt
136 if op == RegWiseOp.GT: 136 ↛ 137line 136 didn't jump to line 137 because the condition on line 136 was never true
137 return RegGt
138 if op == RegWiseOp.LEQ:
139 return RegLeq
140 if op == RegWiseOp.GEQ:
141 return RegGeq
142 if op == RegWiseOp.NOT: 142 ↛ 144line 142 didn't jump to line 144 because the condition on line 142 was always true
143 return RegNot
144 raise ValueError("op type not supported")
146 def set_value(self, var: Bit | BitRegister, val: Constant) -> None:
147 """Set value of var to val recursively."""
148 for i, arg in enumerate(self.args):
149 if isinstance(arg, Bit | BitRegister):
150 if arg == var:
151 self.args[i] = val
152 elif isinstance(arg, LogicExp): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true
153 arg.set_value(var, val)
155 @staticmethod
156 def _const_eval(args: list[Constant]) -> Constant:
157 """Evaluate expression given constant values for all args."""
158 raise NotImplementedError
160 def eval_vals(self) -> ArgType:
161 """Attempt to evaluate all sub-expressions; simple constant folding."""
162 rval: ArgType = self
163 for i, arg in filter_by_type(self.args, LogicExp): 163 ↛ 164line 163 didn't jump to line 164 because the loop on line 163 never started
164 self.args[i] = arg.eval_vals()
165 if all(isinstance(a, Constant) for a in self.args): 165 ↛ 168line 165 didn't jump to line 168 because the condition on line 165 was always true
166 with contextlib.suppress(NotImplementedError):
167 rval = self._const_eval(cast("list[Constant]", self.args))
168 return rval
170 def all_inputs(self) -> set[Bit | BitRegister]:
171 """
172 :return: All variables involved in expression.
173 """
174 outset: set[Bit | BitRegister] = set()
176 for arg in self.args:
177 if isinstance(arg, LogicExp):
178 outset.update(arg.all_inputs())
179 continue
180 if isinstance(self, BitLogicExp):
181 if isinstance(arg, Bit):
182 outset.add(arg)
183 elif isinstance(arg, BitRegister):
184 outset.add(arg)
185 return outset
187 def all_inputs_ordered(self) -> list[Bit | BitRegister]:
188 """
189 :return: All variables involved in expression, in order of first appearance.
190 """
191 # use dict[Union[Bit, BitRegister], None] instead of set[Union[Bit, BitRegister]] to preserve order
192 outset: dict[Bit | BitRegister, None] = {}
194 for arg in self.args:
195 if isinstance(arg, LogicExp):
196 outset.update(dict.fromkeys(arg.all_inputs_ordered()))
197 continue
198 if isinstance(self, BitLogicExp):
199 if isinstance(arg, Bit):
200 outset[arg] = None
201 elif isinstance(arg, BitRegister):
202 outset[arg] = None
203 return list(outset)
205 def __eq__(self, other: object) -> bool:
206 if not isinstance(other, LogicExp): 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true
207 return False
208 return (self.op == other.op) and (self.args == other.args)
210 def to_dict(self) -> dict[str, Any]:
211 """Output JSON serializable nested dictionary."""
212 out: dict[str, Any] = {"op": str(self.op)}
213 args_ser: list[dict | Constant | list[str | int]] = []
215 for arg in self.args:
216 if isinstance(arg, LogicExp):
217 args_ser.append(arg.to_dict())
218 elif isinstance(arg, Constant):
219 args_ser.append(arg)
220 elif isinstance(arg, Bit):
221 args_ser.append(arg.to_list())
222 elif isinstance(arg, BitRegister): 222 ↛ 215line 222 didn't jump to line 215 because the condition on line 222 was always true
223 args_ser.append({"name": arg.name, "size": arg.size})
225 out["args"] = args_ser
226 return out
228 @classmethod
229 def from_dict(cls, dic: dict[str, Any]) -> "LogicExp":
230 """Load from JSON serializable nested dictionary."""
231 opset_name, op_name = dic["op"].split(".", 2)
232 opset = BitWiseOp if opset_name == "BitWiseOp" else RegWiseOp
233 op = next(o for o in opset if o.name == op_name)
234 args: list[ArgType] = []
235 for arg_ser in dic["args"]:
236 if isinstance(arg_ser, Constant):
237 args.append(arg_ser)
238 elif isinstance(arg_ser, list):
239 args.append(Bit(arg_ser[0], arg_ser[1]))
240 elif isinstance(arg_ser, dict): 240 ↛ 235line 240 didn't jump to line 235 because the condition on line 240 was always true
241 if "op" in arg_ser:
242 args.append(LogicExp.from_dict(arg_ser))
243 else:
244 args.append(BitRegister(arg_ser["name"], arg_ser["size"]))
245 return create_logic_exp(op, args)
247 def _rename_args_recursive(
248 self, cmap: dict[Bit, Bit], renamed_regs: set[str]
249 ) -> bool:
250 success = False
251 for i, arg in enumerate(self.args):
252 if isinstance(arg, Bit):
253 if arg in cmap:
254 self.args[i] = cmap[arg]
255 success = True
256 elif isinstance(arg, BitRegister):
257 if arg.name in renamed_regs:
258 raise ValueError(
259 f"""Can't rename bits in {arg.__repr__()} """
260 """because the register is being used """
261 """in a register-wise logic expression."""
262 )
263 elif isinstance(arg, LogicExp):
264 success |= arg._rename_args_recursive( # noqa: SLF001
265 cmap, renamed_regs
266 )
267 return success
269 def rename_args(self, cmap: dict[Bit, Bit]) -> bool:
270 """Rename the Bits according to a Bit map. Raise ValueError if
271 a bit is being used in a register-wise expression.
272 """
273 if all(old_bit == new_bit for old_bit, new_bit in cmap.items()):
274 return False
275 renamed_regs = {key.reg_name for key in cmap}
276 return self._rename_args_recursive(cmap, renamed_regs)
279BitArgType = Union[LogicExp, Bit, Constant] # noqa: UP007
280RegArgType = Union[LogicExp, BitRegister, Constant] # noqa: UP007
283class BitLogicExp(LogicExp):
284 """Expression acting only on Bit or Constant types."""
286 def __and__(self, other: BitArgType) -> "BitAnd":
287 return BitAnd(self, other)
289 def __rand__(self, other: BitArgType) -> "BitAnd":
290 return BitAnd(self, other)
292 def __or__(self, other: BitArgType) -> "BitOr":
293 return BitOr(self, other)
295 def __ror__(self, other: BitArgType) -> "BitOr":
296 return BitOr(self, other)
298 def __xor__(self, other: BitArgType) -> "BitXor":
299 return BitXor(self, other)
301 def __rxor__(self, other: BitArgType) -> "BitXor":
302 return BitXor(self, other)
305class RegLogicExp(LogicExp):
306 """Expression acting only on BitRegister or Constant types."""
308 def __and__(self, other: RegArgType) -> "RegAnd":
309 return RegAnd(self, other)
311 def __rand__(self, other: RegArgType) -> "RegAnd":
312 return RegAnd(self, other)
314 def __or__(self, other: RegArgType) -> "RegOr":
315 return RegOr(self, other)
317 def __ror__(self, other: RegArgType) -> "RegOr":
318 return RegOr(self, other)
320 def __xor__(self, other: RegArgType) -> "RegXor":
321 return RegXor(self, other)
323 def __rxor__(self, other: RegArgType) -> "RegXor":
324 return RegXor(self, other)
326 def __add__(self, other: RegArgType) -> "RegAdd":
327 return RegAdd(self, other)
329 def __sub__(self, other: RegArgType) -> "RegSub":
330 return RegSub(self, other)
332 def __mul__(self, other: RegArgType) -> "RegMul":
333 return RegMul(self, other)
335 def __floordiv__(self, other: RegArgType) -> "RegDiv":
336 return RegDiv(self, other)
338 def __pow__(self, other: RegArgType) -> "RegPow":
339 return RegPow(self, other)
341 def __lshift__(self, other: RegArgType) -> "RegLsh":
342 return RegLsh(self, other)
344 def __rshift__(self, other: RegArgType) -> "RegRsh":
345 return RegRsh(self, other)
348class BinaryOp(LogicExp):
349 """Expresion for operation on two arguments."""
351 def __str__(self) -> str:
352 return f"({self.args[0]} {self.op.value} {self.args[1]})"
355class UnaryOp(LogicExp):
356 """Expression for operation on one argument."""
358 def __str__(self) -> str:
359 return f"({self.op.value} {self.args[0]})"
362class NullaryOp(LogicExp):
363 """Expression for operation on no arguments (i.e. constant)."""
365 def __str__(self) -> str:
366 return f"({self.op.value})"
369class And(BinaryOp):
370 @staticmethod
371 def _const_eval(args: list[Constant]) -> Constant:
372 return args[0] & args[1]
374 def eval_vals(self) -> ArgType:
375 rval: ArgType = super().eval_vals()
376 if 0 in self.args:
377 return 0
378 return rval
381class Or(BinaryOp):
382 @staticmethod
383 def _const_eval(args: list[Constant]) -> Constant:
384 return args[0] | args[1]
387class Xor(BinaryOp):
388 @staticmethod
389 def _const_eval(args: list[Constant]) -> Constant:
390 return args[0] ^ args[1]
393class BitAnd(And, BitLogicExp):
394 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None:
395 self.op = BitWiseOp.AND
396 self.args = [arg1, arg2]
399class BitOr(Or, BitLogicExp):
400 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None:
401 self.op = BitWiseOp.OR
402 self.args = [arg1, arg2]
404 def eval_vals(self) -> ArgType:
405 rval: ArgType = super().eval_vals()
406 if 1 in self.args: 406 ↛ 408line 406 didn't jump to line 408 because the condition on line 406 was always true
407 return 1
408 return rval
411class BitXor(Xor, BitLogicExp):
412 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None:
413 self.op = BitWiseOp.XOR
414 self.args = [arg1, arg2]
417class BitNot(UnaryOp, BitLogicExp):
418 def __init__(self, arg1: BitArgType) -> None:
419 self.op = BitWiseOp.NOT
420 self.args = [arg1]
422 @staticmethod
423 def _const_eval(args: list[Constant]) -> Constant:
424 return 1 - args[0]
427class BitZero(NullaryOp, BitLogicExp):
428 def __init__(self) -> None:
429 self.op = BitWiseOp.ZERO
430 self.args = []
432 @staticmethod
433 def _const_eval(args: list[Constant]) -> Constant:
434 return 0
437class BitOne(NullaryOp, BitLogicExp):
438 def __init__(self) -> None:
439 self.op = BitWiseOp.ONE
440 self.args = []
442 @staticmethod
443 def _const_eval(args: list[Constant]) -> Constant:
444 return 1
447class RegAnd(And, RegLogicExp):
448 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
449 self.op = RegWiseOp.AND
450 self.args = [arg1, arg2]
453class RegOr(Or, RegLogicExp):
454 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
455 self.op = RegWiseOp.OR
456 self.args = [arg1, arg2]
459class RegXor(Xor, RegLogicExp):
460 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
461 self.op = RegWiseOp.XOR
462 self.args = [arg1, arg2]
465class RegAdd(BinaryOp, RegLogicExp):
466 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
467 self.op = RegWiseOp.ADD
468 self.args = [arg1, arg2]
471class RegSub(BinaryOp, RegLogicExp):
472 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
473 self.op = RegWiseOp.SUB
474 self.args = [arg1, arg2]
477class RegMul(BinaryOp, RegLogicExp):
478 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
479 self.op = RegWiseOp.MUL
480 self.args = [arg1, arg2]
483class RegDiv(BinaryOp, RegLogicExp):
484 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
485 self.op = RegWiseOp.DIV
486 self.args = [arg1, arg2]
489class RegPow(BinaryOp, RegLogicExp):
490 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
491 self.op = RegWiseOp.POW
492 self.args = [arg1, arg2]
495class RegLsh(BinaryOp, RegLogicExp):
496 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
497 self.op = RegWiseOp.LSH
498 self.args = [arg1, arg2]
501class RegNeg(UnaryOp, RegLogicExp):
502 def __init__(self, arg1: RegArgType) -> None:
503 self.op = RegWiseOp.NEG
504 self.args = [arg1]
507class RegNot(UnaryOp, RegLogicExp):
508 def __init__(self, arg1: RegArgType) -> None:
509 self.op = RegWiseOp.NOT
510 self.args = [arg1]
513class RegRsh(BinaryOp, RegLogicExp):
514 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
515 self.op = RegWiseOp.RSH
516 self.args = [arg1, arg2]
519class PredicateExp(BinaryOp):
520 """
521 A binary predicate where the arguments are either
522 Bits, BitRegisters, or Constants.
523 """
526class Eq(PredicateExp):
527 @staticmethod
528 def _const_eval(args: list[Constant]) -> Constant:
529 return args[0] == args[1]
532class Neq(PredicateExp):
533 @staticmethod
534 def _const_eval(args: list[Constant]) -> Constant:
535 return 1 - Eq._const_eval(args) # noqa: SLF001
538class BitEq(Eq, BitLogicExp):
539 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None:
540 self.op = BitWiseOp.EQ
541 self.args = [arg1, arg2]
544class BitNeq(Neq, BitLogicExp):
545 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None:
546 self.op = BitWiseOp.NEQ
547 self.args = [arg1, arg2]
550class RegEq(Eq, RegLogicExp):
551 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
552 self.op = RegWiseOp.EQ
553 self.args = [arg1, arg2]
556class RegNeq(Neq, RegLogicExp):
557 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
558 self.op = RegWiseOp.NEQ
559 self.args = [arg1, arg2]
562class RegLt(PredicateExp, RegLogicExp):
563 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
564 self.op = RegWiseOp.LT
565 self.args = [arg1, arg2]
567 @staticmethod
568 def _const_eval(args: list[Constant]) -> Constant:
569 return args[0] < args[1]
572class RegGt(PredicateExp, RegLogicExp):
573 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
574 self.op = RegWiseOp.GT
575 self.args = [arg1, arg2]
577 @staticmethod
578 def _const_eval(args: list[Constant]) -> Constant:
579 return args[0] > args[1]
582class RegLeq(PredicateExp, RegLogicExp):
583 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
584 self.op = RegWiseOp.LEQ
585 self.args = [arg1, arg2]
587 @staticmethod
588 def _const_eval(args: list[Constant]) -> Constant:
589 return args[0] <= args[1]
592class RegGeq(PredicateExp, RegLogicExp):
593 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None:
594 self.op = RegWiseOp.GEQ
595 self.args = [arg1, arg2]
597 @staticmethod
598 def _const_eval(args: list[Constant]) -> Constant:
599 return args[0] >= args[1]
602def reg_eq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp:
603 """Function to express a BitRegister equality predicate, i.e.
604 for a register ``r``, ``(r == 5)`` is expressed as ``reg_eq(r, 5)``"""
605 return RegEq(register, value)
608def reg_neq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp:
609 """Function to express a BitRegister inequality predicate, i.e.
610 for a register ``r``, ``(r != 5)`` is expressed as ``reg_neq(r, 5)``"""
611 return RegNeq(register, value)
614def reg_lt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp:
615 """Function to express a BitRegister less than predicate, i.e.
616 for a register ``r``, ``(r < 5)`` is expressed as ``reg_lt(r, 5)``"""
617 return RegLt(register, value)
620def reg_gt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp:
621 """Function to express a BitRegister greater than predicate, i.e.
622 for a register ``r``, ``(r > 5)`` is expressed as ``reg_gt(r, 5)``"""
623 return RegGt(register, value)
626def reg_leq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp:
627 """Function to express a BitRegister less than or equal to predicate,
628 i.e. for a register ``r``, ``(r <= 5)`` is expressed as ``reg_leq(r, 5)``"""
629 return RegLeq(register, value)
632def reg_geq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp:
633 """Function to express a BitRegister greater than or equal to
634 predicate, i.e. for a register ``r``, ``(r >= 5)`` is expressed as
635 ``reg_geq(r, 5)``"""
636 return RegGeq(register, value)
639def if_bit(bit: Bit | BitLogicExp) -> PredicateExp:
640 """Equivalent of ``if bit:``."""
641 return BitEq(bit, 1)
644def if_not_bit(bit: Bit | BitLogicExp) -> PredicateExp:
645 """Equivalent of ``if not bit:``."""
646 return BitEq(bit, 0)
649def create_bit_logic_exp( # noqa: PLR0911
650 op: BitWiseOp, args: Sequence[BitArgType]
651) -> BitLogicExp:
652 """
653 Builds the :py:class:`LogicExp` corresponding to applying the given
654 :py:class:`BitWiseOp` to some sequence of bits.
655 """
656 if op == BitWiseOp.AND:
657 assert len(args) == 2 # noqa: PLR2004
658 return BitAnd(args[0], args[1])
659 if op == BitWiseOp.OR:
660 assert len(args) == 2 # noqa: PLR2004
661 return BitOr(args[0], args[1])
662 if op == BitWiseOp.XOR:
663 assert len(args) == 2 # noqa: PLR2004
664 return BitXor(args[0], args[1])
665 if op == BitWiseOp.NOT:
666 assert len(args) == 1
667 return BitNot(args[0])
668 if op == BitWiseOp.EQ:
669 assert len(args) == 2 # noqa: PLR2004
670 return BitEq(args[0], args[1])
671 if op == BitWiseOp.NEQ:
672 assert len(args) == 2 # noqa: PLR2004
673 return BitNeq(args[0], args[1])
674 if op == BitWiseOp.ZERO:
675 assert len(args) == 0
676 return BitZero()
677 if op == BitWiseOp.ONE: # noqa: RET503 677 ↛ exitline 677 didn't return from function 'create_bit_logic_exp' because the condition on line 677 was always true
678 assert len(args) == 0
679 return BitOne()
682def create_reg_logic_exp( # noqa: PLR0911, PLR0912
683 op: RegWiseOp, args: Sequence[RegArgType]
684) -> RegLogicExp:
685 """
686 Builds the :py:class:`LogicExp` corresponding to applying the given
687 :py:class:`RegWiseOp` to some sequence of registers.
688 """
689 if op == RegWiseOp.AND:
690 assert len(args) == 2 # noqa: PLR2004
691 return RegAnd(args[0], args[1])
692 if op == RegWiseOp.OR:
693 assert len(args) == 2 # noqa: PLR2004
694 return RegOr(args[0], args[1])
695 if op == RegWiseOp.XOR:
696 assert len(args) == 2 # noqa: PLR2004
697 return RegXor(args[0], args[1])
698 if op == RegWiseOp.ADD:
699 assert len(args) == 2 # noqa: PLR2004
700 return RegAdd(args[0], args[1])
701 if op == RegWiseOp.SUB:
702 if len(args) == 2: # noqa: PLR2004 702 ↛ 704line 702 didn't jump to line 704 because the condition on line 702 was always true
703 return RegSub(args[0], args[1])
704 if len(args) == 1:
705 return RegNeg(args[0])
706 if op == RegWiseOp.NEG: 706 ↛ 707line 706 didn't jump to line 707 because the condition on line 706 was never true
707 assert len(args) == 1
708 return RegNeg(args[0])
709 if op == RegWiseOp.MUL:
710 assert len(args) == 2 # noqa: PLR2004
711 return RegMul(args[0], args[1])
712 if op == RegWiseOp.DIV:
713 assert len(args) == 2 # noqa: PLR2004
714 return RegDiv(args[0], args[1])
715 if op == RegWiseOp.POW:
716 assert len(args) == 2 # noqa: PLR2004
717 return RegPow(args[0], args[1])
718 if op == RegWiseOp.LSH:
719 assert len(args) == 2 # noqa: PLR2004
720 return RegLsh(args[0], args[1])
721 if op == RegWiseOp.RSH:
722 assert len(args) == 2 # noqa: PLR2004
723 return RegRsh(args[0], args[1])
724 if op == RegWiseOp.EQ:
725 assert len(args) == 2 # noqa: PLR2004
726 return RegEq(args[0], args[1])
727 if op == RegWiseOp.NEQ:
728 assert len(args) == 2 # noqa: PLR2004
729 return RegNeq(args[0], args[1])
730 if op == RegWiseOp.LT:
731 assert len(args) == 2 # noqa: PLR2004
732 return RegLt(args[0], args[1])
733 if op == RegWiseOp.GT:
734 assert len(args) == 2 # noqa: PLR2004
735 return RegGt(args[0], args[1])
736 if op == RegWiseOp.LEQ:
737 assert len(args) == 2 # noqa: PLR2004
738 return RegLeq(args[0], args[1])
739 if op == RegWiseOp.GEQ:
740 assert len(args) == 2 # noqa: PLR2004
741 return RegGeq(args[0], args[1])
742 if op == RegWiseOp.NOT: 742 ↛ 745line 742 didn't jump to line 745 because the condition on line 742 was always true
743 assert len(args) == 1
744 return RegNot(args[0])
745 raise ValueError("op type not supported")
748def create_logic_exp(op: Ops, args: Sequence[ArgType]) -> LogicExp:
749 """
750 Builds the :py:class:`LogicExp` corresponding to applying the given
751 :py:class:`BitWiseOp` or :py:class:`RegWiseOp` to some sequence of
752 arguments.
753 """
754 if isinstance(op, BitWiseOp):
755 bit_args = []
756 args = [
757 arg[0] if isinstance(arg, BitRegister) and arg.size == 1 else arg
758 for arg in args
759 ]
760 for arg in args:
761 assert isinstance(arg, BitLogicExp | Bit | Constant)
762 bit_args.append(arg)
763 return create_bit_logic_exp(op, bit_args)
764 assert isinstance(op, RegWiseOp)
765 reg_args = []
766 for arg in args:
767 assert isinstance(arg, RegLogicExp | BitRegister | Constant)
768 reg_args.append(arg)
769 return create_reg_logic_exp(op, reg_args)
772def create_predicate_exp( # noqa: PLR0911
773 op: Ops, args: Sequence[ArgType]
774) -> PredicateExp:
775 """
776 Builds the :py:class:`LogicExp` corresponding to applying a given
777 comparison predicate to some sequence of arguments.
778 """
779 if op == BitWiseOp.EQ:
780 assert len(args) == 2 # noqa: PLR2004
781 assert isinstance(args[0], BitLogicExp | Bit | int)
782 assert isinstance(args[1], BitLogicExp | Bit | int)
783 return BitEq(args[0], args[1])
784 if op == BitWiseOp.NEQ:
785 assert len(args) == 2 # noqa: PLR2004
786 assert isinstance(args[0], BitLogicExp | Bit | int)
787 assert isinstance(args[1], BitLogicExp | Bit | int)
788 return BitNeq(args[0], args[1])
789 if op == RegWiseOp.EQ:
790 assert len(args) == 2 # noqa: PLR2004
791 assert isinstance(args[0], RegLogicExp | BitRegister | int)
792 assert isinstance(args[1], RegLogicExp | BitRegister | int)
793 return RegEq(args[0], args[1])
794 if op == RegWiseOp.NEQ:
795 assert len(args) == 2 # noqa: PLR2004
796 assert isinstance(args[0], RegLogicExp | BitRegister | int)
797 assert isinstance(args[1], RegLogicExp | BitRegister | int)
798 return RegNeq(args[0], args[1])
799 if op == RegWiseOp.LT:
800 assert len(args) == 2 # noqa: PLR2004
801 assert isinstance(args[0], RegLogicExp | BitRegister | int)
802 assert isinstance(args[1], RegLogicExp | BitRegister | int)
803 return RegLt(args[0], args[1])
804 if op == RegWiseOp.GT:
805 assert len(args) == 2 # noqa: PLR2004
806 assert isinstance(args[0], RegLogicExp | BitRegister | int)
807 assert isinstance(args[1], RegLogicExp | BitRegister | int)
808 return RegGt(args[0], args[1])
809 if op == RegWiseOp.LEQ:
810 assert len(args) == 2 # noqa: PLR2004
811 assert isinstance(args[0], RegLogicExp | BitRegister | int)
812 assert isinstance(args[1], RegLogicExp | BitRegister | int)
813 return RegLeq(args[0], args[1])
814 if op == RegWiseOp.GEQ: 814 ↛ 819line 814 didn't jump to line 819 because the condition on line 814 was always true
815 assert len(args) == 2 # noqa: PLR2004
816 assert isinstance(args[0], RegLogicExp | BitRegister | int)
817 assert isinstance(args[1], RegLogicExp | BitRegister | int)
818 return RegGeq(args[0], args[1])
819 raise ValueError("op type not supported")