-
Notifications
You must be signed in to change notification settings - Fork 407
[unitaryhack] Add closed- and open-system dynamics propagators #4672
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
Open
friedsam
wants to merge
22
commits into
NVIDIA:main
Choose a base branch
from
friedsam:uh2026-contrib-propagator-3437
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
d9a7b2c
Add closed-system dynamics propagator
friedsam 608c313
Support batched and time-dependent propagators
friedsam f527c3d
Support open-system propagators
friedsam c5a247e
Fix propagator docstring spelling
friedsam 5beddd5
Fix propagator spelling and formatting
friedsam 1a406fb
Fix propagator matrix convention and collapse adjoint operators
friedsam 336311b
Add propagator license headers
friedsam 8357362
Assemble open-system propagators from basis evolution
friedsam 5386a72
Use direct Liouvillian for open-system propagators
friedsam b8a8a19
Move cudaq import to module scope
friedsam a99d137
Add multi-degree propagator test
friedsam 5913a4d
Pass schedule parameters through propagator callback
friedsam 95b1864
Use structured SuperOperator for open-system propagators
friedsam 4cdb7a1
Use structured SuperOperator for open-system propagators
friedsam 88d0e6e
Use evolve collapse operators for open-system propagators
friedsam a1c0ecf
Pass collapse operators through evolve
friedsam 2fd6184
Support batched collapse operators
friedsam e3ee81c
Flatten batched open-system propagator states
friedsam 8077498
Escape technical terms in propagator comments
friedsam ba05bf2
Support open-system intermediate propagators
friedsam 7fa78d6
Use list for intermediate propagator stacking
friedsam 1e43827
Merge branch 'main' into uh2026-contrib-propagator-3437
1tnguyen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| # ============================================================================ # | ||
| # Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # | ||
| # All rights reserved. # | ||
| # # | ||
| # This source code and the accompanying materials are made available under # | ||
| # the terms of the Apache License 2.0 which accompanies this distribution. # | ||
| # ============================================================================ # | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Mapping, Sequence | ||
| from typing import Optional | ||
|
|
||
| import cudaq | ||
| import numpy as np | ||
|
|
||
|
|
||
| def _total_dimension(dimensions: Mapping[int, int]) -> int: | ||
| """Return the product of all local Hilbert-space dimensions.""" | ||
| dimension = 1 | ||
| for local_dimension in dimensions.values(): | ||
| dimension *= local_dimension | ||
| return dimension | ||
|
|
||
|
|
||
| def _identity_state(dimension: int): | ||
| """Return |I> used to evolve closed-system propagators directly.""" | ||
| identity = np.eye(dimension, dtype=np.complex128).reshape(-1) | ||
| return cudaq.State.from_data(identity) | ||
|
|
||
|
|
||
| def _basis_states(dimension: int): | ||
| """Return `Liouville` basis states used to reconstruct Lindblad maps.""" | ||
| states = [] | ||
| for index in range(dimension): | ||
| data = np.zeros(dimension, dtype=np.complex128) | ||
| data[index] = 1.0 | ||
| states.append(cudaq.State.from_data(data)) | ||
| return states | ||
|
|
||
|
|
||
| def _state_to_matrix(state, dimension: int) -> np.ndarray: | ||
| """Convert a `vectorized` propagated identity state back to a matrix.""" | ||
| data = np.array(state).reshape(-1) | ||
| expected_size = dimension * dimension | ||
|
|
||
| if data.size != expected_size: | ||
| raise RuntimeError("Expected propagator state with size " | ||
| f"{expected_size}, got {data.size}.") | ||
|
|
||
| return data.reshape((dimension, dimension)).T | ||
|
|
||
|
|
||
| def _closed_system_generator(hamiltonian): | ||
| """Represent `dU/dt = -i H U` as left multiplication by `-i H`.""" | ||
| generator = cudaq.SuperOperator() | ||
| generator += cudaq.SuperOperator.left_multiply(-1j * hamiltonian) | ||
| return generator | ||
|
|
||
|
|
||
| def _extract_propagator(result, dimension: int, | ||
| store_intermediate_results: bool): | ||
| """Extract closed-system propagators from a CUDA-Q evolve result.""" | ||
| if store_intermediate_results: | ||
| return [ | ||
| _state_to_matrix(state, dimension) | ||
| for state in result.intermediate_states() | ||
| ] | ||
|
|
||
| return _state_to_matrix(result.final_state(), dimension) | ||
|
|
||
|
|
||
| def _extract_batched_basis_propagator(results, | ||
| store_intermediate_results: bool): | ||
| """Stack evolved `Liouville` basis states into dense Lindblad maps.""" | ||
| if store_intermediate_results: | ||
| intermediate_states = [ | ||
| list(single_result.intermediate_states()) | ||
| for single_result in results | ||
| ] | ||
| return [ | ||
| np.column_stack([ | ||
| np.array(states[time_index]).reshape(-1) | ||
| for states in intermediate_states | ||
| ]) | ||
| for time_index in range(len(intermediate_states[0])) | ||
| ] | ||
|
|
||
| columns = [ | ||
| np.array(single_result.final_state()).reshape(-1) | ||
| for single_result in results | ||
| ] | ||
| return np.column_stack(columns) | ||
|
|
||
|
|
||
| def _is_operator_like(value) -> bool: | ||
| """Return True for CUDA-Q operators accepted by the dynamics backend.""" | ||
| return hasattr(value, "to_matrix") | ||
|
|
||
|
|
||
| def _is_collapse_operator_batch(collapse_operators) -> bool: | ||
| """Return True when collapse operators are grouped per Hamiltonian.""" | ||
| return bool(collapse_operators) and not _is_operator_like( | ||
| collapse_operators[0]) | ||
|
|
||
|
|
||
| def _collapse_operator_batches(collapse_operators, batch_size: int): | ||
| """Broadcast collapse operators to match the Hamiltonian batch size.""" | ||
| if not collapse_operators: | ||
| return [[] for _ in range(batch_size)] | ||
|
|
||
| if _is_collapse_operator_batch(collapse_operators): | ||
| if len(collapse_operators) != batch_size: | ||
| raise ValueError("Batched collapse_operators must have the same " | ||
| "length as the Hamiltonian batch.") | ||
| return [list(ops) for ops in collapse_operators] | ||
|
|
||
| return [collapse_operators for _ in range(batch_size)] | ||
|
|
||
|
|
||
| def propagator( | ||
| hamiltonian, | ||
| dimensions: Mapping[int, int], | ||
| schedule, | ||
| *, | ||
| collapse_operators=None, | ||
| store_intermediate_results: bool = False, | ||
| integrator=None, | ||
| max_batch_size: Optional[int] = None, | ||
| ): | ||
| """Compute dynamics propagators. | ||
|
|
||
| For closed-system dynamics, computes the matrix U satisfying the | ||
| Schrodinger-picture propagator equation with initial condition U(t0) = I. | ||
|
|
||
| For open-system dynamics with collapse operators, computes the Lindblad | ||
| map S with initial condition S(t0) = I. This map acts on density | ||
| matrices after matrix-to-vector reshaping and propagates rho(t0) to | ||
| rho(t). | ||
|
|
||
| Args: | ||
| hamiltonian: CUDA-Q operator H(t), or a sequence of operators for | ||
| batched propagator computation. | ||
| dimensions: Mapping from degree-of-freedom index to local dimension. | ||
| schedule: CUDA-Q dynamics schedule. | ||
| collapse_operators: Optional sequence of Lindblad collapse operators. | ||
| If provided, the helper returns the Lindblad map. | ||
| store_intermediate_results: If True, return propagators at the | ||
| intermediate schedule points saved by the dynamics backend. | ||
| integrator: Optional dynamics integrator. | ||
| max_batch_size: Optional maximum batch size for the dynamics backend. | ||
|
|
||
| Returns: | ||
| For closed-system dynamics, returns a dense complex NumPy array with | ||
| shape ``(dim, dim)``. | ||
|
|
||
| For open-system dynamics, returns a dense complex NumPy array with | ||
| shape ``(dim**2, dim**2)``. | ||
|
|
||
| If ``store_intermediate_results`` is True, returns a list of dense | ||
| matrices. For a sequence of Hamiltonians, returns one such result per | ||
| Hamiltonian. | ||
| """ | ||
| collapse_operators = [] if collapse_operators is None else list( | ||
| collapse_operators) | ||
|
|
||
| is_batched = isinstance(hamiltonian, | ||
| Sequence) and not hasattr(hamiltonian, "to_matrix") | ||
| hamiltonians = list(hamiltonian) if is_batched else [hamiltonian] | ||
| collapse_operator_batches = _collapse_operator_batches( | ||
| collapse_operators, len(hamiltonians)) | ||
| open_system = any(collapse_operator_batches) | ||
|
|
||
| system_dimension = _total_dimension(dimensions) | ||
| propagator_dimension = (system_dimension * system_dimension | ||
| if open_system else system_dimension) | ||
| evolution_dimensions = dimensions | ||
|
|
||
| if open_system: | ||
| generators = hamiltonians | ||
| else: | ||
| generators = [_closed_system_generator(h) for h in hamiltonians] | ||
|
|
||
| if open_system: | ||
| initial_states = [ | ||
| _basis_states(propagator_dimension) for _ in generators | ||
| ] | ||
| else: | ||
| initial_states = [ | ||
| _identity_state(propagator_dimension) for _ in generators | ||
| ] | ||
|
1tnguyen marked this conversation as resolved.
|
||
|
|
||
| save_mode = (cudaq.IntermediateResultSave.ALL if store_intermediate_results | ||
| else cudaq.IntermediateResultSave.NONE) | ||
|
|
||
| evolve_collapse_operators = [] | ||
| if open_system: | ||
| evolve_collapse_operators = (collapse_operator_batches if is_batched | ||
| else collapse_operator_batches[0]) | ||
|
|
||
| evolve_generators = generators if is_batched else generators[0] | ||
| evolve_initial_states = initial_states if is_batched else initial_states[0] | ||
|
|
||
| if open_system and is_batched: | ||
| evolve_generators = [] | ||
| evolve_initial_states = [] | ||
| evolve_collapse_operators = [] | ||
| for generator, basis_states, collapse_batch in zip( | ||
| generators, initial_states, collapse_operator_batches): | ||
| for basis_state in basis_states: | ||
| evolve_generators.append(generator) | ||
| evolve_initial_states.append(basis_state) | ||
| evolve_collapse_operators.append(collapse_batch) | ||
|
|
||
| result = cudaq.evolve( | ||
| evolve_generators, | ||
| evolution_dimensions, | ||
| schedule, | ||
| evolve_initial_states, | ||
| collapse_operators=evolve_collapse_operators, | ||
| observables=[], | ||
| store_intermediate_results=save_mode, | ||
| integrator=integrator, | ||
| max_batch_size=max_batch_size, | ||
| ) | ||
|
|
||
| if open_system: | ||
| if is_batched: | ||
| return [ | ||
| _extract_batched_basis_propagator( | ||
| result[index * propagator_dimension:(index + 1) * | ||
| propagator_dimension], store_intermediate_results) | ||
| for index in range(len(generators)) | ||
| ] | ||
| return _extract_batched_basis_propagator(result, | ||
| store_intermediate_results) | ||
|
|
||
| if is_batched: | ||
| return [ | ||
| _extract_propagator(single_result, propagator_dimension, | ||
| store_intermediate_results) | ||
| for single_result in result | ||
| ] | ||
|
|
||
| return _extract_propagator(result, propagator_dimension, | ||
| store_intermediate_results) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.