Coverage for /home/runner/work/tket/tket/pytket/pytket/circuit/logic_exp.py: 90%

555 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-09 15:08 +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. 

14 

15"""Classes and functions for constructing logical 

16expressions over Bit and BitRegister.""" 

17 

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 

23 

24from pytket.circuit import Bit, BitRegister 

25 

26T = TypeVar("T") 

27 

28 

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)) 

32 

33 

34class BitWiseOp(Enum): 

35 """Enum for operations on Bit.""" 

36 

37 AND = "&" 

38 OR = "|" 

39 XOR = "^" 

40 EQ = "==" 

41 NEQ = "!=" 

42 NOT = "~" 

43 ZERO = "0" 

44 ONE = "1" 

45 

46 

47class RegWiseOp(Enum): 

48 """Enum for operations on BitRegister.""" 

49 

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 

68 

69 

70Ops = Union[BitWiseOp, RegWiseOp] # all op enum types # noqa: UP007 

71 

72 

73Constant = int # constants in expression 

74Variable = Union[Bit, BitRegister] # variables in expression # noqa: UP007 

75ArgType = Union["LogicExp", Variable, Constant] # all possible arguments in expression 

76 

77 

78@dataclass(init=False) 

79class LogicExp: 

80 """Logical expressions over Bit or BitRegister. 

81 Encoded as a tree of expressions""" 

82 

83 op: Ops # enum for operation encoded by this node 

84 args: list[ArgType] # arguments of operation 

85 # class level dictionary mapping enum to class 

86 op_cls_dict: ClassVar[dict[Ops, type["LogicExp"]]] = {} 

87 

88 @classmethod 

89 def factory(cls, op: Ops) -> type["LogicExp"]: # noqa: PLR0911, PLR0912 

90 """Return matching operation class for enum.""" 

91 # RegNeg cannot be initialised this way as "-" clashes with SUB 

92 if op == BitWiseOp.AND: 

93 return BitAnd 

94 if op == BitWiseOp.OR: 

95 return BitOr 

96 if op == BitWiseOp.XOR: 

97 return BitXor 

98 if op == BitWiseOp.NOT: 

99 return BitNot 

100 if op == BitWiseOp.EQ: 

101 return BitEq 

102 if op == BitWiseOp.NEQ: 

103 return BitNeq 

104 if op == BitWiseOp.ZERO: 

105 return BitZero 

106 if op == BitWiseOp.ONE: 

107 return BitOne 

108 if op == RegWiseOp.AND: 

109 return RegAnd 

110 if op == RegWiseOp.OR: 

111 return RegOr 

112 if op == RegWiseOp.XOR: 

113 return RegXor 

114 if op == RegWiseOp.ADD: 

115 return RegAdd 

116 if op == RegWiseOp.SUB: 

117 return RegSub 

118 if op == RegWiseOp.MUL: 

119 return RegMul 

120 if op == RegWiseOp.DIV: 

121 return RegDiv 

122 if op == RegWiseOp.POW: 

123 return RegPow 

124 if op == RegWiseOp.LSH: 

125 return RegLsh 

126 if op == RegWiseOp.RSH: 

127 return RegRsh 

128 if op == RegWiseOp.EQ: 

129 return RegEq 

130 if op == RegWiseOp.NEQ: 

131 return RegNeq 

132 if op == RegWiseOp.LT: 

133 return RegLt 

134 if op == RegWiseOp.GT: 134 ↛ 135line 134 didn't jump to line 135 because the condition on line 134 was never true

135 return RegGt 

136 if op == RegWiseOp.LEQ: 

137 return RegLeq 

138 if op == RegWiseOp.GEQ: 

139 return RegGeq 

140 if op == RegWiseOp.NOT: 140 ↛ 142line 140 didn't jump to line 142 because the condition on line 140 was always true

141 return RegNot 

142 raise ValueError("op type not supported") 

143 

144 def set_value(self, var: Variable, val: Constant) -> None: 

145 """Set value of var to val recursively.""" 

146 for i, arg in enumerate(self.args): 

