Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/daquintero/piel
Browse files Browse the repository at this point in the history
  • Loading branch information
daquintero committed Aug 9, 2023
2 parents 0047dca + 86aea72 commit f73710d
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 38 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 40 additions & 30 deletions docs/examples/05_quantum_integration_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,33 @@

# ### Quantum Models

# Let's first check that the quantum models in which we will compose our circuit are actually unitary, otherwise the composed circuit will not be unitary. Note that a circuit being unitary means: $U^\dagger U = 1$ where $U^\dagger$ is the conjugate transpose of the unitary $U$. This is inherently checked in `qutip`. Basically, what it means is that a unitary operation is reversible in time, and that energy is not lost.

quantum_models = piel.models.frequency.get_default_models(type="quantum")
quantum_models["mmi2x2"]()

mmi2x2_qobj = piel.sax_to_ideal_qutip_unitary(
quantum_models["mmi2x2"](), input_ports_order=("o1", "o2")
)
mmi2x2_qobj.check_isunitary()

# We follow the same process as the previous examples, but we use lossless models for the circuit composition.

recursive_netlist = switch_circuit.get_netlist_recursive()
switch_circuit_model, switch_circuit_model_info = sax.circuit(
switch_circuit_model_quantum, switch_circuit_model_quantum_info = sax.circuit(
netlist=recursive_netlist,
models=piel.models.frequency.get_default_models(type="quantum"),
)
default_state_s_parameters = switch_circuit_model()
default_state_unitary = switch_circuit_model_quantum()

# We convert from the `sax` unitary to an ideal "unitary" that can be inputted into a `qutip` model. Fortunately, `piel` has got you covered:
# It is important to note some inherent assumptions and limitations of the translation process.

(
s_parameters_standard_matrix,
unitary_matrix,
input_ports_index_tuple_order,
) = piel.sax_to_s_parameters_standard_matrix(default_state_s_parameters)
s_parameters_standard_matrix
) = piel.sax_to_s_parameters_standard_matrix(default_state_unitary)
unitary_matrix

# ```python
# Array([[ 0. +0.j , 0. +0.j ,
Expand All @@ -59,19 +69,16 @@

import qutip

qutip.cnot()

qutip_qobj = piel.standard_s_parameters_to_qutip_qobj(s_parameters_standard_matrix)

qutip_qobj
switch_circuit_qobj = piel.standard_s_parameters_to_qutip_qobj(unitary_matrix)
switch_circuit_qobj

# ![example_qutip_unitary](../_static/img/examples/05_quantum_integration_basics/example_qutip_unitary.PNG)

qutip_qobj.check_isunitary()
switch_circuit_qobj.check_isunitary()

qutip_qobj.dims
switch_circuit_qobj.dims

qutip_qobj.eigenstates
switch_circuit_qobj.eigenstates

# ### Fock State Evolution Probability

Expand Down Expand Up @@ -131,29 +138,27 @@
# We can extract the section of the unitary that corresponds to this Fock state transition. Note that based on (TODO cite Jeremy), the initial Fock state corresponds to the columns of the unitary and the final Fock states corresponds to the rows of the unitary.

piel.subunitary_selection_on_index(
unitary_matrix=s_parameters_standard_matrix,
unitary_matrix=unitary_matrix,
rows_index=final_fock_state_indices,
columns_index=initial_fock_state_indices,
)

# ```python~
# Array([[ 0.40105772+0.49846345j, 0.4000432 +0.38792986j],
# [ 0.40004322+0.3879298j , -0.5810837 -0.31133226j]], dtype=complex64)
# Array([[0.+0.j , 0.-0.9999998j],
# [0.-0.9999998j, 0.+0.j ]], dtype=complex64)
# ```

# We can now extract the transition amplitude probability accordingly:

piel.fock_transition_probability_amplitude(
initial_fock_state=initial_fock_state,
final_fock_state=final_fock_state,
unitary_matrix=s_parameters_standard_matrix,
unitary_matrix=unitary_matrix,
)

# ```python
# Array(-0.06831534-0.10413378j, dtype=complex64)
# Array(-0.99999964+0.j, dtype=complex64)
# ```
#
# TODO this is not numerically right but the functions are fine because we need to verify the unitary-ness of the model composed matrices.

# ### Fock-State Generation

Expand All @@ -170,34 +175,43 @@

# We can also integrated the `piel` toolchain, with another set of packages for quantum photonic system design such as those provided by `XanaduAI`. We will use their `thewalrus` package to calculate the permanent of our matrix. For example, we can do this for our full circuit unitary:

piel.sax_circuit_permanent(default_state_s_parameters)
piel.sax_circuit_permanent(default_state_unitary)

# We might want to calculate the permanent of subsections of the larger unitary to calculate certain operations probability:

s_parameters_standard_matrix.shape
unitary_matrix.shape

# For, example, we need to just calculate it for the first submatrix component, or a particular switch unitary within a larger circuit. This would be indexed when starting from the first row and column as `start_index` = (0,0) and `stop_index` = (`unitary_size`, `unitary_size`). Note that an error will be raised if a non-unitary matrix is inputted. Some examples are:

