Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weighted trajectories #2369

Merged
merged 31 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0994713
WIP commit: weighted trajectories
pmenczel Mar 24, 2024
4f86221
Merge branch 'multitraj-improvements' into weighted-trajectories
pmenczel Mar 24, 2024
1ea5a12
WIP commit 2: weighted trajectories
pmenczel Mar 24, 2024
80ec47f
Merge branch 'multitraj-improvements' into weighted-trajectories
pmenczel Mar 25, 2024
b85af2e
Weighted trajectories
pmenczel Mar 28, 2024
4b81f97
Small fixes
pmenczel Mar 29, 2024
e65d497
Merge branch 'qutip:master' into weighted-trajectories
pmenczel Mar 29, 2024
4a79860
More small fixes
pmenczel Mar 29, 2024
24c7952
Code formatting
pmenczel Mar 29, 2024
ab87d6f
Suggestions from code review
pmenczel Apr 1, 2024
ada5c04
photocurrent with weights
pmenczel Apr 1, 2024
66c2ce6
Removed photocurrent from nm_mcsolve tests
pmenczel Apr 1, 2024
b059661
Revert stochastic solvers to Result instead of TrajectoryResult
pmenczel Apr 1, 2024
b7eadd7
Towncrier entry
pmenczel Apr 1, 2024
fedb376
Merging results with weighted trajectories, and mcsolve and nm_mcsolv…
pmenczel Apr 1, 2024
f643fdf
Remove unnecessary fields
pmenczel Apr 3, 2024
d048fef
Moved runs_weights to MultiTrajResult
pmenczel Apr 3, 2024
ba9071b
Updated target tolerance calculation for weighted traj
pmenczel Apr 3, 2024
95caad4
Started adding tests
pmenczel Apr 3, 2024
f4585e8
Added tests for merging multi trajectory results / mcresults / nmmcre…
pmenczel Apr 4, 2024
e60e283
Added improved sampling to nmmcsolve tests
pmenczel Apr 4, 2024
283e060
Fix typo in mcsolve test
pmenczel Apr 4, 2024
5fabdd3
Performance improvement
pmenczel Apr 4, 2024
64b5251
Merge remote-tracking branch 'upstream/master' into weighted-trajecto…
pmenczel Apr 18, 2024
9208fea
Fixed target tolerance calculation
pmenczel Apr 18, 2024
3f774b2
Merge remote-tracking branch 'upstream/master' into weighted-trajecto…
pmenczel Apr 25, 2024
7f72b23
Merge branch 'qutip:master' into weighted-trajectories
pmenczel May 3, 2024
18a75b9
Created multitrajresult module
pmenczel May 6, 2024
bf604c3
Documentation for _TrajectorySum class
pmenczel May 6, 2024
c48b550
Updated nmmcsolve guide and fixed broken links
pmenczel May 6, 2024
cd96a59
Don't fix random seed in test
pmenczel May 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 6 additions & 4 deletions doc/apidoc/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,17 @@ Solver Options and Results
:inherited-members:
:exclude-members: add_processor, add, add_end_condition

.. autoclass:: qutip.solver.result.TrajectoryResult
:show-inheritance:
:members:

.. autoclass:: qutip.solver.result.McResult
:show-inheritance:
:members:
:inherited-members:
:exclude-members: add_processor, add, add_end_condition

.. autoclass:: qutip.solver.result.NmmcResult
:show-inheritance:
:members:
:inherited-members:
:exclude-members: add_processor, add, add_end_condition

.. _classes-piqs:

Expand Down
31 changes: 12 additions & 19 deletions qutip/solver/mcsolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ..core import QobjEvo, spre, spost, Qobj, unstack_columns
from .multitraj import MultiTrajSolver, _MultiTrajRHS
from .solver_base import Solver, Integrator, _solver_deprecation
from .result import McResult, McTrajectoryResult, McResultImprovedSampling
from .result import McResult
from .mesolve import mesolve, MESolver
from ._feedback import _QobjFeedback, _DataFeedback, _CollapseFeedback
import qutip.core.data as _data
Expand Down Expand Up @@ -401,7 +401,7 @@ class MCSolver(MultiTrajSolver):
Options for the evolution.
"""
name = "mcsolve"
_trajectory_resultclass = McTrajectoryResult
_resultclass = McResult
_mc_integrator_class = MCIntegrator
solver_options = {
"progress_bar": "text",
Expand Down Expand Up @@ -480,6 +480,9 @@ def _run_one_traj(self, seed, state, tlist, e_ops, **integrator_kwargs):
"""
seed, result = super()._run_one_traj(seed, state, tlist, e_ops,
**integrator_kwargs)
jump_prob_floor = integrator_kwargs.get('jump_prob_floor', 0)
if jump_prob_floor > 0:
result.add_relative_weight(1 - jump_prob_floor)
result.collapse = self._integrator.collapses
return seed, result