147 if isinstance(arg, Bit | BitRegister): 

148 if arg == var: 

149 self.args[i] = val 

150 elif isinstance(arg, LogicExp): 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true

151 arg.set_value(var, val) 

152 

153 @staticmethod 

154 def _const_eval(args: list[Constant]) -> Constant: 

155 """Evaluate expression given constant values for all args.""" 

156 raise NotImplementedError 

157 

158 def eval_vals(self) -> ArgType: 

159 """Attempt to evaluate all sub-expressions; simple constant folding.""" 

160 rval: ArgType = self 

161 for i, arg in filter_by_type(self.args, LogicExp): 161 ↛ 162line 161 didn't jump to line 162 because the loop on line 161 never started

162 self.args[i] = arg.eval_vals() 

163 if all(isinstance(a, Constant) for a in self.args): 163 ↛ 166line 163 didn't jump to line 166 because the condition on line 163 was always true

164 with contextlib.suppress(NotImplementedError): 

165 rval = self._const_eval(cast("list[Constant]", self.args)) 

166 return rval 

167 

168 def all_inputs(self) -> set[Variable]: 

169 """ 

170 :return: All variables involved in expression. 

171 :rtype: Set[Variable] 

172 """ 

173 outset: set[Variable] = set() 

174 

175 for arg in self.args: 

176 if isinstance(arg, LogicExp): 

177 outset.update(arg.all_inputs()) 

178 continue 

179 if isinstance(self, BitLogicExp): 

180 if isinstance(arg, Bit): 

181 outset.add(arg) 

182 elif isinstance(arg, BitRegister): 

183 outset.add(arg) 

184 return outset 

185 

186 def all_inputs_ordered(self) -> list[Variable]: 

187 """ 

188 :return: All variables involved in expression, in order of first appearance. 

189 :rtype: list[Variable] 

190 """ 

191 # use dict[Variable, None] instead of set[Variable] to preserve order 

192 outset: dict[Variable, None] = {} 

193 

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) 

204 

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) 

209 

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]] = [] 

214 

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}) 

224 

225 out["args"] = args_ser 

226 return out 

227 

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) 

246 

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(cmap, renamed_regs) # noqa: SLF001 

265 return success 

266 

267 def rename_args(self, cmap: dict[Bit, Bit]) -> bool: 

268 """Rename the Bits according to a Bit map. Raise ValueError if 

269 a bit is being used in a register-wise expression. 

270 """ 

271 if all(old_bit == new_bit for old_bit, new_bit in cmap.items()): 

272 return False 

273 renamed_regs = {key.reg_name for key in cmap} 

274 return self._rename_args_recursive(cmap, renamed_regs) 

275 

276 

277BitArgType = Union[LogicExp, Bit, Constant] # noqa: UP007 

278RegArgType = Union[LogicExp, BitRegister, Constant] # noqa: UP007 

279 

280 

281class BitLogicExp(LogicExp): 

282 """Expression acting only on Bit or Constant types.""" 

283 

284 def __and__(self, other: BitArgType) -> "BitAnd": 

285 return BitAnd(self, other) 

286 

287 def __rand__(self, other: BitArgType) -> "BitAnd": 

288 return BitAnd(self, other) 

289 

290 def __or__(self, other: BitArgType) -> "BitOr": 

291 return BitOr(self, other) 

292 

293 def __ror__(self, other: BitArgType) -> "BitOr": 

294 return BitOr(self, other) 

295 

296 def __xor__(self, other: BitArgType) -> "BitXor": 

297 return BitXor(self, other) 

298 

299 def __rxor__(self, other: BitArgType) -> "BitXor": 

300 return BitXor(self, other) 

301 

302 

303class RegLogicExp(LogicExp): 

304 """Expression acting only on BitRegister or Constant types.""" 

305 

306 def __and__(self, other: RegArgType) -> "RegAnd": 

307 return RegAnd(self, other) 

308 

309 def __rand__(self, other: RegArgType) -> "RegAnd": 

310 return RegAnd(self, other) 

