# 2. A Gaussian XOR problem¶

Our second example is the XOR problem.
We place four points over the cartesian axes so to create a centered
square; the two points on *x*-axis are labeled as 1, while those on
*y*-axis as 0.

## 2.1. Dataset generation¶

We use numpy to generate a dataset of 200 points by sampling 50 points from 4 gaussian distibutions centered at the said points. The label of each sample is given by the center of its distribution.

```
import numpy as np
n_pc = 50 # Number of points per cluster
# Create a matrix of vertices of the centered square
X = np.asarray(n_pc * [[1.5, 0.]] +
n_pc * [[-1.5, 0.]] +
n_pc * [[0., -1.5]] +
n_pc * [[0., 1.5]]
)
# Add gaussian noise
X += .5 * np.random.randn(*X.shape)
# Create target vecor
y = np.concatenate((np.zeros(2*n_pc), np.ones(2*n_pc)))
```

This generates the following dataset, where the circles represent the samples and the squares the distribution centers.

## 2.2. Circuit definition¶

Now, we define the circuit structure using the `make_circuit`

function.

```
def make_circuit(bdr, x, params):
"""Generate the circuit corresponding to input `x` and `params`.
Parameters
----------
bdr : circuitBuilder
A circuit builder.
x : vector-like
Input sample
params : vector-like
Parameter vector.
Returns
-------
circuitBuilder
Instructed builder
"""
bdr.allin(x[[0,1]])
bdr.cz(0, 1).allin(params[[0,1]])
bdr.cz(0, 1).allin(params[[2,3]])
return bdr
```

## 2.3. Model training¶

Finally, we can create and train the classifier.
We instantiate the `circuitML`

subclass that we prefer, in this case the one using the fast *manyq* simualtor, specifying the number of qubits and of parameters.

```
from polyadicqml.manyq import mqCircuitML
nbqbits = 2
nbparams = 6
qc = mqCircuitML(make_circuit=make_circuit,
nbqbits=nbqbits, nbparams=nbparams)
```

Then, we create and train the quantum classifer, specifying on which bitstrings we want to read the predicted classes.

```
from polyadicqml import Classifier
bitstr = ['00', '01']
model = Classifier(qc, bitstr)
model.fit(X, y)
```

## 2.4. Predict on new data¶

We can use a model to predict on some new datapoints `X_test`

that it
never saw before.
To obtain the bitstring probabilities, we can just call the model:

```
pred_prob = model.predict_proba(X_test)
```

Then, we can retrieve the label of each point as the argmax of the corresponding probabilities. Otherwise, we can combine the two operations by using the shorthand:

```
y_pred = model(X_test)
```

For instance, going back to our XOR problem, we can predict the label of each point on a grid that covers , to assess the model accuracy. Using some list comprehension, it would look like this:

```
t = np.linspace(-np.pi,np.pi, num = 50)
X_test = np.array([[t1, t2] for t1 in t for t2 in t])
y_pred = model(X_test)
```

We can now plot the predictions and see that the model is very close to the bayesian prediction (whose decision boundaries are shown as grey lines), which is the best possible.

## 2.5. Source code¶

The example script, producing the plots, can be found in the GitHub example
page as `example-XOR.py`

.