our_subunitary = piel.subunitary_selection_on_range(
s_parameters_standard_matrix, stop_index=(1, 1), start_index=(0, 0)
unitary_matrix, stop_index=(1, 1), start_index=(0, 0)
)
our_subunitary

# ```python
# Array([[ 0.40105772+0.49846345j, -0.45904815-0.197149j ],
# [-0.4590482 -0.197149j , -0.8361797 +0.13278401j]], dtype=complex64)
# Array([[ 0. +0.j, 0. +0.j],
# [ 0. +0.j, -0.9999999+0.j]], dtype=complex64)
# ```

# We can now calculate the permanent of this submatrix:

piel.unitary_permanent(our_subunitary)

# ```python
# ((-0.2296868-0.18254918j), 0.0)
# (0j, 0.0)
# ```

# ## Lossy Models

# What we will do now is explore how our circuit behaves when composing it with more realistic physical models.

recursive_netlist = switch_circuit.get_netlist_recursive()
switch_circuit_model_classical, switch_circuit_model_classical_info = sax.circuit(
netlist=recursive_netlist,
models=piel.models.frequency.get_default_models(type="classical"),
)
default_state_s_parameters = switch_circuit_model_classical()

# Let's first convert to a standard S-Parameter matrix:

(
Expand All @@ -217,10 +231,6 @@
# 0.11507231-0.45943272j, -0.5810837 -0.31133226j]], dtype=complex64)
# ```

import numpy as np

np.asarray(s_parameters_standard_matrix)

# We can explore some properties of this matrix:

s_parameters_standard_matrix.shape
31 changes: 26 additions & 5 deletions piel/integration/sax_qutip.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import qutip # NOQA : F401
import sax
import numpy as np
from ..tools.qutip.unitary import matrix_to_qutip_qobj
from piel.tools.sax.utils import sax_to_s_parameters_standard_matrix

__all__ = [
"sax_to_ideal_qutip_unitary",
"verify_sax_model_is_unitary",
]


def sax_to_ideal_qutip_unitary(sax_input: sax.SType):
def sax_to_ideal_qutip_unitary(
sax_input: sax.SType, input_ports_order: tuple | None = None
):
"""
This function converts the calculated S-parameters into a standard Unitary matrix topology so that the shape and
dimensions of the matrix can be observed.
Expand Down Expand Up @@ -42,6 +44,7 @@ def sax_to_ideal_qutip_unitary(sax_input: sax.SType):
Args:
sax_input (sax.SType): A dictionary of S-parameters in the form of a SDict from `sax`.
input_ports_order (tuple | None): The order of the input ports. If None, the default order is used.
Returns:
qobj_unitary (qutip.Qobj): A QuTip QObj representation of the S-parameters in a unitary matrix.
Expand All @@ -50,7 +53,25 @@ def sax_to_ideal_qutip_unitary(sax_input: sax.SType):
(
s_parameters_standard_matrix,
input_ports_index_tuple_order,
) = sax_to_s_parameters_standard_matrix(sax_input)
s_parameter_standard_matrix_numpy = np.asarray(s_parameters_standard_matrix)
qobj_unitary = matrix_to_qutip_qobj(s_parameter_standard_matrix_numpy)
) = sax_to_s_parameters_standard_matrix(
sax_input=sax_input, input_ports_order=input_ports_order
)
qobj_unitary = matrix_to_qutip_qobj(s_parameters_standard_matrix)
return qobj_unitary


def verify_sax_model_is_unitary(
model: sax.SType, input_ports_order: tuple | None = None
) -> bool:
"""
Verify that the model is unitary.
Args:
model (dict): The model to verify.
input_ports_order (tuple | None): The order of the input ports. If None, the default order is used.
Returns:
bool: True if the model is unitary, False otherwise.
"""
qobj = sax_to_ideal_qutip_unitary(model, input_ports_order=input_ports_order)
return qobj.check_isunitary()
5 changes: 3 additions & 2 deletions piel/models/frequency/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@


def get_default_models(
custom_defaults: dict | None = None, type: Literal["default", "quantum"] = "default"
custom_defaults: dict | None = None,
type: Literal["classical", "quantum"] = "classical",
) -> dict:
"""
Returns the default models dictionary.
Expand All @@ -41,7 +42,7 @@ def get_default_models(
if custom_defaults is not None:
return custom_defaults
else:
if type == "default":
if type == "classical":
return __default_models_dictionary__
elif type == "quantum":
return __default_quantum_models_dictionary__ #
Expand Down
1 change: 1 addition & 0 deletions piel/tools/qutip/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .config import *
from .fock import *
from .unitary import *
3 changes: 3 additions & 0 deletions piel/tools/qutip/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import qutip

qutip.settings.atol = 1e-6 # TODO config
2 changes: 1 addition & 1 deletion piel/tools/qutip/unitary.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import jax.numpy as jnp
import numpy as np
from typing import Optional
import qutip # NOQA : F401
import qutip

__all__ = [
"standard_s_parameters_to_qutip_qobj",
Expand Down

0 comments on commit f73710d

Please sign in to comment.