Expand All @@ -488,26 +491,23 @@ def run(self, state, tlist, ntraj=1, *,
# Overridden to sample the no-jump trajectory first. Then, the no-jump
# probability is used as a lower-bound for random numbers in future
# monte carlo runs
if not self.options.get("improved_sampling", False):
if not self.options["improved_sampling"]:
return super().run(state, tlist, ntraj=ntraj, args=args,
e_ops=e_ops, timeout=timeout,
target_tol=target_tol, seeds=seeds)
stats, seeds, result, map_func, map_kw, state0 = self._initialize_run(
state,
ntraj,
args=args,
e_ops=e_ops,
timeout=timeout,
target_tol=target_tol,
seeds=seeds,

seeds, result, map_func, map_kw, state0 = self._initialize_run(
state, ntraj, args=args, e_ops=e_ops,
timeout=timeout, target_tol=target_tol, seeds=seeds
)

# first run the no-jump trajectory
start_time = time()
seed0, no_jump_result = self._run_one_traj(seeds[0], state0, tlist,
e_ops, no_jump=True)
_, state, _ = self._integrator.get_state(copy=False)
no_jump_prob = self._integrator._prob_func(state)
result.no_jump_prob = no_jump_prob
no_jump_result.add_absolute_weight(no_jump_prob)
result.add((seed0, no_jump_result))
result.stats['no jump run time'] = time() - start_time

Expand Down Expand Up @@ -542,13 +542,6 @@ def _get_integrator(self):
self._init_integrator_time = time() - _time_start
return mc_integrator

@property
def _resultclass(self):
if self.options.get("improved_sampling", False):
return McResultImprovedSampling
else:
return McResult

@property
def options(self):
"""
Expand Down
8 changes: 4 additions & 4 deletions qutip/solver/multitraj.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .result import Result, MultiTrajResult
from .result import TrajectoryResult, MultiTrajResult
from .parallel import _get_map
from time import time
from .solver_base import Solver
Expand Down Expand Up @@ -50,7 +50,7 @@ class MultiTrajSolver(Solver):
"""
name = "generic multi trajectory"
_resultclass = MultiTrajResult
_trajectory_resultclass = Result
_trajectory_resultclass = TrajectoryResult
_avail_integrators = {}

# Class of option used by the solver
Expand Down Expand Up @@ -148,7 +148,7 @@ def _initialize_run(self, state, ntraj=1, args=None, e_ops=(),
})
state0 = self._prepare_state(state)
stats['preparation time'] += time() - start_time
return stats, seeds, result, map_func, map_kw, state0
return seeds, result, map_func, map_kw, state0

def run(self, state, tlist, ntraj=1, *,
args=None, e_ops=(), timeout=None, target_tol=None, seeds=None):
Expand Down Expand Up @@ -211,7 +211,7 @@ def run(self, state, tlist, ntraj=1, *,
The simulation will end when the first end condition is reached
between ``ntraj``, ``timeout`` and ``target_tol``.
"""
stats, seeds, result, map_func, map_kw, state0 = self._initialize_run(
seeds, result, map_func, map_kw, state0 = self._initialize_run(
state,
ntraj,
args=args,
Expand Down
55 changes: 37 additions & 18 deletions qutip/solver/nm_mcsolve.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
__all__ = ['nm_mcsolve', 'NonMarkovianMCSolver']

import functools
import numbers

import numpy as np
import scipy

from .result import NmmcResult
from .multitraj import MultiTrajSolver
from .mcsolve import MCSolver, MCIntegrator
from .mesolve import MESolver, mesolve
from .result import NmmcResult, NmmcTrajectoryResult
from .cy.nm_mcsolve import RateShiftCoefficient, SqrtRealCoefficient
from ..core.coefficient import ConstantCoefficient
from ..core import (
Expand Down Expand Up @@ -114,6 +113,9 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *,
``norm_tol`` are the tolerance in time and norm respectively.
An error will be raised if the collapse could not be found within
``norm_steps`` tries.
- | improved_sampling : Bool
| Whether to use the improved sampling algorithm from Abdelhafez et
al. PRA (2019)
- | mc_corr_eps : float
| Small number used to detect non-physical collapse caused by
numerical imprecision.
Expand Down Expand Up @@ -217,7 +219,10 @@ def initialize(self, t0, cache='clear'):
# to pre-compute the continuous contribution to the martingale
self._t_prev = t0
self._continuous_martingale_at_t_prev = 1
self._discrete_martingale = 1

# _discrete_martingale is a list of (time, factor) such that
# mu_d(t) is the product of all factors with time < t
self._discrete_martingale = []

if np.array_equal(cache, 'clear'):
self._precomputed_continuous_martingale = {}
Expand All @@ -239,7 +244,7 @@ def add_collapse(self, collapse_time, collapse_channel):
rate = self._nm_solver.rate(collapse_time, collapse_channel)
shift = self._nm_solver.rate_shift(collapse_time)
factor = rate / (rate + shift)
self._discrete_martingale *= factor
self._discrete_martingale.append((collapse_time, factor))

def value(self, t):
if self._t_prev is None:
Expand All @@ -256,7 +261,13 @@ def value(self, t):
self._t_prev = t
self._continuous_martingale_at_t_prev = mu_c

return self._discrete_martingale * mu_c
# find value of discrete martingale at given time
mu_d = 1
for time, factor in self._discrete_martingale:
if t > time:
mu_d *= factor

return mu_d * mu_c

def _compute_continuous_martingale(self, t1, t2):
if t1 == t2:
Expand Down Expand Up @@ -339,15 +350,12 @@ class NonMarkovianMCSolver(MCSolver):
"norm_steps": 5,
"norm_t_tol": 1e-6,
"norm_tol": 1e-4,
"improved_sampling": False,
"completeness_rtol": 1e-5,
"completeness_atol": 1e-8,
"martingale_quad_limit": 100,
}

# both classes will be partially initialized in constructor
_trajectory_resultclass = NmmcTrajectoryResult
_mc_integrator_class = NmMCIntegrator

def __init__(
self, H, ops_and_rates, args=None, options=None,
):
Expand Down Expand Up @@ -380,14 +388,11 @@ def __init__(
for op, sqrt_shifted_rate
in zip(self.ops, self._sqrt_shifted_rates)
]
self._trajectory_resultclass = functools.partial(
NmmcTrajectoryResult, __nm_solver=self,
)
self._mc_integrator_class = functools.partial(
NmMCIntegrator, __martingale=self._martingale,
)
super().__init__(H, c_ops, options=options)

def _mc_integrator_class(self, *args):
return NmMCIntegrator(*args, __martingale=self._martingale)

def _check_completeness(self, ops_and_rates):
"""
Checks whether ``sum(Li.dag() * Li)`` is proportional to the identity
Expand Down Expand Up @@ -509,9 +514,8 @@ def sqrt_shifted_rate(self, t, i):
# the run.
#
# Regarding (b), in the start/step-interface we just include the martingale
# in the step method. In order to include the martingale in the
# run-interface, we use a custom trajectory-resultclass that grabs the
# martingale value from the NonMarkovianMCSolver whenever a state is added.
# in the step method. In the run-interface, the martingale is added as a
# relative weight to the trajectory result at the end of `_run_one_traj`.

def start(self, state, t0, seed=None):
self._martingale.initialize(t0, cache='clear')
Expand All @@ -524,6 +528,17 @@ def step(self, t, *, args=None, copy=True):
state = ket2dm(state)
return state * self.current_martingale()

def _run_one_traj(self, seed, state, tlist, e_ops, **integrator_kwargs):
"""
Run one trajectory and return the result.
"""
seed, result = super()._run_one_traj(seed, state, tlist, e_ops,
**integrator_kwargs)
martingales = [self._martingale.value(t) for t in tlist]
result.add_relative_weight(martingales)
result.trace = martingales
return seed, result

def run(self, state, tlist, ntraj=1, *, args=None, **kwargs):
# update `args` dictionary before precomputing martingale
self._argument(args)
Expand Down Expand Up @@ -596,6 +611,10 @@ def options(self):
norm_steps: int, default: 5
Maximum number of tries to find the collapse.

improved_sampling: Bool, default: False
Whether to use the improved sampling algorithm
of Abdelhafez et al. PRA (2019)

completeness_rtol: float, default: 1e-5
Used in determining whether the given Lindblad operators satisfy
a certain completeness relation. If they do not, an additional
Expand Down