Skip to content

Commit

Permalink
Merge branch 'main' into 2024-11/init-docstrings
Browse files Browse the repository at this point in the history
  • Loading branch information
mpharrigan authored Oct 31, 2024
2 parents dd87609 + bc155b6 commit affe3e5
Show file tree
Hide file tree
Showing 63 changed files with 5,412 additions and 471 deletions.
23 changes: 18 additions & 5 deletions dev_tools/qualtran_dev_tools/notebook_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
import qualtran.bloqs.data_loading.qrom_base
import qualtran.bloqs.data_loading.select_swap_qrom
import qualtran.bloqs.factoring.ecc
import qualtran.bloqs.factoring.mod_exp
import qualtran.bloqs.factoring.rsa
import qualtran.bloqs.gf_arithmetic.gf2_add_k
import qualtran.bloqs.gf_arithmetic.gf2_addition
import qualtran.bloqs.gf_arithmetic.gf2_inverse
Expand Down Expand Up @@ -435,6 +435,10 @@
qualtran.bloqs.arithmetic.comparison._SQ_CMP_DOC,
qualtran.bloqs.arithmetic.comparison._LEQ_DOC,
qualtran.bloqs.arithmetic.comparison._CLinearDepthGreaterThan_DOC,
qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_GREATERTHAN_DOC,
qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_GREATERTHANEQUAL_DOC,
qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_LESSTHAN_DOC,
qualtran.bloqs.arithmetic.comparison._LINEAR_DEPTH_HALF_LESSTHANEQUAL_DOC,
],
),
NotebookSpecV2(
Expand Down Expand Up @@ -493,6 +497,8 @@
qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_DOC,
qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_K_DOC,
qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_DOC,
qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_K_DOC,
qualtran.bloqs.mod_arithmetic.mod_addition._CTRL_SCALE_MOD_ADD_DOC,
],
),
NotebookSpecV2(
Expand All @@ -515,10 +521,17 @@
],
),
NotebookSpecV2(
title='Modular Exponentiation',
module=qualtran.bloqs.factoring.mod_exp,
bloq_specs=[qualtran.bloqs.factoring.mod_exp._MODEXP_DOC],
directory=f'{SOURCE_DIR}/bloqs/factoring',
title='Modular Divison',
module=qualtran.bloqs.mod_arithmetic.mod_division,
bloq_specs=[qualtran.bloqs.mod_arithmetic.mod_division._KALISKI_MOD_INVERSE_DOC],
),
NotebookSpecV2(
title='Factoring RSA',
module=qualtran.bloqs.factoring.rsa,
bloq_specs=[
qualtran.bloqs.factoring.rsa.rsa_phase_estimate._RSA_PE_BLOQ_DOC,
qualtran.bloqs.factoring.rsa.rsa_mod_exp._RSA_MODEXP_DOC,
],
),
NotebookSpecV2(
title='Elliptic Curve Addition',
Expand Down
95 changes: 69 additions & 26 deletions dev_tools/ui-export.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,37 @@
"import hashlib\n",
"import json\n",
"\n",
"from qualtran import CompositeBloq\n",
"from qualtran import Adjoint, CompositeBloq, Controlled\n",
"from qualtran.bloqs.rotations.programmable_rotation_gate_array import ProgrammableRotationGateArray\n",
"\n",
"def bloq_attrs(bloq):\n",
" if isinstance(bloq, CompositeBloq):\n",
" return {}\n",
" if isinstance(bloq, ProgrammableRotationGateArray):\n",
" return {}\n",
" if isinstance(bloq, (Adjoint, Controlled)):\n",
" return bloq_attrs(bloq.subbloq)\n",
"\n",
" return attrs.asdict(bloq)\n",
"\n",
"def bloq_filename(bloq):\n",
" unhashed = json.dumps(bloq_attrs(bloq), cls=BloqEncoder)\n",
"\n",
" return hashlib.md5(unhashed.encode(), usedforsecurity=False).hexdigest() + '.json'"
" attrs_dict = bloq_attrs(bloq)\n",
" attrs_keys = list(attrs_dict.keys())\n",
" attrs_keys.sort()\n",
"\n",
" prefix = ''\n",
" if isinstance(bloq, Adjoint):\n",
" prefix = 'Adjoint_'\n",
" if isinstance(bloq, Controlled):\n",
" prefix = 'Controlled_'\n",
"\n",
" attrs_list = [\n",
" [key, attrs_dict[key]]\n",
" for key in attrs_keys\n",
" ] if attrs_keys else []\n",
" unhashed = json.dumps(attrs_list, cls=BloqEncoder)\n",
"\n",
" return prefix + hashlib.md5(unhashed.encode(), usedforsecurity=False).hexdigest() + '.json'"
]
},
{
Expand Down Expand Up @@ -113,56 +129,83 @@
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"from pathlib import Path\n",
"\n",
"from qualtran_dev_tools.notebook_specs import NB_BY_SECTION\n",
"from qualtran_dev_tools.parse_docstrings import get_markdown_docstring\n",
"\n",
"for section in NB_BY_SECTION:\n",
" for notebook_spec in section[1]:\n",
" for bloq_spec in notebook_spec.bloq_specs:\n",
" Path(f'ui_export/{bloq_spec.bloq_cls.__name__}').mkdir(parents=True, exist_ok=True)\n",
"\n",
" doc_name = f'ui_export/{bloq_spec.bloq_cls.__name__}/docs.txt'\n",
" if not os.path.isfile(doc_name):\n",
" with open(doc_name, 'w') as doc_file:\n",
" doc_file.write('\\n'.join(get_markdown_docstring(bloq_spec.bloq_cls)))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"from pathlib import Path\n",
"\n",
"from qualtran_dev_tools.all_call_graph import get_all_call_graph\n",
"from qualtran_dev_tools.notebook_specs import NB_BY_SECTION\n",
"from qualtran import Adjoint, Controlled\n",
"from qualtran.drawing.musical_score import get_musical_score_data\n",
"\n",
"examples = [\n",
" example\n",
" for section in NB_BY_SECTION\n",
" for notebook_spec in section[1]\n",
" for bloq_spec in notebook_spec.bloq_specs\n",
" for example in bloq_spec.examples\n",
"]\n",
"\n",
"call_graph = get_all_call_graph(examples)\n",
"\n",
"def bloq_score(bloq):\n",
" try:\n",
" return get_musical_score_data(bloq.decompose_bloq())\n",
" except:\n",
" return None\n",
"\n",
"def write_example(bloq):\n",
" call_graph, _ = bloq.call_graph(max_depth=1)\n",
"def bloq_name(bloq):\n",
" if (isinstance(bloq, (Adjoint, Controlled))):\n",
" return bloq_name(bloq.subbloq)\n",
"\n",
" for child_bloq, _ in call_graph.succ[bloq].items():\n",
" write_example(child_bloq)\n",
" return bloq.__class__.__name__\n",
"\n",
" file_name = f'ui_export/{bloq.__class__.__name__}/{bloq_filename(bloq)}'\n",
"def write_example(bloq):\n",
" file_name = f'ui_export/{bloq_name(bloq)}/{bloq_filename(bloq)}'\n",
" if not os.path.isfile(file_name):\n",
" bloq_dict = {\n",
" 'name': bloq.__class__.__name__,\n",
" 'name': str(bloq),\n",
" 'attrs': bloq_attrs(bloq),\n",
" 'score': bloq_score(bloq),\n",
" 'msd': bloq_score(bloq),\n",
" 'callees': list(\n",
" {\n",
" 'name': child_bloq.__class__.__name__,\n",
" 'name': bloq_name(child_bloq),\n",
" 'filename': bloq_filename(child_bloq)\n",
" }\n",
" for child_bloq, _ in call_graph.succ[bloq].items()\n",
" for child_bloq in call_graph.neighbors(bloq)\n",
" )\n",
" }\n",
"\n",
" Path(f'ui_export/{bloq_name(bloq)}').mkdir(parents=True, exist_ok=True)\n",
"\n",
" with open(file_name, 'w') as f:\n",
" json.dump(bloq_dict, f, indent=2, cls=BloqEncoder)\n",
"\n",
"for section in NB_BY_SECTION:\n",
" for notebook_spec in section[1]:\n",
" for bloq_spec in notebook_spec.bloq_specs:\n",
" Path(f'ui_export/{bloq_spec.bloq_cls.__name__}').mkdir(parents=True, exist_ok=True)\n",
"\n",
" doc_name = f'ui_export/{bloq_spec.bloq_cls.__name__}/docs.txt'\n",
" if not os.path.isfile(doc_name):\n",
" with open(doc_name, 'w') as doc_file:\n",
" doc_file.write('\\n'.join(get_markdown_docstring(bloq_spec.bloq_cls)))\n",
"\n",
" for example in bloq_spec.examples:\n",
" write_example(example.make())"
"for bloq, _ in call_graph.nodes.items():\n",
" write_example(bloq)"
]
}
],
Expand Down
3 changes: 2 additions & 1 deletion docs/bloqs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ Bloqs Library
mod_arithmetic/mod_addition.ipynb
mod_arithmetic/mod_subtraction.ipynb
mod_arithmetic/mod_multiplication.ipynb
factoring/mod_exp.ipynb
mod_arithmetic/mod_division.ipynb
factoring/rsa/rsa.ipynb
factoring/ecc/ec_add.ipynb
factoring/ecc/ecc.ipynb

