Metadata-Version: 2.1
Name: qiskit-ptesting
Version: 0.1.2
Summary: Property-based testing framework for qiskit algorithms.
Home-page: https://gitlab.com/twistercool/Qiskit-PTesting
Author: Pierre Brassart
Author-email: pierrebrassart80@hotmail.fr
License: Apache 2.0
Keywords: qiskit property testing
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE

# Qiskit-PTesting

   This project aims to implement property-based testing for quantum circuits using the qiskit library for Python 3.6 or more.

   To learn more about qiskit, follow this [link](https://qiskit.org/)

   I also recommend following [their tutorials](https://qiskit.org/learn/):

   To learn more about property-based testing, follow these ressources:
   [Intro to property based testing](https://dev.to/jdsteinhauser/intro-to-property-based-testing-2cj8)
   [Wikipedia page](https://en.wikipedia.org/wiki/Property\_testing)


## Installation

   1) Run `pip install qiskit-ptesting` in a terminal environement

   2) In any python file where you want to define property tests, add:
```python
   from Qiskit_PTesting import QiskitPropertyTest, TestProperty, Qarg
```

   That's it, you should be able to define tests in the directory


# Usage

   1) Create a superclass of "QiskitPropertyTest" using any name you want
   2) In that class, define 3 functions (optionally leave them out for default behaviour):
   - `property(self)`
   - `quantumFunction(self, qc)`
   - `assertions(self)`
   3) Inside of the function `property()`, define a TestProperty object and return it
   4) Inside of the function `quantumFunction()`, define which steps are needed to be applied to qc (the quantum circuit). All of the generated tests will have those operations applied.
   5) Inside of the function `assertion()`, define which properties you would like to hold true using the built-in assertions.
   6) Run the test class you created using the "runTests()" method.



## How to define a TestProperty

   A TestProperty object contains all of the necessary information to generate random tests.

   It contains:
   1) `p_value`: The p\_value for all tests (float between 0 and 1)
   2) `nbTests`: The number of randomly generated tests (int greater than 0)
   3) `nbTrials`: The number of times each generated test will be run, otherwise called the amount of trials (int greater than 0)
   4) `nbMeasurement`: The number of times each trial will be measured (int greater than 0)
   5) `nbQubits`: The amount of required qubits for each test (int greater than 0, or a list of 2 integers, the 2nd one being greater of equal to the first)
   6) `nbClassicalBits`: The amount of classical bits required for each test(int greater or equal to 0)
   7) `qargs`: A dictionary of Qarg objects for each qubit that you want to initialise to a specific range/value (can be empty)

   These are the default values:

```python
testProperty = TestProperty(p_value=0.01,
                            nbTests=10,
                            nbTrials=100,
                            nbMeasurements=500,
                            nbQubits=5,
                            nbClassicalBits=0,
                            qargs={})
```


   NbQubits can be an integer or a list.
   In that case, the framework will generate each test with a random amount of qubits between the two specified values.


## How to initalise a Qarg

   A Qarg object holds 4 ints that define 2 ranges.
   A test property will apply this Qarg to initialise a qubit to a random value between those 2 ranges.
   Any qubit of a quantum circuit can be initilised using 2 values: a theta and a phi.
   The first range specifies what values theta can be used to initialise a qubit.
   The second range specifies what values phi can take.

   Here is an example Qarg, that specifies that a qubit needs to be initialised with w theta between 0 and 90 degrees, and a phi of 40 to 60 degrees:

```python
qarg = Qarg(0, 90, 45, 60)
```

   The same Qarg can be specified using radians, if the last paramter, isRad, is set to True:

```python
from math import pi
qarg = Qarg(0, pi/2, pi/4, pi/3, True)
```


   A Qarg can also be specified with a statevector with two complex values in the following way:

```python
from math import sqrt
qarg = Qarg([1/sqrt(2), 1/sqrt(2)])
```

   The above code initialises a qubit to the state |+>

   Finally, some common states can be directly initialised with just one string:

- Qarg("0") initialises to state 0
- Qarg("1") to state 1
- Qarg("+") to state +
- Qarg("-") to state -
- Qarg("Any") to any state on the Bloch sphere




# Assertions

   5 assertions are up to your disposition: (4 currently work properly)

### Assert the equality of inequality of qubits:
```python
   assertEqual(qu0, qu1, qu0_pre=False, qu1_pre=False, basis="z", filter_qc=None, backend="aer_simulator")
   assertNotEqual(qu0, qu1, qu0_pre=False, qu1_pre=False, basis="z", filter_qc=None, backend="aer_simulator")
```

   This assertion requires 2 arguments, which are the indexes of the qubits to be tested, and 2 optional arguments that specify whether the qubits are to be tested before the quantumFunction() is applied.
   It defaults to False, so if no arguments are specified there, it will compare the qubits after the function is applied.
   This assertion tests whether the probabilities of measuring two qubits in the states |0> or |1> are the same.
   The tests are done on the Z-axis.

### Assert the probability of a qubit to be in state |0>
```python
assertProbability(qu0, expectedProba, qu0_pre=False, basis="z", filter_qc=None, backend="aer_simulator")
assertNotProbability(qu0, expectedProba, qu0_pre=False, basis="z", filter_qc=None, backend="aer_simulator")
```

   This assertion requires 2 arguments: first, the index of the qubit to be tested, and secondly the expected probability of measuring the qubit in the state |0> along the Z-axis.
   It can also optionally take in an extra bool argument, that specifies whether the sampling will occur before the quantumFunction is applied.
   It defaults to False, so the sampling occurs after the function.

### Assert that a qubit has teleported into another
```python
assertTeleported(sent, received, basis="z", backend="aer_simulator")
```

   This assertion requires 2 positional arguments: a sent and a received qubit.
   It evaluates whether quantum teleportation has occured between the qubits.

### Assert that two qubits are entangled
```python
assertEntangled(qu0, qu1, basis="z", backend="aer_simulator")
assertNotEntangled(qu0, qu1, basis="z", backend="aer_simulator")
```

   This assertion evaluates whether the qubits show evidence of entanglement along a certain basis.

### Assert that a qubit has transformed its state (to be finished soon)
```python
assertTransformed(...)
```


# Examples

```python
from Qiskit_PTesting.Qiskit_PTesting import QiskitPropertyTest, TestProperty, Qarg

class example(QiskitPropertyTest):
   def property(self):
      return TestProperty(nbQubits=2,
                          qargs={0: Qarg("0"),
                                 1: Qarg("+")})

   def quantumFunction(self, qc):
      qc.h(0)

   def assertions(self):
      self.assertEqual(0, 1)
```



# Inner-workings of the framework

There are 4 main parts in this project:
- Test case generator
- Test execution engine
- Statistical analysis engine
- The programming interface