311 

312 def __or__(self, other: RegArgType) -> "RegOr": 

313 return RegOr(self, other) 

314 

315 def __ror__(self, other: RegArgType) -> "RegOr": 

316 return RegOr(self, other) 

317 

318 def __xor__(self, other: RegArgType) -> "RegXor": 

319 return RegXor(self, other) 

320 

321 def __rxor__(self, other: RegArgType) -> "RegXor": 

322 return RegXor(self, other) 

323 

324 def __add__(self, other: RegArgType) -> "RegAdd": 

325 return RegAdd(self, other) 

326 

327 def __sub__(self, other: RegArgType) -> "RegSub": 

328 return RegSub(self, other) 

329 

330 def __mul__(self, other: RegArgType) -> "RegMul": 

331 return RegMul(self, other) 

332 

333 def __floordiv__(self, other: RegArgType) -> "RegDiv": 

334 return RegDiv(self, other) 

335 

336 def __pow__(self, other: RegArgType) -> "RegPow": 

337 return RegPow(self, other) 

338 

339 def __lshift__(self, other: RegArgType) -> "RegLsh": 

340 return RegLsh(self, other) 

341 

342 def __rshift__(self, other: RegArgType) -> "RegRsh": 

343 return RegRsh(self, other) 

344 

345 

346class BinaryOp(LogicExp): 

347 """Expresion for operation on two arguments.""" 

348 

349 def __str__(self) -> str: 

350 return f"({self.args[0]} {self.op.value} {self.args[1]})" 

351 

352 

353class UnaryOp(LogicExp): 

354 """Expression for operation on one argument.""" 

355 

356 def __str__(self) -> str: 

357 return f"({self.op.value} {self.args[0]})" 

358 

359 

360class NullaryOp(LogicExp): 

361 """Expression for operation on no arguments (i.e. constant).""" 

362 

363 def __str__(self) -> str: 

364 return f"({self.op.value})" 

365 

366 

367class And(BinaryOp): 

368 @staticmethod 

369 def _const_eval(args: list[Constant]) -> Constant: 

370 return args[0] & args[1] 

371 

372 def eval_vals(self) -> ArgType: 

373 rval: ArgType = super().eval_vals() 

374 if 0 in self.args: 

375 return 0 

376 return rval 

377 

378 

379class Or(BinaryOp): 

380 @staticmethod 

381 def _const_eval(args: list[Constant]) -> Constant: 

382 return args[0] | args[1] 

383 

384 

385class Xor(BinaryOp): 

386 @staticmethod 

387 def _const_eval(args: list[Constant]) -> Constant: 

388 return args[0] ^ args[1] 

389 

390 

391class BitAnd(And, BitLogicExp): 

392 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None: 

393 self.op = BitWiseOp.AND 

394 self.args = [arg1, arg2] 

395 

396 

397class BitOr(Or, BitLogicExp): 

398 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None: 

399 self.op = BitWiseOp.OR 

400 self.args = [arg1, arg2] 

401 

402 def eval_vals(self) -> ArgType: 

403 rval: ArgType = super().eval_vals() 

404 if 1 in self.args: 404 ↛ 406line 404 didn't jump to line 406 because the condition on line 404 was always true

405 return 1 

406 return rval 

407 

408 

409class BitXor(Xor, BitLogicExp): 

410 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None: 

411 self.op = BitWiseOp.XOR 

412 self.args = [arg1, arg2] 

413 

414 

415class BitNot(UnaryOp, BitLogicExp): 

416 def __init__(self, arg1: BitArgType) -> None: 

417 self.op = BitWiseOp.NOT 

418 self.args = [arg1] 

419 

420 @staticmethod 

421 def _const_eval(args: list[Constant]) -> Constant: 

422 return 1 - args[0] 

423 

424 

425class BitZero(NullaryOp, BitLogicExp): 

426 def __init__(self) -> None: 

427 self.op = BitWiseOp.ZERO 

428 self.args = [] 

429 

430 @staticmethod 

