Trouble evolving a Hamiltonian with no trainable parameters

Hello! I constructed a class that allows me to create a quantum system with a Hamiltonian that has two parts: a time-independent part and time-dependent part:

class QNN_layer():

    def __init__(self, n_qubits, n_basis, timespan = 1, basis='fourier', pulse_width = 0.5e-2):
        
        self.n_qubits = n_qubits
        self.n_basis = n_basis
        self.timespan = timespan
        self.basis = basis
        self.pulse_width = pulse_width

        self.create_parameterized_hamiltonian()

    def create_parameterized_hamiltonian(self):

        self.create_all_to_all_hamiltonian()
        self.create_control_hamiltonian()

        self.H = self.H_all_to_all + self.H_control

    def create_all_to_all_hamiltonian(self):
        # Defining the collective S_z operator
        # self.all_to_all_fields = [self.get_constant_field() for i in range(self.n_qubits)]

        S_z = (1/2)*sum([qml.PauliZ(i) for i in range(self.n_qubits)])
        self.S_z_squared = S_z @ S_z

        # self.H_all_to_all = qml.dot([self.get_constant_field()], [self.S_z_squared])
        self.H_all_to_all = qml.dot([1.0], [self.S_z_squared])
        
    def create_control_hamiltonian(self):
         MORE HERE BUT NOT RELEVANT

I’m trying to create a function that evolves the time-independent part in some interval ts=[0.0, 1.0] and calculates a value:

def get_loss_function_no_control(qnn_layer, ts, S_x, S_z, dev):

    @jax.jit
    @qml.qnode(dev, interface="jax")
    def evol_no_control(param_vector):

        # hamitlonian_params = dictionary_to_hamiltonian_parameters(params_dict)

        param_list = qnn_layer.vector_to_hamiltonian_parameters(param_vector)

        for i in range(qnn_layer.n_qubits): # Initializing in CSS
            qml.Hadamard(wires=i)

        qml.evolve(qnn_layer.H_all_to_all)(ts=ts) # Just doing the squeezing

        return qml.var(S_z), qml.expval(S_x)
    
    def loss_no_control(param_vector):
        var, exp = evol_no_control(param_vector)
        
        return qnn_layer.n_qubits * (var / (exp**2))

    return loss_no_control

# Seed to make the results reproducible
seed = 0

# Setting up the problem
n_qubits = 5
n_basis = 6
timespan = 1.0
basis = 'gaussian'
pulse_width = 0.5e-2

ts = jnp.array([0.0, 1.0])

dev = qml.device("default.qubit.jax", wires = n_qubits)
qnn_layer = QNN_layer(n_qubits, n_basis, timespan=timespan, basis=basis, pulse_width=pulse_width)

S_x = (1/2)*sum([qml.PauliX(i) for i in range(qnn_layer.n_qubits)])
S_z = (1/2)*sum([qml.PauliZ(i) for i in range(qnn_layer.n_qubits)])

# Seed to make the results reproducible
seed = 0

loss_no_control = get_loss_function_no_control(qnn_layer, ts, S_x, S_z, dev)

param_vector = qnn_layer.get_random_parameter_vector(seed)

print(f'Initial loss: {loss_no_control(param_vector)}')

However, I get the error

{
	"name": "TypeError",
	"message": "'Evolution' object is not callable",
	"stack": "---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[69], line 8
      4 loss_no_control = get_loss_function_no_control(qnn_layer, ts, S_x, S_z, dev)
      6 param_vector = qnn_layer.get_random_parameter_vector(seed)
----> 8 print(f'Initial loss: {loss_no_control(param_vector)}')

Cell In[68], line 19, in get_loss_function_no_control.<locals>.loss_no_control(param_vector)
     18 def loss_no_control(param_vector):
---> 19     var, exp = evol_no_control(param_vector)
     21     return qnn_layer.n_qubits * (var / (exp**2))

    [... skipping hidden 11 frame]

File ~/.../pennylane/workflow/qnode.py:1164, in QNode.__call__(self, *args, **kwargs)
   1162 if qml.capture.enabled():
   1163     return qml.capture.qnode_call(self, *args, **kwargs)
-> 1164 return self._impl_call(*args, **kwargs)

File ~/.../pennylane/workflow/qnode.py:1144, in QNode._impl_call(self, *args, **kwargs)
   1141     override_shots = kwargs[\"shots\"]
   1143 # construct the tape
-> 1144 self.construct(args, kwargs)
   1146 original_grad_fn = [self.gradient_fn, self.gradient_kwargs, self.device]
   1147 self._update_gradient_fn(shots=override_shots, tape=self._tape)

File ~/.../pennylane/logging/decorators.py:61, in log_string_debug_func.<locals>.wrapper_entry(*args, **kwargs)
     54     s_caller = \"::L\".join(
     55         [str(i) for i in inspect.getouterframes(inspect.currentframe(), 2)[1][1:3]]
     56     )
     57     lgr.debug(
     58         f\"Calling {f_string} from {s_caller}\",
     59         **_debug_log_kwargs,
     60     )
---> 61 return func(*args, **kwargs)

File ~/.../pennylane/workflow/qnode.py:966, in QNode.construct(self, args, kwargs)
    964 with pldb_device_manager(self.device):
    965     with qml.queuing.AnnotatedQueue() as q:
--> 966         self._qfunc_output = self.func(*args, **kwargs)
    968 self._tape = QuantumScript.from_queue(q, shots)
    970 params = self.tape.get_parameters(trainable_only=False)

Cell In[68], line 14, in get_loss_function_no_control.<locals>.evol_no_control(param_vector)
     11 for i in range(qnn_layer.n_qubits): # Initializing in CSS
     12     qml.Hadamard(wires=i)
---> 14 qml.evolve(qnn_layer.H_all_to_all)(ts=ts) # Just doing the squeezing
     16 return qml.var(S_z), qml.expval(S_x)

TypeError: 'Evolution' object is not callable"
}

I see that here, instead of having a ParameterizedEvolution object as I was getting before with time-dependent control fields, now I’m just getting an Evolution object. How can I make it such that I calculate the value in loss_no_control that I need? Thank you!

Hi @NickGut0711 ,

I’m not being able to reproduce your problem.

Could you please share a minimal reproducible example so that we can look for the root of the problem? A minimal reproducible example (or minimal working example) is the simplest version of the code that reproduces the problem. It should be self-contained, including all necessary imports, data, functions, etc., so that we can copy-paste the code and reproduce the problem. However it shouldn’t contain any unnecessary data, functions, …, for example gates and functions that can be removed to simplify the code.

If you’re not sure what this means then please make sure to check out this video.

That being said, it might be good to note that for time-dependent evolution you might want to use qml.evolve() and for time-independent evolution you might want to use qml.ApproxTimeEvolution().

Your error seems to be related to the time-dependent evolution part. I would recommend starting from the examples in the documentation for qml.evolve() and then adapting them to your use case. For example you might want to use t= instead of ts=. It can also help to consult the documentation for qml.pulse.ParametrizedEvolution().

I hope this helps!