diff --git a/pygridsynth/cli.py b/pygridsynth/cli.py index de9e22e..78a938b 100644 --- a/pygridsynth/cli.py +++ b/pygridsynth/cli.py @@ -1,5 +1,6 @@ import argparse +from .config import GridsynthConfig from .gridsynth import gridsynth_gates helps = { @@ -22,28 +23,41 @@ def main(): parser.add_argument("theta", type=str) parser.add_argument("epsilon", type=str) - parser.add_argument("--dps", type=int, default=None) - parser.add_argument("--dtimeout", "-dt", type=float, default=None, help=helps["dt"]) - parser.add_argument("--ftimeout", "-ft", type=float, default=None, help=helps["ft"]) - parser.add_argument("--dloop", "-dl", type=int, default=10, help=helps["dl"]) - parser.add_argument("--floop", "-fl", type=int, default=10, help=helps["fl"]) - parser.add_argument("--seed", type=int, default=0, help=helps["seed"]) + parser.add_argument("--dps", type=int) + parser.add_argument("--dtimeout", "-dt", type=float, help=helps["dt"]) + parser.add_argument("--ftimeout", "-ft", type=float, help=helps["ft"]) + parser.add_argument("--dloop", "-dl", type=int, help=helps["dl"]) + parser.add_argument("--floop", "-fl", type=int, help=helps["fl"]) + parser.add_argument("--seed", type=int, help=helps["seed"]) parser.add_argument("--verbose", "-v", action="store_true") parser.add_argument("--time", "-t", action="store_true") parser.add_argument("--showgraph", "-g", action="store_true") + parser.add_argument("--upto_phase", "-ph", action="store_true") args = parser.parse_args() - gates = gridsynth_gates( - theta=args.theta, - epsilon=args.epsilon, - dps=args.dps, - dtimeout=args.dtimeout, - ftimeout=args.ftimeout, - dloop=args.dloop, - floop=args.floop, - seed=args.seed, - verbose=args.verbose, - measure_time=args.time, - show_graph=args.showgraph, - ) + cfg_args = dict() + if args.dps is not None: + cfg_args["dps"] = args.dps + if args.dtimeout is not None: + cfg_args["dtimeout"] = args.dtimeout + if args.ftimeout is not None: + cfg_args["ftimeout"] = args.ftimeout + if args.dloop is not None: + cfg_args["dloop"] = args.dloop + if args.floop is not None: + cfg_args["floop"] = args + if args.seed is not None: + cfg_args["seed"] = args.seed + if args.verbose: + cfg_args["verbose"] = args.verbose + if args.time: + cfg_args["measure_time"] = args.time + if args.showgraph: + cfg_args["show_graph"] = args.showgraph + if args.upto_phase: + cfg_args["upto_phase"] = args.upto_phase + + cfg = GridsynthConfig(**cfg_args) + + gates = gridsynth_gates(theta=args.theta, epsilon=args.epsilon, cfg=cfg) return gates diff --git a/pygridsynth/config.py b/pygridsynth/config.py new file mode 100644 index 0000000..02a90e5 --- /dev/null +++ b/pygridsynth/config.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass, field +from typing import Optional + +from .loop_controller import LoopController + + +@dataclass +class GridsynthConfig: + dps: Optional[int] = None + seed: int = 0 + dloop: int = 10 + floop: int = 10 + dtimeout: Optional[float] = None + ftimeout: Optional[float] = None + verbose: bool = False + measure_time: bool = False + show_graph: bool = False + upto_phase: bool = False + + loop_controller: LoopController = field(init=False) + + def __post_init__(self): + self.loop_controller = LoopController( + dloop=self.dloop, + floop=self.floop, + dtimeout=self.dtimeout, + ftimeout=self.ftimeout, + ) diff --git a/pygridsynth/diophantine.py b/pygridsynth/diophantine.py index 2d2c374..5538d6b 100644 --- a/pygridsynth/diophantine.py +++ b/pygridsynth/diophantine.py @@ -3,8 +3,7 @@ import random import warnings -from pygridsynth.loop_controller import LoopController - +from .loop_controller import LoopController from .ring import DOmega, ZOmega, ZRootTwo NO_SOLUTION = "no solution" @@ -454,7 +453,8 @@ def _diophantine(xi, loop_controller: LoopController): return v * t -def diophantine_dyadic(xi, loop_controller=None): +def diophantine_dyadic(xi, seed=0, loop_controller=None): + set_random_seed(seed) if loop_controller is None: loop_controller = LoopController() diff --git a/pygridsynth/gridsynth.py b/pygridsynth/gridsynth.py index 5cfcec1..99471d0 100644 --- a/pygridsynth/gridsynth.py +++ b/pygridsynth/gridsynth.py @@ -3,7 +3,8 @@ import mpmath -from .diophantine import NO_SOLUTION, diophantine_dyadic, set_random_seed +from .config import GridsynthConfig +from .diophantine import NO_SOLUTION, diophantine_dyadic from .loop_controller import LoopController from .mymath import solve_quadratic, sqrt from .quantum_gate import Rz @@ -115,15 +116,18 @@ def get_synthesized_unitary(gates): return DOmegaUnitary.from_gates(gates).to_complex_matrix -def gridsynth( - theta, - epsilon, - dps=None, - loop_controller=None, - verbose=False, - measure_time=False, - show_graph=False, -): +def gridsynth(theta, epsilon, cfg=None, **kwargs): + if cfg is None: + cfg = GridsynthConfig(**kwargs) + elif kwargs: + warnings.warn( + "When 'cfg' is provided, 'kwargs' are ignored.", + stacklevel=2, + ) + + if cfg.dps is None: + cfg.dps = _dps_for_epsilon(epsilon) + if isinstance(theta, float): warnings.warn( ( @@ -148,42 +152,38 @@ def gridsynth( stacklevel=2, ) - if dps is None: - dps = _dps_for_epsilon(epsilon) - with mpmath.workdps(dps): + with mpmath.workdps(cfg.dps): theta = mpmath.mpmathify(theta) epsilon = mpmath.mpmathify(epsilon) - if loop_controller is None: - loop_controller = LoopController() epsilon_region = EpsilonRegion(theta, epsilon) unit_disk = UnitDisk() k = 0 - if measure_time: + if cfg.measure_time: start = time.time() transformed = to_upright_set_pair( - epsilon_region, unit_disk, verbose=verbose, show_graph=show_graph + epsilon_region, unit_disk, verbose=cfg.verbose, show_graph=cfg.show_graph ) - if measure_time: + if cfg.measure_time: print(f"to_upright_set_pair: {time.time() - start} s") - if verbose: + if cfg.verbose: print("------------------") time_of_solve_TDGP = 0 time_of_diophantine_dyadic = 0 while True: - if measure_time: + if cfg.measure_time: start = time.time() sol = solve_TDGP( epsilon_region, unit_disk, *transformed, k, - verbose=verbose, - show_graph=show_graph, + verbose=cfg.verbose, + show_graph=cfg.show_graph, ) - if measure_time: + if cfg.measure_time: time_of_solve_TDGP += time.time() - start start = time.time() @@ -191,7 +191,9 @@ def gridsynth( if (z * z.conj).residue == 0: continue xi = 1 - DRootTwo.fromDOmega(z.conj * z) - w = diophantine_dyadic(xi, loop_controller=loop_controller) + w = diophantine_dyadic( + xi, seed=cfg.seed, loop_controller=cfg.loop_controller + ) if w != NO_SOLUTION: z = z.reduce_denomexp() w = w.reduce_denomexp() @@ -203,78 +205,43 @@ def gridsynth( u_approx = DOmegaUnitary(z, w, 0) else: u_approx = DOmegaUnitary(z, w.mul_by_omega(), 0) - if measure_time: + if cfg.measure_time: time_of_diophantine_dyadic += time.time() - start print(f"time of solve_TDGP: {time_of_solve_TDGP * 1000} ms") print( "time of diophantine_dyadic: " f"{time_of_diophantine_dyadic * 1000} ms" ) - if verbose: + if cfg.verbose: print(f"{z=}, {w=}") print("------------------") return u_approx - if measure_time: + if cfg.measure_time: time_of_diophantine_dyadic += time.time() - start k += 1 -def gridsynth_circuit( - theta, - epsilon, - wires=[0], - decompose_phase_gate=True, - dps=None, - loop_controller=None, - verbose=False, - measure_time=False, - show_graph=False, -): - if isinstance(theta, float): +def gridsynth_circuit(theta, epsilon, wires=[0], cfg=None, **kwargs): + if cfg is None: + cfg = GridsynthConfig(**kwargs) + elif kwargs: warnings.warn( - ( - f"pygridsynth is synthesizing the angle {theta}. " - "Please verify that this is the intended value. " - "Using float may introduce precision errors; " - "consider using mpmath.mpf for exact precision." - ), - UserWarning, + "When 'cfg' is provided, 'kwargs' are ignored.", stacklevel=2, ) - if isinstance(epsilon, float): - warnings.warn( - ( - f"pygridsynth is using epsilon={epsilon} as the tolerance. " - "Please verify that this is the intended value. " - "Using float may introduce precision errors; " - "consider using mpmath.mpf for exact precision." - ), - UserWarning, - stacklevel=2, - ) + if cfg.dps is None: + cfg.dps = _dps_for_epsilon(epsilon) - if dps is None: - dps = _dps_for_epsilon(epsilon) - with mpmath.workdps(dps): - theta = mpmath.mpmathify(theta) - epsilon = mpmath.mpmathify(epsilon) - start_total = time.time() if measure_time else 0.0 - u_approx = gridsynth( - theta=theta, - epsilon=epsilon, - dps=dps, - loop_controller=loop_controller, - verbose=verbose, - measure_time=measure_time, - show_graph=show_graph, - ) + with mpmath.workdps(cfg.dps): + start_total = time.time() if cfg.measure_time else 0.0 + u_approx = gridsynth(theta=theta, epsilon=epsilon, cfg=cfg) - start = time.time() if measure_time else 0.0 + start = time.time() if cfg.measure_time else 0.0 circuit = decompose_domega_unitary( - u_approx, wires=wires, decompose_phase_gate=decompose_phase_gate + u_approx, wires=wires, upto_phase=cfg.upto_phase ) - if measure_time: + if cfg.measure_time: print( f"time of decompose_domega_unitary: {(time.time() - start) * 1000} ms" ) @@ -283,65 +250,24 @@ def gridsynth_circuit( return circuit -def gridsynth_gates( - theta, - epsilon, - dps=None, - dtimeout=None, - ftimeout=None, - dloop=10, - floop=10, - seed=0, - verbose=False, - measure_time=False, - show_graph=False, - decompose_phase_gate=True, -): - if isinstance(theta, float): - warnings.warn( - ( - f"pygridsynth is synthesizing the angle {theta}. " - "Please verify that this is the intended value. " - "Using float may introduce precision errors; " - "consider using mpmath.mpf for exact precision." - ), - UserWarning, - stacklevel=2, - ) - - if isinstance(epsilon, float): +def gridsynth_gates(theta, epsilon, cfg=None, **kwargs): + if cfg is None: + cfg = GridsynthConfig(**kwargs) + elif kwargs: warnings.warn( - ( - f"pygridsynth is using epsilon={epsilon} as the tolerance. " - "Please verify that this is the intended value. " - "Using float may introduce precision errors; " - "consider using mpmath.mpf for exact precision." - ), - UserWarning, + "When 'cfg' is provided, 'kwargs' are ignored.", stacklevel=2, ) - set_random_seed(seed) - - loop_controller = LoopController( - dloop=dloop, floop=floop, dtimeout=dtimeout, ftimeout=ftimeout - ) + if cfg.dps is None: + cfg.dps = _dps_for_epsilon(epsilon) - if dps is None: - dps = _dps_for_epsilon(epsilon) - with mpmath.workdps(dps): - theta = mpmath.mpmathify(theta) - epsilon = mpmath.mpmathify(epsilon) + with mpmath.workdps(cfg.dps): circuit = gridsynth_circuit( theta=theta, epsilon=epsilon, wires=[0], - decompose_phase_gate=decompose_phase_gate, - dps=dps, - loop_controller=loop_controller, - verbose=verbose, - measure_time=measure_time, - show_graph=show_graph, + cfg=cfg, ) return circuit.to_simple_str() diff --git a/pygridsynth/synthesis_of_cliffordT.py b/pygridsynth/synthesis_of_cliffordT.py index 38ae1bd..d5e49de 100644 --- a/pygridsynth/synthesis_of_cliffordT.py +++ b/pygridsynth/synthesis_of_cliffordT.py @@ -50,7 +50,7 @@ def t_power_and_h(m): return t_power_and_h(m), unitary -def decompose_domega_unitary(unitary, wires, decompose_phase_gate=True): +def decompose_domega_unitary(unitary, wires, upto_phase=False): circuit = QuantumCircuit() while unitary.k > 0: g, unitary = _reduce_denomexp(unitary, wires=wires) @@ -74,7 +74,7 @@ def decompose_domega_unitary(unitary, wires, decompose_phase_gate=True): for _ in range(m_S): circuit.append(SGate(target_qubit=wires[0])) unitary = unitary.mul_by_S_power_from_left(-m_S) - if decompose_phase_gate: + if not upto_phase: for _ in range(m_W): circuit.append(WGate()) else: diff --git a/tests/test_exec_time.py b/tests/test_exec_time.py index 6792c31..c5b56a3 100644 --- a/tests/test_exec_time.py +++ b/tests/test_exec_time.py @@ -12,9 +12,7 @@ def test_exec_time(): t0 = time.perf_counter() dps = 15 + int(-mpmath.log10(mpmath.mpmathify(eps)) * 2.5) mpmath.mp.dps = dps - gates = gridsynth_gates( - mpmath.mpmathify(theta), mpmath.mpmathify(eps), decompose_phase_gate=False - ) + gates = gridsynth_gates(mpmath.mpmathify(theta), mpmath.mpmathify(eps)) t1 = time.perf_counter() print(f"eps: {eps}, TL: {TL}, time: {t1 - t0:.2f} seconds")