##### Copyright 2020 The TensorFlow Authors.

```
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
```

# MNIST classification

    View on TensorFlow.org

    Run in Google Colab

    View source on GitHub

    Download notebook

This tutorial builds a quantum neural network (QNN) to classify a simplified version of MNIST, similar to the approach used in Farhi et al. The performance of the quantum neural network on this classical data problem is compared with a classical neural network.

## Setup

```
!pip install tensorflow==2.3.1
```

    Collecting tensorflow==2.3.1
      Downloading https://files.pythonhosted.org/packages/eb/18/374af421dfbe74379a458e58ab40cf46b35c3206ce8e183e28c1c627494d/tensorflow-2.3.1-cp37-cp37m-manylinux2010_x86_64.whl (320.4MB)
         |████████████████████████████████| 320.4MB 50kB/s
    Requirement already satisfied: absl-py>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.12.0)
    Requirement already satisfied: keras-preprocessing<1.2,>=1.1.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.1.2)
    Requirement already satisfied: gast==0.3.3 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.3.3)
    Requirement already satisfied: six>=1.12.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.15.0)
    Collecting numpy<1.19.0,>=1.16.0
      Downloading https://files.pythonhosted.org/packages/d6/c6/58e517e8b1fb192725cfa23c01c2e60e4e6699314ee9684a1c5f5c9b27e1/numpy-1.18.5-cp37-cp37m-manylinux1_x86_64.whl (20.1MB)
         |████████████████████████████████| 20.1MB 1.3MB/s
    Requirement already satisfied: opt-einsum>=2.3.2 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (3.3.0)
    Collecting tensorflow-estimator<2.4.0,>=2.3.0
      Downloading https://files.pythonhosted.org/packages/e9/ed/5853ec0ae380cba4588eab1524e18ece1583b65f7ae0e97321f5ff9dfd60/tensorflow_estimator-2.3.0-py2.py3-none-any.whl (459kB)
         |████████████████████████████████| 460kB 47.3MB/s
    Requirement already satisfied: wrapt>=1.11.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.12.1)
    Requirement already satisfied: wheel>=0.26 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.36.2)
    Requirement already satisfied: google-pasta>=0.1.8 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.2.0)
    Requirement already satisfied: tensorboard<3,>=2.3.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (2.4.1)
    Requirement already satisfied: h5py<2.11.0,>=2.10.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (2.10.0)
    Requirement already satisfied: grpcio>=1.8.6 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.32.0)
    Requirement already satisfied: astunparse==1.6.3 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.6.3)
    Requirement already satisfied: protobuf>=3.9.2 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (3.12.4)
    Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.1.0)
    Requirement already satisfied: google-auth-oauthlib<0.5,>=0.4.1 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (0.4.4)
    Requirement already satisfied: markdown>=2.6.8 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.3.4)
    Requirement already satisfied: requests<3,>=2.21.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (2.23.0)
    Requirement already satisfied: google-auth<2,>=1.6.3 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.28.1)
    Requirement already satisfied: werkzeug>=0.11.15 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.0.1)
    Requirement already satisfied: setuptools>=41.0.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (56.0.0)
    Requirement already satisfied: tensorboard-plugin-wit>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.8.0)
    Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from google-auth-oauthlib<0.5,>=0.4.1->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.3.0)
    Requirement already satisfied: importlib-metadata; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from markdown>=2.6.8->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.10.1)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.24.3)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (2020.12.5)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (2.10)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.0.4)
    Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (0.2.8)
    Requirement already satisfied: rsa<5,>=3.1.4; python_version >= "3.6" in /usr/local/lib/python3.7/dist-packages (from google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (4.7.2)
    Requirement already satisfied: cachetools<5.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (4.2.1)
    Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib<0.5,>=0.4.1->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.1.0)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata; python_version < "3.8"->markdown>=2.6.8->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.4.1)
    Requirement already satisfied: typing-extensions>=3.6.4; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from importlib-metadata; python_version < "3.8"->markdown>=2.6.8->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.7.4.3)
    Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/dist-packages (from pyasn1-modules>=0.2.1->google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (0.4.8)
    ERROR: datascience 0.10.6 has requirement folium==0.2.1, but you'll have folium 0.8.3 which is incompatible.
    ERROR: albumentations 0.1.12 has requirement imgaug<0.2.7,>=0.2.5, but you'll have imgaug 0.2.9 which is incompatible.
    Installing collected packages: numpy, tensorflow-estimator, tensorflow
      Found existing installation: numpy 1.19.5
        Uninstalling numpy-1.19.5:
          Successfully uninstalled numpy-1.19.5
      Found existing installation: tensorflow-estimator 2.4.0
        Uninstalling tensorflow-estimator-2.4.0:
          Successfully uninstalled tensorflow-estimator-2.4.0
      Found existing installation: tensorflow 2.4.1
        Uninstalling tensorflow-2.4.1:
          Successfully uninstalled tensorflow-2.4.1
    Successfully installed numpy-1.18.5 tensorflow-2.3.1 tensorflow-estimator-2.3.0

Install TensorFlow Quantum:

```
!pip install tensorflow-quantum
```

    Collecting tensorflow-quantum
      Downloading https://files.pythonhosted.org/packages/53/02/878b2d4e7711f5c7f8dff9ff838e8ed84d218a359154ce06c7c01178a125/tensorflow_quantum-0.4.0-cp37-cp37m-manylinux2010_x86_64.whl (5.9MB)
         |████████████████████████████████| 5.9MB 4.2MB/s
    Collecting sympy==1.5
      Downloading https://files.pythonhosted.org/packages/4d/a7/25d5d6b3295537ab90bdbcd21e464633fb4a0684dd9a065da404487625bb/sympy-1.5-py2.py3-none-any.whl (5.6MB)
         |████████████████████████████████| 5.6MB 26.4MB/s
    Collecting cirq==0.9.1
      Downloading https://files.pythonhosted.org/packages/18/05/39c24828744b91f658fd1e5d105a9d168da43698cfaec006179c7646c71c/cirq-0.9.1-py3-none-any.whl (1.6MB)
         |████████████████████████████████| 1.6MB 45.9MB/s
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy==1.5->tensorflow-quantum) (1.2.1)
    Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (3.7.4.3)
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.1.5)
    Requirement already satisfied: networkx~=2.4 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (2.5.1)
    Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.4.1)
    Requirement already satisfied: google-api-core[grpc]<2.0.0dev,>=1.14.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.26.3)
    Requirement already satisfied: protobuf~=3.12.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (3.12.4)
    Requirement already satisfied: numpy~=1.16 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.18.5)
    Requirement already satisfied: sortedcontainers~=2.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (2.3.0)
    Requirement already satisfied: matplotlib~=3.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (3.2.2)
    Collecting freezegun~=0.3.15
      Downloading https://files.pythonhosted.org/packages/17/5d/1b9d6d3c7995fff473f35861d674e0113a5f0bd5a72fe0199c3f254665c7/freezegun-0.3.15-py2.py3-none-any.whl
    Requirement already satisfied: requests~=2.18 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (2.23.0)
    Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas->cirq==0.9.1->tensorflow-quantum) (2.8.1)
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas->cirq==0.9.1->tensorflow-quantum) (2018.9)
    Requirement already satisfied: decorator<5,>=4.3 in /usr/local/lib/python3.7/dist-packages (from networkx~=2.4->cirq==0.9.1->tensorflow-quantum) (4.4.2)
    Requirement already satisfied: google-auth<2.0dev,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.28.1)
    Requirement already satisfied: setuptools>=40.3.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (56.0.0)
    Requirement already satisfied: six>=1.13.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.15.0)
    Requirement already satisfied: packaging>=14.3 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (20.9)
    Requirement already satisfied: googleapis-common-protos<2.0dev,>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.53.0)
    Requirement already satisfied: grpcio<2.0dev,>=1.29.0; extra == "grpc" in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.32.0)
    Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq==0.9.1->tensorflow-quantum) (2.4.7)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq==0.9.1->tensorflow-quantum) (0.10.0)
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq==0.9.1->tensorflow-quantum) (1.3.1)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (2020.12.5)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (1.24.3)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (3.0.4)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (2.10)
    Requirement already satisfied: rsa<5,>=3.1.4; python_version >= "3.6" in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (4.7.2)
    Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (0.2.8)
    Requirement already satisfied: cachetools<5.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (4.2.1)
    Requirement already satisfied: pyasn1>=0.1.3 in /usr/local/lib/python3.7/dist-packages (from rsa<5,>=3.1.4; python_version >= "3.6"->google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (0.4.8)
    Installing collected packages: sympy, freezegun, cirq, tensorflow-quantum
      Found existing installation: sympy 1.7.1
        Uninstalling sympy-1.7.1:
          Successfully uninstalled sympy-1.7.1
    Successfully installed cirq-0.9.1 freezegun-0.3.15 sympy-1.5 tensorflow-quantum-0.4.0

Now import TensorFlow and the module dependencies:

```
import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np
import seaborn as sns
import collections

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
```

## 1. Load the data

In this tutorial you will build a binary classifier to distinguish between the digits 3 and 6, following Farhi et al. This section covers the data handling that:

- Loads the raw data from Keras.
- Filters the dataset to only 3s and 6s.
- Downscales the images so they fit can fit in a quantum computer.
- Removes any contradictory examples.
- Converts the binary images to Cirq circuits.
- Converts the Cirq circuits to TensorFlow Quantum circuits.

### 1.1 Load the raw data

Load the MNIST dataset distributed with Keras.

```
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

# Rescale the images from [0,255] to the [0.0,1.0] range.
x_train, x_test = x_train[..., np.newaxis]/255.0, x_test[..., np.newaxis]/255.0

print("Number of original training examples:", len(x_train))
print("Number of original test examples:", len(x_test))
```

    Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
    11493376/11490434 [==============================] - 0s 0us/step
    Number of original training examples: 60000
    Number of original test examples: 10000

Filter the dataset to keep just the 3s and 6s,  remove the other classes. At the same time convert the label, `y`, to boolean: `True` for `3` and `False` for 6.

```
def filter_36(x, y):
    keep = (y == 3) | (y == 6)
    x, y = x[keep], y[keep]
    y = y == 3
    return x,y
```

```
x_train, y_train = filter_36(x_train, y_train)
x_test, y_test = filter_36(x_test, y_test)

print("Number of filtered training examples:", len(x_train))
print("Number of filtered test examples:", len(x_test))
```

    Number of filtered training examples: 12049
    Number of filtered test examples: 1968

Show the first example:

```
print(y_train[0])

plt.imshow(x_train[0, :, :, 0])
plt.colorbar()
```

### 1.2 Downscale the images

An image size of 28x28 is much too large for current quantum computers. Resize the image down to 4x4:

```
x_train_small = tf.image.resize(x_train, (4,4)).numpy()
x_test_small = tf.image.resize(x_test, (4,4)).numpy()
```

Again, display the first training example—after resize:

```
print(y_train[0])

plt.imshow(x_train_small[0,:,:,0], vmin=0, vmax=1)
plt.colorbar()
```

### 1.3 Remove contradictory examples

From section *3.3 Learning to Distinguish Digits* of Farhi et al., filter the dataset to remove images that are labeled as belonging to both classes.

This is not a standard machine-learning procedure, but is included in the interest of following the paper.

```
def remove_contradicting(xs, ys):
    mapping = collections.defaultdict(set)
    orig_x = {}
    # Determine the set of labels for each unique image:
    for x,y in zip(xs,ys):
       orig_x[tuple(x.flatten())] = x
       mapping[tuple(x.flatten())].add(y)

    new_x = []
    new_y = []
    for flatten_x in mapping:
      x = orig_x[flatten_x]
      labels = mapping[flatten_x]
      if len(labels) == 1:
          new_x.append(x)
          new_y.append(next(iter(labels)))
      else:
          # Throw out images that match more than one label.
          pass

    num_uniq_3 = sum(1 for value in mapping.values() if len(value) == 1 and True in value)
    num_uniq_6 = sum(1 for value in mapping.values() if len(value) == 1 and False in value)
    num_uniq_both = sum(1 for value in mapping.values() if len(value) == 2)

    print("Number of unique images:", len(mapping.values()))
    print("Number of unique 3s: ", num_uniq_3)
    print("Number of unique 6s: ", num_uniq_6)
    print("Number of unique contradicting labels (both 3 and 6): ", num_uniq_both)
    print()
    print("Initial number of images: ", len(xs))
    print("Remaining non-contradicting unique images: ", len(new_x))

    return np.array(new_x), np.array(new_y)
```

The resulting counts do not closely match the reported values, but the exact procedure is not specified.

It is also worth noting here that applying filtering contradictory examples at this point does not totally prevent the model from receiving contradictory training examples: the next step binarizes the data which will cause more collisions.

```
x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)
```

    Number of unique images: 10387
    Number of unique 3s:  4912
    Number of unique 6s:  5426
    Number of unique contradicting labels (both 3 and 6):  49

    Initial number of images:  12049
    Remaining non-contradicting unique images:  10338

### 1.4 Encode the data as quantum circuits

To process images using a quantum computer, Farhi et al. proposed representing each pixel with a qubit, with the state depending on the value of the pixel. The first step is to convert to a binary encoding.

```
THRESHOLD = 0.5

x_train_bin = np.array(x_train_nocon > THRESHOLD, dtype=np.float32)
x_test_bin = np.array(x_test_small > THRESHOLD, dtype=np.float32)
```

If you were to remove contradictory images at this point you would be left with only 193, likely not enough for effective training.

```
_ = remove_contradicting(x_train_bin, y_train_nocon)
```

    Number of unique images: 193
    Number of unique 3s:  80
    Number of unique 6s:  69
    Number of unique contradicting labels (both 3 and 6):  44

    Initial number of images:  10338
    Remaining non-contradicting unique images:  149

The qubits at pixel indices with values that exceed a threshold, are rotated through an $X$ gate.

```
def convert_to_circuit(image):
    """Encode truncated classical image into quantum datapoint."""
    values = np.ndarray.flatten(image)
    qubits = cirq.GridQubit.rect(4, 4)
    circuit = cirq.Circuit()
    for i, value in enumerate(values):
        if value:
            circuit.append(cirq.X(qubits[i]))
    return circuit

x_train_circ = [convert_to_circuit(x) for x in x_train_bin]
x_test_circ = [convert_to_circuit(x) for x in x_test_bin]
```

Here is the circuit created for the first example (circuit diagrams do not show qubits with zero gates):

```
SVGCircuit(x_train_circ[0])
```

    findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.

Compare this circuit to the indices where the image value exceeds the threshold:

```
bin_img = x_train_bin[0,:,:,0]
indices = np.array(np.where(bin_img)).T
indices
```

    array([[2, 2],
           [3, 1]])

Convert these `Cirq` circuits to tensors for `tfq`:

```
x_train_tfcirc = tfq.convert_to_tensor(x_train_circ)
x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)
```

## 2. Quantum neural network

There is little guidance for a quantum circuit structure that classifies images. Since the classification is based on the expectation of the readout qubit, Farhi et al. propose using two qubit gates, with the readout qubit always acted upon. This is similar in some ways to running small a Unitary RNN across the pixels.

### 2.1 Build the model circuit

This following example shows this layered approach. Each layer uses *n* instances of the same gate, with each of the data qubits acting on the readout qubit.

Start with a simple class that will add a layer of these gates to a circuit:

```
class CircuitLayerBuilder():
    def __init__(self, data_qubits, readout):
        self.data_qubits = data_qubits
        self.readout = readout

    def add_layer(self, circuit, gate, prefix):
        for i, qubit in enumerate(self.data_qubits):
            symbol = sympy.Symbol(prefix + '-' + str(i))
            circuit.append(gate(qubit, self.readout)**symbol)
```

Build an example circuit layer to see how it looks:

```
demo_builder = CircuitLayerBuilder(data_qubits = cirq.GridQubit.rect(4,1),
                                   readout=cirq.GridQubit(-1,-1))

circuit = cirq.Circuit()
demo_builder.add_layer(circuit, gate = cirq.XX, prefix='xx')
SVGCircuit(circuit)
```

Now build a two-layered model, matching the data-circuit size, and include the preparation and readout operations.

```
def create_quantum_model():
    """Create a QNN model circuit and readout operation to go along with it."""
    data_qubits = cirq.GridQubit.rect(4, 4)  # a 4x4 grid.
    readout = cirq.GridQubit(-1, -1)         # a single qubit at [-1,-1]
    circuit = cirq.Circuit()

    # Prepare the readout qubit.
    circuit.append(cirq.X(readout))
    circuit.append(cirq.H(readout))

    builder = CircuitLayerBuilder(
        data_qubits = data_qubits,
        readout=readout)

    # Then add layers (experiment by adding more).
    builder.add_layer(circuit, cirq.XX, "xx1")
    builder.add_layer(circuit, cirq.ZZ, "zz1")

    # Finally, prepare the readout qubit.
    circuit.append(cirq.H(readout))

    return circuit, cirq.Z(readout)
```

```
model_circuit, model_readout = create_quantum_model()
```

### 2.2 Wrap the model-circuit in a tfq-keras model

Build the Keras model with the quantum components. This model is fed the "quantum data", from `x_train_circ`, that encodes the classical data. It uses a *Parametrized Quantum Circuit* layer, `tfq.layers.PQC`, to train the model circuit, on the quantum data.

To classify these images, Farhi et al. proposed taking the expectation of a readout qubit in a parameterized circuit. The expectation returns a value between 1 and -1.

```
# Build the Keras model.
model = tf.keras.Sequential([
    # The input is the data-circuit, encoded as a tf.string
    tf.keras.layers.Input(shape=(), dtype=tf.string),
    # The PQC layer returns the expected value of the readout gate, range [-1,1].
    tfq.layers.PQC(model_circuit, model_readout),
])
```

Next, describe the training procedure to the model, using the `compile` method.

Since the the expected readout is in the range `[-1,1]`, optimizing the hinge loss is a somewhat natural fit.

Note: Another valid approach would be to shift the output range to `[0,1]`, and treat it as the probability the model assigns to class `3`. This could be used with a standard a `tf.losses.BinaryCrossentropy` loss.

To use the hinge loss here you need to make two small adjustments. First convert the labels, `y_train_nocon`, from boolean to `[-1,1]`, as expected by the hinge loss.

```
y_train_hinge = 2.0*y_train_nocon-1.0
y_test_hinge = 2.0*y_test-1.0
```

Second, use a custiom `hinge_accuracy` metric that correctly handles `[-1, 1]` as the `y_true` labels argument.
`tf.losses.BinaryAccuracy(threshold=0.0)` expects `y_true` to be a boolean, and so can't be used with hinge loss).

```
def hinge_accuracy(y_true, y_pred):
    y_true = tf.squeeze(y_true) > 0.0
    y_pred = tf.squeeze(y_pred) > 0.0
    result = tf.cast(y_true == y_pred, tf.float32)

    return tf.reduce_mean(result)
```

```
model.compile(
    loss=tf.keras.losses.Hinge(),
    optimizer=tf.keras.optimizers.Adam(),
    metrics=[hinge_accuracy])
```

```
print(model.summary())
```

    Model: "sequential"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #
    =================================================================
    pqc (PQC)                    (None, 1)                 32
    =================================================================
    Total params: 32
    Trainable params: 32
    Non-trainable params: 0
    _________________________________________________________________
    None

### Train the quantum model

Now train the model—this takes about 45 min. If you don't want to wait that long, use a small subset of the data (set `NUM_EXAMPLES=500`, below). This doesn't really affect the model's progress during training (it only has 32 parameters, and doesn't need much data to constrain these). Using fewer examples just ends training earlier (5min), but runs long enough to show that it is making progress in the validation logs.