431 def _const_eval(args: list[Constant]) -> Constant: 

432 return 0 

433 

434 

435class BitOne(NullaryOp, BitLogicExp): 

436 def __init__(self) -> None: 

437 self.op = BitWiseOp.ONE 

438 self.args = [] 

439 

440 @staticmethod 

441 def _const_eval(args: list[Constant]) -> Constant: 

442 return 1 

443 

444 

445class RegAnd(And, RegLogicExp): 

446 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

447 self.op = RegWiseOp.AND 

448 self.args = [arg1, arg2] 

449 

450 

451class RegOr(Or, RegLogicExp): 

452 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

453 self.op = RegWiseOp.OR 

454 self.args = [arg1, arg2] 

455 

456 

457class RegXor(Xor, RegLogicExp): 

458 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

459 self.op = RegWiseOp.XOR 

460 self.args = [arg1, arg2] 

461 

462 

463class RegAdd(BinaryOp, RegLogicExp): 

464 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

465 self.op = RegWiseOp.ADD 

466 self.args = [arg1, arg2] 

467 

468 

469class RegSub(BinaryOp, RegLogicExp): 

470 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

471 self.op = RegWiseOp.SUB 

472 self.args = [arg1, arg2] 

473 

474 

475class RegMul(BinaryOp, RegLogicExp): 

476 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

477 self.op = RegWiseOp.MUL 

478 self.args = [arg1, arg2] 

479 

480 

481class RegDiv(BinaryOp, RegLogicExp): 

482 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

483 self.op = RegWiseOp.DIV 

484 self.args = [arg1, arg2] 

485 

486 

487class RegPow(BinaryOp, RegLogicExp): 

488 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

489 self.op = RegWiseOp.POW 

490 self.args = [arg1, arg2] 

491 

492 

493class RegLsh(BinaryOp, RegLogicExp): 

494 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

495 self.op = RegWiseOp.LSH 

496 self.args = [arg1, arg2] 

497 

498 

499class RegNeg(UnaryOp, RegLogicExp): 

500 def __init__(self, arg1: RegArgType) -> None: 

501 self.op = RegWiseOp.NEG 

502 self.args = [arg1] 

503 

504 

505class RegNot(UnaryOp, RegLogicExp): 

506 def __init__(self, arg1: RegArgType) -> None: 

507 self.op = RegWiseOp.NOT 

508 self.args = [arg1] 

509 

510 

511class RegRsh(BinaryOp, RegLogicExp): 

512 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

513 self.op = RegWiseOp.RSH 

514 self.args = [arg1, arg2] 

515 

516 

517class PredicateExp(BinaryOp): 

518 """ 

519 A binary predicate where the arguments are either 

520 Bits, BitRegisters, or Constants. 

521 """ 

522 

523 

524class Eq(PredicateExp): 

525 @staticmethod 

526 def _const_eval(args: list[Constant]) -> Constant: 

527 return args[0] == args[1] 

528 

529 

530class Neq(PredicateExp): 

531 @staticmethod 

532 def _const_eval(args: list[Constant]) -> Constant: 

533 return 1 - Eq._const_eval(args) # noqa: SLF001 

534 

535 

536class BitEq(Eq, BitLogicExp): 

537 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None: 

538 self.op = BitWiseOp.EQ 

539 self.args = [arg1, arg2] 

540 

541 

542class BitNeq(Neq, BitLogicExp): 

543 def __init__(self, arg1: BitArgType, arg2: BitArgType) -> None: 

544 self.op = BitWiseOp.NEQ 

545 self.args = [arg1, arg2] 

546 

547 

548class RegEq(Eq, RegLogicExp): 

549 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

550 self.op = RegWiseOp.EQ 

551 self.args = [arg1, arg2] 

552 

553 

554class RegNeq(Neq, RegLogicExp): 

555 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

556 self.op = RegWiseOp.NEQ 

557 self.args = [arg1, arg2] 

558 

559 

560class RegLt(PredicateExp, RegLogicExp): 

561 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

562 self.op = RegWiseOp.LT 

