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

558 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-07-02 13:01 +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 __hash__(self) -> int: 

211 return hash((self.op, self.args)) 

212 

213 def to_dict(self) -> dict[str, Any]: 

214 """Output JSON serializable nested dictionary.""" 

215 out: dict[str, Any] = {"op": str(self.op)} 

216 args_ser: list[dict | Constant | list[str | int]] = [] 

217 

218 for arg in self.args: 

219 if isinstance(arg, LogicExp): 

220 args_ser.append(arg.to_dict()) 

221 elif isinstance(arg, Constant): 

222 args_ser.append(arg) 

223 elif isinstance(arg, Bit): 

224 args_ser.append(arg.to_list()) 

225 elif isinstance(arg, BitRegister): 225 ↛ 218line 225 didn't jump to line 218 because the condition on line 225 was always true

226 args_ser.append({"name": arg.name, "size": arg.size}) 

227 

228 out["args"] = args_ser 

229 return out 

230 

231 @classmethod 

232 def from_dict(cls, dic: dict[str, Any]) -> "LogicExp": 

233 """Load from JSON serializable nested dictionary.""" 

234 opset_name, op_name = dic["op"].split(".", 2) 

235 opset = BitWiseOp if opset_name == "BitWiseOp" else RegWiseOp 

236 op = next(o for o in opset if o.name == op_name) 

237 args: list[ArgType] = [] 

238 for arg_ser in dic["args"]: 

239 if isinstance(arg_ser, Constant): 

240 args.append(arg_ser) 

241 elif isinstance(arg_ser, list): 

242 args.append(Bit(arg_ser[0], arg_ser[1])) 

243 elif isinstance(arg_ser, dict): 243 ↛ 238line 243 didn't jump to line 238 because the condition on line 243 was always true

244 if "op" in arg_ser: 

245 args.append(LogicExp.from_dict(arg_ser)) 

246 else: 

247 args.append(BitRegister(arg_ser["name"], arg_ser["size"])) 

248 return create_logic_exp(op, args) 

249 

250 def _rename_args_recursive( 

251 self, cmap: dict[Bit, Bit], renamed_regs: set[str] 

252 ) -> bool: 

253 success = False 

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

255 if isinstance(arg, Bit): 

256 if arg in cmap: 

257 self.args[i] = cmap[arg] 

258 success = True 

259 elif isinstance(arg, BitRegister): 

260 if arg.name in renamed_regs: 

261 raise ValueError( 

262 f"""Can't rename bits in {arg.__repr__()} """ 

263 """because the register is being used """ 

264 """in a register-wise logic expression.""" 

265 ) 

266 elif isinstance(arg, LogicExp): 

267 success |= arg._rename_args_recursive( # noqa: SLF001 

268 cmap, renamed_regs 

269 ) 

270 return success 

271 

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

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

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