```
EPOCHS = 3
BATCH_SIZE = 32

NUM_EXAMPLES = len(x_train_tfcirc)
```

```
x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES]
y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]
```

Training this model to convergence should achieve >85% accuracy on the test set.

```
qnn_history = model.fit(
      x_train_tfcirc_sub, y_train_hinge_sub,
      batch_size=32,
      epochs=EPOCHS,
      verbose=1,
      validation_data=(x_test_tfcirc, y_test_hinge))

qnn_results = model.evaluate(x_test_tfcirc, y_test)
```

    Epoch 1/3
    206/324 [==================>...........] - ETA: 3:31 - loss: 0.7359 - hinge_accuracy: 0.8359

Note: The training accuracy reports the average over the epoch. The validation accuracy is evaluated at the end of each epoch.

## 3. Classical neural network

While the quantum neural network works for this simplified MNIST problem, a basic classical neural network can easily outperform a QNN on this task. After a single epoch, a classical neural network can achieve >98% accuracy on the holdout set.

In the following example, a classical neural network is used for for the 3-6 classification problem using the entire 28x28 image instead of subsampling the image. This easily converges to nearly 100% accuracy of the test set.

```
def create_classical_model():
    # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu', input_shape=(28,28,1)))
    model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
    model.add(tf.keras.layers.Dropout(0.25))
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(128, activation='relu'))
    model.add(tf.keras.layers.Dropout(0.5))
    model.add(tf.keras.layers.Dense(1))
    return model

model = create_classical_model()
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              optimizer=tf.keras.optimizers.Adam(),
              metrics=['accuracy'])

model.summary()
```

