From 388416ab7ee74dba44dcacfc75f05e5c44ea2316 Mon Sep 17 00:00:00 2001 From: Fionn Malone Date: Fri, 13 Oct 2023 19:05:14 +0000 Subject: [PATCH] Address review comments. --- qualtran/bloqs/chemistry/trotter.ipynb | 114 ++++++++++++++++--------- qualtran/bloqs/chemistry/trotter.py | 85 ++++++++---------- 2 files changed, 112 insertions(+), 87 deletions(-) diff --git a/qualtran/bloqs/chemistry/trotter.ipynb b/qualtran/bloqs/chemistry/trotter.ipynb index 9c2462d88..e2a064cbf 100644 --- a/qualtran/bloqs/chemistry/trotter.ipynb +++ b/qualtran/bloqs/chemistry/trotter.ipynb @@ -5,7 +5,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# `Trotter Costs`\n", + "# Trotter Costs\n", "\n", "We want to estimate the cost of implementing time evolution of a wavefunction:\n", "\n", @@ -32,7 +32,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ```First Quantized Grid Based Hamiltonian```\n", + "## First Quantized Grid Based Hamiltonian\n", "\n", "Ultimately we are interested in understanding the dynamics of real chemical systems. This requires us to study the ab-initio chemistry Hamiltonian which is given as (in atomic units)\n", "\n", @@ -63,6 +63,7 @@ "print(\"delta = \", delta)\n", "x_scl = delta * x_int\n", "assert (x_scl[-1] - x_scl[0]) - L < 1e-12\n", + "\n", "print(f\"unscaled grid points = {x_int}\")\n", "print(f\"scaled grid points = {x_scl}\")\n" ] @@ -144,7 +145,13 @@ "$$\n", "|\\psi(t)\\rangle \\approx \\mathrm{QFT} e^{-i\\delta t T} \\mathrm{QFT}^{\\dagger} e^{-i\\delta t U} e^{-i \\delta t V} |\\psi(0)\\rangle\n", "$$\n", - "so that all the terms can be implemented via a gate which implements something of the form $e^{-i \\delta t \\phi_\\alpha }$ via phasing gates.\n", + "so that all the terms can be implemented via a gate which implements something of the form $e^{-i \\delta t \\phi_\\alpha }$ via phasing gates.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "We can represent our $\\eta$-electron wavefunction as \n", "$$\n", @@ -157,11 +164,23 @@ "\n", "\\begin{align}\n", "&\\sum_{r_1\\cdots r_\\eta} c(r_1, \\cdots, r_\\eta) |r_1\\cdots r_\\eta\\rangle \\\\\n", - " &\\rightarrow \\sum_{r_1\\cdots r_\\eta} c(r_1, \\cdots, r_\\eta) |r_1\\cdots r_\\eta\\rangle|V(r_1\\cdots r_\\eta)\\rangle \\hspace{10em} \\text{Compute pairwise potential in ancilla registers} \\\\\n", - " &\\rightarrow \\sum_{r_1\\cdots r_\\eta} e^{-i \\delta t V(r_1\\cdots r_\\eta)} c(r_1, \\cdots, r_\\eta) |r_1\\cdots r_\\eta\\rangle|V(r_1\\cdots r_\\eta)\\rangle \\hspace{5.5em} \\text{Phase the state with computed potential} \\\\ \n", - " &\\rightarrow \\sum_{r_1\\cdots r_\\eta} e^{-i \\delta t V(r_1\\cdots r_\\eta)} c(r_1, \\cdots, r_\\eta) |r_1\\cdots r_\\eta\\rangle|0\\cdots0\\rangle \\hspace{7.8em} \\text{Uncompute potential in ancilla register} \\\\ \n", + " \\rightarrow &\\sum_{r_1\\cdots r_\\eta} c(r_1, \\cdots, r_\\eta) |r_1\\cdots r_\\eta\\rangle \n", + " |V(r_1\\cdots r_\\eta)\\rangle\n", + " \\hspace{10.4em} \\text{Compute pairwise potential in ancilla registers} \\\\\n", + " \\rightarrow &\\sum_{r_1\\cdots r_\\eta} e^{-i \\delta t V(r_1\\cdots r_\\eta)}\n", + " c(r_1, \\cdots, r_\\eta) |r_1\\cdots r_\\eta\\rangle|V(r_1\\cdots r_\\eta)\\rangle\n", + " \\hspace{5.5em} \\text{Phase the state with computed potential} \\\\ \n", + " \\rightarrow &\\sum_{r_1\\cdots r_\\eta} e^{-i \\delta t V(r_1\\cdots r_\\eta)}\n", + " c(r_1, \\cdots, r_\\eta) |r_1\\cdots r_\\eta\\rangle|0\\cdots0\\rangle\n", + " \\hspace{7.8em} \\text{Uncompute potential in ancilla register} \\\\ \n", "\\end{align}\n", - "in the above the ancilla register storing the value of the potential is of size $> 2n + 2$.\n", + "in the above the ancilla register storing the value of the potential is of size $> 2n + 2$.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "To compute the potential we need to evaluate $\\frac{1}{r_{ij}} \\equiv \\frac{1}{|r_i - r_j|}$ which can be done in two steps:\n", "\n", @@ -187,7 +206,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ```Function interpolation```\n", + "## Function interpolation\n", "\n", "The basic idea is to first fit a polynomial to $\\frac{1}{r_{ij}}$ to use as input for a Newton-Raphson step. In practice we do not want to try to fit $r_{ij}^{-1}$ over the entire range of values as this will lead to a poor quality fit. Instead we combine two ideas: piecewise interpolation and scaling. Piecewise interpolation allows us to boost the accuracy of our fit by splitting our domain into $k$ subintervals and fitting a polynomial in these smaller subintervals, which can then be pieced together on fly depending on the input value $\\bar{x}$. The second idea is to use the scaling properties of our function to only fit in a small subinterval, and appropriately scaling it if we need to know its value outside of this interval. That is, we know that $\\frac{1}{\\sqrt{x}} = \\frac{1}{\\sqrt{a}\\sqrt{x'}}$ for $x' \\in [1, 2]$, say.\n", "\n", @@ -229,8 +248,9 @@ "poly_fit_one = polynomial_approx_range_one(xs_one)\n", "xs_two = np.linspace(1.5, 2.0, 10)\n", "poly_fit_two = polynomial_approx_range_two(xs_two)\n", - "plt.plot(xs_one, np.abs(1.0 / xs_one**0.5 - poly_fit_one), marker='o', lw=0)\n", - "plt.plot(xs_two, np.abs(1.0 / xs_two**0.5 - poly_fit_two), marker='x', lw=0)\n", + "plt.plot(xs_one, np.abs(1.0 / xs_one**0.5 - poly_fit_one), marker='o', lw=0, label=\"range 1\")\n", + "plt.plot(xs_two, np.abs(1.0 / xs_two**0.5 - poly_fit_two), marker='x', lw=0, label=\"range 2\")\n", + "plt.legend()\n", "plt.xlabel(\"$x$\")\n", "plt.ylabel(\"Absolute Error\")\n", "plt.yscale(\"log\")\n", @@ -256,12 +276,36 @@ " yprime = 0.5 * y0 * (3 + delta - y0**2 * x) \n", " return yprime\n", "delta_range_one = 5.1642030908180720584e-9\n", - "delta_range_two = 3.6279794522852781448e-10\n", - "\n", - "nr_one = newton_raphson_step(xs_one, poly_fit_one, delta=delta_range_one)\n", - "nr_two = newton_raphson_step(xs_two, poly_fit_two, delta=delta_range_two)\n", - "plt.plot(xs_one, np.abs(1.0 / xs_one**0.5 - nr_one), marker='o', lw=0)\n", - "plt.plot(xs_two, np.abs(1.0 / xs_two**0.5 - nr_two), marker='x', lw=0)\n", + "delta_range_two = 3.6279794522852781448e-10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nr_one = newton_raphson_step(xs_one, poly_fit_one, delta=delta_range_one)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nr_two = newton_raphson_step(xs_two, poly_fit_two, delta=delta_range_two)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.plot(xs_one, np.abs(1.0 / xs_one**0.5 - nr_one), marker='o', lw=0, label=\"range 1\")\n", + "plt.plot(xs_two, np.abs(1.0 / xs_two**0.5 - nr_two), marker='x', lw=0, label=\"range 2\")\n", + "plt.legend()\n", "plt.xlabel(\"$x$\")\n", "plt.ylabel(\"Absolute Error\")\n", "plt.yscale(\"log\")\n", @@ -274,7 +318,7 @@ "source": [ "Ok great! We have a very accurate approximation to our function using if we can load some polynomial coefficients and perform some arithmetic. Next we will describe how we can use variable spaced QROM to efficiently load only a small amount of data to enable this procedure. \n", "\n", - "## ```Variable Spaced QROM and Fitting the Entire Domain```\n", + "## Variable Spaced QROM and Fitting the Entire Domain\n", "\n", "In this section we will discuss how we can use QROM along with function interpolation to approximate our function over the entire domain to high precision. But first let us convince ourselves that our two subintervals are sufficient and we can indeed fit our function by appropriately scaling our polynomial. For reasons that will become clear later, let us first group our allowed values of $r_{ij}^2$ in a logarithmic fashion as $0, 1, 2, 3, [4, 5], [6, 7], [8, 11], [12, 15], [16, 23], [24, 31], ...$ and so on. We can associate each of these integer intervals with our polynomial domains by noting that $[4, 5] = 2^2[1, 3/2]$, $[12, 15] = 2^3[3/2, 2]$, etc. Thus, we can evaluate our function for any value $r_{ij}^2$ after determining which \"bin\" it belongs to, determining the appropriate power of two, and scaling our polynomial appropriately. Let's test this below:" ] @@ -375,7 +419,7 @@ "source": [ "Ok! You might be wondering what is special about the intervals we chose to bin our integer $r_{ij}^2$ values by, and how QROM is connected with this. We'll try to explain that now.\n", "\n", - "### ```QROM```\n", + "### QROM\n", "Recall that ultimately we need to load our polynomial coefficients onto the quantum computer before we can evaluate the inverse square root. Data loading can be achieved through QROM which implements: \n", "\n", "$$\n", @@ -385,9 +429,9 @@ "That is, given a selection register $l$, QROM can load the binary representation of the $l$-th data element of $d$ into a target register of a given size.\n", "In our case $|r_{ij}^2\\rangle$ is the selection register of size $2 n + 2$, and we want to load $b$-bit binary representations of our polynomial coefficients $\\{a_0, a_1, a_2, a_3\\}$ and $\\{b_0, b_1, b_2, b_3\\}$ for the two ranges.\n", "\n", - "In principle we could load all of the (scaled) coefficients for each value of $r_{ij}$ in the register, but this would incur a cost that goes like $L-1$ Toffolis, where $L=2^{2n + 2}$ which would be unacceptably large. But we just saw that much of this data would be duplicated as we only need the coefficients for each subinterval, of which there are a logarithmic amount. Fortunately we can exploit the structure of the unary iteration circuit to exploit this redundancy and significantly reduce our costs and arrive at `variably space QROM`.\n", + "In principle we could load all of the (scaled) coefficients for each value of $r_{ij}$ in the register, but this would incur a cost that goes like $L-1$ Toffolis, where $L=2^{2n + 2}$ which would be unacceptably large. But we just saw that much of this data would be duplicated as we only need the coefficients for each subinterval, of which there are a logarithmic amount. Fortunately we can exploit the structure of the unary iteration circuit to exploit this redundancy and significantly reduce our costs and arrive at variably space QROM.\n", "\n", - "To understand where the reduction in cost comes from, consider the `unary iteration` circuit which is used during QROM construction. There, depending on the particular binary representation of the selection register, different parts of the tree are traversed before writing the data to a register. As we only care about certain subintervals (which were conveniently chosen as powers of two), we can delete parts of our unary iteration circuit where we want to repeat the data for that range of integers. The number of allowed integers in each range is determined in the following way: for the starting integer of the range $l$: if $k$ is the most significant bit of $l$, then the number of integers in our range is $2^{k-2}$ (we only bin integers when $k \\ge 2$). This is identical to how we set up our binning procedure earlier. Below is some code to generate the ranges given the values of the most significant bits of the selection register. " + "To understand where the reduction in cost comes from, consider the unary iteration circuit which is used during QROM construction. There, depending on the particular binary representation of the selection register, different parts of the tree are traversed before writing the data to a register. As we only care about certain subintervals (which were conveniently chosen as powers of two), we can delete parts of our unary iteration circuit where we want to repeat the data for that range of integers. The number of allowed integers in each range is determined in the following way: for the starting integer of the range $l$: if $k$ is the most significant bit of $l$, then the number of integers in our range is $2^{k-2}$ (we only bin integers when $k \\ge 2$). This is identical to how we set up our binning procedure earlier. Below is some code to generate the ranges given the values of the most significant bits of the selection register. " ] }, { @@ -438,7 +482,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# ```Trotter Unitaries```\n", + "## Trotter Unitaries\n", "\n", "Ok, now that we have the setup straight in our heads lets build our bloqs and perform resource estimations!" ] @@ -448,7 +492,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ```Kinetic Energy Bloq```\n", + "## Kinetic Energy Bloq\n", "Recall that the kinetic energy is diagonal in the momentum basis, which we are assuming our state is in following a ```QFT```. The basic algorithm is then\n", "\n", "1. For each electron $i$ compute $|\\mathbf{k}^2 \\rangle = |k_x^2 + k_y^2 + k_j^2\\rangle$ of size $2n + 2$.\n", @@ -485,7 +529,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ```Potential Energy Bloq```\n", + "## Potential Energy Bloq\n", "\n", "Here we consider the electron-electron interaction\n", "\\begin{align}\n", @@ -539,7 +583,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ```Comparison to Costs in Paper```\n", + "## Comparison to Costs in Paper\n", "\n", "We're now in a position to compare our qualtran costs to those in the paper, which were rough estimates." ] @@ -583,20 +627,14 @@ " cirq.ops.common_gates.CXPowGate,\n", ")\n", "def generalize(bloq):\n", - " if isinstance(bloq, Split):\n", - " return None\n", " if isinstance(bloq, CirqGateAsBloq):\n", " if isinstance(bloq.gate, cirq_ft.algos.And) and (len(bloq.gate.cv) == 2):\n", " return And(cv1=and_cv0, cv2=and_cv1, adjoint=bloq.gate.adjoint)\n", - " if \"QROM\" in bloq.pretty_name():\n", + " if isinstance(bloq.gate, cirq_ft.QROM):\n", " bloq.gate.__class__.__repr__ = custom_repr\n", " if isinstance(bloq.gate, cirq_cliffords_ignore):\n", " return None\n", - " if isinstance(bloq, Join):\n", - " return None\n", - " if isinstance(bloq, Allocate):\n", - " return None\n", - " if isinstance(bloq, Free):\n", + " if isinstance(bloq, (Join, Split, Allocate, Free)):\n", " return None\n", " if isinstance(bloq, RotationBloq):\n", " return attrs.evolve(bloq, angle=phi)\n", @@ -609,8 +647,8 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.bloqs.chemistry.trotter import PolynmomialEvaluation \n", - "poly_eval = PolynmomialEvaluation(14, 15, 24)\n", + "from qualtran.bloqs.chemistry.trotter import PolynmomialEvaluationInverseSquareRoot \n", + "poly_eval = PolynmomialEvaluationInverseSquareRoot(14, 15, 24)\n", "graph, sigma = get_bloq_counts_graph(poly_eval, generalizer=generalize)\n", "GraphvizCounts(graph).get_svg()" ] @@ -631,8 +669,8 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.bloqs.chemistry.trotter import NewtonRaphson\n", - "nr = NewtonRaphson(14, 15, 24)\n", + "from qualtran.bloqs.chemistry.trotter import NewtonRaphsonApproxInverseSquareRoot\n", + "nr = NewtonRaphsonApproxInverseSquareRoot(14, 15, 24)\n", "graph, sigma = get_bloq_counts_graph(nr, generalizer=generalize)\n", "GraphvizCounts(graph).get_svg()\n" ] @@ -707,11 +745,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ```Appendix```\n", + "## Appendix\n", "\n", "Note in practice we really use fixed point representation of floating numbers.\n", "\n", - "### ```Fixed point and binary arithmetic```\n", + "### Fixed point and binary arithmetic\n", "\n", "To evaluate the polynomial and Newton-Raphson step we need fixed-point arithmetic (addition, multiplication, squaring and scaling). Recall that fixed-point real valued (between 0 and 1) numbers are approximated as (using a convention where the most significant bit is yielded first)\n", "\n", diff --git a/qualtran/bloqs/chemistry/trotter.py b/qualtran/bloqs/chemistry/trotter.py index bfd936980..c7ab4306a 100644 --- a/qualtran/bloqs/chemistry/trotter.py +++ b/qualtran/bloqs/chemistry/trotter.py @@ -16,7 +16,7 @@ from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING import numpy as np -from attrs import frozen +from attrs import field, frozen from cirq_ft import TComplexity from cirq_ft.algos.qrom import QROM from cirq_ft.infra.bit_tools import float_as_fixed_width_int @@ -43,13 +43,13 @@ def get_inverse_square_root_poly_coeffs() -> Tuple[NDArray, NDArray]: """Polynomial coefficients for approximating inverse square root. This function returns the coefficients of a piecewise cubic polynomial - interpolation to the inverse square root, defined over two intervals [1, - 3/2] (a) and [3/2, 2] (b). The coefficients were provided by the reference - below in the context of computing the Coulomb potential. + interpolation to the inverse square root, defined over two intervals [1, 3/2] (a) and + [3/2, 2] (b). The coefficients were provided by the reference below in the + context of computing the Coulomb potential. References: [Quantum computation of stopping power for inertial fusion target design] - (https://browse.arxiv.org/pdf/2308.12352.pdf) pg. 12 / 13. + (https://arxiv.org/abs/2308.12352) pg. 12 / 13. """ poly_coeffs_a = np.array( [ @@ -73,7 +73,7 @@ def get_inverse_square_root_poly_coeffs() -> Tuple[NDArray, NDArray]: def build_qrom_data_for_poly_fit( selection_bitsize: int, target_bitsize: int, poly_coeffs: Tuple[NDArray, NDArray] ) -> NDArray: - """Build QROM data from polynomial coefficients from Referenence. + """Build QROM data from polynomial coefficients from the referenence. Args: selection_bitsize: Number of bits for QROM selection register. This is @@ -99,7 +99,7 @@ def build_qrom_data_for_poly_fit( # entries so as to use the variable spaced QROM implementation. The # repeated ranges occur for l :-> l + 2^k, and are repeated twice for # coeffs_a and coeffs_b. We need to scale the coefficients by 2^{-(k-1)} to - # correclty account for the selection range (r_{ij}^2) Our coefficients are + # correctly account for the selection range (r_{ij}^2). Our coefficients are # initially defined in the range [1, 3/2] for "_a" and [3/2, 2] for "_b". data = np.zeros((4, 2 ** (selection_bitsize)), dtype=np.int_) for i, (a, b) in enumerate(zip(poly_coeffs_a, poly_coeffs_b)): @@ -142,7 +142,7 @@ class QuantumVariableRotation(Bloq): bitsize: The number of bits encoding the phase angle $\phi_j$. Register: - - phi: a bitsize size register storing the angle $\phi_j$. + phi: a bitsize size register storing the angle $\phi_j$. References: (Faster quantum chemistry simulation on fault-tolerant quantum @@ -169,8 +169,8 @@ def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple @frozen -class NewtonRaphson(Bloq): - r"""Bloq implementing a single Newton-Raphson step +class NewtonRaphsonApproxInverseSquareRoot(Bloq): + r"""Bloq implementing a single Newton-Raphson step to approximate the inverse square root. Given a (polynomial) approximation for $y_n = 1/sqrt{x}$ we can approximate the inverse square root by @@ -194,9 +194,9 @@ class NewtonRaphson(Bloq): output_bitsize: The number of bits to store the output of the NewtonRaphson step. Register: - - xsq: an input_bitsize size register storing the value x^2. - - ply: an size register storing the value x^2. - - trg: a target_bitsize size register storing the output of the newton raphson step. + x_sq: an input_bitsize size register storing the value x^2. + poly: an poly_bitsize size register storing the value x^2. + target: a target_bitsize size register storing the output of the newton raphson step. References: (Faster quantum chemistry simulation on fault-tolerant quantum @@ -210,9 +210,9 @@ class NewtonRaphson(Bloq): def signature(self) -> Signature: return Signature( [ - Register('xsq', bitsize=self.x_sq_bitsize), - Register('ply', bitsize=self.poly_bitsize), - Register('trg', self.target_bitsize), + Register('x_sq', bitsize=self.x_sq_bitsize), + Register('poly', bitsize=self.poly_bitsize), + Register('target', self.target_bitsize), ] ) @@ -243,22 +243,22 @@ def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple @frozen -class PolynmomialEvaluation(Bloq): - r"""Bloq to evaluate polynomial from QROM +class PolynmomialEvaluationInverseSquareRoot(Bloq): + r"""Bloq to evaluate a polynomial approximation to inverse Square root from QROM. Args: in_bitsize: The number of bits encoding the input registers. out_bitsize: The number of bits encoding the input registers. Register: - - in_c{0,1,2,3}: QROM input containing the 4 polynomial coefficients. + in_c{0,1,2,3}: QROM input containing the 4 polynomial coefficients. - out: Output register to store polynomial approximation to inverse square root. References: (Quantum computation of stopping power for inertial fusion target design )[https://arxiv.org/pdf/2308.12352.pdf] """ - xsq_bitsize: int + x_sq_bitsize: int poly_bitsize: int out_bitsize: int @@ -266,11 +266,8 @@ class PolynmomialEvaluation(Bloq): def signature(self) -> Signature: return Signature( [ - Register('xsq', bitsize=self.xsq_bitsize), - Register('in_c0', bitsize=self.poly_bitsize), - Register('in_c1', bitsize=self.poly_bitsize), - Register('in_c2', bitsize=self.poly_bitsize), - Register('in_c3', bitsize=self.poly_bitsize), + Register('x_sq', bitsize=self.x_sq_bitsize), + Register('in_coeff', bitsize=self.poly_bitsize, shape=(4,)), Register('out', bitsize=self.out_bitsize), ] ) @@ -288,14 +285,14 @@ def t_complexity(self) -> 'TComplexity': ) def bloq_counts(self, ssa: Optional['SympySymbolAllocator'] = None) -> Set[Tuple[int, Bloq]]: - # This should probably by scale int by float rather than 3 real - # multiplications as x in 49 is an integer. + # This should probably be scale int by float rather than 3 real + # multiplications as x in Eq. 49 of the reference is an integer. return {(3, MultiplyTwoReals(self.poly_bitsize)), (3, Add(self.poly_bitsize))} @frozen class KineticEnergy(Bloq): - """Bloq for Kinetic energy unitary. + """Bloq for the Kinetic energy unitary defined in the reference. Args: num_elec: The number of electrons. @@ -362,7 +359,7 @@ class PairPotential(Bloq): """ bitsize: int - qrom_data: Tuple[Tuple[int], ...] + qrom_data: Tuple[Tuple[int], ...] = field(repr=False) poly_bitsize: int = 15 inv_sqrt_bitsize: int = 24 label: str = "V" @@ -382,15 +379,8 @@ def pretty_name(self) -> str: def short_name(self) -> str: return f'U_{self.label}(dt)_ij' - def __repr__(self) -> str: - # overwriting this to avoid data entering repr which is used during bloq count visualization. - return ( - "PairPotential(bitsize={self.bitsize}, poly_bitsize={self.poly_bitsize}, " - "inv_sqrt_bitsize={self.inv_sqrt_bitsize})" - ) - def build_composite_bloq( - self, bb: BloqBuilder, *, system_i: SoquetT, system_j + self, bb: BloqBuilder, *, system_i: SoquetT, system_j: SoquetT ) -> Dict[str, SoquetT]: # compute r_i - r_j # r_i + (-r_j), in practice we need to flip the sign bit, but this is just 3 cliffords. @@ -424,30 +414,27 @@ def build_composite_bloq( # Compute the polynomial from the polynomial coefficients stored in QROM poly_out = bb.allocate(self.poly_bitsize) - sos, qrom_anc_c0, qrom_anc_c1, qrom_anc_c2, qrom_anc_c3, poly_out = bb.add( - PolynmomialEvaluation( - xsq_bitsize=bitsize_rij_sq, + sos, [qrom_anc_c0, qrom_anc_c1, qrom_anc_c2, qrom_anc_c3], poly_out = bb.add( + PolynmomialEvaluationInverseSquareRoot( + x_sq_bitsize=bitsize_rij_sq, poly_bitsize=self.poly_bitsize, out_bitsize=self.poly_bitsize, ), - xsq=sos, - in_c0=qrom_anc_c0, - in_c1=qrom_anc_c1, - in_c2=qrom_anc_c2, - in_c3=qrom_anc_c3, + x_sq=sos, + in_coeff=np.array([qrom_anc_c0, qrom_anc_c1, qrom_anc_c2, qrom_anc_c3]), out=poly_out, ) # Do a Newton-Raphson step to obtain a more accurate estimate of r_{ij}^{-1} inv_sqrt_sos = bb.allocate(self.inv_sqrt_bitsize) sos, poly_out, inv_sqrt_sos = bb.add( - NewtonRaphson( + NewtonRaphsonApproxInverseSquareRoot( x_sq_bitsize=bitsize_rij_sq, poly_bitsize=self.poly_bitsize, target_bitsize=self.inv_sqrt_bitsize, ), - xsq=sos, - ply=poly_out, - trg=inv_sqrt_sos, + x_sq=sos, + poly=poly_out, + target=inv_sqrt_sos, ) inv_sqrt_sos = bb.add( QuantumVariableRotation(phi_bitsize=self.inv_sqrt_bitsize), phi=inv_sqrt_sos