563 self.args = [arg1, arg2] 

564 

565 @staticmethod 

566 def _const_eval(args: list[Constant]) -> Constant: 

567 return args[0] < args[1] 

568 

569 

570class RegGt(PredicateExp, RegLogicExp): 

571 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

572 self.op = RegWiseOp.GT 

573 self.args = [arg1, arg2] 

574 

575 @staticmethod 

576 def _const_eval(args: list[Constant]) -> Constant: 

577 return args[0] > args[1] 

578 

579 

580class RegLeq(PredicateExp, RegLogicExp): 

581 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

582 self.op = RegWiseOp.LEQ 

583 self.args = [arg1, arg2] 

584 

585 @staticmethod 

586 def _const_eval(args: list[Constant]) -> Constant: 

587 return args[0] <= args[1] 

588 

589 

590class RegGeq(PredicateExp, RegLogicExp): 

591 def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: 

592 self.op = RegWiseOp.GEQ 

593 self.args = [arg1, arg2] 

594 

595 @staticmethod 

596 def _const_eval(args: list[Constant]) -> Constant: 

597 return args[0] >= args[1] 

598 

599 

600def reg_eq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: 

601 """Function to express a BitRegister equality predicate, i.e. 

602 for a register ``r``, ``(r == 5)`` is expressed as ``reg_eq(r, 5)``""" 

603 return RegEq(register, value) 

604 

605 

606def reg_neq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: 

607 """Function to express a BitRegister inequality predicate, i.e. 

608 for a register ``r``, ``(r != 5)`` is expressed as ``reg_neq(r, 5)``""" 

609 return RegNeq(register, value) 

610 

611 

612def reg_lt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: 

613 """Function to express a BitRegister less than predicate, i.e. 

614 for a register ``r``, ``(r < 5)`` is expressed as ``reg_lt(r, 5)``""" 

615 return RegLt(register, value) 

616 

617 

618def reg_gt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: 

619 """Function to express a BitRegister greater than predicate, i.e. 

620 for a register ``r``, ``(r > 5)`` is expressed as ``reg_gt(r, 5)``""" 

621 return RegGt(register, value) 

622 

623 

624def reg_leq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: 

625 """Function to express a BitRegister less than or equal to predicate, 

626 i.e. for a register ``r``, ``(r <= 5)`` is expressed as ``reg_leq(r, 5)``""" 

627 return RegLeq(register, value) 

628 

629 

630def reg_geq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: 

631 """Function to express a BitRegister greater than or equal to 

632 predicate, i.e. for a register ``r``, ``(r >= 5)`` is expressed as 

633 ``reg_geq(r, 5)``""" 

634 return RegGeq(register, value) 

635 

636 

637def if_bit(bit: Bit | BitLogicExp) -> PredicateExp: 

638 """Equivalent of ``if bit:``.""" 

639 return BitEq(bit, 1) 

640 

641 

642def if_not_bit(bit: Bit | BitLogicExp) -> PredicateExp: 

643 """Equivalent of ``if not bit:``.""" 

644 return BitEq(bit, 0) 

645 

646 

647def create_bit_logic_exp(op: BitWiseOp, args: Sequence[BitArgType]) -> BitLogicExp: # noqa: PLR0911 

648 """ 

649 Builds the :py:class:`LogicExp` corresponding to applying the given 

650 :py:class:`BitWiseOp` to some sequence of bits. 

651 """ 

652 if op == BitWiseOp.AND: 

653 assert len(args) == 2 # noqa: PLR2004 

654 return BitAnd(args[0], args[1]) 

655 if op == BitWiseOp.OR: 

656 assert len(args) == 2 # noqa: PLR2004 

657 return BitOr(args[0], args[1]) 

658 if op == BitWiseOp.XOR: 

659 assert len(args) == 2 # noqa: PLR2004 

660 return BitXor(args[0], args[1]) 

661 if op == BitWiseOp.NOT: 

662 assert len(args) == 1 

663 return BitNot(args[0]) 

664 if op == BitWiseOp.EQ: 