```
model.fit(x_train,
          y_train,
          batch_size=128,
          epochs=1,
          verbose=1,
          validation_data=(x_test, y_test))

cnn_results = model.evaluate(x_test, y_test)
```

The above model has nearly 1.2M parameters. For a more fair comparison, try a 37-parameter model, on the subsampled images:

```
def create_fair_classical_model():
    # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Flatten(input_shape=(4,4,1)))
    model.add(tf.keras.layers.Dense(2, activation='relu'))
    model.add(tf.keras.layers.Dense(1))
    return model

model = create_fair_classical_model()
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              optimizer=tf.keras.optimizers.Adam(),
              metrics=['accuracy'])

model.summary()
```

```
model.fit(x_train_bin,
          y_train_nocon,
          batch_size=128,
          epochs=20,
          verbose=2,
          validation_data=(x_test_bin, y_test))

fair_nn_results = model.evaluate(x_test_bin, y_test)
```

## 4. Comparison

Higher resolution input and a more powerful model make this problem easy for the CNN. While a classical model of similar power (~32 parameters) trains to a similar accuracy in a fraction of the time. One way or the other, the classical neural network easily outperforms the quantum neural network. For classical data, it is difficult to beat a classical neural network.

```
qnn_accuracy = qnn_results[1]
cnn_accuracy = cnn_results[1]
fair_nn_accuracy = fair_nn_results[1]

sns.barplot(["Quantum", "Classical, full", "Classical, fair"],
            [qnn_accuracy, cnn_accuracy, fair_nn_accuracy])
```


```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)

# Hide the code completely

# from IPython.display import HTML
# tag = HTML('''<style>
# div.input {
#     display:none;
# }
# </style>''')
# display(tag)
```

Toggle cell visibility here.

```python
%matplotlib notebook

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import sympy as sym

from ipywidgets import widgets, Layout
from ipywidgets import interact

from IPython.display import Latex, display, Markdown # For displaying Markdown and LaTeX code

from matplotlib import patches
```

## Sistema di controllo dell'azimut di una antenna

Un esempio di un sistema di controllo dell'azimut di una antenna è mostrato schematicamente nella figura in basso a sinistra. L'obiettivo di questo sistema di controllo è mantenere la posizione desiderata dell'antenna impostando l'angolo desiderato $\theta_{ref}$ con il potenziometro di riferimento (RP). Lo schema a blocchi di questo sistema (mostrato nella figura in basso a destra) inizia quindi con il segnale $\theta_{ref}$, che viene convertito in tensione $U_1$. La tensione $U_2$ viene quindi sottratta da $U_1$. $U_2$ è l'uscita dal potenziometro di misurazione (MP), che fornisce le informazioni sull'angolo effettivo. La differenza di tensione $U_1-U_2$ rappresenta l'errore che ci dice quanto l'angolo effettivo differisce da quello desiderato. In base a questo errore il controller agisce sull'elettromotore che (tramite ingranaggi) fa ruotare l'antenna in modo da ridurre l'errore. $d_w$ è un disturbo dovuto al vento che fa ruotare l'antenna in modo casuale.

         Rappresentazione schematica del sistema di controllo dell'azimut di una antenna
         Diagramma a blocchi del sistema di controllo dell'azimut di una antenna

         Legenda: RP = potenziometro di riferimento, MP = potenziometro di misurazione, dw = disturbo dovuto al vento.

---

### Come usare questo notebook?

- Spostare i cursori per modificare i valori dell'angolo azimutale dell'antenna desiderato ($\theta_{ref}$), del disturbo dovuto al vento ($d_w$) e dei coefficienti di controllo proporzionale ($K_p$), integrale ($K_i$) e derivativo ($K_d$).

- Premere i pulsanti per alternare tra il tipo di controller proporzionale (P), proporzionale-integrale (PI) e proporzionale-integrale-derivativo (PID).

---

### Note

- La dimensione della freccia rossa sulla rappresentazione schematica dell'antenna è proporzionale all'entità del disturbo dovuto al vento ($d_w$), mentre la direzione della freccia indica la direzione del disturbo.
- La linea blu tratteggiata sulla rappresentazione schematica dell'antenna indica l'angolo effettivo.
- La linea verde tratteggiata sulla rappresentazione schematica dell'antenna indica l'angolo desiderato.
- La linea rossa tratteggiata sulla rappresentazione schematica dell'antenna indica l'angolo effettivo precedente.

