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

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[ 

76 "LogicExp", Bit | BitRegister, Constant 

77] # all possible arguments in expression 

78 

79 

80@dataclass(init=False) 

81class LogicExp: 

82 """Logical expressions over Bit or BitRegister. 

83 Encoded as a tree of expressions""" 

84 

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"]]] = {} 

89 

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

145 

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) 

154 

155 @staticmethod 

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

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

158 raise NotImplementedError 

159 

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 

169 

170 def all_inputs(self) -> set[Bit | BitRegister]: 

171 """ 

172 :return: All variables involved in expression. 

173 """ 

174 outset: set[Bit | BitRegister] = set() 

175 

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 

186 

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] = {} 

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( # noqa: SLF001 

265 cmap, renamed_regs 

266 ) 

267 return success 

268 

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) 

277 

278 

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

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

281 

282 

283class BitLogicExp(LogicExp): 

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

285 

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

287 return BitAnd(self, other) 

288 

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

290 return BitAnd(self, other) 

291 

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

293 return BitOr(self, other) 

294 

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

296 return BitOr(self, other) 

297 

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

299 return BitXor(self, other) 

300 

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

302 return BitXor(self, other) 

303 

304 

305class RegLogicExp(LogicExp): 

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

307 

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

309 return RegAnd(self, other) 

310 

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

312 return RegAnd(self, other) 

313 

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

315 return RegOr(self, other) 

316 

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

318 return RegOr(self, other) 

319 

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

321 return RegXor(self, other) 

322 

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

324 return RegXor(self, other) 

325 

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

327 return RegAdd(self, other) 

328 

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

330 return RegSub(self, other) 

331 

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

333 return RegMul(self, other) 

334 

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

336 return RegDiv(self, other) 

337 

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

339 return RegPow(self, other) 

340 

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

342 return RegLsh(self, other) 

343 

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

345 return RegRsh(self, other) 

346 

347 

348class BinaryOp(LogicExp): 

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

350 

351 def __str__(self) -> str: 

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

353 

354 

355class UnaryOp(LogicExp): 

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

357 

358 def __str__(self) -> str: 

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

360 

361 

362class NullaryOp(LogicExp): 

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

364 

365 def __str__(self) -> str: 

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

367 

368 

369class And(BinaryOp): 

370 @staticmethod 

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

372 return args[0] & args[1] 

373 

374 def eval_vals(self) -> ArgType: 

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

376 if 0 in self.args: 

377 return 0 

378 return rval 

379 

380 

381class Or(BinaryOp): 

382 @staticmethod 

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

384 return args[0] | args[1] 

385 

386 

387class Xor(BinaryOp): 

388 @staticmethod 

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

390 return args[0] ^ args[1] 

391 

392 

393class BitAnd(And, BitLogicExp): 

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

395 self.op = BitWiseOp.AND 

396 self.args = [arg1, arg2] 

397 

398 

399class BitOr(Or, BitLogicExp): 

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

401 self.op = BitWiseOp.OR 

402 self.args = [arg1, arg2] 

403 

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 

409 

410 

411class BitXor(Xor, BitLogicExp): 

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

413 self.op = BitWiseOp.XOR 

414 self.args = [arg1, arg2] 

415 

416 

417class BitNot(UnaryOp, BitLogicExp): 

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

419 self.op = BitWiseOp.NOT 

420 self.args = [arg1] 

421 

422 @staticmethod 

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

424 return 1 - args[0] 

425 

426 

427class BitZero(NullaryOp, BitLogicExp): 

428 def __init__(self) -> None: 

429 self.op = BitWiseOp.ZERO 

430 self.args = [] 

431 

432 @staticmethod 

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

434 return 0 

435 

436 

437class BitOne(NullaryOp, BitLogicExp): 

438 def __init__(self) -> None: 

439 self.op = BitWiseOp.ONE 

440 self.args = [] 

441 

442 @staticmethod 

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

444 return 1 

445 

446 

447class RegAnd(And, RegLogicExp): 

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

449 self.op = RegWiseOp.AND 

450 self.args = [arg1, arg2] 

451 

452 

453class RegOr(Or, RegLogicExp): 

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