665 assert len(args) == 2 # noqa: PLR2004 

666 return BitEq(args[0], args[1]) 

667 if op == BitWiseOp.NEQ: 

668 assert len(args) == 2 # noqa: PLR2004 

669 return BitNeq(args[0], args[1]) 

670 if op == BitWiseOp.ZERO: 

671 assert len(args) == 0 

672 return BitZero() 

673 if op == BitWiseOp.ONE: # noqa: RET503 673 ↛ exitline 673 didn't return from function 'create_bit_logic_exp' because the condition on line 673 was always true

674 assert len(args) == 0 

675 return BitOne() 

676 

677 

678def create_reg_logic_exp(op: RegWiseOp, args: Sequence[RegArgType]) -> RegLogicExp: # noqa: PLR0911, PLR0912 

679 """ 

680 Builds the :py:class:`LogicExp` corresponding to applying the given 

681 :py:class:`RegWiseOp` to some sequence of registers. 

682 """ 

683 if op == RegWiseOp.AND: 

684 assert len(args) == 2 # noqa: PLR2004 

685 return RegAnd(args[0], args[1]) 

686 if op == RegWiseOp.OR: 

687 assert len(args) == 2 # noqa: PLR2004 

688 return RegOr(args[0], args[1]) 

689 if op == RegWiseOp.XOR: 

690 assert len(args) == 2 # noqa: PLR2004 

691 return RegXor(args[0], args[1]) 

692 if op == RegWiseOp.ADD: 

693 assert len(args) == 2 # noqa: PLR2004 

694 return RegAdd(args[0], args[1]) 

695 if op == RegWiseOp.SUB: 

696 if len(args) == 2: # noqa: PLR2004 696 ↛ 698line 696 didn't jump to line 698 because the condition on line 696 was always true

697 return RegSub(args[0], args[1]) 

698 if len(args) == 1: 

699 return RegNeg(args[0]) 

700 if op == RegWiseOp.NEG: 700 ↛ 701line 700 didn't jump to line 701 because the condition on line 700 was never true

701 assert len(args) == 1 

702 return RegNeg(args[0]) 

703 if op == RegWiseOp.MUL: 

704 assert len(args) == 2 # noqa: PLR2004 

705 return RegMul(args[0], args[1]) 

706 if op == RegWiseOp.DIV: 

707 assert len(args) == 2 # noqa: PLR2004 

708 return RegDiv(args[0], args[1]) 

709 if op == RegWiseOp.POW: 

710 assert len(args) == 2 # noqa: PLR2004 

711 return RegPow(args[0], args[1]) 

712 if op == RegWiseOp.LSH: 

713 assert len(args) == 2 # noqa: PLR2004 

714 return RegLsh(args[0], args[1]) 

715 if op == RegWiseOp.RSH: 

716 assert len(args) == 2 # noqa: PLR2004 

717 return RegRsh(args[0], args[1]) 

718 if op == RegWiseOp.EQ: 

719 assert len(args) == 2 # noqa: PLR2004 

720 return RegEq(args[0], args[1]) 

721 if op == RegWiseOp.NEQ: 

722 assert len(args) == 2 # noqa: PLR2004 

723 return RegNeq(args[0], args[1]) 

724 if op == RegWiseOp.LT: 

725 assert len(args) == 2 # noqa: PLR2004 

726 return RegLt(args[0], args[1]) 

727 if op == RegWiseOp.GT: 

728 assert len(args) == 2 # noqa: PLR2004 

729 return RegGt(args[0], args[1]) 

730 if op == RegWiseOp.LEQ: 

731 assert len(args) == 2 # noqa: PLR2004 

732 return RegLeq(args[0], args[1]) 

733 if op == RegWiseOp.GEQ: 

734 assert len(args) == 2 # noqa: PLR2004 

735 return RegGeq(args[0], args[1]) 

736 if op == RegWiseOp.NOT: 736 ↛ 739line 736 didn't jump to line 739 because the condition on line 736 was always true

737 assert len(args) == 1 