È possibile selezionare tra due diverse opzioni per la visualizzazione dei risultati:
1. Resettare la rappresentazione schematica quando si modifica il tipo di controller.
2. Resettare il grafico quando viene modificato il tipo di controller.

```python
# define system constants
_Kpot = 0.318

_K1 = 100
_a = 100
_Km = 2.083
_am = 1.71
_Kg = 0.1
_R = 8
_Kt = 0.5
_Tv = 200 #in milliseconds

#set current theta and theta reference:
th = [0,0,0,0,0,0]
thref = [0,0,0,0,0,0]
# disturbance:
m = [0,0,0,0,0,0]
#joined together (first theta reference, second disturbance, then theta measured):
variables = [thref, m, th]

# variables of controller:
_K = 1
_taui = 1
_taud = 1

```

```python
# symbolic calculus:
taui, taud, K, s, z = sym.symbols('taui, taud, K, s, z')

_alpha=0.1
#controller:
P = K
I = K/(taui*s)
D = K*taud*s/(_alpha*taud*s+1)

def make_model(controller):
    if controller == 'P':
        C = P
    elif controller == 'PI':
        C = P+I
    elif controller == 'PID':
        C = P+I+D
    else:
        print('Sistema di controllo non modellato')

    tf_s = C*_K1*_Km*_Kg*_Kpot/(s*(s+_a)*(s+_am)+C*_K1*_Km*_Kg*_Kpot)
    tf_s = tf_s.simplify()

    tf_z = tf_s.subs(s,2/(_Tv/1000)*(z-1)/(z+1))
    tf_z = tf_z.simplify()

    num = [sym.fraction(tf_z.factor())[0].expand().coeff(z, i) for i in reversed(range(1+sym.degree(sym.fraction(tf_z.factor())[0], gen=z)))]
    den = [sym.fraction(tf_z.factor())[1].expand().coeff(z, i) for i in reversed(range(1+sym.degree(sym.fraction(tf_z.factor())[1], gen=z)))]
    #print(num)
    #print(den)

    tf_sM = _Km*_Kg*_R*(s+_a)/(s*(s+_a)*(s+_am)*_Kt+C*_K1*_Km*_Kg*_Kpot*_Kt)

    tf_zM = tf_sM.subs(s,2/(_Tv/1000)*(z-1)/(z+1))
    tf_zM = tf_zM.simplify()
    num_M = [sym.fraction(tf_zM.factor())[0].expand().coeff(z, i) for i in reversed(range(1+sym.degree(sym.fraction(tf_zM.factor())[0], gen=z)))]
    #print(num_M)
    #print(den_M)

    #print('\n........finished........')
    return sym.lambdify((K, taui, taud), [np.array(num), -np.array(num_M), -np.array(den)])

z_transform_p = make_model('P')
z_transform_pi = make_model('PI')
z_transform_pid = make_model('PID')
```

```python
def calculate_next(z_transform):
    variables[-1][0] = 0 # set current to zero
    z_transform = z_transform(_K, _taui, _taud)

    temp = 0
    for i in range(len(z_transform)): # for every polynomial
        for j in range(len(z_transform[i])): # for every term in polynomial
            temp += z_transform[i][j] * variables[i][j]

    return temp / z_transform[-1][0]*(-1)
```