Expand Down
2 changes: 1 addition & 1 deletion qualtran/_infra/Bloqs-Tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@
"metadata": {},
"outputs": [],
"source": [
"from qualtran.bloqs.factoring import ModExp\n",
"from qualtran.bloqs.factoring.rsa import ModExp\n",
"\n",
"mod_exp = ModExp(base=8, mod=13*17, exp_bitsize=3, x_bitsize=1024)\n",
"show_bloq(mod_exp)"
Expand Down
7 changes: 6 additions & 1 deletion qualtran/_infra/bloq.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,12 @@ def _my_add_controlled(
add_controlled: A function with the signature documented above that the system
can use to automatically wire up the new control registers.
"""
from qualtran import Controlled
from qualtran import Controlled, CtrlSpec
from qualtran.bloqs.mcmt.controlled_via_and import ControlledViaAnd

if ctrl_spec != CtrlSpec():
# reduce controls to a single qubit
return ControlledViaAnd.make_ctrl_system(self, ctrl_spec=ctrl_spec)

return Controlled.make_ctrl_system(self, ctrl_spec=ctrl_spec)

Expand Down
16 changes: 16 additions & 0 deletions qualtran/_infra/controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Sequence,
Tuple,
TYPE_CHECKING,
TypeAlias,
Union,
)

Expand All @@ -45,6 +46,9 @@
from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator
from qualtran.simulation.classical_sim import ClassicalValT

ControlBit: TypeAlias = int
"""A control bit, either 0 or 1."""


def _cvs_convert(
cvs: Union[
Expand Down Expand Up @@ -250,6 +254,18 @@ def from_cirq_cv(
bloq_cvs.append(curr_cvs)
return CtrlSpec(tuple(qdtypes), tuple(bloq_cvs))

def get_single_ctrl_bit(self) -> ControlBit:
"""If controlled by a single qubit, return the control bit, otherwise raise"""
if self.num_qubits != 1:
raise ValueError(f"expected a single qubit control, got {self.num_qubits}")

(qdtype,) = self.qdtypes
(cv,) = self.cvs
(idx,) = Register('', qdtype, cv.shape).all_idxs()
(control_bit,) = qdtype.to_bits(cv[idx])

return int(control_bit)


class AddControlledT(Protocol):
"""The signature for the `add_controlled` callback part of `ctrl_system`.
Expand Down
20 changes: 20 additions & 0 deletions qualtran/_infra/controlled_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,26 @@ def test_ctrl_spec_to_cirq_cv_roundtrip():
assert CtrlSpec.from_cirq_cv(cirq_cv, qdtypes=ctrl_spec.qdtypes, shapes=ctrl_spec.shapes)


@pytest.mark.parametrize(
"ctrl_spec", [CtrlSpec(), CtrlSpec(cvs=[1]), CtrlSpec(cvs=np.atleast_2d([1]))]
)
def test_ctrl_spec_single_bit_one(ctrl_spec: CtrlSpec):
assert ctrl_spec.get_single_ctrl_bit() == 1


@pytest.mark.parametrize(
"ctrl_spec", [CtrlSpec(cvs=0), CtrlSpec(cvs=[0]), CtrlSpec(cvs=np.atleast_2d([0]))]
)
def test_ctrl_spec_single_bit_zero(ctrl_spec: CtrlSpec):
assert ctrl_spec.get_single_ctrl_bit() == 0


@pytest.mark.parametrize("ctrl_spec", [CtrlSpec(cvs=[1, 1]), CtrlSpec(qdtypes=QUInt(2), cvs=0)])
def test_ctrl_spec_single_bit_raises(ctrl_spec: CtrlSpec):
with pytest.raises(ValueError):
ctrl_spec.get_single_ctrl_bit()


def _test_cirq_equivalence(bloq: Bloq, gate: 'cirq.Gate'):
import cirq

Expand Down
44 changes: 43 additions & 1 deletion qualtran/_infra/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,9 +772,14 @@ class QMontgomeryUInt(QDType):
bitsize: The number of qubits used to represent the integer.
References:
[Montgomery modular multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication)
[Montgomery modular multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication).
[Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126133 Cat Qubits](https://arxiv.org/abs/2302.06639).
Gouzien et al. 2023.
We follow Montgomery form as described in the above paper; namely, r = 2^bitsize.
"""

# TODO(https://github.com/quantumlib/Qualtran/issues/1471): Add modulus p as a class member.
bitsize: SymbolicInt

@property
Expand Down Expand Up @@ -810,6 +815,43 @@ def assert_valid_classical_val_array(
if np.any(val_array >= 2**self.bitsize):
raise ValueError(f"Too-large classical values encountered in {debug_str}")

def montgomery_inverse(self, xm: int, p: int) -> int:
"""Returns the modular inverse of an integer in montgomery form.
Args:
xm: An integer in montgomery form.
p: The modulus of the finite field.
"""
return ((pow(xm, -1, p)) * pow(2, 2 * self.bitsize, p)) % p

def montgomery_product(self, xm: int, ym: int, p: int) -> int:
"""Returns the modular product of two integers in montgomery form.
Args:
xm: The first montgomery form integer for the product.
ym: The second montgomery form integer for the product.
p: The modulus of the finite field.
"""
return (xm * ym * pow(2, -self.bitsize, p)) % p

def montgomery_to_uint(self, xm: int, p: int) -> int:
"""Converts an integer in montgomery form to a normal form integer.
Args:
xm: An integer in montgomery form.
p: The modulus of the finite field.
"""
return (xm * pow(2, -self.bitsize, p)) % p

def uint_to_montgomery(self, x: int, p: int) -> int:
"""Converts an integer into montgomery form.
Args:
x: An integer.
p: The modulus of the finite field.
"""
return (x * pow(2, int(self.bitsize), p)) % p


@attrs.frozen
class QGF(QDType):
Expand Down
26 changes: 26 additions & 0 deletions qualtran/_infra/data_types_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ def test_qmontgomeryuint():
assert is_symbolic(QMontgomeryUInt(sympy.Symbol('x')))


@pytest.mark.parametrize('p', [13, 17, 29])
@pytest.mark.parametrize('val', [1, 5, 7, 9])
def test_qmontgomeryuint_operations(val, p):
qmontgomeryuint_8 = QMontgomeryUInt(8)
# Convert value to montgomery form and get the modular inverse.
val_m = qmontgomeryuint_8.uint_to_montgomery(val, p)
mod_inv = qmontgomeryuint_8.montgomery_inverse(val_m, p)

# Calculate the product in montgomery form and convert back to normal form for assertion.
assert (
qmontgomeryuint_8.montgomery_to_uint(
qmontgomeryuint_8.montgomery_product(val_m, mod_inv, p), p
)
== 1
)


@pytest.mark.parametrize('p', [13, 17, 29])
@pytest.mark.parametrize('val', [1, 5, 7, 9])
def test_qmontgomeryuint_conversions(val, p):
qmontgomeryuint_8 = QMontgomeryUInt(8)
assert val == qmontgomeryuint_8.montgomery_to_uint(
qmontgomeryuint_8.uint_to_montgomery(val, p), p
)


def test_qgf():
qgf_256 = QGF(characteristic=2, degree=8)
assert str(qgf_256) == 'QGF(2**8)'
Expand Down
Loading

0 comments on commit affe3e5

Please sign in to comment.