738 return RegNot(args[0]) 

739 raise ValueError("op type not supported") 

740 

741 

742def create_logic_exp(op: Ops, args: Sequence[ArgType]) -> LogicExp: 

743 """ 

744 Builds the :py:class:`LogicExp` corresponding to applying the given 

745 :py:class:`BitWiseOp` or :py:class:`RegWiseOp` to some sequence of 

746 arguments. 

747 """ 

748 if isinstance(op, BitWiseOp): 

749 bit_args = [] 

750 args = [ 

751 arg[0] if isinstance(arg, BitRegister) and arg.size == 1 else arg 

752 for arg in args 

753 ] 

754 for arg in args: 

755 assert isinstance(arg, BitLogicExp | Bit | Constant) 

756 bit_args.append(arg) 

757 return create_bit_logic_exp(op, bit_args) 

758 assert isinstance(op, RegWiseOp) 

759 reg_args = [] 

760 for arg in args: 

761 assert isinstance(arg, RegLogicExp | BitRegister | Constant) 

762 reg_args.append(arg) 

763 return create_reg_logic_exp(op, reg_args) 

764 

765 

766def create_predicate_exp(op: Ops, args: Sequence[ArgType]) -> PredicateExp: # noqa: PLR0911 

767 """ 

768 Builds the :py:class:`LogicExp` corresponding to applying a given 

769 comparison predicate to some sequence of arguments. 

770 """ 

771 if op == BitWiseOp.EQ: 

772 assert len(args) == 2 # noqa: PLR2004 

773 assert isinstance(args[0], BitLogicExp | Bit | int) 

774 assert isinstance(args[1], BitLogicExp | Bit | int) 

775 return BitEq(args[0], args[1]) 

776 if op == BitWiseOp.NEQ: 

777 assert len(args) == 2 # noqa: PLR2004 

778 assert isinstance(args[0], BitLogicExp | Bit | int) 

779 assert isinstance(args[1], BitLogicExp | Bit | int) 

780 return BitNeq(args[0], args[1]) 

781 if op == RegWiseOp.EQ: 

782 assert len(args) == 2 # noqa: PLR2004 

783 assert isinstance(args[0], RegLogicExp | BitRegister | int) 

784 assert isinstance(args[1], RegLogicExp | BitRegister | int) 

785 return RegEq(args[0], args[1]) 

786 if op == RegWiseOp.NEQ: 

787 assert len(args) == 2 # noqa: PLR2004 

788 assert isinstance(args[0], RegLogicExp | BitRegister | int) 

789 assert isinstance(args[1], RegLogicExp | BitRegister | int) 

790 return RegNeq(args[0], args[1]) 

791 if op == RegWiseOp.LT: 

792 assert len(args) == 2 # noqa: PLR2004 

793 assert isinstance(args[0], RegLogicExp | BitRegister | int) 

794 assert isinstance(args[1], RegLogicExp | BitRegister | int) 

795 return RegLt(args[0], args[1]) 

796 if op == RegWiseOp.GT: 

797 assert len(args) == 2 # noqa: PLR2004 

798 assert isinstance(args[0], RegLogicExp | BitRegister | int) 

799 assert isinstance(args[1], RegLogicExp | BitRegister | int) 

800 return RegGt(args[0], args[1]) 

801 if op == RegWiseOp.LEQ: 

802 assert len(args) == 2 # noqa: PLR2004 

803 assert isinstance(args[0], RegLogicExp | BitRegister | int) 

804 assert isinstance(args[1], RegLogicExp | BitRegister | int) 

805 return RegLeq(args[0], args[1]) 

806 if op == RegWiseOp.GEQ: 806 ↛ 811line 806 didn't jump to line 811 because the condition on line 806 was always true

807 assert len(args) == 2 # noqa: PLR2004 

808 assert isinstance(args[0], RegLogicExp | BitRegister | int) 

809 assert isinstance(args[1], RegLogicExp | BitRegister | int) 

810 return RegGeq(args[0], args[1]) 

811 raise ValueError("op type not supported")