275 """ 

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

277 return False 

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

279 return self._rename_args_recursive(cmap, renamed_regs) 

280 

281 

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

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

284 

285 

286class BitLogicExp(LogicExp): 

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

288 

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

290 return BitAnd(self, other) 

291 

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

293 return BitAnd(self, other) 

294 

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

296 return BitOr(self, other) 

297 

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

299 return BitOr(self, other) 

300 

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

302 return BitXor(self, other) 

303 

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

305 return BitXor(self, other) 

306 

307 

308class RegLogicExp(LogicExp): 

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

310 

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

312 return RegAnd(self, other) 

313 

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

315 return RegAnd(self, other) 

316 

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

318 return RegOr(self, other) 

319 

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

321 return RegOr(self, other) 

322 

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

324 return RegXor(self, other) 

325 

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

327 return RegXor(self, other) 

328 

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

330 return RegAdd(self, other) 

331 

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

333 return RegSub(self, other) 

334 

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

336 return RegMul(self, other) 

337 

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

339 return RegDiv(self, other) 

340 

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

342 return RegPow(self, other) 

343 

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

345 return RegLsh(self, other) 

346 

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

348 return RegRsh(self, other) 

349 

350 

351class BinaryOp(LogicExp): 

352 """Expression for operation on two arguments.""" 

353 

354 def __str__(self) -> str: 

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

356 

357 

358class UnaryOp(LogicExp): 

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

360 

361 def __str__(self) -> str: 

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

363 

364 

365class NullaryOp(LogicExp): 

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

367 

368 def __str__(self) -> str: 

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

370 

371 

372class And(BinaryOp): 

373 @staticmethod 

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

375 return args[0] & args[1] 

376 

377 def eval_vals(self) -> ArgType: 

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

379 if 0 in self.args: 

380 return 0 

381 return rval 

382 

383 

384class Or(BinaryOp): 

385 @staticmethod 

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

387 return args[0] | args[1] 

388 

389 

390class Xor(BinaryOp): 

391 @staticmethod 

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

393 return args[0] ^ args[1] 

394 

395 

396class BitAnd(And, BitLogicExp): 

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

398 self.op = BitWiseOp.AND 

399 self.args = [arg1, arg2] 

400 

401 

402class BitOr(Or, BitLogicExp): 

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

404 self.op = BitWiseOp.OR 

405 self.args = [arg1, arg2] 

406 

407 def eval_vals(self) -> ArgType: 

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

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

410 return 1 

411 return rval 

412 

413 

414class BitXor(Xor, BitLogicExp): 

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

416 self.op = BitWiseOp.XOR 

417 self.args = [arg1, arg2] 

418 

419 

420class BitNot(UnaryOp, BitLogicExp): 

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

422 self.op = BitWiseOp.NOT 

423 self.args = [arg1] 

424 

425 @staticmethod 

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

427 return 1 - args[0] 

428 

429 

430class BitZero(NullaryOp, BitLogicExp): 

431 def __init__(self) -> None: 

432 self.op = BitWiseOp.ZERO 

433 self.args = [] 

434 

435 @staticmethod 

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

437 return 0 

438 

439 

440class BitOne(NullaryOp, BitLogicExp): 

441 def __init__(self) -> None: 

442 self.op = BitWiseOp.ONE 

443 self.args = [] 

444 

445 @staticmethod 

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

447 return 1 

448 

449 

450class RegAnd(And, RegLogicExp): 

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

452 self.op = RegWiseOp.AND 

453 self.args = [arg1, arg2] 

454 

455 

456class RegOr(Or, RegLogicExp): 

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

458 self.op = RegWiseOp.OR 

459 self.args = [arg1, arg2] 

460 

461 

462class RegXor(Xor, RegLogicExp): 

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

464 self.op = RegWiseOp.XOR 

465 self.args = [arg1, arg2] 

466 

467 

468class RegAdd(BinaryOp, RegLogicExp): 

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

470 self.op = RegWiseOp.ADD 

471 self.args = [arg1, arg2] 

472 

473 

474class RegSub(BinaryOp, RegLogicExp): 

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

476 self.op = RegWiseOp.SUB 

477 self.args = [arg1, arg2] 

478 

479 

480class RegMul(BinaryOp, RegLogicExp): 

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

482 self.op = RegWiseOp.MUL 

483 self.args = [arg1, arg2] 

484 

485 

486class RegDiv(BinaryOp, RegLogicExp): 

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

488 self.op = RegWiseOp.DIV 

489 self.args = [arg1, arg2] 

490 

491 

492class RegPow(BinaryOp, RegLogicExp): 

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

494 self.op = RegWiseOp.POW 

495 self.args = [arg1, arg2] 

496 

497 

498class RegLsh(BinaryOp, RegLogicExp): 

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

500 self.op = RegWiseOp.LSH 

501 self.args = [arg1, arg2] 

502 

503 

504class RegNeg(UnaryOp, RegLogicExp): 

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

506 self.op = RegWiseOp.NEG 

507 self.args = [arg1] 

508 

509 

510class RegNot(UnaryOp, RegLogicExp): 

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

512 self.op = RegWiseOp.NOT 

513 self.args = [arg1] 

514 

515 

516class RegRsh(BinaryOp, RegLogicExp): 

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

518 self.op = RegWiseOp.RSH 

519 self.args = [arg1, arg2] 

520 

521 

522class PredicateExp(BinaryOp): 

523 """ 

524 A binary predicate where the arguments are either 

525 Bits, BitRegisters, or Constants. 

