New features since last release
Custom measurement processes ๐
-
Custom measurements can now be facilitated with the addition of the
qml.measurements
module. (#3286) (#3343) (#3288) (#3312) (#3287) (#3292) (#3287) (#3326) (#3327) (#3388) (#3439) (#3466)Within
qml.measurements
are new subclasses that allow for the possibility to create custom measurements:SampleMeasurement
: represents a sample-based measurementStateMeasurement
: represents a state-based measurementMeasurementTransform
: represents a measurement process that requires the application of a batch transform
Creating a custom measurement involves making a class that inherits from one of the classes above. An example is given below. Here, the measurement computes the number of samples obtained of a given state:
from pennylane.measurements import SampleMeasurement
class CountState(SampleMeasurement):
def __init__(self, state: str):
self.state = state # string identifying the state, e.g. "0101"
wires = list(range(len(state)))
super().__init__(wires=wires)
def process_samples(self, samples, wire_order, shot_range, bin_size):
counts_mp = qml.counts(wires=self._wires)
counts = counts_mp.process_samples(samples, wire_order, shot_range, bin_size)
return counts.get(self.state, 0)
def __copy__(self):
return CountState(state=self.state)
We can now execute the new measurement in a QNode as follows.
dev = qml.device("default.qubit", wires=1, shots=10000)
@qml.qnode(dev)
def circuit(x):
qml.RX(x, wires=0)
return CountState(state="1")
>>> circuit(1.23)
tensor(3303., requires_grad=True)
Differentiability is also supported for this new measurement process:
>>> x = qml.numpy.array(1.23, requires_grad=True)
>>> qml.grad(circuit)(x)
4715.000000000001
For more information about these new features, see the documentation for qml.measurements
.
ZX Calculus ๐งฎ
- QNodes can now be converted into ZX diagrams via the PyZX framework. (#3446)
ZX diagrams are the medium for which we can envision a quantum circuit as a graph in the ZX-calculus language, showing properties of quantum protocols in a visually compact and logically complete fashion.
QNodes decorated with @qml.transforms.to_zx
will return a PyZX graph that represents the computation in the ZX-calculus language.
dev = qml.device("default.qubit", wires=2)
@qml.transforms.to_zx
@qml.qnode(device=dev)
def circuit(p):
qml.RZ(p[0], wires=1),
qml.RZ(p[1], wires=1),
qml.RX(p[2], wires=0),
qml.PauliZ(wires=0),
qml.RZ(p[3], wires=1),
qml.PauliX(wires=1),
qml.CNOT(wires=[0, 1]),
qml.CNOT(wires=[1, 0]),
qml.SWAP(wires=[0, 1]),
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
>>> params = [5 / 4 * np.pi, 3 / 4 * np.pi, 0.1, 0.3]
>>> circuit(params)
Graph(20 vertices, 23 edges)
Information about PyZX graphs can be found in the PyZX Graphs API.
QChem databases and basis sets โ๏ธ
-
The symbols and geometry of a compound from the PubChem database can now be accessed via
qchem.mol_data()
. (#3289) (#3378)>>> import pennylane as qml >>> from pennylane.qchem import mol_data >>> mol_data("BeH2") (['Be', 'H', 'H'], tensor([[ 4.79404621, 0.29290755, 0. ], [ 3.77945225, -0.29290755, 0. ], [ 5.80882913, -0.29290755, 0. ]], requires_grad=True)) >>> mol_data(223, "CID") (['N', 'H', 'H', 'H', 'H'], tensor([[ 0. , 0. , 0. ], [ 1.82264085, 0.52836742, 0.40402345], [ 0.01417295, -1.67429735, -0.98038991], [-0.98927163, -0.22714508, 1.65369933], [-0.84773114, 1.373075 , -1.07733286]], requires_grad=True))
-
Perform quantum chemistry calculations with two new basis sets:
6-311g
andCC-PVDZ
. (#3279)>>> symbols = ["H", "He"] >>> geometry = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], requires_grad=False) >>> charge = 1 >>> basis_names = ["6-311G", "CC-PVDZ"] >>> for basis_name in basis_names: ... mol = qml.qchem.Molecule(symbols, geometry, charge=charge, basis_name=basis_name) ... print(qml.qchem.hf_energy(mol)()) [-2.84429531] [-2.84061284]
A bunch of new operators ๐
-
The controlled CZ gate and controlled Hadamard gate are now available via
qml.CCZ
andqml.CH
, respectively. (#3408)>>> ccz = qml.CCZ(wires=[0, 1, 2]) >>> qml.matrix(ccz) [[ 1 0 0 0 0 0 0 0] [ 0 1 0 0 0 0 0 0] [ 0 0 1 0 0 0 0 0] [ 0 0 0 1 0 0 0 0] [ 0 0 0 0 1 0 0 0] [ 0 0 0 0 0 1 0 0] [ 0 0 0 0 0 0 1 0] [ 0 0 0 0 0 0 0 -1]] >>> ch = qml.CH(wires=[0, 1]) >>> qml.matrix(ch) [[ 1. 0. 0. 0. ] [ 0. 1. 0. 0. ] [ 0. 0. 0.70710678 0.70710678] [ 0. 0. 0.70710678 -0.70710678]]
-
Three new parametric operators,
qml.CPhaseShift00
,qml.CPhaseShift01
, andqml.CPhaseShift10
, are now available. Each of these operators performs a phase shift akin toqml.ControlledPhaseShift
but on different positions of the state vector. (#2715)>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) >>> def circuit(): ... qml.PauliX(wires=1) ... qml.CPhaseShift01(phi=1.23, wires=[0,1]) ... return qml.state() ... >>> circuit() tensor([0. +0.j , 0.33423773+0.9424888j, 1. +0.j , 0. +0.j ], requires_grad=True)
-
A new gate operation called
qml.FermionicSWAP
has been added. This implements the exchange of spin orbitals representing fermionic-modes while maintaining proper anti-symmetrization. (#3380)dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(phi): qml.BasisState(np.array([0, 1]), wires=[0, 1]) qml.FermionicSWAP(phi, wires=[0, 1]) return qml.state()
>>> circuit(0.1) tensor([0. +0.j , 0.99750208+0.04991671j, 0.00249792-0.04991671j, 0. +0.j ], requires_grad=True)
-
Create operators defined from a generator via
qml.ops.op_math.Evolution
. (#3375)
qml.ops.op_math.Evolution
defines the exponential of an operator $\hat{O}$ of the form $e^{ix\hat{O}}$, with a single trainable parameter, $x$. Limiting to a single trainable parameter allows the use of qml.gradients.param_shift
to find the gradient with respect to the parameter $x$.
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev, diff_method=qml.gradients.param_shift)
def circuit(phi):
qml.ops.op_math.Evolution(qml.PauliX(0), -.5 * phi)
return qml.expval(qml.PauliZ(0))
>>> phi = np.array(1.2)
>>> circuit(phi)
tensor(0.36235775, requires_grad=True)
>>> qml.grad(circuit)(phi)
-0.9320390495504149
- The qutrit Hadamard gate,
qml.THadamard
, is now available. (#3340)
The operation accepts a subspace
keyword argument which determines which variant of the qutrit Hadamard to use.
>>> th = qml.THadamard(wires=0, subspace=[0, 1])
>>> qml.matrix(th)
array([[ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j],
[ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 1. +0.j]])
New transforms, functions, and more ๐ฏ
- Calculating the purity of arbitrary quantum states is now supported. (#3290)
The purity can be calculated in an analogous fashion to, say, the Von Neumann entropy:
-
qml.math.purity
can be used as an in-line function:>>> x = [1, 0, 0, 1] / np.sqrt(2) >>> qml.math.purity(x, [0, 1]) 1.0 >>> qml.math.purity(x, [0]) 0.5 >>> x = [[1 / 2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1 / 2]] >>> qml.math.purity(x, [0, 1]) 0.5
-
qml.qinfo.transforms.purity
can transform a QNode returning a state to a
function that returns the purity:dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) def circuit(x): qml.IsingXX(x, wires=[0, 1]) return qml.state()
>>> qml.qinfo.transforms.purity(circuit, wires=[0])(np.pi / 2) 0.5 >>> qml.qinfo.transforms.purity(circuit, wires=[0, 1])(np.pi / 2) 1.0
As with the other methods in qml.qinfo
, the purity is fully differentiable:
>>> param = np.array(np.pi / 4, requires_grad=True)
>>> qml.grad(qml.qinfo.transforms.purity(circuit, wires=[0]))(param)
-0.5
- A new gradient transform,
qml.gradients.spsa_grad
, that is based on the idea of SPSA is now available. (#3366)
This new transform allows users to compute a single estimate of a quantum gradient using simultaneous perturbation of parameters and a stochastic approximation. A QNode that takes, say, an argument x
, the approximate gradient can be computed as follows.
>>> dev = qml.device("default.qubit", wires=2)
>>> x = np.array(0.4, requires_grad=True)
>>> @qml.qnode(dev)
... def circuit(x):
... qml.RX(x, 0)
... qml.RX(x, 1)
... return qml.expval(qml.PauliZ(0))
>>> grad_fn = qml.gradients.spsa_grad(circuit, h=0.1, num_directions=1)
>>> grad_fn(x)
array(-0.38876964)
The argument num_directions
determines how many directions of simultaneous perturbation are used, which is proportional to the number of circuit evaluations. See the SPSA gradient transform documentation for details. Note that the full SPSA optimizer is already available as qml.SPSAOptimizer
.
-
Multiple mid-circuit measurements can now be combined arithmetically to create new conditionals. (#3159)
dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=0) qml.Hadamard(wires=1) m0 = qml.measure(wires=0) m1 = qml.measure(wires=1) combined = 2 * m1 + m0 qml.cond(combined == 2, qml.RX)(1.3, wires=2) return qml.probs(wires=2)
>>> circuit() [0.90843735 0.09156265]
-
A new method called
pauli_decompose()
has been added to theqml.pauli
module, which takes a hermitian matrix, decomposes it in the Pauli basis, and returns it either as aqml.Hamiltonian
orqml.PauliSentence
instance. (#3384) -
Operation
orHamiltonian
instances can now be generated from aqml.PauliSentence
orqml.PauliWord
via the newoperation()
andhamiltonian()
methods. (#3391)>>> pw = qml.pauli.PauliWord({0: 'X', 1: 'Y'}) >>> print(pw.operation()) PauliX(wires=[0]) @ PauliY(wires=[1]) >>> print(pw.hamiltonian()) (1) [X0 Y1] >>> ps = qml.pauli.PauliSentence({pw: -1.23}) >>> print(ps.operation()) -1.23*(PauliX(wires=[0]) @ PauliY(wires=[1])) >>> print(ps.hamiltonian()) (-1.23) [X0 Y1]
-
A
sum_expand
function has been added for tapes, which splits a tape measuring aSum
expectation into mutliple tapes of summand expectations, and provides a function to recombine the results. (#3230)
(Experimental) More interface support for multi-measurement and gradient output types ๐งช
-
The autograd and Tensorflow interfaces now support devices with shot vectors when
qml.enable_return()
has been called. (#3374) (#3400)Here is an example using Tensorflow:
import tensorflow as tf qml.enable_return() dev = qml.device("default.qubit", wires=2, shots=[1000, 2000, 3000]) @qml.qnode(dev, diff_method="parameter-shift", interface="tf") def circuit(a): qml.RY(a, wires=0) qml.RX(0.2, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.probs([0, 1])
>>> a = tf.Variable(0.4) >>> with tf.GradientTape() as tape: ... res = circuit(a) ... res = tf.stack([tf.experimental.numpy.hstack(r) for r in res]) ... >>> res <tf.Tensor: shape=(3, 5), dtype=float64, numpy= array([[0.902, 0.951, 0. , 0. , 0.049], [0.898, 0.949, 0. , 0. , 0.051], [0.892, 0.946, 0. , 0. , 0.054]])> >>> tape.jacobian(res, a) <tf.Tensor: shape=(3, 5), dtype=float64, numpy= array([[-0.345 , -0.1725 , 0. , 0. , 0.1725 ], [-0.383 , -0.1915 , 0. , 0. , 0.1915 ], [-0.38466667, -0.19233333, 0. , 0. , 0.19233333]])>
-
The PyTorch interface is now fully supported when
qml.enable_return()
has been called, allowing the calculation of the Jacobian and the Hessian using custom differentiation methods (e.g., parameter-shift, finite difference, or adjoint). (#3416)import torch qml.enable_return() dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift", interface="torch") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.probs([0, 1])
>>> a = torch.tensor(0.1, requires_grad=True) >>> b = torch.tensor(0.2, requires_grad=True) >>> torch.autograd.functional.jacobian(circuit, (a, b)) ((tensor(-0.0998), tensor(0.)), (tensor([-0.0494, -0.0005, 0.0005, 0.0494]), tensor([-0.0991, 0.0991, 0.0002, -0.0002])))
-
The JAX-JIT interface now supports first-order gradient computation when
qml.enable_return()
has been called. (#3235) (#3445)import jax from jax import numpy as jnp jax.config.update("jax_enable_x64", True) qml.enable_return() dev = qml.device("lightning.qubit", wires=2) @jax.jit @qml.qnode(dev, interface="jax-jit", diff_method="parameter-shift") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=0) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) a, b = jnp.array(1.0), jnp.array(2.0)
>>> jax.jacobian(circuit, argnums=[0, 1])(a, b) ((Array(0.35017549, dtype=float64, weak_type=True), Array(-0.4912955, dtype=float64, weak_type=True)), (Array(5.55111512e-17, dtype=float64, weak_type=True), Array(0., dtype=float64, weak_type=True)))
Improvements ๐
-
qml.pauli.is_pauli_word
now supports instances ofqml.Hamiltonian
. (#3389) -
When
qml.probs
,qml.counts
, andqml.sample
are called with no arguments, they measure all wires. Calling any of the aforementioned measurements with an empty wire list (e.g.,qml.sample(wires=[])
) will raise an error. (#3299) -
Made
qml.gradients.finite_diff
more convenient to use with custom data type observables/devices by reducing the number of magic methods that need to be defined in the custom data type to supportfinite_diff
. (#3426) -
The
qml.ISWAP
gate is now natively supported ondefault.mixed
, improving on its efficiency. (#3284) -
Added more input validation to
qml.transforms.hamiltonian_expand
such that Hamiltonian objects with no terms raise an error. (#3339) -
Continuous integration checks are now performed for Python 3.11 and Torch v1.13. Python 3.7 is dropped. (#3276)
-
qml.Tracker
now also logs results intracker.history
when tracking the execution of a circuit. (#3306) -
The execution time of
Wires.all_wires
has been improved by avoiding data type changes and making use ofitertools.chain
. (#3302) -
Printing an instance of
qml.qchem.Molecule
is now more concise and informational. (#3364) -
The error message for
qml.transforms.insert
when it fails to diagonalize non-qubit-wise-commuting observables is now more detailed. (#3381) -
Extended the
qml.equal
function toqml.Hamiltonian
andTensor
objects. (#3390) -
QuantumTape._process_queue
has been moved toqml.queuing.process_queue
to disentangle its functionality from theQuantumTape
class. (#3401) -
QPE can now accept a target operator instead of a matrix and target wires pair. (#3373)
-
The
qml.ops.op_math.Controlled.map_wires
method now usesbase.map_wires
internally instead of the private_wires
property setter. (#3405) -
A new function called
qml.tape.make_qscript
has been created for converting a quantum function into a quantum script. This replacesqml.transforms.make_tape
. (#3429) -
Add a
_pauli_rep
attribute to operators to integrate the new Pauli arithmetic classes with native PennyLane objects. (#3443) -
Extended the functionality of
qml.matrix
to qutrits. (#3508) -
The
qcut.py
file inpennylane/transforms/
has been reorganized into multiple files that are now inpennylane/transforms/qcut/
. (#3413) -
A warning now appears when creating a
Tensor
object with overlapping wires, informing that this can lead to undefined behaviour. (#3459) -
Extended the
qml.equal
function toqml.ops.op_math.Controlled
andqml.ops.op_math.ControlledOp
objects. (#3463) -
Nearly every instance of
with QuantumTape()
has been replaced withQuantumScript
construction. (#3454) -
Added
validate_subspace
static method toqml.Operator
to check the validity of the subspace of certain
qutrit operations. (#3340) -
qml.equal
now supports operators created viaqml.s_prod
,qml.pow
,qml.exp
, andqml.adjoint
. (#3471) -
Devices can now disregard observable grouping indices in Hamiltonians through the optional
use_grouping
attribute. (#3456) -
Add the optional argument
lazy=True
to functionsqml.s_prod
,qml.prod
andqml.op_sum
to allow simplification. (#3483) -
Updated the
qml.transforms.zyz_decomposition
function such that it now supports broadcast operators. This means that single-qubitqml.QubitUnitary
operators, instantiated from a batch of unitaries, can now be decomposed. (#3477) -
The performance of executing circuits under the
jax.vmap
transformation has been improved by being able to leverage the batch-execution capabilities of some devices. (#3452) -
The tolerance for converting openfermion Hamiltonian complex coefficients to real ones has been modified to prevent conversion errors. (#3367)
-
OperationRecorder
now inherits fromAnnotatedQueue
andQuantumScript
instead ofQuantumTape
. (#3496) -
Updated
qml.transforms.split_non_commuting
to support the new return types. (#3414) -
Updated
qml.transforms.mitigate_with_zne
to support the new return types. (#3415) -
Updated
qml.transforms.metric_tensor
,qml.transforms.adjoint_metric_tensor
,qml.qinfo.classical_fisher
, andqml.qinfo.quantum_fisher
to support the new return types. (#3449) -
Updated
qml.transforms.batch_params
andqml.transforms.batch_input
to support the new return types. (#3431) -
Updated
qml.transforms.cut_circuit
andqml.transforms.cut_circuit_mc
to support the new return types. (#3346) -
Limit NumPy version to
<1.24
. (#3346)
Breaking changes ๐
-
Python 3.7 support is no longer maintained. PennyLane will be maintained for versions 3.8 and up. (#3276)
-
The
log_base
attribute has been moved fromMeasurementProcess
to the newVnEntropyMP
andMutualInfoMP
classes, which inherit fromMeasurementProcess
. (#3326) -
qml.utils.decompose_hamiltonian()
has been removed. Please useqml.pauli.pauli_decompose()
instead. (#3384) -
The
return_type
attribute ofMeasurementProcess
has been removed where possible. Useisinstance
checks instead. (#3399) -
Instead of having an
OrderedDict
attribute called_queue
,AnnotatedQueue
now inherits fromOrderedDict
and encapsulates the queue. Consequentially, this also applies to theQuantumTape
class which inherits fromAnnotatedQueue
. (#3401) -
The
ShadowMeasurementProcess
class has been renamed toClassicalShadowMP
. (#3388) -
The
qml.Operation.get_parameter_shift
method has been removed. Thegradients
module should be used for general parameter-shift rules instead. (#3419) -
The signature of the
QubitDevice.statistics
method has been changed fromdef statistics(self, observables, shot_range=None, bin_size=None, circuit=None):
to
def statistics(self, circuit: QuantumTape, shot_range=None, bin_size=None):
- The
MeasurementProcess
class is now an abstract class andreturn_type
is now a property of the class. (#3434)
Deprecations ๐
Deprecations cycles are tracked at doc/developement/deprecations.rst.
-
The following methods are deprecated: (#3281)
qml.tape.get_active_tape
: Useqml.QueuingManager.active_context()
insteadqml.transforms.qcut.remap_tape_wires
: Useqml.map_wires
insteadqml.tape.QuantumTape.inv()
: Useqml.tape.QuantumTape.adjoint()
insteadqml.tape.stop_recording()
: Useqml.QueuingManager.stop_recording()
insteadqml.tape.QuantumTape.stop_recording()
: Useqml.QueuingManager.stop_recording()
insteadqml.QueuingContext
is nowqml.QueuingManager
QueuingManager.safe_update_info
andAnnotatedQueue.safe_update_info
: Useupdate_info
instead.
-
qml.transforms.measurement_grouping
has been deprecated. Useqml.transforms.hamiltonian_expand
instead. (#3417) -
The
observables
argument inQubitDevice.statistics
is deprecated. Please usecircuit
instead. (#3433) -
The
seed_recipes
argument inqml.classical_shadow
andqml.shadow_expval
is deprecated. A new argumentseed
has been added, which defaults to None and can contain an integer with the wanted seed. (#3388) -
qml.transforms.make_tape
has been deprecated. Please useqml.tape.make_qscript
instead. (#3478)
Documentation ๐
-
Added documentation on parameter broadcasting regarding both its usage and technical aspects. (#3356)
The quickstart guide on circuits as well as the the documentation of QNodes and Operators now contain introductions and details on parameter broadcasting. The QNode documentation mostly contains usage details, the Operator documentation is concerned with implementation details and a guide to support broadcasting in custom operators.
-
The return type statements of gradient and Hessian transforms and a series of other functions that are a
batch_transform
have been corrected. (#3476) -
Developer documentation for the queuing module has been added. (#3268)
-
More mentions of diagonalizing gates for all relevant operations have been corrected. (#3409)
The docstrings for
compute_eigvals
used to say that the diagonalizing gates implemented $U$, the unitary such that $O = U \Sigma U^{\dagger}$, where $O$ is the original observable and $\Sigma$ a diagonal matrix. However, the diagonalizing gates actually implement $U^{\dagger}$, since $\langle \psi | O | \psi \rangle = \langle \psi | U \Sigma U^{\dagger} | \psi \rangle$, making $U^{\dagger} | \psi \rangle$ the actual state being measured in the $Z$-basis. -
A warning about using
dill
to pickle and unpickle datasets has been added. (#3505)
Bug fixes ๐
-
Fixed a bug that prevented
qml.gradients.param_shift
from being used for broadcasted tapes. (#3528) -
Fixed a bug where
qml.transforms.hamiltonian_expand
didn't preserve the type of the input results in its output. (#3339) -
Fixed a bug that made
qml.gradients.param_shift
raise an error when used with unshifted terms only in a custom recipe, and when using any unshifted terms at all under the new return type system. (#3177) -
The original tape
_obs_sharing_wires
attribute is updated during its expansion. (#3293) -
An issue with
drain=False
in the adaptive optimizer has been fixed. Before the fix, the operator pool needed to be reconstructed inside the optimization pool whendrain=False
. With this fix, this reconstruction is no longer needed. (#3361) -
If the device originally has no shots but finite shots are dynamically specified, Hamiltonian expansion now occurs. (#3369)
-
qml.matrix(op)
now fails if the operator truly has no matrix (e.g.,qml.Barrier
) to matchop.matrix()
. (#3386) -
The
pad_with
argument in theqml.AmplitudeEmbedding
template is now compatible with all interfaces. (#3392) -
Operator.pow
now queues its constituents by default. (#3373) -
Fixed a bug where a QNode returning
qml.sample
would produce incorrect results when run on a device defined with a shot vector. (#3422) -
The
qml.data
module now works as expected on Windows. (#3504)
Contributors โ๏ธ
This release contains contributions from (in alphabetical order):
Guillermo Alonso, Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Thomas Bromley, Astral Cai, Albert Mitjans Coma, Ahmed Darwish, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Pieter Eendebak, Lillian M. A. Frederiksen, Diego Guala, Katharine Hyatt, Josh Izaac, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee James O'Riordan, Mudit Pandey, Kevin Shen, Matthew Silverman, Jay Soni, Antal Szรกva, David Wierichs, Moritz Willmann, and Filippo Vicentini.