Skip to content

Commit

Permalink
Merge branch 'main' into release/0.12.0
Browse files Browse the repository at this point in the history
# Conflicts:
#	perceval/components/generic_interferometer.py
#	tests/test_converter_cqasm.py
  • Loading branch information
ericbrts committed Sep 24, 2024
2 parents b0746c0 + 2c9fd40 commit 97e265c
Show file tree
Hide file tree
Showing 23 changed files with 1,437 additions and 1,382 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/autotests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
python_v:
description: 'python version'
required: true
default: '3.10'
default: '3.11'
type: choice
options:
- '3.9'
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install .
python -m pip install .[qiskit_bridge,qutip_bridge,myqlm_bridge,cqasm_bridge]
python -m pip install -r tests/requirements.txt
- name: Lint with flake8
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ jobs:
echo "os=macos-latest" >> $GITHUB_OUTPUT
fi
echo "commit_ref=${{ github.event.inputs.commit_ref || '' }}" >> $GITHUB_OUTPUT
echo "python_v=${{ github.event.inputs.python_v || '3.9' }}" >> $GITHUB_OUTPUT
echo "python_v_cp=cp$( echo '${{github.event.inputs.python_v || '3.9'}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_OUTPUT
echo "folder_env=${{ github.event.inputs.os || 'MiniMac_arm64' }}-CPython-${{ github.event.inputs.python_v || '3.9' }}" >> $GITHUB_OUTPUT
echo "folder_file_json=.benchmarks/${{ github.event.inputs.os || 'MiniMac_arm64' }}-CPython-${{ github.event.inputs.python_v || '3.9' }}/log/${{ github.run_number }}_$( git describe --tags )_${{ github.sha }}.json" >> $GITHUB_OUTPUT
echo "python_v=${{ github.event.inputs.python_v || '3.11' }}" >> $GITHUB_OUTPUT
echo "python_v_cp=cp$( echo '${{github.event.inputs.python_v || '3.11'}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_OUTPUT
echo "folder_env=${{ github.event.inputs.os || 'MiniMac_arm64' }}-CPython-${{ github.event.inputs.python_v || '3.11' }}" >> $GITHUB_OUTPUT
echo "folder_file_json=.benchmarks/${{ github.event.inputs.os || 'MiniMac_arm64' }}-CPython-${{ github.event.inputs.python_v || '3.11' }}/log/${{ github.run_number }}_$( git describe --tags )_${{ github.sha }}.json" >> $GITHUB_OUTPUT
echo "gh_branch=${{ github.event.inputs.gh_branch || 'main' }}" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -92,9 +92,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install .[qiskit_bridge,qutip_bridge,myqlm_bridge,cqasm_bridge]
python -m pip install -r tests/requirements.txt
python -m pip install .
- name: Run benchmark
run: |
Expand Down
47 changes: 45 additions & 2 deletions .github/workflows/rerun-notebooks-and-build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,49 @@ on:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
perceval-extensive-autotests:
name: Run PyTest on ${{ matrix.os }} and with python version ${{ matrix.version }}
if: always()
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: [3.9, 3.12] # only running with oldest and newest version
steps:
- if: runner.os == 'Linux'
name: Initialize PYTHON_V_CP linux
run: |
echo "PYTHON_V_CP=cp$( echo '${{matrix.version}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV
- if: runner.os != 'Linux'
name: Initialize PYTHON_V_CP notLinux
run: |
echo "PYTHON_V_CP=cp$( echo '${{matrix.version}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV
shell: Bash

- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install .
python -m pip install -r tests/requirements.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
build-env:
name: Rerun notebooks and build docs
# The type of runner that the job will run on
Expand All @@ -30,12 +73,12 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install .
python -m pip install .[qiskit_bridge,qutip_bridge,myqlm_bridge,cqasm_bridge]
python -m pip install -r docs/requirements.txt -r docs/source/notebooks/requirements.txt
- name: Rerun notebooks
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pip install -e .
```
Or to use converters:
```bash
pip install .[qiskit_bridge, qutip_bridge, myqlm_bridge]
pip install .[qiskit_bridge,qutip_bridge,myqlm_bridge,cqasm_bridge]
```

# Running tests and benchmarks
Expand Down
4 changes: 2 additions & 2 deletions benchmark/benchmark_QML-DE-solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ def computation(params):
coefs = lambda_random # coefficients of the M observable
# initial condition with the two universal interferometers and the phase shift in the middle

U_1 = pcvl.Matrix.random_unitary(N, params[:2 * N2])
U_2 = pcvl.Matrix.random_unitary(N, params[2 * N2:4 * N2])
U_1 = pcvl.Matrix.parametrized_unitary(N, params[:2 * N2])
U_2 = pcvl.Matrix.parametrized_unitary(N, params[2 * N2:4 * N2])

# Circuit creation
px = pcvl.P("px")
Expand Down
12 changes: 6 additions & 6 deletions docs/source/notebooks/Differential_equation_resolution.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,9 @@
"parameters = np.random.normal(size=4*m**2)\n",
"\n",
"px = pcvl.P(\"px\")\n",
"c = pcvl.Unitary(pcvl.Matrix.random_unitary(m, parameters[:2 * m ** 2]), name=\"W1\")\\\n",
"c = pcvl.Unitary(pcvl.Matrix.parametrized_unitary(m, parameters[:2 * m ** 2]), name=\"W1\")\\\n",
" // (0, pcvl.PS(px))\\\n",
" // pcvl.Unitary(pcvl.Matrix.random_unitary(m, parameters[2 * m ** 2:]), name=\"W2\")\n",
" // pcvl.Unitary(pcvl.Matrix.parametrized_unitary(m, parameters[2 * m ** 2:]), name=\"W2\")\n",
"\n",
"backend = pcvl.BackendFactory().get_backend(\"SLOS\")\n",
"backend.set_circuit(pcvl.Unitary(pcvl.Matrix.random_unitary(m)))\n",
Expand Down Expand Up @@ -328,8 +328,8 @@
" f_theta_0 = 0 # boundary condition\n",
" coefs = lambda_random # coefficients of the M observable\n",
" # initial condition with the two universal interferometers and the phase shift in the middle\n",
" U_1 = pcvl.Matrix.random_unitary(m, params[:2 * m ** 2])\n",
" U_2 = pcvl.Matrix.random_unitary(m, params[2 * m ** 2:])\n",
" U_1 = pcvl.Matrix.parametrized_unitary(m, params[:2 * m ** 2])\n",
" U_2 = pcvl.Matrix.parametrized_unitary(m, params[2 * m ** 2:])\n",
"\n",
" px = pcvl.P(\"x\")\n",
" c = pcvl.Unitary(U_2) // (0, pcvl.PS(px)) // pcvl.Unitary(U_1)\n",
Expand Down Expand Up @@ -474,8 +474,8 @@
"source": [
"def plot_solution(m, N, X, optim_params, lambda_random):\n",
" Y = []\n",
" U_1 = pcvl.Matrix.random_unitary(m, optim_params[:2 * m ** 2])\n",
" U_2 = pcvl.Matrix.random_unitary(m, optim_params[2 * m ** 2:])\n",
" U_1 = pcvl.Matrix.parametrized_unitary(m, optim_params[:2 * m ** 2])\n",
" U_2 = pcvl.Matrix.parametrized_unitary(m, optim_params[2 * m ** 2:])\n",
" px = pcvl.P(\"x\")\n",
" c = pcvl.Unitary(U_2) // (0, pcvl.PS(px)) // pcvl.Unitary(U_1)\n",
"\n",
Expand Down
6 changes: 1 addition & 5 deletions docs/source/notebooks/Gedik_qudit.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,7 @@
],
"source": [
"# Define the physical parameters that we need.\n",
"remote_pr.set_parameters({\n",
" \"HOM\": .95,\n",
" \"transmittance\": .1,\n",
" \"g2\": .01\n",
"})\n",
"remote_pr.noise = pcvl.NoiseModel(indistinguishability=.95, transmittance=.1, g2=.01)\n",
"remote_pr.min_detected_photons_filter(1)\n",
"\n",
"# Add all the necessary components to the processor.\n",
Expand Down
2 changes: 1 addition & 1 deletion docs/source/notebooks/LOv_rewriting_rules.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@
}
],
"source": [
"a=pcvl.Circuit.generic_interferometer(4, lambda idx:pcvl.Circuit(2)//pcvl.PS(phi=random.random())//pcvl.BS(theta=random.random()), depth=8, shape=\"rectangle\")\n",
"a=pcvl.GenericInterferometer(4, lambda idx:pcvl.Circuit(2)//pcvl.PS(phi=random.random())//pcvl.BS(theta=random.random()), depth=8, shape=pcvl.InterferometerShape.RECTANGLE)\n",
"pcvl.pdisplay(a, recursive=True, render_size=0.7)"
]
},
Expand Down
4 changes: 2 additions & 2 deletions docs/source/reference/logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ To log a message, you can use it the same way as the python logger:

.. code-block:: python
from perceval.utils import logger
from perceval.utils import get_logger
get_logger().info('I log something as info')
# or
logger = get_logger()
Expand Down Expand Up @@ -166,7 +166,7 @@ Example

.. code-block:: python
from perceval.utils.logging import get_logger(), channel, level
from perceval.utils.logging import get_logger, channel, level
logger = get_logger()
logger.enable_file()
logger.set_level(level.info, channel.resources)
Expand Down
2 changes: 1 addition & 1 deletion docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Alternatively, if you are interested in contributing to the project - you can cl
(venv) $ git clone https://github.com/quandela/Perceval
(venv) $ cd Perceval
(venv) $ pip install . # or pip install -e . for developers
(venv) $ pip install . # or pip install -e . for developers or pip install .[qiskit_bridge,qutip_bridge,myqlm_bridge,cqasm_bridge] to install bridges
Tutorial
--------
Expand Down
6 changes: 3 additions & 3 deletions perceval/components/core_catalog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
from .heralded_cnot import HeraldedCnotItem
from .heralded_cz import HeraldedCzItem
from .generic_2mode import Generic2ModeItem
from .mzi import MZIPhaseFirst, MZIPhaseLast
from .mzi import MZIPhaseFirst, MZIPhaseLast, SymmetricMZI
from .postprocessed_ccz import PostProcessedCCZItem
from .toffoli import ToffoliItem
from .controlled_rotation_gates import PostProcessedControledRotationsItem
from .controlled_rotation_gates import PostProcessedControlledRotationsItem

catalog_items = [KLMCnotItem, HeraldedCnotItem, PostProcessedCnotItem, HeraldedCzItem, Generic2ModeItem, MZIPhaseFirst,
MZIPhaseLast, PostProcessedCCZItem, ToffoliItem, PostProcessedControledRotationsItem
MZIPhaseLast, SymmetricMZI, PostProcessedCCZItem, ToffoliItem, PostProcessedControlledRotationsItem
]
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def build_control_gate_unitary(n: int, alpha: float) -> Matrix:
return U


class PostProcessedControledRotationsItem(CatalogItem):
class PostProcessedControlledRotationsItem(CatalogItem):
article_ref = "https://arxiv.org/abs/2405.01395"
description = r"""n-qubit controlled rotation gate C...CZ(alpha) with 2*n ancillary modes and a post-selection function"""
params_doc = {
Expand Down
19 changes: 19 additions & 0 deletions perceval/components/core_catalog/mzi.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def generate(self, i: int):

_NAME_MZI_PHASE_FIRST = "mzi phase first"
_NAME_MZI_PHASE_LAST = "mzi phase last"
_NAME_SYMMETRIC_MZI = "symmetric mzi"


class MZIPhaseFirst(AMZI):
Expand Down Expand Up @@ -97,3 +98,21 @@ def build_circuit(self, **kwargs) -> Circuit:
phi_a, phi_b, theta_a, theta_b = self._handle_params(**kwargs)
return (Circuit(2, name="MZI")
// BS(theta=theta_a) // (1, PS(phi=phi_a)) // BS(theta=theta_b) // (1, PS(phi=phi_b)))


class SymmetricMZI(AMZI):
str_repr = r""" ╭─────╮╭─────╮╭─────╮
0:──┤BS.Rx├┤phi_a├┤BS.Rx├──:0
│ │╰─────╯│ │
│ │╭─────╮│ │
1:──┤ ├┤phi_b├┤ ├──:1
╰─────╯╰─────╯╰─────╯ """
see_also = _NAME_MZI_PHASE_FIRST

def __init__(self):
super().__init__(_NAME_SYMMETRIC_MZI)

def build_circuit(self, **kwargs) -> Circuit:
phi_a, phi_b, theta_a, theta_b = self._handle_params(**kwargs)
return (Circuit(2, name="MZI")
// BS(theta=theta_a) // (0, PS(phi=phi_a)) // (1, PS(phi=phi_b))) // BS(theta=theta_b)
48 changes: 45 additions & 3 deletions perceval/components/generic_interferometer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

from .linear_circuit import ACircuit, Circuit


from perceval.utils import InterferometerShape
from perceval.utils.logging import get_logger, channel

Expand All @@ -41,13 +42,19 @@ class GenericInterferometer(Circuit):
:param m: number of modes
:param fun_gen: generator function for the building components, index is an integer allowing to generate
named parameters - for instance:
:code:`fun_gen=lambda idx: phys.BS()//(0, phys.PS(pcvl.P(f"phi_{idx}")))`
:code:`fun_gen=lambda idx: pcvl.BS()//(0, pcvl.PS(pcvl.P(f"phi_{idx}")))`
:param shape: The output interferometer shape (InterferometerShape.RECTANGLE or InterferometerShape.TRIANGLE)
:param depth: if None, maximal depth is :math:`m-1` for rectangular shape, :math:`m` for triangular shape.
Can be used with :math:`2*m` to reproduce :cite:`fldzhyan2020optimal`.
:param phase_shifter_fun_gen: a function generating a phase_shifter circuit.
:param phase_at_output: if True creates a layer of phase shifters at the output of the generated interferometer
else creates it in the input (default: False)
:param upper_component_gen fun_gen: generator function for the building the upper component, index is an integer allowing to generate
named parameters - for instance:
:code:`fun_gen=lambda idx: pcvl.PS(pcvl.P(f"phi_upper_{idx}"))`
:param lower_component_gen: generator function for the building the lower component, index is an integer allowing to generate
named parameters - for instance:
:code:`fun_gen=lambda idx: pcvl.PS(pcvl.P(f"phi_lower_{idx}"))`
See :cite:`fldzhyan2020optimal`, :cite:`clements2016optimal` and :cite:`reck1994experimental`
"""
Expand All @@ -57,7 +64,9 @@ def __init__(self,
shape: InterferometerShape = InterferometerShape.RECTANGLE,
depth: int = None,
phase_shifter_fun_gen: Callable[[int], ACircuit] = None,
phase_at_output: bool = False):
phase_at_output: bool = False,
upper_component_gen: Callable[[int], ACircuit] = None,
lower_component_gen: Callable[[int], ACircuit] = None):
assert isinstance(shape, InterferometerShape),\
f"Wrong type for shape, expected InterferometerShape, got {type(shape)}"
super().__init__(m)
Expand All @@ -66,6 +75,8 @@ def __init__(self,
self._depth_per_mode = [0] * m
self._pattern_generator = fun_gen
self._has_input_phase_layer = False
self._upper_component_gen = upper_component_gen
self._lower_component_gen = lower_component_gen
if phase_shifter_fun_gen and not phase_at_output:
self._has_input_phase_layer = True
for i in range(0, m):
Expand All @@ -74,6 +85,8 @@ def __init__(self,
if shape == InterferometerShape.RECTANGLE:
self._build_rectangle()
elif shape == InterferometerShape.TRIANGLE:
if upper_component_gen or lower_component_gen:
get_logger().warn(f"upper_component_gen or lower_component_gen cannot be applied for shape {shape}")
self._build_triangle()
else:
raise NotImplementedError(f"Shape {shape} not supported")
Expand Down Expand Up @@ -105,15 +118,44 @@ def set_identity_mode(self):
for p in self.get_parameters():
p.set_value(math.pi)

def _add_single_mode_component(self, mode: int, component: ACircuit) -> None:
"""Add a component to the circuit, check if it's a one mode circuit
:param mode: mode to add the component
:param component: component to add
"""
assert component.m == 1, f"Component should always be a one mode circuit, instead it's a {component.m} modes circuit"
self.add(mode, component)

def _add_upper_component(self, i_depth: int) -> None:
"""Add a component with upper_component_gen between the interferometer on the first mode
:param i_depth: depth index of the interferometer
"""
if self._upper_component_gen and i_depth % 2 == 1:
self._add_single_mode_component(0, self._upper_component_gen(i_depth // 2))

def _add_lower_component(self, i_depth: int) -> None:
"""Add a component with lower_component_gen between the interferometer on the last mode
:param i_depth: depth index of the interferometer
"""
# If m is even, the component is added at even depth index, else it's added in at odd depth index
if (self._lower_component_gen and
((i_depth % 2 == 1 and self.m % 2 == 0) or (i_depth % 2 == 0 and self.m % 2 == 1))):
self._add_single_mode_component(self.m - 1, self._lower_component_gen(i_depth // 2))

def _build_rectangle(self):
max_depth = self.m if self._depth is None else self._depth
idx = 0
for i in range(0, max_depth):
self._add_upper_component(i)
self._add_lower_component(i)
for j in range(0+i%2, self.m-1, 2):
if self._depth is not None and (self._depth_per_mode[j] == self._depth
or self._depth_per_mode[j+1] == self._depth):
continue
self.add((j, j+1), self._pattern_generator(idx), merge=True, x_grid=i)
self.add((j, j+1), self._pattern_generator(idx), merge=True)
self._depth_per_mode[j] += 1
self._depth_per_mode[j+1] += 1
idx += 1
Expand Down
3 changes: 2 additions & 1 deletion perceval/components/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def samples(self, max_samples: int, max_shots: int = None, progress_callback=Non
from perceval.simulators import NoisySamplingSimulator
assert isinstance(self.backend, ASamplingBackend), "A sampling backend is required to call samples method"
sampling_simulator = NoisySamplingSimulator(self.backend)
sampling_simulator.sleep_between_batches = 0 # Remove sleep time between batches of samples in local simulation
sampling_simulator.set_circuit(self.linear_circuit())
sampling_simulator.set_selection(
min_detected_photons_filter=self._min_detected_photons_filter, postselect=self.post_select_fn, heralds=self.heralds)
Expand Down Expand Up @@ -334,7 +335,7 @@ def log_resources(self, method: str, extra_parameters: dict):
elif isinstance(self._input_state, SVDistribution):
my_dict['n'] = self._input_state.n_max
else:
get_logger().info(f"Cannot get n for type {type(self._input_state)}", channel.resource)
get_logger().error(f"Cannot get n for type {type(self._input_state)}", channel.general)
if extra_parameters:
my_dict.update(extra_parameters)
if self.noise: # TODO: PCVL-782
Expand Down
Loading

0 comments on commit 97e265c

Please sign in to comment.