From 6c8843cbfbd562427ea3c916d7c6ea71c86e0ee1 Mon Sep 17 00:00:00 2001 From: Patrick <> Date: Sun, 5 May 2024 16:02:37 +0200 Subject: [PATCH] fix documentation build and search --- .gitignore | 1 + .readthedocs.yaml | 26 ++++++------------ README.md | 6 ++--- doc/Makefile | 2 +- doc/guide/guide-control.rst | 9 ++++--- doc/requirements.txt | 16 +++++------ doc/rtd-environment.yml | 2 +- src/qutip_qoc/__init__.py | 3 ++- src/qutip_qoc/_optimizer.py | 4 +-- src/qutip_qoc/objective.py | 39 ++++++++++++++++++++------- src/qutip_qoc/result.py | 54 ++++++++++++++++++++++++++++++++++--- src/qutip_qoc/time.py | 18 ++++++++++--- 12 files changed, 126 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index 0b2a98b..2ff1cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ src/qutip_qoc/version.py # Documentation doc/apidoc/*.rst +doc/.DS_Store # PyInstaller # Usually these files are written by a python script from a template diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 44196d7..8b35d12 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -2,29 +2,19 @@ # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details -# Required version: 2 -# Set the version of Python and other tools you might need +formats: + - pdf + build: os: ubuntu-22.04 tools: - python: "3.10" - apt_packages: - - pandoc - #jobs: - # pre_build: - # # Each command is executed in a new shell process - # - cd doc; python source/tutorials-website/create_tutorials_html.py + python: "mambaforge-4.10" + +conda: + environment: doc/rtd-environment.yml -# Build documentation in the docs/ directory with Sphinx sphinx: configuration: doc/conf.py - -# We recommend specifying your dependencies to enable reproducible builds: -# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - requirements: doc/requirements.txt - - method: pip - path: . + fail_on_warning: false diff --git a/README.md b/README.md index 46ae033..9dde70f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # QuTiP - Quantum Optimal Control -The qutip-qoc package builds up on the `qutip-qtrl` [package](https://github.com/qutip/qutip-qtrl). +The `qutip-qoc` package builds up on the `qutip-qtrl` [package](https://github.com/qutip/qutip-qtrl). It enhances it by providing two additional algorithms to optimize analytically defined control functions. The first one is an extension of Gradient Optimization of Analytic conTrols [(GOAT)](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.120.150401). @@ -12,8 +12,8 @@ gradient driven `scipy.optimize.minimize` methods. The package also aims for a more general way of defining control problems with QuTiP and makes switching between the four control algorithms (GOAT, JOPT, and GRAPE and CRAB implemented in qutip-qtrl) very easy. -As with qutip-qtrl, the qutip-qoc package aims at providing advanced tools for the optimal control of quantum devices. -Compared to other libraries for quantum optimal control, qutip-qoc puts additional emphasis on the physics layer and the interaction with the QuTiP package. +As with `qutip-qtrl`, the `qutip-qoc` package aims at providing advanced tools for the optimal control of quantum devices. +Compared to other libraries for quantum optimal control, `qutip-qoc` puts additional emphasis on the physics layer and the interaction with the QuTiP package. Along with the extended GOAT and JOPT algorithms the package offers support for both the CRAB and GRAPE methods defined in `qutip-qtrl`. If you would like to know the future development plan and ideas, have a look at the [qutip roadmap and ideas](https://qutip.readthedocs.io/en/latest/development/roadmap.html). diff --git a/doc/Makefile b/doc/Makefile index 15ed6b9..946708c 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -20,7 +20,7 @@ help: @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) html: - sphinx-apidoc -f -e -P -o ./apidoc/ ../src/qutip_qoc/ + sphinx-apidoc -f -e -P -o ./apidoc/ ../src/qutip_qoc/ ../src/qutip_qoc/pulse.* @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/doc/guide/guide-control.rst b/doc/guide/guide-control.rst index c7a02a4..b2029cd 100644 --- a/doc/guide/guide-control.rst +++ b/doc/guide/guide-control.rst @@ -262,15 +262,16 @@ Eventually, the optimization for a desired `fid_err_targ` can be started by call result = qoc.optimize_pulses( objectives=[objective], # list of objectives control_parameters={ - "p": {"guess": p_guess, "bounds": p_bounds}, - "q": {"guess": q_guess, "bounds": q_bounds} + "p": {"guess": p_guess, "bounds": p_bounds}, + "q": {"guess": q_guess, "bounds": q_bounds}, }, tlist=np.linspace(0, 1, 100), algorithm_kwargs={ - "alg": "GOAT", - "fid_err_targ": 0.1, + "fid_err_targ": 0.1, + "alg": "GOAT", }, ) .. TODO: add examples + Examples for Liouvillian dynamics and multi-objective optimization will follow soon. diff --git a/doc/requirements.txt b/doc/requirements.txt index 8c65c54..117b440 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,15 +3,10 @@ scipy>=1.10.1 jax>=0.4.23 jaxlib>=0.4.23 qutip>=5.0.1 -cython==0.29.33 -sphinx==6.1.3 -sphinx_rtd_theme==1.2.0 -sphinxcontrib-bibtex==2.5.0 -readthedocs-sphinx-search==0.2.0 +cython>=0.29.33 numpydoc==1.5.0 matplotlib>=3.6.1 docutils==0.18.1 -sphinxcontrib-bibtex==2.5.0 alabaster==0.7.12 Babel==2.9.1 certifi==2020.12.5 @@ -27,11 +22,14 @@ pyparsing==2.4.7 pytz==2021.1 requests==2.25.1 snowballstemmer==2.1.0 -sphinxcontrib-applehelp==1.0.2 +Sphinx==6.1.3 +sphinx-gallery==0.12.2 +sphinx-rtd-theme==1.2.1 +sphinxcontrib-applehelp==1.0.3 +sphinxcontrib-bibtex==2.5.0 sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 urllib3==1.26.4 -#wincertstore==0.2.1 diff --git a/doc/rtd-environment.yml b/doc/rtd-environment.yml index 98a6853..6e2e72c 100644 --- a/doc/rtd-environment.yml +++ b/doc/rtd-environment.yml @@ -6,7 +6,7 @@ dependencies: - scipy>=1.10.1 - jax>=0.4.23 - jaxlib>=0.4.23 - - cython==0.29.33 + - cython>=0.29.33 - sphinx==6.1.3 - sphinx_rtd_theme==1.2.0 - sphinxcontrib-bibtex==2.5.0 diff --git a/src/qutip_qoc/__init__.py b/src/qutip_qoc/__init__.py index cb57944..9151256 100644 --- a/src/qutip_qoc/__init__.py +++ b/src/qutip_qoc/__init__.py @@ -1,7 +1,8 @@ from .version import version as __version__ # noqa from qutip_qoc.time import TimeInterval +from qutip_qoc.result import Result from qutip_qoc.objective import Objective from qutip_qoc.pulse_optim import optimize_pulses -__all__ = ["TimeInterval", "Objective", "optimize_pulses"] +__all__ = ["TimeInterval", "Result", "Objective", "optimize_pulses"] diff --git a/src/qutip_qoc/_optimizer.py b/src/qutip_qoc/_optimizer.py index 3675217..1bd647c 100644 --- a/src/qutip_qoc/_optimizer.py +++ b/src/qutip_qoc/_optimizer.py @@ -124,7 +124,7 @@ def min_callback(self, intermediate_result: OptimizeResult): if intermediate_result.fun < self._result.infidelity: if intermediate_result.fun > 0: if self.inside_bounds(intermediate_result.x): - self._result.update( + self._result._update( intermediate_result.fun, intermediate_result.x ) raise StopIteration @@ -164,7 +164,7 @@ def opt_callback(self, x, f, accept): ) terminate = False elif self.inside_bounds(x): - self._result.update(f, x) + self._result._update(f, x) return terminate diff --git a/src/qutip_qoc/objective.py b/src/qutip_qoc/objective.py index 4e83d43..986961a 100644 --- a/src/qutip_qoc/objective.py +++ b/src/qutip_qoc/objective.py @@ -19,19 +19,40 @@ class Objective: """ A class for storing information about an optimization objective. - *Examples* - >>> initial = qt.basis(2, 0) - >>> target = qt.basis(2, 1) + Examples + -------- + To specify an objective for GOAT, provide a gradient function:: - >>> sin = lambda t, p: np.sin(p * t) + initial = qt.basis(2, 0) + target = qt.basis(2, 1) - >>> def d_sin(t, p, idx): - >>> if idx==0: return t * np.cos(t) # wrt p - >>> if idx==1: return p * np.cos(t) # wrt t + sin = lambda t, p: np.sin(p * t) - >>> H = [qt.sigmax(), [qt.sigmay(), sin, {'grad': d_sin}]] + def d_sin(t, p, idx): + if idx==0: return t * np.cos(t) # w.r.t. p + if idx==1: return p * np.cos(t) # w.r.t. t - >>> obj = Objective(initial, H, target) + H = [qt.sigmax(), [qt.sigmay(), sin, {'grad': d_sin}]] + + obj = Objective(initial, H, target) + + + To specify an objective for JOPT, no need for gradient function:: + + initial = qt.basis(2, 0) + target = qt.basis(2, 1) + + @jax.jit + def sin(t, p) + return jax.numpy.sin(p * t) + + def d_sin(t, p, idx): + if idx==0: return t * np.cos(t) # w.r.t. p + if idx==1: return p * np.cos(t) # w.r.t. t + + H = [qt.sigmax(), [qt.sigmay(), sin]] + + obj = Objective(initial, H, target) Attributes ---------- diff --git a/src/qutip_qoc/result.py b/src/qutip_qoc/result.py index 30a3013..469e5b2 100644 --- a/src/qutip_qoc/result.py +++ b/src/qutip_qoc/result.py @@ -30,8 +30,8 @@ class Result: """ Class for storing the results of a pulse control optimization run. - Attributes: - ----------- + Attributes + ---------- objectives : list of :class:`qutip_qoc.Objective` List of objectives to be optimized. @@ -59,6 +59,9 @@ class Result: message : str Reason for termination. + optimized_params : list of ndarray + List of optimized parameters. + guess_controls : list of ndarray List of guess control pulses used to initialize the optimization. @@ -161,12 +164,18 @@ def __repr__(self): @property def total_seconds(self): + """ + Total time in seconds the optimization took. + """ if self._total_seconds is None: self._total_seconds = sum(self.iter_seconds) return self._total_seconds @property def optimized_params(self): + """ + Parameter values after optimization. + """ if self._optimized_params is None: # reshape (optimized) new_parameters array to match # shape and type of the guess_parameters list @@ -196,6 +205,9 @@ def optimized_params(self, params): @property def optimized_controls(self): + """ + Control pulses after optimization. + """ if self._optimized_controls is None: opt_ctrl = [] @@ -228,6 +240,9 @@ def optimized_controls(self): @property def guess_controls(self): + """ + Control pulses before the optimization. + """ if self._guess_controls is None: if self.qtrl_optimizers: qtrl_res = self.qtrl_optimizers[0]._create_result() @@ -263,6 +278,9 @@ def guess_controls(self): @property def optimized_H(self): + """ + Optimized Hamiltonians with optimized controls. + """ if self._optimized_H is None: opt_H = [] @@ -301,6 +319,9 @@ def optimized_H(self): @property def final_states(self): + """ + Evolved system states after optimization. + """ if self._final_states is None: states = [] @@ -342,17 +363,26 @@ def final_states(self): self._final_states = states return self._final_states - def update(self, infidelity, parameters): + def _update(self, infidelity, parameters): + """ + Used to update the result during optimization. + """ self.infidelity = infidelity self.new_params = parameters def dump(self, filename): + """ + Save the result to a file. + """ with open(filename, "wb") as dump_fh: pickler = pickle.Pickler(dump_fh) pickler.dump(self) @classmethod def load(cls, filename, objectives=None): + """ + Load a objective from a file. + """ with open(filename, "rb") as dump_fh: result = pickle.load(dump_fh) result.objectives = objectives @@ -360,6 +390,9 @@ def load(cls, filename, objectives=None): @property def evo_full_final(self): + """ + Deprecated, use final_states[0] instead. + """ warnings.warn( "evo_full_final is deprecated, use final_states[0] instead", DeprecationWarning, @@ -368,6 +401,9 @@ def evo_full_final(self): @property def fid_err(self): + """ + Deprecated, use infidelity instead. + """ warnings.warn( "fid_err is deprecated, use infidelity instead", DeprecationWarning ) @@ -375,6 +411,9 @@ def fid_err(self): @property def grad_norm_final(self): + """ + Deprecated, not supported. + """ warnings.warn( "grad_norm_final is deprecated, it is not supported", DeprecationWarning ) @@ -382,6 +421,9 @@ def grad_norm_final(self): @property def termination_reason(self): + """ + Deprecated, use message instead. + """ warnings.warn( "termination_reason is deprecated, use message instead", DeprecationWarning ) @@ -389,11 +431,17 @@ def termination_reason(self): @property def num_iter(self): + """ + Deprecated, use n_iters instead. + """ warnings.warn("num_iter is deprecated, use n_iters instead", DeprecationWarning) return self.n_iters @property def wall_time(self): + """ + Deprecated, use total_seconds instead. + """ warnings.warn( "wall_time is deprecated, use total_seconds instead", DeprecationWarning ) diff --git a/src/qutip_qoc/time.py b/src/qutip_qoc/time.py index d895e12..2f6e302 100644 --- a/src/qutip_qoc/time.py +++ b/src/qutip_qoc/time.py @@ -37,14 +37,23 @@ def __call__(self): @property def tslots(self): + """ + If not provided, it is derived from evo_time and n_tslots. + """ if self._tslots is None: - n_tslots = self.n_tslots - if self._evo_time: # derive from evo_time - self._tslots = np.linspace(0.0, self._evo_time, n_tslots) + if self._evo_time and self._n_tslots: # derive from evo_time + self._tslots = np.linspace(0.0, self._evo_time, self.n_tslots) + else: + raise ValueError( + "Either tslots or evo_time + n_tslots must be specified." + ) return self._tslots @property def evo_time(self): + """ + If not provided, it is derived from the last element of `tslots`. + """ if self._evo_time is None: tslots = self.tslots self._evo_time = tslots[-1] @@ -52,6 +61,9 @@ def evo_time(self): @property def n_tslots(self): + """ + If not provided, it is derived from the length of `tslots`. + """ if self._n_tslots is None: if self._tslots is not None: self._n_tslots = len(self._tslots)