455 self.op = RegWiseOp.OR 

456 self.args = [arg1, arg2] 

457 

458 

459class RegXor(Xor, RegLogicExp): 

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

461 self.op = RegWiseOp.XOR 

462 self.args = [arg1, arg2] 

463 

464 

465class RegAdd(BinaryOp, RegLogicExp): 

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

467 self.op = RegWiseOp.ADD 

468 self.args = [arg1, arg2] 

469 

470 

471class RegSub(BinaryOp, RegLogicExp): 

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

473 self.op = RegWiseOp.SUB 

474 self.args = [arg1, arg2] 

475 

476 

477class RegMul(BinaryOp, RegLogicExp): 

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

479 self.op = RegWiseOp.MUL 

480 self.args = [arg1, arg2] 

481 

482 

483class RegDiv(BinaryOp, RegLogicExp): 

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

485 self.op = RegWiseOp.DIV 

486 self.args = [arg1, arg2] 

487 

488 

489class RegPow(BinaryOp, RegLogicExp): 

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

491 self.op = RegWiseOp.POW 

492 self.args = [arg1, arg2] 

493 

494 

495class RegLsh(BinaryOp, RegLogicExp): 

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

497 self.op = RegWiseOp.LSH 

498 self.args = [arg1, arg2] 

499 

500 

501class RegNeg(UnaryOp, RegLogicExp): 

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

503 self.op = RegWiseOp.NEG 

504 self.args = [arg1] 

505 

506 

507class RegNot(UnaryOp, RegLogicExp): 

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

509 self.op = RegWiseOp.NOT 

510 self.args = [arg1] 

511 

512 

513class RegRsh(BinaryOp, RegLogicExp): 

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

515 self.op = RegWiseOp.RSH 

516 self.args = [arg1, arg2] 

517 

518 

519class PredicateExp(BinaryOp): 

520 """ 

521 A binary predicate where the arguments are either 

522 Bits, BitRegisters, or Constants. 

523 """ 

524 

525 

526class Eq(PredicateExp): 

527 @staticmethod 

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

529 return args[0] == args[1] 

530 

531 

532class Neq(PredicateExp): 

533 @staticmethod 

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

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

536 

537 

538class BitEq(Eq, BitLogicExp): 

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

540 self.op = BitWiseOp.EQ 

541 self.args = [arg1, arg2] 

542 

543 

544class BitNeq(Neq, BitLogicExp): 

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

546 self.op = BitWiseOp.NEQ 

547 self.args = [arg1, arg2] 

548 

549 

550class RegEq(Eq, RegLogicExp): 

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

552 self.op = RegWiseOp.EQ 

553 self.args = [arg1, arg2] 

554 

555 

556class RegNeq(Neq, RegLogicExp): 

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

558 self.op = RegWiseOp.NEQ 

559 self.args = [arg1, arg2] 

560 

561 

562class RegLt(PredicateExp, RegLogicExp): 

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

564 self.op = RegWiseOp.LT 

565 self.args = [arg1, arg2] 

566 

567 @staticmethod 

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

569 return args[0] < args[1] 

570 

571 

572class RegGt(PredicateExp, RegLogicExp): 

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

574 self.op = RegWiseOp.GT 

575 self.args = [arg1, arg2] 

576 

577 @staticmethod 

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

579 return args[0] > args[1] 

580 

581 

582class RegLeq(PredicateExp, RegLogicExp): 

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

584 self.op = RegWiseOp.LEQ 

585 self.args = [arg1, arg2] 

586 

587 @staticmethod 

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

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

590 

591 

592class RegGeq(PredicateExp, RegLogicExp): 

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

594 self.op = RegWiseOp.GEQ 

595 self.args = [arg1, arg2] 

596 

597 @staticmethod 

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

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

600 

601 

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) 

606 

607 

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) 

612 

613 

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) 

618 

619 

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) 

624 

625 

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) 

630 

631 

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) 

637 

638 

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

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

641 return BitEq(bit, 1) 

642 

643 

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

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

646 return BitEq(bit, 0) 

647 

648 

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

680 

681 

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

746 

747 

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) 

770 

771 

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