hi @CatalinaAlbornoz
No unfortunately I haven’t solved the issue. I think I understand what map_wires() did to H_tapered but then after updating the code to use a consecutive range(0,8) for the wires argument triggers a runtime error that suggests some tapered operations are still acting on wire 8.
import pennylane as qml
from jax import numpy as jnp
import jax
jax.config.update('jax_enable_x64', True)
jax.config.update("jax_platform_name", "cpu")
Tapering the molecular Hamiltonian
symbols = ['Li','H']
geometry = jnp.array([[0.0000, 0.0000, -1.5065],
[0.0000, 0.0000, 1.5065]])
n_electrons = 4
charge = 0
molecule = qml.qchem.Molecule(symbols, geometry, charge=charge)
H, qubits = qml.qchem.molecular_hamiltonian(molecule, mapping='jordan_wigner')#, active_electrons=2)# active_electrons=2, active_orbitals=3, mapping='parity')
generators = qml.symmetry_generators(H)
paulixops = qml.paulix_ops(generators, qubits)
for idx, generator in enumerate(generators):
print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}")
generator 1: Z(6) @ Z(7), paulix_op: X(7)
generator 2: Z(8) @ Z(9), paulix_op: X(9)
generator 3: Z(0) @ Z(2) @ Z(4) @ Z(6) @ Z(8) @ Z(10), paulix_op: X(10)
generator 4: Z(1) @ Z(3) @ Z(5) @ Z(6) @ Z(8) @ Z(11), paulix_op: X(11)
paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons)
print(paulix_sector)
[1, 1, 1, 1]
H_tapered = qml.taper(H, generators, paulixops, paulix_sector)
H_tapered_coeffs, H_tapered_ops = H_tapered.terms()
H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops)
#print(H_tapered)
tap_wires = sorted(H_tapered.wires)
print (tap_wires)
print (H_tapered[0:3])
sorted_wires = range(len(tap_wires))
print (sorted_wires)
wire_map = dict(zip(tap_wires, sorted_wires))
print (wire_map)
H_tapered = H_tapered.map_wires(wire_map)
print(H_tapered[0:3])
[0, 1, 2, 3, 4, 5, 6, 8]
(-3.9776266333429193 * I(), -0.3857829818098049 * (Z(1) @ Z(3) @ Z(5) @ Z(6) @ Z(8)), -0.3857829818098048 * (Z(6) @ Z(8) @ Z(0) @ Z(2) @ Z(4)))
range(0, 8)
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 8: 7}
(-3.9776266333429193 * I(), -0.3857829818098049 * (Z(1) @ Z(3) @ Z(5) @ Z(6) @ Z(7)), -0.3857829818098048 * (Z(6) @ Z(7) @ Z(0) @ Z(2) @ Z(4)))
H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires)
num_wires = len(H_tapered.wires)
H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=sorted_wires)
print (H_sparse)
print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16))
print (H_tapered_sparse)
print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4))
SparseHamiltonian(<Compressed Sparse Row sparse matrix of dtype 'complex128'
with 102400 stored elements and shape (4096, 4096)>, wires=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
Eigenvalues of H:
[-7.88241061 -7.80635148 -7.80635148 -7.74919247 -7.76638866 -7.76638866
-7.76638866 -7.72622282 -7.72622282 -7.72622282 -7.72622282 -7.71643788
-7.71643788 -7.71643788 -7.71643788 -7.71643788]
SparseHamiltonian(<Compressed Sparse Row sparse matrix of dtype 'complex128'
with 6464 stored elements and shape (256, 256)>, wires=[0, 1, 2, 3, 4, 5, 6, 7])
Eigenvalues of H_tapered:
[-7.88241061 -7.76638866 -7.74919247 -7.48243879]
Tapering the reference state
state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector,
num_electrons=n_electrons, num_wires=len(sorted_wires))
print(state_tapered)
[1 1 1 1 0 0 0 0]
dev = qml.device("default.qubit", wires=H.wires)
@qml.qnode(dev, interface="jax")
def circuit():
qml.BasisState(jnp.array([1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]), wires=H.wires)
return qml.state()
qubit_state = circuit()
HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state
print(f"HF energy: {jnp.real(HF_energy):.8f} Ha")
# Tapered Hamiltonian
num_wires = len(sorted_wires)
dev = qml.device("lightning.qubit", wires=sorted_wires)
@qml.qnode(dev, interface="jax")
def circuit():
qml.BasisState(jnp.array([1, 1, 1, 1, 0, 0, 0, 0 ]), wires=sorted_wires)
return qml.state()
qubit_state = circuit()
HF_energy = qubit_state.T @ H_tapered.sparse_matrix(wire_order=sorted_wires).toarray() @ qubit_state
print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha")
HF energy: -7.86204208 Ha
HF energy (tapered): -7.86204208 Ha
VQE simulation
singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires))
tapered_doubles = [
qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector,
wire_order=H.wires, op_wires=double) for double in doubles
]
tapered_singles = [
qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector,
wire_order=H.wires, op_wires=single) for single in singles
]
dev = qml.device("lightning.qubit", wires=sorted_wires)
@qml.qnode(dev, interface="jax")
def tapered_circuit(params):
qml.BasisState(jnp.array([1, 1, 1, 1, 0, 0, 0, 0]), wires=sorted_wires)
for idx, tapered_op in enumerate(tapered_doubles + tapered_singles):
tapered_op(params[idx])
return qml.expval(H_tapered)
import optax
import catalyst
opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent
init_params = jnp.zeros(len(doubles) + len(singles))
def update_step(i, params, opt_state):
"""Perform a single gradient update step"""
grads = catalyst.grad(tapered_circuit)(params)
updates, opt_state = opt.update(grads, opt_state)
params = optax.apply_updates(params, updates)
return (params, opt_state)
loss_history = []
opt_state = opt.init(init_params)
params = init_params
for i in range(1, 21):
params, opt_state = update_step(i, params, opt_state)
energy = tapered_circuit(params)
if not i % 5:
print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}")
---------------------------------------------------------------------------
WireError Traceback (most recent call last)
Cell In[15], line 17
14 params = init_params
16 for i in range(1, 21):
---> 17 params, opt_state = update_step(i, params, opt_state)
18 energy = tapered_circuit(params)
19 if not i % 5:
Cell In[15], line 6, in update_step(i, params, opt_state)
4 def update_step(i, params, opt_state):
5 """Perform a single gradient update step"""
----> 6 grads = catalyst.grad(tapered_circuit)(params)
7 updates, opt_state = opt.update(grads, opt_state)
8 params = optax.apply_updates(params, updates)
File ~/miniforge3/envs/pennylane/lib/python3.12/site-packages/catalyst/api_extensions/differentiation.py:705, in GradCallable.__call__(self, *args, **kwargs)
703 results = jax.value_and_grad(self.fn, argnums=argnums)(*args, **kwargs)
704 else:
--> 705 results = jax.grad(self.fn, argnums=argnums)(*args, **kwargs)
706 else:
707 assert (
708 not self.grad_params.with_value
709 ), "value_and_grad cannot be used with a Jacobian"
[... skipping hidden 10 frame]
File ~/miniforge3/envs/pennylane/lib/python3.12/site-packages/pennylane/workflow/qnode.py:905, in QNode.__call__(self, *args, **kwargs)
903 if qml.capture.enabled():
904 return capture_qnode(self, *args, **kwargs)
--> 905 return self._impl_call(*args, **kwargs)
File ~/miniforge3/envs/pennylane/lib/python3.12/site-packages/pennylane/workflow/qnode.py:881, in QNode._impl_call(self, *args, **kwargs)
878 # Calculate the classical jacobians if necessary
879 self._transform_program.set_classical_component(self, args, kwargs)
--> 881 res = qml.execute(
882 (tape,),
883 device=self.device,
884 diff_method=self.diff_method,
885 interface=interface,
886 transform_program=self._transform_program,
887 gradient_kwargs=self.gradient_kwargs,
888 **self.execute_kwargs,
889 )
890 res = res[0]
892 # convert result to the interface in case the qfunc has no parameters
File ~/miniforge3/envs/pennylane/lib/python3.12/site-packages/pennylane/workflow/execution.py:227, in execute(tapes, device, diff_method, interface, transform_program, inner_transform, config, grad_on_execution, gradient_kwargs, cache, cachesize, max_diff, device_vjp, mcm_config, gradient_fn)
222 transform_program, inner_transform = _setup_transform_program(
223 transform_program, device, config, cache, cachesize
224 )
226 #### Executing the configured setup #####
--> 227 tapes, post_processing = transform_program(tapes)
229 if transform_program.is_informative:
230 return post_processing(tapes)
File ~/miniforge3/envs/pennylane/lib/python3.12/site-packages/pennylane/transforms/core/transform_program.py:580, in TransformProgram.__call__(self, tapes)
578 if argnums is not None:
579 tape.trainable_params = argnums[j]
--> 580 new_tapes, fn = transform(tape, *targs, **tkwargs)
581 execution_tapes.extend(new_tapes)
583 fns.append(fn)
File ~/miniforge3/envs/pennylane/lib/python3.12/site-packages/pennylane/devices/preprocess.py:148, in validate_device_wires(tape, wires, name)
142 raise WireError(
143 f"Cannot run circuit(s) on {name} as abstract wires are present in the device: {wires}. "
144 f"Abstract wires are not yet supported."
145 )
147 if extra_wires := set(tape.wires) - set(wires):
--> 148 raise WireError(
149 f"Cannot run circuit(s) on {name} as they contain wires "
150 f"not found on the device: {extra_wires}"
151 )
153 modified = False
154 new_ops = None
WireError: Cannot run circuit(s) on lightning.qubit as they contain wires not found on the device: {8}