Skip to content

Commit

Permalink
use blocks as noise operators
Browse files Browse the repository at this point in the history
  • Loading branch information
Charles MOUSSA committed Nov 26, 2024
1 parent 7749b4f commit b819959
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 17 deletions.
5 changes: 4 additions & 1 deletion docs/content/time_dependent.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ omega_param = FeatureParameter("omega")
td_generator = omega_param * (t * X(0) + t**2 * Y(1))

# Create parameterized HamEvo block
noise_operators = [torch.eye(4, dtype=torch.complex128)]
noise_operators = [X(0) + Y(0)]
hamevo = HamEvo(td_generator, t, noise_operators = noise_operators)

values = {"omega": torch.tensor(10.0), "duration": torch.tensor(1.0)}
Expand All @@ -78,3 +78,6 @@ out_state = run(hamevo, values = values, configuration = config)

print(out_state)
```

!!! warning "Noise operators definition"
Note it is not possible to define `noise_operators` with parametric operators, and `noise_operators` should have the same or a subset of the qubit support of the `HamEvo` instance.
13 changes: 12 additions & 1 deletion qadence/backends/pyqtorch/convert_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,17 @@ def convert_block(
else:
generator = convert_block(block.generator, n_qubits, config)[0] # type: ignore[arg-type]
time_param = config.get_param_name(block)[0]

# convert noise operators here
noise_operators: list = [
convert_block(noise_block, config=config)[0] for noise_block in block.noise_operators
]
if len(noise_operators) > 0:
# squeeze batch size for noise operators
noise_operators = [
pyq_op.tensor(full_support=qubit_support).squeeze(-1) for pyq_op in noise_operators
]

return [
pyq.HamiltonianEvolution(
qubit_support=qubit_support,
Expand All @@ -253,7 +264,7 @@ def convert_block(
duration=duration,
solver=config.ode_solver,
steps=config.n_steps_hevo,
noise_operators=block.noise_operators,
noise_operators=noise_operators,
)
]

Expand Down
24 changes: 15 additions & 9 deletions qadence/operations/ham_evo.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def __init__(
parameter: TParameter,
qubit_support: tuple[int, ...] = None,
duration: TParameter | None = None,
noise_operators: list[Tensor] = list(),
noise_operators: list[AbstractBlock] = list(),
):
params = {}
if qubit_support is None and not isinstance(generator, AbstractBlock):
Expand Down Expand Up @@ -153,14 +153,20 @@ def __init__(
self.time_param = parameter
self.generator = generator
self.duration = duration
if len(noise_operators) > 0 and not all(
[
len(op.size()) == 2 and op.size(0) == op.size(1) == 2 ** len(self.qubit_support)
for op in noise_operators
]
):
correct_shape = (2 ** len(self.qubit_support),) * 2
raise ValueError(f"Noise operators should be square tensors of size {correct_shape}")

if len(noise_operators) > 0:
if not all(
[
len(set(op.qubit_support + self.qubit_support) - set(self.qubit_support)) == 0
for op in noise_operators
]
):
raise ValueError(
"Noise operators should be defined"
" over the same or a subset of the qubit support"
)
if True in [op.is_parametric for op in noise_operators]:
raise ValueError("Parametric operators are not supported")
self.noise_operators = noise_operators

@classmethod
Expand Down
36 changes: 30 additions & 6 deletions tests/backends/pyq/test_time_dependent_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@
from metrics import MIDDLE_ACCEPTANCE
from pyqtorch.utils import SolverType

from qadence import AbstractBlock, HamEvo, QuantumCircuit, QuantumModel, Register, run
from qadence import (
AbstractBlock,
HamEvo,
QuantumCircuit,
QuantumModel,
Register,
block_to_tensor,
run,
)
from qadence.operations import RZ, I, X


@pytest.mark.parametrize("duration", [0.5, 1.0])
Expand Down Expand Up @@ -56,22 +65,22 @@ def test_time_dependent_generator(


@pytest.mark.parametrize("duration", [0.5, 1.0])
@pytest.mark.parametrize("noise_op", [I(0) * I(1), X(0)])
def test_noisy_time_dependent_generator(
qadence_generator: AbstractBlock,
qutip_generator: Callable,
time_param: str,
feature_param_x: float,
feature_param_y: float,
duration: float,
noise_op: AbstractBlock,
) -> None:
n_steps = 500
ode_solver = SolverType.DP5_ME
n_qubits = 2
n_qubits_pow2 = 2**n_qubits

# Define jump operators
# Note that we squeeze to remove the batch dimension
list_ops = [torch.eye(n_qubits_pow2, dtype=torch.complex128)]
list_ops = [noise_op]

# simulate with qadence HamEvo using QuantumModel
hamevo = HamEvo(qadence_generator, time_param, noise_operators=list_ops)
Expand All @@ -94,9 +103,24 @@ def test_noisy_time_dependent_generator(

# simulate with qutip
t_points = np.linspace(0, duration, n_steps)
list_ops_qutip = [qutip.qeye(n_qubits_pow2)]
result = qutip.mesolve(qutip_generator, qutip.basis(n_qubits_pow2, 0), t_points, list_ops_qutip)
noise_tensor = (
block_to_tensor(noise_op, qubit_support=tuple(range(n_qubits)), use_full_support=True)
.squeeze(0)
.numpy()
)
list_ops_qutip = [qutip.Qobj(noise_tensor)]
result = qutip.mesolve(qutip_generator, qutip.basis(2**n_qubits, 0), t_points, list_ops_qutip)

state_qutip = torch.tensor(result.states[-1].full()).unsqueeze(0)
assert torch.allclose(state_qadence0, state_qutip, atol=MIDDLE_ACCEPTANCE)
assert torch.allclose(state_qadence1, state_qutip, atol=MIDDLE_ACCEPTANCE)


@pytest.mark.parametrize("noise_op", [I(0) * I(1) * I(3), X(3), RZ(0, "theta")])
def test_error_noise_operators_hamevo(
qadence_generator: AbstractBlock,
time_param: str,
noise_op: AbstractBlock,
) -> None:
with pytest.raises(ValueError):
hamevo = HamEvo(qadence_generator, time_param, noise_operators=[noise_op])

0 comments on commit b819959

Please sign in to comment.