diff --git a/src/openfermion/__init__.py b/src/openfermion/__init__.py index 793dae7e7..64a1e1935 100644 --- a/src/openfermion/__init__.py +++ b/src/openfermion/__init__.py @@ -266,6 +266,7 @@ error_bound, error_operator, trotter_steps_required, + trotter_steps_required_propagator, vpe_single_circuit, vpe_circuits_single_timestep, standard_vpe_rotation_set, diff --git a/src/openfermion/circuits/__init__.py b/src/openfermion/circuits/__init__.py index d596b5c83..9b22180dc 100644 --- a/src/openfermion/circuits/__init__.py +++ b/src/openfermion/circuits/__init__.py @@ -97,6 +97,7 @@ error_bound, error_operator, trotter_steps_required, + trotter_steps_required_propagator, ) from .vpe_circuits import ( diff --git a/src/openfermion/circuits/trotter/__init__.py b/src/openfermion/circuits/trotter/__init__.py index bc98ad67e..ff82a6b20 100644 --- a/src/openfermion/circuits/trotter/__init__.py +++ b/src/openfermion/circuits/trotter/__init__.py @@ -43,4 +43,9 @@ from .trotter_algorithm import TrotterAlgorithm, TrotterStep -from .trotter_error import error_bound, error_operator, trotter_steps_required +from .trotter_error import ( + error_bound, + error_operator, + trotter_steps_required, + trotter_steps_required_propagator, +) diff --git a/src/openfermion/circuits/trotter/trotter_error.py b/src/openfermion/circuits/trotter/trotter_error.py index 81b47a087..f3a375edc 100644 --- a/src/openfermion/circuits/trotter/trotter_error.py +++ b/src/openfermion/circuits/trotter/trotter_error.py @@ -178,7 +178,20 @@ def error_bound(terms, tight=False): def trotter_steps_required(trotter_error_bound, time, energy_precision): - """Determine the number of Trotter steps for accurate simulation. + r"""Determine the number of Trotter steps for accurate energy estimation. + + This function calculates the number of steps required to bound the + systematic energy shift (eigenvalue shift) of the effective Hamiltonian + to a given precision. This is appropriate for applications like Quantum + Phase Estimation (QPE) where we only care about the eigenvalues of the + propagator. See appendix B from https://arxiv.org/pdf/1312.1695 + This uses the following definition of error for time t and + number of steps r: + + + $$ + \lvert E - E^{TS} \rvert = \frac{t^2}{r^2} E_{bound}(H) + $$ Args: trotter_error_bound (float): Upper bound on Trotter error in the @@ -187,11 +200,46 @@ def trotter_steps_required(trotter_error_bound, time, energy_precision): energy_precision (float): Acceptable shift in state energy. Returns: - The integer minimum number of Trotter steps required for - simulation to the desired precision. + The integer minimum number of Trotter steps required. + """ + if energy_precision <= 0: + raise ValueError("energy_precision must be strictly positive.") + if trotter_error_bound < 0: + raise ValueError("trotter_error_bound must be non-negative.") + if time == 0: + return 0 + return max(1, int(ceil(abs(time) * sqrt(trotter_error_bound / energy_precision)))) - Notes: - The number of Trotter steps required is an upper bound on the - true requirement, which may be lower. + +def trotter_steps_required_propagator(trotter_error_bound, time, prop_precision): + r"""Determine the number of Trotter steps for accurate state evolution. + + This function calculates the number of steps required to bound the + error in the time evolution operator (propagator error / spectral norm error) + to a given fidelity precision. This is appropriate for quantum dynamics + applications where we care about the accuracy of the state itself over + time. This uses the following definition of error for time t and + number of steps r: + + $$ + \lVert U - U^{TS} \rVert = \frac{t^3}{r^2} E_{bound}(H) + $$ + + Args: + trotter_error_bound (float): Upper bound on Trotter error in the + state of interest. + time (float): The total simulation time. + prop_precision (float): Acceptable error in the time evolution + operator (propagator error). + + Returns: + The integer minimum number of Trotter steps required. """ - return int(ceil(time * sqrt(trotter_error_bound / energy_precision))) + if prop_precision <= 0: + raise ValueError("prop_precision must be strictly positive.") + if trotter_error_bound < 0: + raise ValueError("trotter_error_bound must be non-negative.") + if time == 0: + return 0 + atime = abs(time) + return max(1, int(ceil(atime * sqrt(atime * trotter_error_bound / prop_precision)))) diff --git a/src/openfermion/circuits/trotter/trotter_error_test.py b/src/openfermion/circuits/trotter/trotter_error_test.py index 201459747..fd5296d28 100644 --- a/src/openfermion/circuits/trotter/trotter_error_test.py +++ b/src/openfermion/circuits/trotter/trotter_error_test.py @@ -24,6 +24,7 @@ error_operator, error_bound, trotter_steps_required, + trotter_steps_required_propagator, ) @@ -158,11 +159,67 @@ def test_trotter_steps_required(self): self.assertEqual( trotter_steps_required(trotter_error_bound=0.3, time=2.5, energy_precision=0.04), 7 ) + self.assertEqual( + trotter_steps_required_propagator( + trotter_error_bound=0.3, time=2.5, prop_precision=0.04 + ), + 11, + ) def test_trotter_steps_required_negative_time(self): self.assertEqual( - trotter_steps_required(trotter_error_bound=0.1, time=3.3, energy_precision=0.11), 4 + trotter_steps_required(trotter_error_bound=0.1, time=-3.3, energy_precision=0.11), 4 + ) + self.assertEqual( + trotter_steps_required_propagator( + trotter_error_bound=0.1, time=-3.3, prop_precision=0.11 + ), + 6, + ) + + def test_trotter_steps_required_zero_time(self): + self.assertEqual( + trotter_steps_required(trotter_error_bound=0.3, time=0.0, energy_precision=0.04), 0 + ) + self.assertEqual( + trotter_steps_required_propagator( + trotter_error_bound=0.3, time=0.0, prop_precision=0.04 + ), + 0, ) + def test_trotter_steps_required_zero_error_bound(self): + # Should return at least 1 step for non-zero time even if error bound is 0 + self.assertEqual( + trotter_steps_required(trotter_error_bound=0.0, time=2.5, energy_precision=0.04), 1 + ) + self.assertEqual( + trotter_steps_required_propagator( + trotter_error_bound=0.0, time=2.5, prop_precision=0.04 + ), + 1, + ) + + def test_trotter_steps_required_invalid_precision(self): + with self.assertRaises(ValueError): + trotter_steps_required(trotter_error_bound=0.3, time=2.5, energy_precision=0.0) + with self.assertRaises(ValueError): + trotter_steps_required(trotter_error_bound=0.3, time=2.5, energy_precision=-0.04) + with self.assertRaises(ValueError): + trotter_steps_required_propagator(trotter_error_bound=0.3, time=2.5, prop_precision=0.0) + with self.assertRaises(ValueError): + trotter_steps_required_propagator( + trotter_error_bound=0.3, time=2.5, prop_precision=-0.04 + ) + + def test_trotter_steps_required_invalid_error_bound(self): + with self.assertRaises(ValueError): + trotter_steps_required(trotter_error_bound=-0.3, time=2.5, energy_precision=0.04) + with self.assertRaises(ValueError): + trotter_steps_required_propagator( + trotter_error_bound=-0.3, time=2.5, prop_precision=0.04 + ) + def test_return_type(self): self.assertIsInstance(trotter_steps_required(0.1, 0.1, 0.1), int) + self.assertIsInstance(trotter_steps_required_propagator(0.1, 0.1, 0.1), int)