526 """ 

527 

528 

529class Eq(PredicateExp): 

530 @staticmethod 

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

532 return args[0] == args[1] 

533 

534 

535class Neq(PredicateExp): 

536 @staticmethod 

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

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

539 

540 

541class BitEq(Eq, BitLogicExp): 

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

543 self.op = BitWiseOp.EQ 

544 self.args = [arg1, arg2] 

545 

546 

547class BitNeq(Neq, BitLogicExp): 

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

549 self.op = BitWiseOp.NEQ 

550 self.args = [arg1, arg2] 

551 

552 

553class RegEq(Eq, RegLogicExp): 

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

555 self.op = RegWiseOp.EQ 

556 self.args = [arg1, arg2] 

557 

558 

559class RegNeq(Neq, RegLogicExp): 

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

561 self.op = RegWiseOp.NEQ 

562 self.args = [arg1, arg2] 

563 

564 

565class RegLt(PredicateExp, RegLogicExp): 

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

567 self.op = RegWiseOp.LT 

568 self.args = [arg1, arg2] 

569 

570 @staticmethod 

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

572 return args[0] < args[1] 

573 

574 

575class RegGt(PredicateExp, RegLogicExp): 

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

577 self.op = RegWiseOp.GT 

578 self.args = [arg1, arg2] 

579 

580 @staticmethod 

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

582 return args[0] > args[1] 

583 

584 

585class RegLeq(PredicateExp, RegLogicExp): 

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

587 self.op = RegWiseOp.LEQ 

588 self.args = [arg1, arg2] 

589 

590 @staticmethod 

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

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

593 

594 

595class RegGeq(PredicateExp, RegLogicExp): 

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

597 self.op = RegWiseOp.GEQ 

598 self.args = [arg1, arg2] 

599 

600 @staticmethod 

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

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

603 

604 

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

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

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

608 return RegEq(register, value) 

609 

610 

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

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

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

614 return RegNeq(register, value) 

615 

616 

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

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

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

620 return RegLt(register, value) 

621 

622 

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

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

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

626 return RegGt(register, value) 

627 

628 

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

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

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

632 return RegLeq(register, value) 

633 

634 

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

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

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

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

639 return RegGeq(register, value) 

640 

641 

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

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

644 return BitEq(bit, 1) 

645 

646 

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

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

649 return BitEq(bit, 0) 

650 

651 

652def create_bit_logic_exp( # noqa: PLR0911 

653 op: BitWiseOp, args: Sequence[BitArgType] 

654) -> BitLogicExp: 

655 """ 

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

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

658 """ 

659 match op: 

660 case BitWiseOp.AND: 

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

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

663 case BitWiseOp.OR: 

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

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

666 case BitWiseOp.XOR: 

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

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

669 case BitWiseOp.NOT: 

670 assert len(args) == 1 

671 return BitNot(args[0]) 

672 case BitWiseOp.EQ: 

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

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

675 case BitWiseOp.NEQ: 

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

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

678 case BitWiseOp.ZERO: 

679 assert len(args) == 0 

680 return BitZero() 

681 case BitWiseOp.ONE: 681 ↛ exitline 681 didn't return from function 'create_bit_logic_exp' because the pattern on line 681 always matched

682 assert len(args) == 0 

683 return BitOne() 

684 

685 

686def create_reg_logic_exp( # noqa: PLR0911, PLR0912 

687 op: RegWiseOp, args: Sequence[RegArgType] 

688) -> RegLogicExp: 

689 """ 

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

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

692 """ 

693 if op == RegWiseOp.AND: 

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

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

696 if op == RegWiseOp.OR: 

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

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

699 if op == RegWiseOp.XOR: 

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

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

702 if op == RegWiseOp.ADD: 

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

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

705 if op == RegWiseOp.SUB: 

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

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

708 if len(args) == 1: 

709 return RegNeg(args[0]) 

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

711 assert len(args) == 1 

712 return RegNeg(args[0]) 

713 if op == RegWiseOp.MUL: 

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

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

716 if op == RegWiseOp.DIV: 

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

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

719 if op == RegWiseOp.POW: 

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

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

722 if op == RegWiseOp.LSH: 

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

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

725 if op == RegWiseOp.RSH: 

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

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

728 if op == RegWiseOp.EQ: 

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

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

731 if op == RegWiseOp.NEQ: 

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

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

734 if op == RegWiseOp.LT: 

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

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

737 if op == RegWiseOp.GT: 

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

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

740 if op == RegWiseOp.LEQ: 

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

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

743 if op == RegWiseOp.GEQ: 

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

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

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

747 assert len(args) == 1 

748 return RegNot(args[0]) 

749 raise ValueError("op type not supported") 

750 

751 

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

753 """ 

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

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

756 arguments. 

757 """ 

758 if isinstance(op, BitWiseOp): 

759 bit_args = [] 

760 args = [ 

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

762 for arg in args 

763 ] 

764 for arg in args: 

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

766 bit_args.append(arg) 

767 return create_bit_logic_exp(op, bit_args) 

768 assert isinstance(op, RegWiseOp) 

769 reg_args = [] 

770 for arg in args: 

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

772 reg_args.append(arg) 

773 return create_reg_logic_exp(op, reg_args) 

774 

775 

776def create_predicate_exp( # noqa: PLR0911 

777 op: Ops, args: Sequence[ArgType] 

778) -> PredicateExp: 

779 """ 

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

781 comparison predicate to some sequence of arguments. 

782 """ 

783 if op == BitWiseOp.EQ: 

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

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

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

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

788 if op == BitWiseOp.NEQ: 

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

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

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

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

793 if op == RegWiseOp.EQ: 

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

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

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

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

798 if op == RegWiseOp.NEQ: 

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

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

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

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

803 if op == RegWiseOp.LT: 

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

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

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

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

808 if op == RegWiseOp.GT: 

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

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

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

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

813 if op == RegWiseOp.LEQ: 

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

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

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

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

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

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

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

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

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

823 raise ValueError("op type not supported")