Skip to content

Commit

Permalink
Release version 0.4.1
Browse files Browse the repository at this point in the history
Co-authored-by: Ian Fan <[email protected]>
Co-authored-by: Dimitri Kartsaklis <[email protected]>
Co-authored-by: Neil Ortega <[email protected]>
  • Loading branch information
4 people committed Apr 11, 2024
1 parent 19da7cd commit 00812e1
Show file tree
Hide file tree
Showing 39 changed files with 819 additions and 177 deletions.
10 changes: 7 additions & 3 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, "3.10", "3.11" ]
python-version: [ 3.9, "3.10", "3.11", "3.12" ]
outputs:
error-check: ${{ steps.error-check.conclusion }}
steps:
Expand Down Expand Up @@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, "3.10", "3.11" ]
python-version: [ 3.9, "3.10", "3.11", "3.12" ]
steps:
- uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python-version }}
Expand All @@ -60,6 +60,10 @@ jobs:
run: pip install .
- name: Check package import works
run: python -c 'import lambeq'
- name: Install downgraded Qiskit (for compatibility reasons)
# This should be removed once the following PR is merged:
# https://github.com/PennyLaneAI/pennylane-qiskit/pull/493
run: pip install 'qiskit<1' pytket-qiskit qiskit-ibm-runtime
- name: Install extra dependencies and tester
run: pip install .[extras] .[test]
- name: Locate bobcat pre-trained model cache
Expand Down Expand Up @@ -102,7 +106,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, "3.10", "3.11" ]
python-version: [ 3.9, "3.10", "3.11", "3.12" ]
steps:
- uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python-version }}
Expand Down
80 changes: 80 additions & 0 deletions contrib/convert_discopy_to_lambeq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Script for converting DisCoPy circuits to lambeq circuits.
This requires `discopy>=1.1.7` to work.
"""
from argparse import ArgumentParser
import glob
import os
import pickle
import sys

import discopy
from lambeq.backend.quantum import Diagram as Circuit


MIN_DISCOPY_VERSION = '1.1.7'


def convert_discopy_to_lambeq(pkl_path: str) -> list[Circuit]:
"""Convert pickled DisCoPy circuits from disk to lambeq circuits.
Parameters
----------
pkl_path: path to pickle file or directory containing pickle files
Returns
-------
List of lambeq circuits
"""

if pkl_path.endswith(".pkl"):
pkl_files = [pkl_path]
else:
pkl_files = glob.glob(pkl_path + "/*.pkl")

lmbq_circs = []
for pkl_file in pkl_files:
with open(pkl_file, "rb") as f:
dcp_circs = pickle.load(f)

if not isinstance(dcp_circs, list):
dcp_circs = [dcp_circs]

for dcp_circ in dcp_circs:
lmbq_circ = Circuit.from_discopy(dcp_circ)
lmbq_circs.append(lmbq_circ)

return lmbq_circs


def main():
# Conversion is only supported from DisCoPy v1.1.7 and onwards.
if not discopy.__version__ >= MIN_DISCOPY_VERSION:
raise AssertionError(f'`discopy>={MIN_DISCOPY_VERSION}` is required by this script.')

parser = ArgumentParser(description='Convert DisCoPy circuits to lambeq circuits')
parser.add_argument('--discopy-circuit-path',
type=str,
help='Either a directory containing the pickled DisCoPy '
'circuits or a specific pickle file.',
default='discopy_circs')
parser.add_argument('--output-dir',
type=str,
help='The directory where the output lambeq circuits '
'will be saved. The file will have the filename '
'`lambeq_circs.pkl`.',
default='output')
args = parser.parse_args()

# Create output dir if it doesn't exist yet
os.makedirs(args.output_dir, exist_ok=True)

lmbq_circs = convert_discopy_to_lambeq(args.discopy_circuit_path)
pickle.dump(lmbq_circs,
open(f'{args.output_dir}/lambeq_circs.pkl', 'wb'))


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions docs/package-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Concrete implementations of classical and quantum :term:`ansätze <ansatz (plura
lambeq.ansatz.MPSAnsatz
lambeq.ansatz.Sim14Ansatz
lambeq.ansatz.Sim15Ansatz
lambeq.ansatz.Sim4Ansatz
lambeq.ansatz.SpiderAnsatz
lambeq.ansatz.StronglyEntanglingAnsatz
lambeq.ansatz.Symbol
Expand Down
2 changes: 2 additions & 0 deletions docs/puml/ansatz.puml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class IQPAnsatz {}
class StronglyEntanglingAnsatz {}
class Sim14Ansatz {}
class Sim15Ansatz {}
class Sim4Ansatz {}

class Symbol {
size: int
Expand All @@ -55,6 +56,7 @@ CircuitAnsatz <|-- IQPAnsatz
CircuitAnsatz <|-- StronglyEntanglingAnsatz
CircuitAnsatz <|-- Sim14Ansatz
CircuitAnsatz <|-- Sim15Ansatz
CircuitAnsatz <|-- Sim4Ansatz

MPSAnsatz::split_functor *-left- backend.grammar.Functor
MPSAnsatz::tensor_functor *-- backend.grammar.Functor
Expand Down
23 changes: 23 additions & 0 deletions docs/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
Release notes
=============

.. _rel-0.4.1:

`0.4.1 <https://github.com/CQCL/lambeq/releases/tag/0.4.1>`_
------------------------------------------------------------

Added:

- Support for Python 3.12.
- A new :py:class:`~lambeq.Sim4Ansatz` based on the Sim `et al.` paper [SJA2019]_.
- A new argument in :py:meth:`.Trainer.fit` for specifying an :py:attr:`early_stopping_criterion` other than validation loss.
- A new argument :py:attr:`collapse_noun_phrases` in methods of :py:class:`.CCGParser` and :py:class:`.CCGTree` classes (for example, see :py:meth:`.CCGParser.sentence2diagram`) that allows the user to maintain noun phrases in the derivation or collapse them into nouns as desired.
- Raised meaningful exception when users try to convert to/from DisCoPy 1.1.0

Changed:

- An internal refactoring of module :py:mod:`.backend.drawing` in view of planned new features.
- Updated random number generation in :py:class:`~lambeq.TketModel` by using the recommended :py:meth:`numpy.random.default_rnd` method.

Fixed:

- Handling of possible empty ``Bra`` s and ``Ket`` s during conversion from DisCoPy.
- Fixed a bug in JIT compilation of mixed circuit evaluations.

.. _rel-0.4.0:

`0.4.0 <https://github.com/CQCL/lambeq/releases/tag/0.4.0>`_
Expand Down
5 changes: 5 additions & 0 deletions docs/tutorials/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[qiskit.ibmq]
ibmqx_token = "my_API_token"

[honeywell.global]
user_email = "my_Honeywell/Quantinuum_account_email"
1 change: 1 addition & 0 deletions docs/tutorials/parameterise.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
" \":py:class:`~.IQPAnsatz`\", \"Instantaneous Quantum Polynomial ansatz. An IQP ansatz interleaves layers of Hadamard gates with diagonal unitaries. This class uses ``n_layers-1`` adjacent CRz gates to implement each diagonal unitary (see [Hea2019]_)\"\n",
" \":py:class:`~.Sim14Ansatz`\", \"A modification of Circuit 14 from [SJA2019]_. Replaces circuit-block construction with two rings of CRx gates, in opposite orientation.\"\n",
" \":py:class:`~.Sim15Ansatz`\", \"A modification of Circuit 15 from [SJA2019]_. Replaces circuit-block construction with two rings of CNOT gates, in opposite orientation.\"\n",
" \":py:class:`~.Sim4Ansatz`\", \"Circuit 4 from [SJA2019]_. Uses a layer each of Rx and Rz gates, followed by a ladder of CRx gates per layer. \"\n",
" \":py:class:`~.StronglyEntanglingAnsatz`\", \"Ansatz using three single qubit rotations (RzRyRz) followed by a ladder of CNOT gates with different ranges per layer. Adapted from the :term:`PennyLane` implementation of :py:mod:`StronglyEntanglingLayers`.\""
]
},
Expand Down
61 changes: 31 additions & 30 deletions docs/tutorials/trainer-quantum.ipynb

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions lambeq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
'MPSAnsatz',
'Sim14Ansatz',
'Sim15Ansatz',
'Sim4Ansatz',
'SpiderAnsatz',
'StronglyEntanglingAnsatz',
'Symbol',
Expand All @@ -53,8 +54,6 @@

'VerbosityLevel',

'diagram2str',

'Reader',
'LinearReader',
'TreeReader',
Expand Down Expand Up @@ -107,11 +106,10 @@

from lambeq import ansatz, core, rewrite, text2diagram, tokeniser, training
from lambeq.ansatz import (BaseAnsatz, CircuitAnsatz, IQPAnsatz, MPSAnsatz,
Sim14Ansatz, Sim15Ansatz, SpiderAnsatz,
Sim14Ansatz, Sim15Ansatz, Sim4Ansatz, SpiderAnsatz,
StronglyEntanglingAnsatz, Symbol, TensorAnsatz)
from lambeq.core.globals import VerbosityLevel
from lambeq.core.types import AtomicType
from lambeq.backend.drawing.text_printer import diagram2str
from lambeq.rewrite import (CoordinationRewriteRule, CurryRewriteRule,
DiagramRewriter, RemoveCupsRewriter,
RemoveSwapsRewriter, Rewriter, RewriteRule,
Expand Down
4 changes: 2 additions & 2 deletions lambeq/ansatz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
# limitations under the License.

__all__ = ['BaseAnsatz', 'CircuitAnsatz', 'IQPAnsatz', 'MPSAnsatz',
'Sim14Ansatz', 'Sim15Ansatz', 'SpiderAnsatz',
'Sim14Ansatz', 'Sim15Ansatz', 'Sim4Ansatz', 'SpiderAnsatz',
'StronglyEntanglingAnsatz', 'Symbol', 'TensorAnsatz']

from lambeq.ansatz.base import BaseAnsatz, Symbol
from lambeq.ansatz.circuit import (CircuitAnsatz, IQPAnsatz,
Sim14Ansatz, Sim15Ansatz,
Sim14Ansatz, Sim15Ansatz, Sim4Ansatz,
StronglyEntanglingAnsatz)
from lambeq.ansatz.tensor import MPSAnsatz, SpiderAnsatz, TensorAnsatz
77 changes: 75 additions & 2 deletions lambeq/ansatz/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

__all__ = ['CircuitAnsatz',
'IQPAnsatz',
'Sim4Ansatz',
'Sim14Ansatz',
'Sim15Ansatz',
'StronglyEntanglingAnsatz']
Expand Down Expand Up @@ -77,6 +78,8 @@ def __init__(self,
The number of layers used by the ansatz.
n_single_qubit_params : int
The number of single qubit rotations used by the ansatz.
It only affects wires that `ob_map` maps to a single
qubit.
circuit : callable
Circuit generator used by the ansatz. This is a function
(or a class constructor) that takes a number of qubits and
Expand Down Expand Up @@ -181,6 +184,8 @@ def __init__(self,
The number of layers used by the ansatz.
n_single_qubit_params : int, default: 3
The number of single qubit rotations used by the ansatz.
It only affects wires that `ob_map` maps to a single
qubit.
discard : bool, default: False
Discard open wires instead of post-selecting.
Expand Down Expand Up @@ -218,7 +223,7 @@ class Sim14Ansatz(CircuitAnsatz):
Replaces circuit-block construction with two rings of CRx gates, in
opposite orientation.
Paper at: https://arxiv.org/pdf/1905.10876.pdf
Paper at: https://arxiv.org/abs/1905.10876
Code adapted from DisCoPy.
Expand All @@ -240,6 +245,8 @@ def __init__(self,
The number of layers used by the ansatz.
n_single_qubit_params : int, default: 3
The number of single qubit rotations used by the ansatz.
It only affects wires that `ob_map` maps to a single
qubit.
discard : bool, default: False
Discard open wires instead of post-selecting.
Expand Down Expand Up @@ -286,7 +293,7 @@ class Sim15Ansatz(CircuitAnsatz):
Replaces circuit-block construction with two rings of CNOT gates, in
opposite orientation.
Paper at: https://arxiv.org/pdf/1905.10876.pdf
Paper at: https://arxiv.org/abs/1905.10876
Code adapted from DisCoPy.
Expand All @@ -308,6 +315,8 @@ def __init__(self,
The number of layers used by the ansatz.
n_single_qubit_params : int, default: 3
The number of single qubit rotations used by the ansatz.
It only affects wires that `ob_map` maps to a single
qubit.
discard : bool, default: False
Discard open wires instead of post-selecting.
Expand Down Expand Up @@ -348,6 +357,68 @@ def circuit(self, n_qubits: int, params: np.ndarray) -> Circuit:
return circuit # type: ignore[return-value]


class Sim4Ansatz(CircuitAnsatz):
"""Circuit 4 from Sim et al.
Ansatz with a layer of Rx and Rz gates, followed by a
ladder of CRxs.
Paper at: https://arxiv.org/abs/1905.10876
"""

def __init__(self,
ob_map: Mapping[Ty, int],
n_layers: int,
n_single_qubit_params: int = 3,
discard: bool = False) -> None:
"""Instantiate a Sim 4 ansatz.
Parameters
----------
ob_map : dict
A mapping from :py:class:`lambeq.backend.grammar.Ty` to
the number of qubits it uses in a circuit.
n_layers : int
The number of layers used by the ansatz.
n_single_qubit_params : int, default: 3
The number of single qubit rotations used by the ansatz.
It only affects wires that `ob_map` maps to a single
qubit.
discard : bool, default: False
Discard open wires instead of post-selecting.
"""
super().__init__(ob_map,
n_layers,
n_single_qubit_params,
self.circuit,
discard,
[Rx, Rz])

def params_shape(self, n_qubits: int) -> tuple[int, ...]:
return (self.n_layers, 3 * n_qubits - 1)

def circuit(self, n_qubits: int, params: np.ndarray) -> Circuit:
if n_qubits == 1:
circuit = Rx(params[0]) >> Rz(params[1]) >> Rx(params[2])
else:
circuit = Id(n_qubits)

for thetas in params:
circuit >>= Id().tensor(*map(Rx, thetas[:n_qubits]))
circuit >>= Id().tensor(*map(Rz,
thetas[n_qubits:2 * n_qubits]))

crxs = Id(n_qubits)
for i in range(n_qubits - 1):
crxs = crxs.CRx(thetas[2 * n_qubits + i], i, i + 1)

circuit >>= crxs

return circuit # type: ignore[return-value]


class StronglyEntanglingAnsatz(CircuitAnsatz):
"""Strongly entangling ansatz.
Expand Down Expand Up @@ -380,6 +451,8 @@ def __init__(self,
The number of circuit layers used by the ansatz.
n_single_qubit_params : int, default: 3
The number of single qubit rotations used by the ansatz.
It only affects wires that `ob_map` maps to a single
qubit.
ranges : list of int, optional
The range of the CNOT gate between wires in each layer. By
default, the range starts at one (i.e. adjacent wires) and
Expand Down
Loading

0 comments on commit 00812e1

Please sign in to comment.