Hello,

I have a hybrid quantum neuronal network build with pennylane and TensorFlow. For my application I need to train it, save it to disk and later reload it into another application. Unfortunately, using the standard TensorFlow model.safe function on the entire model does not work, as TensorFlow can not process the pennylane information correctly.

(Depending on the format used for saving, the error is raised either during saving or loading. When saving the weights into an ‘.h5’ file, they can be loaded without raising an error, but the resulting model is not equal to the model that has been saved.)

With some help I came to the conclusion to split the hybrid network into classical and quantum layers and save both of them separately. For this, I have the following function:

```
def save_hybrid_model( model , c_layers_path , q_layer_path ):
# 1) split off
quantum_layer = model.layers[-1] # in my case the quantum layer is always the last layer
model.pop() # pops off last layer (i.e.e quantum layer
# 2) safe classical part
model.save_weights(c_layers_path)
# 3) safe quantum part
qasm_string = quantum_layer.qnode.qtape.to_openqasm()
with open(q_layer_path, "w") as f:
f.write(qasm_string)
```

this is then to be loaded with the following function

```
def load_hybrid_model(c_layers_path, q_layer_path, hybrid_network_shapes):
# 1) Classical Layers
# 1.1) create classical layers
model = build_classical_layers(hybrid_network_shapes)
# 1.2) load classical weights
model.load_weights(c_layers_path)
# 2) Quantum layer
# 2.1) Load the PennyLane QNode from the QASM string
with open(q_layer_path, "r") as f:
qasm_string = f.read()
quantum_circuit = qml.from_qasm(qasm_string)
# 2.2) circuit to quantum layer
quantum_layer = build_quantum_layer(quantum_circuit, hybrid_network_shapes)
# 2.3) add to other layers
model.add(quantum_layer)
```

However, this leads to an error,

```
qiskit.qasm2.exceptions.QASM2ParseError: "<input>:5,3: 'tf' is not a parameter or custom instruction defined in this scope"
```

A look at the ‘qasm_string’ shows that it is something like

```
'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[3];\ncreg c[3];\nrx(tf.Tensor(\n[-0.2504485 -0.16435128 0.10509843 -0.22871123 -0.00794225 -0.50951445\n -0.5434188 0.00815093 -0.4871847 -0.1050231 ], shape=(10,), dtype=float32)) q[0];\nrx(tf.Tensor(\n[ 0.6483429 -0.14002065 0.48616743 -0.10395715 0.23674686 0.3900744\n 0.70458996 0.50224775 0.34395373 0.34140524], shape=(10,), dtype=float32)) q[1];\nrx(tf.Tensor(\n[0.6155473 0.6074002 0.371422 0.5381343 0.69457984 0.75203544\n 0.73663884 0.31011117 0.67915124 0.35447973], shape=(10,), dtype=float32)) q[2];\nrx(tf.Tensor(-0.5243009, shape=(), dtype=float32)) q[0];\nrx(tf.Tensor(0.9711641, shape=(), dtype=float32)) q[1];\nrx(tf.Tensor(-0.03388357, shape=(), dtype=float32)) q[2];\ncx q[0],q[1];\ncx q[1],q[2];\ncx q[2],q[0];\nmeasure q[0] -> c[0];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\n'
```

Therefore, the problem seems to be that the to_openqasm() method converts the circuit into a string that includes the TensorFlow Tensors and which from_qasm() can not read.

I guess this is a bug?

Would anyone know a workaround for this?

I already tried to use sting processing to convert the loaded qasm_string into something from_qasm() can read, but I do not have sufficient knowledge OPENQASM about how to do so.

For reference: I am using pennylane version 0.33.1 and TensorFlow version 2.13.0

And here some further code to reproduce the issue (together with the two functions shown above)

```
import os
import tensorflow as tf
import pennylane as qml
from pennylane import numpy as np
# ---- functions to build hybrid model ---- #
def build_classical_layers(hybrid_network_shapes):
neuron_numbers = hybrid_network_shapes['neuron_numbers']
num_qubits = hybrid_network_shapes['num_qubits']
# Create model
classical_layers = tf.keras.models.Sequential()
# add dense layers
for nn in neuron_numbers:
classical_layers.add(tf.keras.layers.Dense( nn , activation=tf.nn.tanh ))
# one more dense layer to connect to qantum circuit
classical_layers.add(tf.keras.layers.Dense(num_qubits, activation=tf.nn.tanh))
return classical_layers
def build_circuit(hybrid_network_shapes):
num_qubits = hybrid_network_shapes['num_qubits']
output_dim = hybrid_network_shapes['output_dim']
def circuit(inputs, weights):
qml.templates.AngleEmbedding(inputs, wires=range(len(inputs)) )
qml.templates.BasicEntanglerLayers(weights, wires=range(num_qubits) )
return [qml.expval(qml.PauliZ(wires=i)) for i in range(output_dim)]
return circuit
def build_quantum_layer(circuit, hybrid_network_shapes):
num_qubits = hybrid_network_shapes['num_qubits']
output_dim = hybrid_network_shapes['output_dim']
# get device
device = qml.device('default.qubit.tf', wires=num_qubits)
# quantum circuit to Qnode
qnode = qml.QNode(circuit, device=device, interface='tf')
# Qnode to keras layer
weight_shapes = {"weights": ( 1, num_qubits )}
quantum_layer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=output_dim)
return quantum_layer
# ---- create randomised data for training ---- #
def create_random_training_data(hybrid_network_shapes, num_examples=100):
input_dim = hybrid_network_shapes['neuron_numbers'][0]
output_dim = hybrid_network_shapes['output_dim']
# Generate random input data
input_data = np.random.rand(num_examples, input_dim, )
# Generate random output data (assuming a binary classification task)
output_data = np.random.randint(2, size=(num_examples, output_dim))
return input_data, output_data
# ---- puting it all together ---- #
# 0) definitions
c_layers_path = os.path.join('test_models','c_layers')
q_layer_path = os.path.join('test_models','q_layer')
hybrid_network_shapes = { 'neuron_numbers' : [ 21 , 9 , 6 ],
'num_qubits' : 3,
'output_dim' : 1 }
# 1) Build hybrid model
# 1.1) build classical layers
model = build_classical_layers(hybrid_network_shapes)
# 1.2) build quantum ciruit
circuit = build_circuit(hybrid_network_shapes)
# 1.3) add as pennylane quantum layer
model.add(build_quantum_layer(circuit, hybrid_network_shapes))
# 1.4) compile model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# 2) Train model
# 2.1) create example training data
input_data, output_data = create_random_training_data(hybrid_network_shapes)
# 2.2) train for one epoch
model.fit(input_data, output_data, epochs=1, batch_size=10)
# 3) save model
save_hybrid_model( model , c_layers_path , q_layer_path )
# 4) load model (will raise the described error)
load_hybrid_model(c_layers_path , q_layer_path , hybrid_network_shapes)
```