From d5451418f257e0aee61244aed56714b894c0ef09 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:43:51 +0100 Subject: [PATCH] Revert revert of 32-to-64-bit update (#1456) --- pytket/binders/include/UnitRegister.hpp | 4 +- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 6 +++ pytket/pytket/__init__.py | 6 +++ pytket/pytket/_tket/unit_id.pyi | 2 +- pytket/pytket/circuit/add_condition.py | 2 +- pytket/pytket/qasm/qasm.py | 11 ++++++ pytket/tests/classical_test.py | 26 ++++++------- pytket/tests/qasm_test.py | 8 ++++ pytket/tests/strategies.py | 1 + schemas/circuit_v1.json | 10 ++--- tket/conanfile.py | 2 +- tket/include/tket/Ops/ClassicalOps.hpp | 22 +++++------ tket/include/tket/Utils/HelperFunctions.hpp | 4 +- tket/src/Ops/ClassicalOps.cpp | 39 ++++++++++--------- .../Transformations/ContextualReduction.cpp | 4 +- tket/src/Utils/HelperFunctions.cpp | 4 +- tket/test/src/Ops/test_ClassicalOps.cpp | 6 +-- tket/test/src/test_LexiRoute.cpp | 6 +-- 19 files changed, 99 insertions(+), 66 deletions(-) diff --git a/pytket/binders/include/UnitRegister.hpp b/pytket/binders/include/UnitRegister.hpp index 0db7009179..4ecb66d6a3 100644 --- a/pytket/binders/include/UnitRegister.hpp +++ b/pytket/binders/include/UnitRegister.hpp @@ -23,8 +23,8 @@ conventions defined here: registers are up to _TKET_REG_WIDTH wide in bits and are interpreted as equivalent to the C++ type _tket_uint_t */ -#define _TKET_REG_WIDTH 32 -typedef uint32_t _tket_uint_t; +#define _TKET_REG_WIDTH 64 +typedef uint64_t _tket_uint_t; template class UnitRegister { diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 4a24bc0ce6..b2f21af8ff 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -32,7 +32,7 @@ def package(self): cmake.install() def requirements(self): - self.requires("tket/1.3.10@tket/stable") + self.requires("tket/1.3.11@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index edfe2ff17f..be15578764 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +Unreleased +---------- + +* Support classical transforms and predicates, and QASM registers, with up to 64 + bits. Add an attribute to the pytket module to assert this. + 1.29.2 (June 2024) ------------------ diff --git a/pytket/pytket/__init__.py b/pytket/pytket/__init__.py index 6da5f11dc2..8b4b5fee3a 100755 --- a/pytket/pytket/__init__.py +++ b/pytket/pytket/__init__.py @@ -36,3 +36,9 @@ config.write_file(pytket_config_file) __path__ = __import__("pkgutil").extend_path(__path__, __name__) + +"""Flag indicating 64-bit support. + +If True, classical transforms and predicates, and QASM registers, with up to 64 +bits are supported.""" +bit_width_64 = True diff --git a/pytket/pytket/_tket/unit_id.pyi b/pytket/pytket/_tket/unit_id.pyi index 9dbc8cf0ea..249e18c544 100644 --- a/pytket/pytket/_tket/unit_id.pyi +++ b/pytket/pytket/_tket/unit_id.pyi @@ -411,4 +411,4 @@ _DEBUG_ONE_REG_PREFIX: str = 'tk_DEBUG_ONE_REG' _DEBUG_ZERO_REG_PREFIX: str = 'tk_DEBUG_ZERO_REG' _TEMP_BIT_NAME: str = 'tk_SCRATCH_BIT' _TEMP_BIT_REG_BASE: str = 'tk_SCRATCH_BITREG' -_TEMP_REG_SIZE: int = 32 +_TEMP_REG_SIZE: int = 64 diff --git a/pytket/pytket/circuit/add_condition.py b/pytket/pytket/circuit/add_condition.py index a20d94fb09..79abac5209 100644 --- a/pytket/pytket/circuit/add_condition.py +++ b/pytket/pytket/circuit/add_condition.py @@ -107,7 +107,7 @@ def _add_condition( target_bits = pred_exp.to_list() minval = 0 - maxval = (1 << 32) - 1 + maxval = (1 << 64) - 1 if isinstance(condition, RegLt): maxval = pred_val - 1 elif isinstance(condition, RegGt): diff --git a/pytket/pytket/qasm/qasm.py b/pytket/pytket/qasm/qasm.py index b1506b7503..83a05bd639 100644 --- a/pytket/pytket/qasm/qasm.py +++ b/pytket/pytket/qasm/qasm.py @@ -1140,7 +1140,18 @@ def _retrieve_registers( def _parse_range(minval: int, maxval: int, maxwidth: int) -> Tuple[str, int]: + if maxwidth > 64: + raise NotImplementedError("Register width exceeds maximum of 64.") + REGMAX = (1 << maxwidth) - 1 + + if minval > REGMAX: + raise NotImplementedError("Range's lower bound exceeds register capacity.") + elif minval > maxval: + raise NotImplementedError("Range's lower bound exceeds upper bound.") + elif maxval > REGMAX: + maxval = REGMAX + if minval == maxval: return ("==", minval) elif minval == 0: diff --git a/pytket/tests/classical_test.py b/pytket/tests/classical_test.py index 63bfad4cae..8d5ed1fd4d 100644 --- a/pytket/tests/classical_test.py +++ b/pytket/tests/classical_test.py @@ -72,7 +72,7 @@ from pytket.passes import DecomposeClassicalExp, FlattenRegisters -from strategies import reg_name_regex, binary_digits, uint32 # type: ignore +from strategies import reg_name_regex, binary_digits, uint32, uint64 # type: ignore curr_file_path = Path(__file__).resolve().parent @@ -840,7 +840,7 @@ def primitive_reg_logic_exps( RegGeq, ), ): - const_compare = draw(uint32) + const_compare = draw(uint64) args.append(const_compare) else: args.append(draw(bit_regs)) @@ -854,8 +854,8 @@ def primitive_reg_logic_exps( @given( reg_exp=primitive_reg_logic_exps(), constants=strategies.tuples( - uint32, - uint32, + uint64, + uint64, ), ) def test_reg_exp(reg_exp: RegLogicExp, constants: Tuple[int, int]) -> None: @@ -929,7 +929,7 @@ def composite_bit_logic_exps( def composite_reg_logic_exps( draw: DrawType, regs: SearchStrategy[BitRegister] = bit_register(), - constants: SearchStrategy[int] = uint32, + constants: SearchStrategy[int] = uint64, operators: SearchStrategy[Callable] = strategies.sampled_from( [ operator.and_, @@ -979,7 +979,7 @@ def reg_const_predicates( operators: SearchStrategy[ Callable[[Union[RegLogicExp, BitRegister], int], PredicateExp] ] = strategies.sampled_from([reg_eq, reg_neq, reg_lt, reg_gt, reg_leq, reg_geq]), - constants: SearchStrategy[int] = uint32, + constants: SearchStrategy[int] = uint64, ) -> PredicateExp: return draw(operators)(draw(exp), draw(constants)) # type: ignore @@ -1131,10 +1131,10 @@ def test_decomposition_known() -> None: ) check_serialization_roundtrip(circ) - temp_bits = BitRegister(_TEMP_BIT_NAME, 32) + temp_bits = BitRegister(_TEMP_BIT_NAME, 64) def temp_reg(i: int) -> BitRegister: - return BitRegister(f"{_TEMP_BIT_REG_BASE}_{i}", 32) + return BitRegister(f"{_TEMP_BIT_REG_BASE}_{i}", 64) for b in (temp_bits[i] for i in range(0, 10)): conditioned_circ.add_bit(b) @@ -1170,13 +1170,13 @@ def temp_reg(i: int) -> BitRegister: conditioned_circ.add_c_range_predicate(5, 5, registers_lists[2], temp_bits[5]) conditioned_circ.Y(qreg[4], condition_bits=[temp_bits[5]], condition_value=0) conditioned_circ.add_c_range_predicate( - 4, 4294967295, registers_lists[3], temp_bits[6] + 4, 18446744073709551615, registers_lists[3], temp_bits[6] ) conditioned_circ.Z(qreg[5], condition_bits=[temp_bits[6]], condition_value=1) conditioned_circ.add_c_range_predicate(0, 6, registers_lists[4], temp_bits[7]) conditioned_circ.S(qreg[6], condition_bits=[temp_bits[7]], condition_value=1) conditioned_circ.add_c_range_predicate( - 3, 4294967295, registers_lists[5], temp_bits[8] + 3, 18446744073709551615, registers_lists[5], temp_bits[8] ) conditioned_circ.T(qreg[7], condition_bits=[temp_bits[8]], condition_value=1) @@ -1196,7 +1196,7 @@ def temp_reg(i: int) -> BitRegister: decomposed_circ.add_bit(b) decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_0", 3)) - decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_1", 32)) + decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_1", 64)) decomposed_circ.H(qreg[0], condition_bits=[bits[0]], condition_value=1) decomposed_circ.X(qreg[0], condition_bits=[bits[1]], condition_value=1) @@ -1211,11 +1211,11 @@ def temp_reg(i: int) -> BitRegister: decomposed_circ.add_c_range_predicate(0, 5, registers_lists[1], temp_bits[4]) decomposed_circ.add_c_range_predicate(5, 5, registers_lists[2], temp_bits[5]) decomposed_circ.add_c_range_predicate( - 4, 4294967295, registers_lists[3], temp_bits[6] + 4, 18446744073709551615, registers_lists[3], temp_bits[6] ) decomposed_circ.add_c_range_predicate(0, 6, registers_lists[4], temp_bits[7]) decomposed_circ.add_c_range_predicate( - 3, 4294967295, registers_lists[5], temp_bits[8] + 3, 18446744073709551615, registers_lists[5], temp_bits[8] ) decomposed_circ.add_c_xor(bits[5], bits[6], temp_bits[2]) diff --git a/pytket/tests/qasm_test.py b/pytket/tests/qasm_test.py index e05c287374..5652ae6b8e 100644 --- a/pytket/tests/qasm_test.py +++ b/pytket/tests/qasm_test.py @@ -955,6 +955,14 @@ def test_const_condition() -> None: ) +def test_range_with_maxwidth() -> None: + c = Circuit(1) + a = c.add_c_register("a", 8) + c.X(0, condition=reg_geq(a, 1)) + qasm = circuit_to_qasm_str(c, header="hqslib1", maxwidth=63) + assert "if(a>=1) x q[0];" in qasm + + if __name__ == "__main__": test_qasm_correct() test_qasm_qubit() diff --git a/pytket/tests/strategies.py b/pytket/tests/strategies.py index 2f275088ff..8975c896a3 100644 --- a/pytket/tests/strategies.py +++ b/pytket/tests/strategies.py @@ -33,6 +33,7 @@ binary_digits = st.sampled_from((0, 1)) uint32 = st.integers(min_value=1, max_value=1 << 32 - 1) +uint64 = st.integers(min_value=1, max_value=1 << 64 - 1) reg_name_regex = re.compile("[a-z][a-zA-Z0-9_]*") diff --git a/schemas/circuit_v1.json b/schemas/circuit_v1.json index 5e79b4bc8b..af089e7f51 100644 --- a/schemas/circuit_v1.json +++ b/schemas/circuit_v1.json @@ -154,13 +154,13 @@ }, "lower": { "type": "integer", - "maximum": 4294967295, - "description": "The inclusive minimum of the RangePredicate as a uint32." + "maximum": 18446744073709551615, + "description": "The inclusive minimum of the RangePredicate as a uint64." }, "upper": { "type": "integer", - "maximum": 4294967295, - "description": "The inclusive maximum of the RangePredicate as a uint32." + "maximum": 18446744073709551615, + "description": "The inclusive maximum of the RangePredicate as a uint64." } }, "required": [ @@ -1179,4 +1179,4 @@ "additionalProperties": false } } -} \ No newline at end of file +} diff --git a/tket/conanfile.py b/tket/conanfile.py index 68b3ca42c6..1290892558 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.10" + version = "1.3.11" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Ops/ClassicalOps.hpp b/tket/include/tket/Ops/ClassicalOps.hpp index 7cd75fee2f..cdb39bcf7e 100644 --- a/tket/include/tket/Ops/ClassicalOps.hpp +++ b/tket/include/tket/Ops/ClassicalOps.hpp @@ -135,18 +135,18 @@ class ClassicalTransformOp : public ClassicalEvalOp { * @param values table of binary-encoded values * @param name name of operation * - * @pre n <= 32 + * @pre n <= 64 */ ClassicalTransformOp( - unsigned n, const std::vector &values, + unsigned n, const std::vector &values, const std::string &name = "ClassicalTransform"); std::vector eval(const std::vector &x) const override; - std::vector get_values() const { return values_; } + std::vector get_values() const { return values_; } private: - const std::vector values_; + const std::vector values_; }; /** @@ -341,15 +341,15 @@ class RangePredicateOp : public PredicateOp { * @param b upper bound in little-endian encoding */ RangePredicateOp( - unsigned n, uint32_t a = 0, - uint32_t b = std::numeric_limits::max()) + unsigned n, uint64_t a = 0, + uint64_t b = std::numeric_limits::max()) : PredicateOp(OpType::RangePredicate, n, "RangePredicate"), a(a), b(b) {} std::string get_name(bool latex) const override; - uint32_t upper() const { return b; } + uint64_t upper() const { return b; } - uint32_t lower() const { return a; } + uint64_t lower() const { return a; } std::vector eval(const std::vector &x) const override; @@ -359,8 +359,8 @@ class RangePredicateOp : public PredicateOp { bool is_equal(const Op &other) const override; private: - uint32_t a; - uint32_t b; + uint64_t a; + uint64_t b; }; /** @@ -378,7 +378,7 @@ class ExplicitPredicateOp : public PredicateOp { * @param values table of values * @param name name of operation * - * @pre n <= 32 + * @pre n <= 64 */ ExplicitPredicateOp( unsigned n, const std::vector &values, diff --git a/tket/include/tket/Utils/HelperFunctions.hpp b/tket/include/tket/Utils/HelperFunctions.hpp index 0b73c30662..57165cecbe 100644 --- a/tket/include/tket/Utils/HelperFunctions.hpp +++ b/tket/include/tket/Utils/HelperFunctions.hpp @@ -77,9 +77,9 @@ bimap_to_map(MapT& bm) { } /** - * Reverse bits 0,1,...,w-1 of the number v, assuming v < 2^w and w <= 32. + * Reverse bits 0,1,...,w-1 of the number v, assuming v < 2^w and w <= 64. */ -uint32_t reverse_bits(uint32_t v, unsigned w); +uint64_t reverse_bits(uint64_t v, unsigned w); /** * @brief diff --git a/tket/src/Ops/ClassicalOps.cpp b/tket/src/Ops/ClassicalOps.cpp index a3e8439433..cee77a7b39 100644 --- a/tket/src/Ops/ClassicalOps.cpp +++ b/tket/src/Ops/ClassicalOps.cpp @@ -14,6 +14,7 @@ #include "tket/Ops/ClassicalOps.hpp" +#include #include #include "tket/OpType/OpType.hpp" @@ -21,12 +22,12 @@ namespace tket { -static uint32_t u32_from_boolvec(const std::vector &x) { +static uint64_t u64_from_boolvec(const std::vector &x) { unsigned n = x.size(); - if (n > 32) { - throw std::domain_error("Vector of bool exceeds maximum size (32)"); + if (n > 64) { + throw std::domain_error("Vector of bool exceeds maximum size (64)"); } - uint32_t X = 0; + uint64_t X = 0; for (unsigned i = 0; i < n; i++) { if (x[i]) X |= (1u << i); } @@ -99,8 +100,8 @@ static std::shared_ptr classical_from_json( case OpType::RangePredicate: return std::make_shared( j_class.at("n_i").get(), - j_class.at("lower").get(), - j_class.at("upper").get()); + j_class.at("lower").get(), + j_class.at("upper").get()); case OpType::CopyBits: return std::make_shared(j_class.at("n_i").get()); case OpType::SetBits: @@ -119,7 +120,7 @@ static std::shared_ptr classical_from_json( case OpType::ClassicalTransform: return std::make_shared( j_class.at("n_io").get(), - j_class.at("values").get>(), + j_class.at("values").get>(), j_class.at("name").get()); default: throw JsonError( @@ -207,9 +208,9 @@ bool ClassicalEvalOp::is_equal(const Op &op_other) const { if (n_io_ != other.get_n_io()) return false; if (n_o_ != other.get_n_o()) return false; unsigned N = n_i_ + n_io_; - uint32_t xlim = 1u << N; + uint64_t xlim = 1u << N; std::vector v(N); - for (uint32_t x = 0; x < xlim; x++) { + for (uint64_t x = 0; x < xlim; x++) { for (unsigned i = 0; i < N; i++) { v[i] = (x >> i) & 1; } @@ -219,11 +220,11 @@ bool ClassicalEvalOp::is_equal(const Op &op_other) const { } ClassicalTransformOp::ClassicalTransformOp( - unsigned n, const std::vector &values, const std::string &name) + unsigned n, const std::vector &values, const std::string &name) : ClassicalEvalOp(OpType::ClassicalTransform, 0, n, 0, name), values_(values) { - if (n > 32) { - throw std::domain_error("Too many inputs/outputs (maximum is 32)"); + if (n > 64) { + throw std::domain_error("Too many inputs/outputs (maximum is 64)"); } } @@ -231,7 +232,7 @@ std::vector ClassicalTransformOp::eval(const std::vector &x) const { if (x.size() != n_io_) { throw std::domain_error("Incorrect input size"); } - uint32_t val = values_[u32_from_boolvec(x)]; + uint64_t val = values_[u64_from_boolvec(x)]; std::vector y(n_io_); for (unsigned j = 0; j < n_io_; j++) { y[j] = (val >> j) & 1; @@ -319,7 +320,7 @@ std::vector RangePredicateOp::eval(const std::vector &x) const { if (x.size() != n_i_) { throw std::domain_error("Incorrect input size"); } - uint32_t X = u32_from_boolvec(x); + uint64_t X = u64_from_boolvec(x); std::vector y(1); y[0] = (X >= a && X <= b); return y; @@ -336,7 +337,7 @@ bool RangePredicateOp::is_equal(const Op &op_other) const { ExplicitPredicateOp::ExplicitPredicateOp( unsigned n, const std::vector &values, const std::string &name) : PredicateOp(OpType::ExplicitPredicate, n, name), values_(values) { - if (n > 32) { + if (n > 64) { throw std::domain_error("Too many inputs"); } } @@ -346,7 +347,7 @@ std::vector ExplicitPredicateOp::eval(const std::vector &x) const { throw std::domain_error("Incorrect input size"); } std::vector y(1); - y[0] = values_[u32_from_boolvec(x)]; + y[0] = values_[u64_from_boolvec(x)]; return y; } @@ -363,7 +364,7 @@ std::vector ExplicitModifierOp::eval(const std::vector &x) const { throw std::domain_error("Incorrect input size"); } std::vector y(1); - y[0] = values_[u32_from_boolvec(x)]; + y[0] = values_[u64_from_boolvec(x)]; return y; } @@ -416,14 +417,14 @@ bool MultiBitOp::is_equal(const Op &op_other) const { } std::shared_ptr ClassicalX() { - static const std::vector values = {1, 0}; + static const std::vector values = {1, 0}; static const std::shared_ptr op = std::make_shared(1, values, "ClassicalX"); return op; } std::shared_ptr ClassicalCX() { - static const std::vector values = {0, 3, 2, 1}; + static const std::vector values = {0, 3, 2, 1}; static const std::shared_ptr op = std::make_shared(2, values, "ClassicalCX"); return op; diff --git a/tket/src/Transformations/ContextualReduction.cpp b/tket/src/Transformations/ContextualReduction.cpp index ed58687908..8ed79ba945 100644 --- a/tket/src/Transformations/ContextualReduction.cpp +++ b/tket/src/Transformations/ContextualReduction.cpp @@ -308,8 +308,8 @@ std::optional> classical_transform( unsigned n = op->get_desc().n_qubits().value(); unsigned pow2n = 1u << n; TKET_ASSERT(U->cols() == pow2n); - std::vector values(pow2n); - for (uint32_t J = 0; J < pow2n; J++) { + std::vector values(pow2n); + for (uint64_t J = 0; J < pow2n; J++) { // Look at the column U[*,J]. Is there a unique nonzero element? std::optional I = unique_unit_row(*U, J); if (!I) return std::nullopt; diff --git a/tket/src/Utils/HelperFunctions.cpp b/tket/src/Utils/HelperFunctions.cpp index c8a15bf5ce..2014cb3b5a 100644 --- a/tket/src/Utils/HelperFunctions.cpp +++ b/tket/src/Utils/HelperFunctions.cpp @@ -37,8 +37,8 @@ GrayCode gen_graycode(unsigned m_controls) { return gc; } -uint32_t reverse_bits(uint32_t v, unsigned w) { - uint32_t r = 0; +uint64_t reverse_bits(uint64_t v, unsigned w) { + uint64_t r = 0; for (unsigned i = w; i--;) { r |= (v & 1) << i; v >>= 1; diff --git a/tket/test/src/Ops/test_ClassicalOps.cpp b/tket/test/src/Ops/test_ClassicalOps.cpp index bd17067b09..9e4ba89e79 100644 --- a/tket/test/src/Ops/test_ClassicalOps.cpp +++ b/tket/test/src/Ops/test_ClassicalOps.cpp @@ -461,7 +461,7 @@ SCENARIO("Reverse slicing of mixed circuits") { SCENARIO("Pure classical operations") { GIVEN("A pure classical circuit") { - std::vector and_table = {0, 1, 2, 7, 0, 1, 2, 7}; + std::vector and_table = {0, 1, 2, 7, 0, 1, 2, 7}; std::shared_ptr and_ttop = std::make_shared(3, and_table); for (unsigned i = 0; i < 2; i++) { @@ -475,10 +475,10 @@ SCENARIO("Pure classical operations") { } } - uint32_t a = 2, b = 6; + uint64_t a = 2, b = 6; std::shared_ptr rpop = std::make_shared(3, a, b); - for (uint32_t x = 0; x < 8; x++) { + for (uint64_t x = 0; x < 8; x++) { REQUIRE( rpop->eval( {(bool)(x & 1), (bool)((x >> 1) & 1), (bool)((x >> 2) & 1)})[0] == diff --git a/tket/test/src/test_LexiRoute.cpp b/tket/test/src/test_LexiRoute.cpp index 3f2c7d21e4..b333996eb3 100644 --- a/tket/test/src/test_LexiRoute.cpp +++ b/tket/test/src/test_LexiRoute.cpp @@ -1642,7 +1642,7 @@ SCENARIO("Test failing case") { REQUIRE(r_p->apply(cu)); } SCENARIO("Test RangePredicate operations with LexiRoute.") { - std::vector and_table = {0, 1, 2, 7, 0, 1, 2, 7}; + std::vector and_table = {0, 1, 2, 7, 0, 1, 2, 7}; std::shared_ptr and_ttop = std::make_shared(3, and_table); for (unsigned i = 0; i < 2; i++) { @@ -1656,10 +1656,10 @@ SCENARIO("Test RangePredicate operations with LexiRoute.") { } } - uint32_t a = 2, b = 6; + uint64_t a = 2, b = 6; std::shared_ptr rpop = std::make_shared(3, a, b); - for (uint32_t x = 0; x < 8; x++) { + for (uint64_t x = 0; x < 8; x++) { REQUIRE( rpop->eval( {(bool)(x & 1), (bool)((x >> 1) & 1), (bool)((x >> 2) & 1)})[0] ==