```python
fig = plt.figure(figsize=(9.8, 4),num='Sistema di controllo dell\'azimut di una antenna')
# add axes
ax = fig.add_subplot(121)
graph = fig.add_subplot(122)

#set current theta and theta reference:
th = [0,0,0,0,0,0]
thref = [1,0,0,0,0,0]
# disturbance:
m = [.1,0,0,0,0,0]
#joined together (first theta reference, second disturbance, then theta measured):
variables = [thref, m, th]

# variables of controller:
_K = 20
_taui = 10
_taud = 1

new_flag_value = [True, 0] # flag for displaying old value of th, before th_ref was changed [flag, angle]

#slider widgets:
th_ref_widget = widgets.FloatSlider(value=variables[0][0],min=0.0,max=2*np.pi,step=.01,description=r'\(\theta_{ref} \) [rad]',
                    disabled=False,continuous_update=True,orientation='horizontal',readout=True,readout_format='.2f')
m_widget = widgets.FloatSlider(value=variables[1][0],min=-.3,max=.3,step=.01,description=r'\(d_{w} \)',
                    disabled=False,continuous_update=True,orientation='horizontal',readout=True,readout_format='.2f')
K_widget = widgets.FloatSlider(value=_K,min=0.0,max=40,step=.1,description=r'\(K_p \)',
                    disabled=False,continuous_update=True,orientation='horizontal',readout=True,readout_format='.1f')
taui_widget = widgets.FloatSlider(value=_taui,min=0.01,max=60,step=.01,description=r'\(K_i \)',
                    disabled=False,continuous_update=True,orientation='horizontal',readout=True,readout_format='.2f')
taud_widget = widgets.FloatSlider(value=_taud,min=0.0,max=5,step=.1,description=r'\(K_d \)',
                    disabled=False,continuous_update=True,orientation='horizontal',readout=True,readout_format='.2f')
#interact(set_coefficients, setK=K_widget, setthref=th_ref_widget, setm=m_widget, settaui=taui_widget, settaud=taud_widget)

#checkboxes
#checkbox_reset_antenna = widgets.Checkbox(value=False, description='Reset schematic representation of antenna when type of controller is changed', disabled=False)
#checkbox_reset_graph = widgets.Checkbox(value=False, description='Reset graph when type of controller is changed', disabled=False)

checkbox_reset_antenna = widgets.Checkbox(value=False, disabled=False, layout=Layout(width='100px'))
label_scheme = widgets.Label('Resetta la rappresentazione schematica dell\'antenna quando viene cambiato il tipo di controller', layout=Layout(width='600px'))
box1 = widgets.HBox([checkbox_reset_antenna, label_scheme])

checkbox_reset_graph = widgets.Checkbox(value=False, disabled=False, layout=Layout(width='100px'))
label_graph = widgets.Label('Resetta il grafico temporale quando viene cambiato il tipo di controller', layout=Layout(width='500px'))
box2 = widgets.HBox([checkbox_reset_graph, label_graph])

style = {'description_width': 'initial'}

#buttons:
def buttons_clicked(event):
    global controller_type, equation, list_th, list_th_ref, list_time
    controller_type = buttons.options[buttons.index]
    if controller_type =='P':
        taui_widget.disabled=True
        taud_widget.disabled=True
        equation = '$Kp$'
    if controller_type =='PI':
        taui_widget.disabled=False
        taud_widget.disabled=True
        equation = '$Kp\,(1+\dfrac{1}{T_{i}\,s})$'
    if controller_type =='PID':
        taui_widget.disabled=False
        taud_widget.disabled=False
        equation = '$Kp\,(1+\dfrac{1}{T_{i}\,s}+\dfrac{T_{d}\,s}{a\,T_{d}\,s+1})$'
    if checkbox_reset_antenna.value:
        #reset values to zero:
        for i in range(len(variables)):
            for j in range(1, len(variables[i])):
                variables[i][j] = 0
        variables[-1][0] = 0
    if checkbox_reset_graph.value:
        list_th = []
        list_th_ref = []
        list_time = []

buttons = widgets.ToggleButtons(
    options=['P', 'PI', 'PID'],
    description='Seleziona il tipo di controller:',
    disabled=False,
    style=style)
buttons.observe(buttons_clicked)

#updating values
def set_values(event):
    global _K, _taui, _taud
    if event['name'] != 'value':
        return
    if th_ref_widget.value != variables[0][0] and not new_flag_value[0]:
        new_flag_value[0] = True
        new_flag_value[1] = variables[-1][0]

    variables[0][0] = th_ref_widget.value
    variables[1][0] = m_widget.value
    _K = K_widget.value
    _taui = taui_widget.value
    _taud = taud_widget.value
th_ref_widget.observe(set_values)
m_widget.observe(set_values)
K_widget.observe(set_values)
taui_widget.observe(set_values)
taud_widget.observe(set_values)

#displaying widgets:
display(buttons)
vbox1 = widgets.VBox([th_ref_widget, m_widget, K_widget, taui_widget, taud_widget])
vbox2 = widgets.VBox([box1, box2])
hbox = widgets.HBox([vbox1, vbox2])
display(hbox)

#setting at start:
controller_type = 'P'
taui_widget.disabled=True
taud_widget.disabled=True
equation = '$Kp$'
set_values({'name':'value'})

#lists for graph in time:
list_time = []
list_th = []
list_th_ref = []

#previous th before change of th_ref:
prev_th = 0

cycles_flag = True

def update_figure(i_time):
    global cycles_flag, variables, _K, controller_type, equation

    if cycles_flag == True:
        cycles_flag = False
        return

    if controller_type == 'P':
        th = calculate_next(z_transform_p)
    elif controller_type == 'PI':
        th = calculate_next(z_transform_pi)
    elif controller_type == 'PID':
        th = calculate_next(z_transform_pid)
    variables[-1][0] = th

    # save variables for next time step:
    for i in range(len(variables)):
        for j in reversed(range(len(variables[i])-1)):
            variables[i][j+1] = variables[i][j]

    list_time.append((i_time+1)*_Tv/1000)
    list_th.append(th)
    list_th_ref.append(variables[0][0])

    #plot:
    ax.clear()
    ax.plot([-1.5, 1.5, 1.5, -1.5], [-1.5, -1.5, 1.5, 1.5], ',', color='b')

    #plot line:
    ax.plot([np.cos(th)*-.5, np.cos(th)*1.5], [np.sin(th)*-.5, np.sin(th)*1.5], 'b--', linewidth=.7, alpha=.7)

    #plot antenna:
    center1 = 1
    center2 = 3
    d1 = 2.2
    d2 = 5.5
    x1 = center1*np.cos(th)
    y1 = center1*np.sin(th)
    x2 = center2*np.cos(th)
    y2 = center2*np.sin(th)
    arc1 = patches.Arc((x1, y1), d1, d1,
                 angle=th/np.pi*180+180, theta1=-58, theta2=58, linewidth=2, color='black', alpha=.7)
    arc2 = patches.Arc((x2, y2), d2, d2,
                 angle=th/np.pi*180+180, theta1=-20, theta2=20, linewidth=2, color='black', alpha=.7)
    ax.add_patch(arc1)
    ax.add_patch(arc2)
    if m_widget.value > 0:
        ax.plot(0, 0, 'r', alpha=.1, marker=r'$\circlearrowright$',ms=150*m_widget.value)
    elif m_widget.value < 0:
        ax.plot(0, 0, 'r', alpha=.1, marker=r'$\circlearrowleft$',ms=-150*m_widget.value)
    ax.set_title('Rappresentazione schematica dell\'antenna')

    #plot direction of antenna before thref change
    if abs(variables[0][0] - th) < 0.03:
        new_flag_value[0] = False
    if new_flag_value[0]:
        ax.plot([0,np.cos(new_flag_value[1])], [0, np.sin(new_flag_value[1])], 'r-.', alpha=.3, linewidth=0.5)
    #plot desired direction of antenna
    ax.plot([0,np.cos(variables[0][0])], [0, np.sin(variables[0][0])], 'g-.', alpha=.7, linewidth=0.7)

    ax.text(-1, 1.3, 'angolo attuale: %.2f rad' %th)
    ax.text(-1, -1.3, 'Tipo di controller:')
    ax.text(-1, -1.6, equation)

    ax.set_aspect('equal', adjustable='datalim')
    ax.set_xlim(-1.5,1.5)
    ax.set_ylim(-1.5,1.5)
    ax.axis('off')

    graph.clear()
    graph.plot(list_time, list_th_ref, 'g', label='angolo desiderato')
    graph.plot(list_time, list_th, 'b', label='angolo attuale')
    graph.set_xlabel('$t$ [s]')
    graph.set_ylabel('$\\theta$ [rad]')
    graph.legend(loc=4, fontsize=8)
    graph.set_title('Azimut vs. tempo')

    plt.show()

ani = animation.FuncAnimation(fig, update_figure, interval=_Tv)
```

    <IPython.core.display.Javascript object>

    ToggleButtons(description='Seleziona il tipo di controller:', options=('P', 'PI', 'PID'), style=ToggleButtonsS…

    HBox(children=(VBox(children=(FloatSlider(value=1.0, description='\\(\\theta_{ref} \\) [rad]', max=6.283185307…

```python

```


```javascript
%%javascript
    MathJax.Hub.Config({
      TeX: { equationNumbers: { autoNumber: "AMS" } }
    });
```

    <IPython.core.display.Javascript object>

```python
from IPython.display import HTML

HTML('''
<form action="javascript:code_toggle()"><input type="submit" value="Code Toggle"></form>''')
```

<form action="javascript:code_toggle()"><input type="submit" value="Code Toggle"></form>

```python
from IPython.display import HTML

HTML('''
<a href="https://github.com/usnistgov/pfhub/raw/master/benchmarks/benchmark7.ipynb"
   download>
<button type="submit">Download Notebook</button>
</a>
''')
```

<button type="submit">Download Notebook</button>

# Benchmark Problem 7: MMS Allen-Cahn

```python
from IPython.display import HTML

HTML('''{% include jupyter_benchmark_table.html num="[7]" revision=0 %}''')
```

{% include jupyter_benchmark_table.html num="[7]" revision=0 %}

Table of Contents

1  Benchmark Problem 7: MMS Allen-Cahn

2  Overview

3  Governing equation and manufactured solution

4  Domain geometry, boundary conditions, initial conditions, and stopping condition

5  Parameter values

6  Benchmark simulation instructions

6.1  Part (a)

6.2  Part (b)

6.3  Part (c)

7  Submission Guidelines

7.1  Part (a) Guidelines

7.2  Parts (b) and (c) Guidelines

8  Results

9  Feedback

10  Appendix

10.1  Computer algebra systems

10.2  Source term

10.3  Code

10.3.1  Python

10.3.2  C

10.3.3  Fortran

10.3.4  Julia

10.3.5  Mathematica

10.3.6  Matlab

See the journal publication entitled ["Benchmark problems for numerical implementations of phase field models"][benchmark_paper] for more details about the benchmark problems. Furthermore, read [the extended essay][benchmarks] for a discussion about the need for benchmark problems.

[benchmarks]: ../
[benchmark_paper]: http://dx.doi.org/10.1016/j.commatsci.2016.09.022

# Overview

The Method of Manufactured Solutions (MMS) is a powerful technique for verifying the accuracy of a simulation code. In the MMS, one picks a desired solution to the problem at the outset, the "manufactured solution", and then determines the governing equation that will result in that solution. With the exact analytical form of the solution in hand, when the governing equation is solved using a particular simulation code, the deviation from the expected solution can be determined exactly. This deviation can be converted into an error metric to rigously quantify the error for a calculation. This error can be used to determine the order of accuracy of the simulation results to verify simulation codes. It can also be used to compare the computational efficiency of different codes or different approaches for a particular code at a certain level of error. Furthermore, the spatial/temporal distribution can give insight into the conditions resulting in the largest error (high gradients, changes in mesh resolution, etc.).

After choosing a manufactured solution, the governing equation must be modified to force the solution to equal the manufactured solution. This is accomplished by taking the nominal equation that is to be solved (e.g.  Allen-Cahn equation, Cahn-Hilliard equation, Fick's second law, Laplace equation) and adding a source term. This source term is determined by plugging the manufactured solution into the nominal governing equation and setting the source term equal to the residual. Thus, the manufactured solution satisfies the MMS governing equation (the nominal governing equation plus the source term). A more detailed discussion of MMS can be found in [the report by Salari and Knupp][mms_report].

In this benchmark problem, the objective is to use the MMS to rigorously verify phase field simulation codes and then provide a basis of comparison for the computational performance between codes and for various settings for a single code, as discussed above. To this end, the benchmark problem was chosen as a balance between two factors: simplicity, to minimize the development effort required to solve the benchmark, and transferability to a real phase field system of physical interest.

[mms_report]: http://prod.sandia.gov/techlib/access-control.cgi/2000/001444.pdf

# Governing equation and manufactured solution
For this benchmark problem, we use a simple Allen-Cahn equation as the governing equation

$$\begin{equation}
\frac{\partial \eta}{\partial t} = - \left[ 4 \eta \left(\eta - 1 \right) \left(\eta-\frac{1}{2} \right) - \kappa \nabla^2 \eta \right] + S(x,y,t)
\end{equation}$$

where $S(x,y,t)$ is the MMS source term and $\kappa$ is a constant parameter (the gradient energy coefficient).

The manufactured solution, $\eta_{sol}$ is a hyperbolic tangent function, shifted to vary between 0 and 1, with the $x$ position of the middle of the interface ($\eta_{sol}=0.5$) given by the function $\alpha(x,t)$:

$$\begin{equation}
\eta_{sol}(x,y,t) = \frac{1}{2}\left[ 1 - \tanh\left( \frac{y-\alpha(x,t)}{\sqrt{2 \kappa}} \right) \right]
\end{equation}$$

$$\begin{equation}
\alpha(x,t) = \frac{1}{4} + A_1 t \sin\left(B_1 x \right) + A_2 \sin \left(B_2  x + C_2 t \right)
\end{equation}$$

where $A_1$, $B_1$, $A_2$, $B_2$, and $C_2$ are constant parameters.

This manufactured solution is an equilbrium solution of the governing equation, when $S(x,y,t)=0$ and $\alpha(x,t)$ is constant. The closeness of this manufactured solution to a solution of the nominal governing equation increases the likihood that the behavior of simulation codes when solving this benchmark problem is representive of the solution of the regular Allen-Cahn equation (i.e. without the source term). The form of $\alpha(x,t)$ was chosen to yield complex behavior while still retaining a (somewhat) simple functional form. The two spatial sinusoidal terms introduce two controllable length scales to the interfacial shape. Summing them gives a  "beat" pattern with a period longer than the period of either individual term, permitting a domain size that is larger than the wavelength of the sinusoids without a repeating pattern. The temporal sinusoidal term introduces a controllable time scale to the interfacial shape in addition to the phase transformation time scale, while the linear temporal dependence of the other term ensures that the sinusoidal term can go through multiple periods without $\eta_{sol}$ repeating itself.

Inserting the manufactured solution into the governing equation and solving for $S(x,y,t)$ yields:

$$\begin{equation}
S(x,y,t) = \frac{\text{sech}^2 \left[ \frac{y-\alpha(x,t)}{\sqrt{2 \kappa}} \right]}{4 \sqrt{\kappa}} \left[-2\sqrt{\kappa} \tanh \left[\frac{y-\alpha(x,t)}{\sqrt{2 \kappa}} \right] \left(\frac{\partial \alpha(x,t)}{\partial x} \right)^2+\sqrt{2} \left[ \frac{\partial \alpha(x,t)}{\partial t}-\kappa \frac{\partial^2 \alpha(x,t)}{\partial x^2} \right] \right]
\end{equation}$$

where $\alpha(x,t)$ is given above and where:

$$\begin{equation}
\frac{\partial \alpha(x,t)}{\partial x} = A_1 B_1 t \cos\left(B_1 x\right) + A_2 B_2 \cos \left(B_2  x + C_2 t \right)
\end{equation}$$

$$\begin{equation}
\frac{\partial^2 \alpha(x,t)}{\partial x^2} = -A_1 B_1^2 t \sin\left(B_1 x\right) - A_2 B_2^2 \sin \left(B_2  x + C_2 t \right)
\end{equation}$$

$$\begin{equation}
\frac{\partial \alpha(x,t)}{\partial t} = A_1 \sin\left(B_1 x\right) + A_2 C_2 \cos \left(B_2  x + C_2 t \right)
\end{equation}$$

** *N.B.*: Don't transcribe these equations. Please download the appropriate files from the [Appendix](#Appendix) **.

# Domain geometry, boundary conditions, initial conditions, and stopping condition
The domain geometry is a rectangle that spans [0, 1] in $x$ and [0, 0.5] in $y$. This elongated domain was chosen to allow multiple peaks and valleys in $\eta_{sol}$ without stretching the interface too much in the $y$ direction (which causes the thickness of the interface to change) or having large regions where $\eta_{sol}$  never deviates from 0 or 1. Periodic boundary conditions are applied along the $x = 0$ and the $x = 1$ boundaries to accomodate the periodicity of $\alpha(x,t)$. Dirichlet boundary conditions of $\eta$ = 1 and $\eta$ = 0 are applied along the $y = 0$ and the $y = 0.5$ boundaries, respectively. These boundary conditions are chosen to be consistent with $\eta_{sol}(x,y,t)$. The initial condition is the manufactured solution at $t = 0$:

$$
\begin{equation}
\eta_{sol}(x,y,0) = \frac{1}{2}\left[ 1 - \tanh\left( \frac{y-\left(\frac{1}{4}+A_2 \sin(B_2 x) \right)}{\sqrt{2 \kappa}} \right) \right]
\end{equation}
$$

The stopping condition for all calculations is when t = 8 time units, which was chosen to let $\alpha(x,t)$ evolve substantially, while still being slower than the characteristic time for the phase evolution (determined by the CFL condition for a uniform mesh with a reasonable level of resolution of $\eta_{sol}$).

# Parameter values
The nominal parameter values for the governing equation and manufactured solution are given below. The value of $\kappa$ will change in Part (b) in the following section and the values of $\kappa$ and $C_2$ will change in Part (c).

| Parameter | Value |
|-----------|-------|
| $\kappa$  | 0.0004|
| $A_1$     | 0.0075|
| $B_1$     | $8.0 \pi$  |
| $A_2$     | 0.03   |
| $B_2$     | $22.0 \pi$  |
| $C_2$     | $0.0625 \pi$|

# Benchmark simulation instructions
This section describes three sets of tests to conduct using the MMS problem specified above. The primary purpose of the first test is provide a computationally inexpensive problem to verify a simulation code. The second and third tests are more computationally demanding and are primarily designed to serve as a basis for performance comparisons.

## Part (a)
The objective of this test is to verify the accuracy of your simulation code in both time and space. Here, we make use of convergence tests, where either the mesh size (or grid point spacing) or the time step size is systematically changed to determine the response of the error to these quantities. Once a convergence test is completed the order of accuracy can be calculated from the result. The order of accuracy can be compared to the theoretical order of accuracy for the numerical method employed in the simulation. If the two match (to a reasonable degree), then one can be confident that the simulation code is working as expected. The remainder of this subsection will give instructions for convergence tests for this MMS problem.

Implement the MMS problem specified above using the simulation code of your choice. Perform a spatial convergence test by running the simulation for a variety of mesh sizes. For each simulation, determine the discrete $L_2$ norm of the error at $t=8$:

$$\begin{equation}
    L_2 = \sqrt{\sum\limits_{x,y}\left(\eta^{t=8}_{x,y} - \eta_{sol}(x,y,8)\right)^2 \Delta x \Delta y}
\end{equation}$$

For all of these simulations, verify that the time step is small enough that any temporal error is much smaller that the total error. This can be accomplished by decreasing the time step until it has minimal effect on the error. Ensure that at least three simulation results have $L_2$ errors in the range $[5\times10^{-3}, 1\times10^{-4}]$, attempting to cover as much of that range as possible/practical. This maximum and minimum errors in the range roughly represent a poorly resolved simulation and a very well-resolved simulation.

Save the effective element size, $h$, and the $L_2$ error for each simulation.
[Archive this data](https://github.com/usnistgov/pfhub/issues/491) in a
CSV or JSON file, using one column (or key) each for $h$ and $L_2$.
Calculate the effective element size as the square root of the area of
the finest part of the mesh for nonuniform meshes. For irregular meshes
with continuous distributions of element sizes, approximate the effective
element size as the average of the square root of the area of the smallest
5% of the elements. Then [submit your results on the PFHub website](https://pages.nist.gov/pfhub/simulations/upload_form/) as a 2D data set with the effective mesh size as the x-axis column and the $L_2$ error as the y-axis column.

Next, confirm that the observed order of accuracy is approximately equal to the expected value. Calculate the order of accuracy, $p$, with a least squares fit of the following function:

$$\begin{equation}
    \log(E)=p \log(R) + b
\end{equation}$$

where $E$ is the $L_2$ error, $R$ is the effective element size, and b is an intercept. Deviations of ±0.2 or more from the theoretical value are to be expected (depending on the range of errors considered and other factors).

Finally, perform a similar convergence test, but for the time step, systematically changing the time step and recording the $L_2$ error. Use a time step that does not vary over the course of any single simulation. Verify that the spatial discretization error is small enough that it does not substantially contribute to the total error. Once again, ensure that at least three simulations have $L_2$ errors in the range $[5\times10^{-3}, 1\times10^{-4}]$, attempting to cover as much of that range as possible/practical. [Archive the effective mesh size and $L_2$ error](https://github.com/usnistgov/pfhub/issues/491) for each individual simulation in a CSV or JSON file. [Submit your results to the PFHub website](https://pages.nist.gov/pfhub/simulations/upload_form/) as a 2D data set with the time step size as the x-axis column and the $L_2$ error as the y-axis column. Confirm that the observed order of accuracy is approximately equal to the expected value.

## Part (b)
Now that your code has been verified in (a), the objective of this part is to determine the computational performance of your code at various levels of error. These results can then be used to objectively compare the performance between codes or settings within the same code. To make the problem more computationally demanding and stress solvers more than in (a), decrease $\kappa$ by a factor of $256$ to $1.5625\times10^{-6}$. This change will reduce the interfacial thickness by a factor of $16$.

Run a series of simulations, attempting to optimize solver parameters (mesh, time step, tolerances, etc.) to minimize the required computational resources for at least three levels of $L_2$ error in range  $[5\times10^{-3}, 1\times10^{-5}]$. Use the same CPU and processor type for all simulations. For the best of these simulations, save the wall time (in seconds), number of computing cores, normalized computing cost (wall time in seconds $\times$ number of cores $\times$ nominal core speed $/$ 2 GHz), maximum memory usage, and $L_2$ error at $t=8$ for each individual simulation.  [Archive this data](https://github.com/usnistgov/pfhub/issues/491) in a
CSV or JSON file with one column (or key) for each of the quantities mentioned above. [Submit your results to the PFHub website](https://pages.nist.gov/pfhub/simulations/upload_form/) as two 2D data sets. For the first data set use the $L_2$ error as the x-axis column and the normalized computational cost as the y-axis column. For the second data set,  use the $L_2$ error as the x-axis column and the wall time as the y-axis column.

## Part (c)
This final part is designed to stress time integrators even further by increasing the rate of change of $\alpha(x,t)$. Increase $C_2$ to $0.5$. Keep $\kappa= 1.5625\times10^{-6}$ from (b).

Repeat the process from (b), uploading the wall time, number of computing cores, processor speed, normalized computing cost, maximum memory usage, and $L_2$ error at $t=8$ to the PFHub website.

# Submission Guidelines

## Part (a) Guidelines

Two data items are required in the "Data Files" section of the [upload form]. The data items should be labeled as `spatial` and `temporal` in the `Short name of data` box. The 2D radio button should be checked and the columns corresponding to the x-axis (either $\Delta t$ or $\Delta x$) and the y-axis ($e_{L2}$) should be labeled correctly for each CSV file. The CSV file for the spatial data should have the form

```
mesh_size,L2_error
0.002604167,2.55E-06
0.00390625,6.26E-06
...
```

and the CSV file for the temporal data should have the form

```
time_step,L2_error
5.00E-04,5.80162E-06
4.00E-04,4.69709E-06
...

```

## Parts (b) and (c) Guidelines

Two data items are required in the "Data Files" section of the [upload form]. The data items should be labeled as `cost` and `time` in the `Short name of data` box. The 2D radio button should be checked and the columns corresponding to the x-axis ($e_{L2}$) and the y-axis (either $F_{\text{cost}}$ or $t_{\text{wall}}$) should be labeled correctly for each CSV file. The CSV file for the cost data should have the form

```
cores,wall_time,memory,error,cost
1,1.35,25800,0.024275131,1.755
1,4.57,39400,0.010521502,5.941
...
```

Only one CSV file is required with the same link in both data sections.

[upload form]: ../../simulations/upload_form/

# Results
Results from this benchmark problem are displayed on the [simulation result page]({{ site.baseurl }}/simulations) for different codes.

# Feedback
Feedback on this benchmark problem is appreciated. If you have questions, comments, or seek clarification, please contact the [CHiMaD phase field community]({{ site.baseurl }}/community/) through the [Gitter chat channel]({{ site.links.chat }}) or by [email]({{ site.baseurl }}/mailing_list/). If you found an error, please file an [issue on GitHub]({{ site.links.github }}/issues/new).

# Appendix

## Computer algebra systems
Rigorous verification of software frameworks using MMS requires posing the equation and manufacturing the solution with as much complexity as possible. This can be straight-forward, but interesting equations produce complicated source terms. To streamline the MMS workflow, it is strongly recommended that you use a CAS such as SymPy, Maple, or Mathematica to generate source equations and turn it into executable code automatically. For accessibility, we will use [SymPy](http://www.sympy.org/), but so long as vector calculus is supported, CAS will do.

## Source term

```python
# Sympy code to generate expressions for PFHub Problem 7 (MMS)

from sympy import symbols, simplify
from sympy import sin, cos, cosh, tanh, sqrt
from sympy.physics.vector import divergence, gradient, ReferenceFrame, time_derivative
from sympy.utilities.codegen import codegen
from sympy.abc import kappa, S, x, y, t

# Spatial coordinates: x=R[0], y=R[1], z=R[2]
R = ReferenceFrame('R')

# sinusoid amplitudes
A1, A2 = symbols('A1 A2')
B1, B2 = symbols('B1 B2')
C2 = symbols('C2')

# Define interface offset (alpha)
alpha = 0.25 + A1 * t * sin(B1 * R[0]) \
             + A2     * sin(B2 * R[0] + C2 * t)

# Define the solution equation (eta)
eta = 0.5 * (1 - tanh((R[1] - alpha) / sqrt(2*kappa)))

# Compute the source term from the equation of motion
source = simplify(time_derivative(eta, R)
             + 4 * eta * (eta - 1) * (eta - 1/2)
             - kappa * divergence(gradient(eta, R), R))

# Replace R[i] with (x, y)
alpha = alpha.subs({R[0]: x, R[1]: y})
eta = eta.subs({R[0]: x, R[1]: y})
eta0 = eta.subs(t, 0)
source = source.subs({R[0]: x, R[1]: y})

print("alpha =", alpha, "\n")
print("eta =", eta,     "\n")
print("eta0 =", eta0,  "\n")
print("S =", source)
```

    alpha = A1*t*sin(B1*x) + A2*sin(B2*x + C2*t) + 0.25

    eta = -0.5*tanh(sqrt(2)*(-A1*t*sin(B1*x) - A2*sin(B2*x + C2*t) + y - 0.25)/(2*sqrt(kappa))) + 0.5

    eta0 = -0.5*tanh(sqrt(2)*(-A2*sin(B2*x) + y - 0.25)/(2*sqrt(kappa))) + 0.5

    S = -(tanh(sqrt(2)*(A1*t*sin(B1*x) + A2*sin(B2*x + C2*t) - y + 0.25)/(2*sqrt(kappa)))**2 - 1)*(0.5*sqrt(kappa)*((A1*B1*t*cos(B1*x) + A2*B2*cos(B2*x + C2*t))**2 + 1)*tanh(sqrt(2)*(A1*t*sin(B1*x) + A2*sin(B2*x + C2*t) - y + 0.25)/(2*sqrt(kappa))) - 0.5*sqrt(kappa)*tanh(sqrt(2)*(A1*t*sin(B1*x) + A2*sin(B2*x + C2*t) - y + 0.25)/(2*sqrt(kappa))) + 0.25*sqrt(2)*kappa*(A1*B1**2*t*sin(B1*x) + A2*B2**2*sin(B2*x + C2*t)) + 0.25*sqrt(2)*(A1*sin(B1*x) + A2*C2*cos(B2*x + C2*t)))/sqrt(kappa)

## Code

### Python

Copy the first cell under Source Term directly into your program.
For a performance boost, convert the expressions into lambda functions:

```python
from sympy.utilities.lambdify import lambdify

apy = lambdify([x, y], alpha, modules='sympy')
epy = lambdify([x, y], eta,   modules='sympy')
ipy = lambdify([x, y], eta0,  modules='sympy')
Spy = lambdify([x, y], S,     modules='sympy')
```

> Note: Click "Code Toggle" at the top of the page to see the Python expressions.

### C

```python
[(c_name, code), (h_name, header)] = \
codegen([("alpha", alpha),
         ("eta",   eta),
         ("eta0",  eta),
         ("S",     S)],
         language="C",
         prefix="manufactured",
         project="PFHub")
print("manufactured.h:\n")
print(header)
print("\nmanufactured.c:\n")
print(code)
```

    manufactured.h:

    /******************************************************************************
     *                       Code generated with sympy 1.2                        *
     *                                                                            *
     *              See http://www.sympy.org/ for more information.               *
     *                                                                            *
     *                        This file is part of 'PFHub'                        *
     ******************************************************************************/

    #ifndef PFHUB__MANUFACTURED__H
    #define PFHUB__MANUFACTURED__H

    double alpha(double A1, double A2, double B1, double B2, double C2, double t, double x);
    double eta(double A1, double A2, double B1, double B2, double C2, double kappa, double t, double x, double y);
    double eta0(double A1, double A2, double B1, double B2, double C2, double kappa, double t, double x, double y);
    double S(double S);

    #endif

    manufactured.c:

    /******************************************************************************
     *                       Code generated with sympy 1.2                        *
     *                                                                            *
     *              See http://www.sympy.org/ for more information.               *
     *                                                                            *
     *                        This file is part of 'PFHub'                        *
     ******************************************************************************/
    #include "manufactured.h"
    #include <math.h>

    double alpha(double A1, double A2, double B1, double B2, double C2, double t, double x) {

       double alpha_result;
       alpha_result = A1*t*sin(B1*x) + A2*sin(B2*x + C2*t) + 0.25;
       return alpha_result;

    }

    double eta(double A1, double A2, double B1, double B2, double C2, double kappa, double t, double x, double y) {

       double eta_result;
       eta_result = -0.5*tanh((1.0/2.0)*M_SQRT2*(-A1*t*sin(B1*x) - A2*sin(B2*x + C2*t) + y - 0.25)/sqrt(kappa)) + 0.5;
       return eta_result;

    }

    double eta0(double A1, double A2, double B1, double B2, double C2, double kappa, double t, double x, double y) {

       double eta0_result;
       eta0_result = -0.5*tanh((1.0/2.0)*M_SQRT2*(-A1*t*sin(B1*x) - A2*sin(B2*x + C2*t) + y - 0.25)/sqrt(kappa)) + 0.5;
       return eta0_result;

    }

    double S(double S) {

       double S_result;
       S_result = S;
       return S_result;

    }

### Fortran

```python
[(f_name, code), (f_name, header)] = \
codegen([("alpha", alpha),
         ("eta",   eta),
         ("eta0",  eta),
         ("S",     S)],
         language="f95",
         prefix="manufactured",
         project="PFHub")

print("manufactured.f:\n")
print(code)
```

    manufactured.f:

    !******************************************************************************
    !*                       Code generated with sympy 1.2                        *
    !*                                                                            *
    !*              See http://www.sympy.org/ for more information.               *
    !*                                                                            *
    !*                        This file is part of 'PFHub'                        *
    !******************************************************************************

    REAL*8 function alpha(A1, A2, B1, B2, C2, t, x)
    implicit none
    REAL*8, intent(in) :: A1
    REAL*8, intent(in) :: A2
    REAL*8, intent(in) :: B1
    REAL*8, intent(in) :: B2
    REAL*8, intent(in) :: C2
    REAL*8, intent(in) :: t
    REAL*8, intent(in) :: x

    alpha = A1*t*sin(B1*x) + A2*sin(B2*x + C2*t) + 0.25d0

    end function

    REAL*8 function eta(A1, A2, B1, B2, C2, kappa, t, x, y)
    implicit none
    REAL*8, intent(in) :: A1
    REAL*8, intent(in) :: A2
    REAL*8, intent(in) :: B1
    REAL*8, intent(in) :: B2
    REAL*8, intent(in) :: C2
    REAL*8, intent(in) :: kappa
    REAL*8, intent(in) :: t
    REAL*8, intent(in) :: x
    REAL*8, intent(in) :: y

    eta = -0.5d0*tanh(0.70710678118654752d0*kappa**(-0.5d0)*(-A1*t*sin(B1*x &
          ) - A2*sin(B2*x + C2*t) + y - 0.25d0)) + 0.5d0

    end function

    REAL*8 function eta0(A1, A2, B1, B2, C2, kappa, t, x, y)
    implicit none
    REAL*8, intent(in) :: A1
    REAL*8, intent(in) :: A2
    REAL*8, intent(in) :: B1
    REAL*8, intent(in) :: B2
    REAL*8, intent(in) :: C2
    REAL*8, intent(in) :: kappa
    REAL*8, intent(in) :: t
    REAL*8, intent(in) :: x
    REAL*8, intent(in) :: y

    eta0 = -0.5d0*tanh(0.70710678118654752d0*kappa**(-0.5d0)*(-A1*t*sin(B1*x &
          ) - A2*sin(B2*x + C2*t) + y - 0.25d0)) + 0.5d0

    end function

    REAL*8 function S(S)
    implicit none
    REAL*8, intent(in) :: S

    S = S

    end function

### Julia

```python
[(f_name, code)] = \
codegen([("alpha", alpha),
         ("eta",   eta),
         ("eta0",  eta),
         ("S",     S)],
         language="julia",
         prefix="manufactured",
         project="PFHub")

print("manufactured.jl:\n")
print(code)
```

    manufactured.jl:

    #   Code generated with sympy 1.2
    #
    #   See http://www.sympy.org/ for more information.
    #
    #   This file is part of 'PFHub'

    function alpha(A1, A2, B1, B2, C2, t, x)

        out1 = A1.*t.*sin(B1.*x) + A2.*sin(B2.*x + C2.*t) + 0.25

        return out1
    end

    function eta(A1, A2, B1, B2, C2, kappa, t, x, y)

        out1 = -0.5*tanh(sqrt(2)*(-A1.*t.*sin(B1.*x) - A2.*sin(B2.*x + C2.*t) + y - 0.25)./(2*sqrt(kappa))) + 0.5

        return out1
    end

    function eta0(A1, A2, B1, B2, C2, kappa, t, x, y)

        out1 = -0.5*tanh(sqrt(2)*(-A1.*t.*sin(B1.*x) - A2.*sin(B2.*x + C2.*t) + y - 0.25)./(2*sqrt(kappa))) + 0.5

        return out1
    end

    function S(S)

        out1 = S

        return out1
    end

### Mathematica

```python
from sympy.printing import mathematica_code

print("alpha =", mathematica_code(alpha), "\n")
print("eta =", mathematica_code(eta), "\n")
print("eta0 =", mathematica_code(eta0), "\n")
print("S =", mathematica_code(source), "\n")
```

    alpha = A1*t*Sin[B1*x] + A2*Sin[B2*x + C2*t] + 0.25

    eta = -0.5*Tanh[(1/2)*2^(1/2)*(-A1*t*Sin[B1*x] - A2*Sin[B2*x + C2*t] + y - 0.25)/kappa^(1/2)] + 0.5

    eta0 = -0.5*Tanh[(1/2)*2^(1/2)*(-A2*Sin[B2*x] + y - 0.25)/kappa^(1/2)] + 0.5

    S = -(Tanh[(1/2)*2^(1/2)*(A1*t*Sin[B1*x] + A2*Sin[B2*x + C2*t] - y + 0.25)/kappa^(1/2)]^2 - 1)*(0.5*kappa^(1/2)*((A1*B1*t*Cos[B1*x] + A2*B2*Cos[B2*x + C2*t])^2 + 1)*Tanh[(1/2)*2^(1/2)*(A1*t*Sin[B1*x] + A2*Sin[B2*x + C2*t] - y + 0.25)/kappa^(1/2)] - 0.5*kappa^(1/2)*Tanh[(1/2)*2^(1/2)*(A1*t*Sin[B1*x] + A2*Sin[B2*x + C2*t] - y + 0.25)/kappa^(1/2)] + 0.25*2^(1/2)*kappa*(A1*B1^2*t*Sin[B1*x] + A2*B2^2*Sin[B2*x + C2*t]) + 0.25*2^(1/2)*(A1*Sin[B1*x] + A2*C2*Cos[B2*x + C2*t]))/kappa^(1/2)

### Matlab

```python
code = \
codegen([("alpha", alpha),
         ("eta",   eta),
         ("eta0",  eta),
         ("S",     S)],
         language="octave",
         project="PFHub")

print("manufactured.nb:\n")
for f in code[0]:
    print(f)
```

    manufactured.nb:

    alpha.m
    function out1 = alpha(A1, A2, B1, B2, C2, t, x)
      %ALPHA  Autogenerated by sympy
      %   Code generated with sympy 1.2
      %
      %   See http://www.sympy.org/ for more information.
      %
      %   This file is part of 'PFHub'

      out1 = A1.*t.*sin(B1.*x) + A2.*sin(B2.*x + C2.*t) + 0.25;

    end

    function out1 = eta(A1, A2, B1, B2, C2, kappa, t, x, y)

      out1 = -0.5*tanh(sqrt(2)*(-A1.*t.*sin(B1.*x) - A2.*sin(B2.*x + C2.*t) + y - 0.25)./(2*sqrt(kappa))) + 0.5;

    end

    function out1 = eta0(A1, A2, B1, B2, C2, kappa, t, x, y)

      out1 = -0.5*tanh(sqrt(2)*(-A1.*t.*sin(B1.*x) - A2.*sin(B2.*x + C2.*t) + y - 0.25)./(2*sqrt(kappa))) + 0.5;

    end

    function out1 = S(S)

      out1 = S;

    end