piel.integration
================

.. py:module:: piel.integration


Submodules
----------

.. toctree::
   :maxdepth: 1

   /autoapi/piel/integration/amaranth_cocotb/index
   /autoapi/piel/integration/amaranth_openlane/index
   /autoapi/piel/integration/cocotb_sax/index
   /autoapi/piel/integration/gdsfactory_hdl21/index
   /autoapi/piel/integration/gdsfactory_openlane/index
   /autoapi/piel/integration/hdl21_gdsfactory/index
   /autoapi/piel/integration/sax_qutip/index
   /autoapi/piel/integration/sax_thewalrus/index
   /autoapi/piel/integration/signal/index
   /autoapi/piel/integration/thewalrus_qutip/index


Attributes
----------

.. autoapisummary::

   piel.integration.ParsedProtoVLSIR


Functions
---------

.. autoapisummary::

   piel.integration.create_cocotb_truth_table_verification_python_script
   piel.integration.layout_amaranth_truth_table_through_openlane
   piel.integration.layout_truth_table_through_openlane
   piel.integration.create_gdsfactory_component_from_openlane
   piel.integration.gdsfactory_netlist_to_spice_string_connectivity_netlist
   piel.integration.gdsfactory_netlist_to_spice_netlist
   piel.integration.gdsfactory_netlist_with_hdl21_generators
   piel.integration.get_matching_connections
   piel.integration.get_matching_port_nets
   piel.integration.construct_hdl21_module
   piel.integration.convert_connections_to_tuples
   piel.integration.filter_port
   piel.integration.find_most_relevant_gds
   piel.integration.hdl21_module_to_schematic_editor
   piel.integration.generate_raw_yaml_from_module
   piel.integration.generate_raw_netlist_dict_from_module
   piel.integration.sax_circuit_permanent
   piel.integration.sax_to_s_parameters_standard_matrix
   piel.integration.sax_to_ideal_qutip_unitary
   piel.integration.verify_sax_model_is_unitary
   piel.integration.fock_transition_probability_amplitude
   piel.integration.convert_to_network_transmission


Package Contents
----------------

.. py:function:: create_cocotb_truth_table_verification_python_script(module: piel.types.PathTypes, truth_table: piel.types.digital.TruthTable, test_python_module_name: str = 'top_test')

   Creates a cocotb test script for verifying logic defined by the truth table.

   :param module: The path to the module where the test script will be placed.
   :type module: PathTypes
   :param truth_table: A dictionary representing the truth table.
   :type truth_table: TruthTable
   :param test_python_module_name: The name of the test python module. Defaults to "top_test".
   :type test_python_module_name: str, optional

   .. rubric:: Example

   truth_table = {
       "A": [0, 0, 1, 1],
       "B": [0, 1, 0, 1],
       "X": [0, 1, 1, 0]  # Expected output (for XOR logic, as an example)
   }
   create_cocotb_truth_table_verification_python_script(truth_table)


.. py:function:: layout_amaranth_truth_table_through_openlane(amaranth_module: piel.types.DigitalLogicModule, truth_table: piel.types.TruthTable, parent_directory: piel.types.PathTypes, target_directory_name: Optional[str] = None, openlane_version: Literal['v1', 'v2'] = 'v2', openlane_configuration: dict | None = None, **kwargs)

   Implements an Amaranth truth table module through the OpenLane flow.

   This function implements an amaranth truth-table module through the openlane flow. There are several ways to
   implement a module. Fundamentally, this requires the verilog files to be generated from the openlane-module in a
   particular directory. For the particular directory provided, this function will generate the verilog files in the
   corresponding directory. It can also generate the ``openlane`` configuration files for this particular location.

   This function does a few things:

   1. Starts off from a ``amaranth`` module class.
   2. Determines the output directory in which to generate the files, and creates one accordingly if it does not exist.
   3. Generates the verilog files from the ``amaranth`` module class.
   4. Generates the ``openlane`` configuration files for this particular location.
   5. Implements the ``openlane`` flow for this particular location to generate a chip.

   :param amaranth_module: The Amaranth module representing the truth table logic.
   :type amaranth_module: am.Module
   :param truth_table: The truth table files structure containing the logic for the module.
   :type truth_table: TruthTable
   :param parent_directory: The directory where the project will be created or found.
   :type parent_directory: PathTypes
   :param target_directory_name: The name for the target directory. Defaults to the name of the Amaranth module's class.
   :type target_directory_name: Optional[str]
   :param openlane_version: The version of OpenLane to use. Defaults to "v2".
   :type openlane_version: Literal["v1", "v2"]
   :param \*\*kwargs: Additional keyword arguments for OpenLane configuration.

   :returns: None


.. py:function:: layout_truth_table_through_openlane(truth_table: piel.types.TruthTable, parent_directory: piel.types.PathTypes, target_directory_name: Optional[str] = None, openlane_version: Literal['v1', 'v2'] = 'v2', **kwargs)

   Translates a truth table to an OpenLane flow implementation.

   This function takes a truth table and converts it into an OpenLane flow, using the specified OpenLane version.
   It first constructs an Amaranth module from the truth table, and then passes this module to the
   `layout_amaranth_truth_table_through_openlane` function for further processing.

   :param truth_table: The truth table to be converted. It includes input connection, output connection, and the table logic.
   :type truth_table: TruthTable
   :param parent_directory: The directory where the OpenLane project will be created.
   :type parent_directory: PathTypes
   :param target_directory_name: Name of the target directory. If not specified, a default name will be used.
   :type target_directory_name: Optional[str]
   :param openlane_version: Specifies the OpenLane version to use. Defaults to "v2".
   :type openlane_version: Literal["v1", "v2"]
   :param \*\*kwargs: Additional keyword arguments passed to the Amaranth module construction.

   :returns: None


.. py:function:: create_gdsfactory_component_from_openlane(design_name_v1: str | None = None, design_directory: piel.types.PathTypes | None = None, run_name: str | None = None, v1: bool = False) -> piel.types.PhotonicCircuitComponent

.. py:function:: gdsfactory_netlist_to_spice_string_connectivity_netlist(gdsfactory_netlist: dict, models=None)

   Not for the `hdl21` design flow, but useful for other SPICE level conversions.

   This function maps the connections of a netlist to a node that can be used in a SPICE netlist. SPICE netlists are
   in the form of:

   .. code-block:: spice

       RXXXXXXX N1 N2 <VALUE> <MNAME> <L=LENGTH> <W=WIDTH> <TEMP=T>

   This means that every instance, is an electrical type, and we define the two particular nodes in which it is
   connected. This means we need to convert the gdsfactory dictionary netlist into a form that allows us to map the
   connectivity for every instance. Then we can define that as a line of the SPICE netlist with a particular
   electrical model. For passives this works fine when it's a two port network such as sources, or electrical
   elements. However, non-passive elements like transistors have three connection or more which are provided in an ordered form.

   This means that the order of translations is as follows:

   .. code-block::

       1. Extract all instances and required measurement from the netlist, and assign the corresponding parameters on instantiation.
       2. Verify that the measurement have been provided. Each model describes the type of component this is, how many connection it requires and so on.
       3. Map the connections to each instance port as part of the instance dictionary.

   We should get as an output a dictionary in the structure:

   .. code-block::

       {
           instance_1: {
               ...
               "connections": [('straight_1,e1', 'taper_1,e2'),
                               ('straight_1,e2', 'taper_2,e2')],
               'spice_nets': {'e1': 'straight_1__e1___taper_1__e2',
                       'e2': 'straight_1__e2___taper_2__e2'},
               'spice_model': <function piel.measurement.physical.electronic.spice.resistor.basic_resistor()>},
           }
           ...
       }


.. py:function:: gdsfactory_netlist_to_spice_netlist(gdsfactory_netlist: dict, generators: dict, **kwargs) -> piel.types.AnalogueModule

   This function converts a GDSFactory electrical netlist into a standard SPICE netlist. It follows the same
   principle as the `sax` circuit composition.

   Each GDSFactory netlist has a set of instances, each with a corresponding model, and each instance with a given
   set of geometrical settings that can be applied to each particular model. We know the type of SPICE model from
   the instance model we provides.

   We know that the gdsfactory has a set of instances, and we can map unique measurement via sax through our own
   composition circuit. Write the SPICE component based on the model into a total circuit representation in string
   from the reshaped gdsfactory dictionary into our own structure.

   :param gdsfactory_netlist: GDSFactory netlist
   :param generators: Dictionary of Generators

   :returns: hdl21 module or raw SPICE string


.. py:function:: gdsfactory_netlist_with_hdl21_generators(gdsfactory_netlist: dict, generators=None)

   This function allows us to map the ``hdl21`` measurement dictionary in a `sax`-like implementation to the ``GDSFactory`` netlist. This allows us to iterate over each instance in the netlist and construct a circuit after this function.]

   Example usage:

   .. code-block::

       >>> import gdsfactory as gf
       >>> from piel.integration.gdsfactory_hdl21.conversion import gdsfactory_netlist_with_hdl21_generators
       >>> from piel.measurement.physical.electronic import get_default_models
       >>> gdsfactory_netlist_with_hdl21_generators(gdsfactory_netlist=gf.components.mzi2x2_2x2_phase_shifter().get_netlist(exclude_port_types="optical"),generators=get_default_models())

   :param gdsfactory_netlist: The netlist from ``GDSFactory`` to map to the ``hdl21`` measurement dictionary.
   :param generators: The ``hdl21`` measurement dictionary to map to the ``GDSFactory`` netlist.

   :returns: The ``GDSFactory`` netlist with the ``hdl21`` measurement dictionary.


.. py:function:: get_matching_connections(names: list, connections: dict)

   This function returns a list of tuples that match the names of the connections.

   :param names: List of names to match
   :param connections: Dictionary of connections to match

   :returns: List of tuples that match the names of the connections


.. py:function:: get_matching_port_nets(names, connections)

.. py:function:: construct_hdl21_module(spice_netlist: dict, **kwargs) -> piel.types.AnalogueModule

   This function converts a gdsfactory-spice converted netlist using the component measurement into a SPICE circuit.

   Part of the complexity of this function is the multiport nature of some components and measurement, and assigning the
   parameters accordingly into the SPICE function. This is because not every SPICE component will be bi-port,
   and many will have multi-connection and parameters accordingly. Each model can implement the composition into a
   SPICE circuit, but they depend on a set of parameters that must be set from the instance. Another aspect is
   that we may want to assign the component ID according to the type of component. However, we can also assign the
   ID based on the individual instance in the circuit, which is also a reasonable approximation. However,
   it could be said, that the ideal implementation would be for each component model provided to return the SPICE
   instance including connectivity except for the ID.

   # TODO implement validators


.. py:function:: convert_connections_to_tuples(connections: dict)

   Convert from:

   .. code-block::

       {
       'straight_1,e1': 'taper_1,e2',
       'straight_1,e2': 'taper_2,e2',
       'taper_1,e1': 'via_stack_1,e3',
       'taper_2,e1': 'via_stack_2,e1'
       }

   to:

   .. code-block::

       [(('straight_1', 'e1'), ('taper_1', 'e2')), (('straight_1', 'e2'), ('taper_2', 'e2')), (('taper_1', 'e1'),
       ('via_stack_1', 'e3')), (('taper_2', 'e1'), ('via_stack_2', 'e1'))]


.. py:function:: filter_port(port)

   Filter the port name to match spice declaration to gds port name, specifically focused on the SKY130nm technology.


.. py:function:: find_most_relevant_gds(component_name, component_dict=None, custom_mapping=None)

.. py:function:: hdl21_module_to_schematic_editor(module: piel.types.AnalogueModule, yaml_schematic_file_name: str, spice_gds_mapping_method: Callable | None = find_most_relevant_gds, port_filter_method: Callable = filter_port)

   Constructs a SchematicEditor instance from a hdl21 module object.

   :param module: The hdl21 module object.
   :type module: h.module
   :param yaml_schematic_file_name: The yaml schematic file name.
   :type yaml_schematic_file_name: str
   :param spice_gds_mapping_method: The method to map the spice instance name to the component name.
   :type spice_gds_mapping_method: Callable
   :param port_filter_method: The method to filter the port name.
   :type port_filter_method: Callable


.. py:function:: generate_raw_yaml_from_module(module: piel.types.AnalogueModule)

   Generate a raw netlist yaml from a hdl21 module object which could be manually edited for specific instances
   related to the corresponding SPICE.


.. py:function:: generate_raw_netlist_dict_from_module(module: piel.types.AnalogueModule)

   Generate a raw netlist dictionary from a hdl21 module object.
   This just gives us a raw structure of the hdl21 modules, we cannot use this json equivalently to a gdsfactory netlist.


.. py:data:: ParsedProtoVLSIR

.. py:function:: sax_circuit_permanent(sax_input: Any) -> tuple

   The permanent of a unitary is used to determine the state probability of combinatorial Gaussian boson samping systems.

   ``thewalrus`` Ryser's algorithm permananet implementation is described here: https://the-walrus.readthedocs.io/en/latest/gallery/permanent_tutorial.html

   # TODO maybe implement subroutine if computation is taking forever.

   :param sax_input: The sax S-parameter dictionary.
   :type sax_input: sax.SType

   :returns: The circuit permanent and the time it took to compute it.
   :rtype: tuple


.. py:function:: sax_to_s_parameters_standard_matrix(sax_input: Any, input_ports_order: tuple[str] | None = None, round_int: bool | None = None, *args, **kwargs) -> piel.types.SParameterMatrixTuple

   A ``sax`` S-parameter SDict is provided as a dictionary of tuples with (port0, port1) as the key. This
   determines the direction of the scattering relationship. It means that the number of terms in an S-parameter
   matrix is the number of connection squared.

   In order to generalise, this function returns both the S-parameter matrices and the indexing connection based on the
   amount provided. In terms of computational speed, we definitely would like this function to be algorithmically
   very fast. For now, I will write a simple python implementation and optimise in the future.

   It is possible to see the `sax` SDense notation equivalence here:
   https://flaport.github.io/sax/nbs/08_backends.html

   .. code-block:: python

       import jax.numpy as jnp
       from sax.core import SDense

       # Directional coupler SDense representation
       dc_sdense: SDense = (
           jnp.array([[0, 0, τ, κ], [0, 0, κ, τ], [τ, κ, 0, 0], [κ, τ, 0, 0]]),
           {"in0": 0, "in1": 1, "out0": 2, "out1": 3},
       )


       # Directional coupler SDict representation
       # Taken from https://flaport.github.io/sax/nbs/05_models.html
       def coupler(*, coupling: float = 0.5) -> SDict:
           kappa = coupling**0.5
           tau = (1 - coupling) ** 0.5
           sdict = reciprocal(
               {
                   ("in0", "out0"): tau,
                   ("in0", "out1"): 1j * kappa,
                   ("in1", "out0"): 1j * kappa,
                   ("in1", "out1"): tau,
               }
           )
           return sdict

   If we were to relate the mapping accordingly based on the connection indexes, a S-Parameter matrix in the form of
   :math:`S_{(output,i),(input,i)}` would be:

   .. math::

       S = \begin{bmatrix}
               S_{00} & S_{10} \\
               S_{01} & S_{11} \\
           \end{bmatrix} =
           \begin{bmatrix}
           \tau & j \kappa \\
           j \kappa & \tau \\
           \end{bmatrix}

   Note that the standard S-parameter and hence unitary representation is in the form of:

   .. math::

       S = \begin{bmatrix}
               S_{00} & S_{01} \\
               S_{10} & S_{11} \\
           \end{bmatrix}


   .. math::

       \begin{bmatrix}
           b_{1} \\
           \vdots \\
           b_{n}
       \end{bmatrix}
       =
       \begin{bmatrix}
           S_{11} & \dots & S_{1n} \\
           \vdots & \ddots & \vdots \\
           S_{n1} & \dots & S_{nn}
       \end{bmatrix}
       \begin{bmatrix}
           a_{1} \\
           \vdots \\
           a_{n}
       \end{bmatrix}

   TODO check with Floris, does this mean we need to transpose the matrix?
   TODO document round_int

   :param sax_input: The sax S-parameter dictionary.
   :type sax_input: sax.SType
   :param input_ports_order: The connection order tuple containing the names and order of the input connection.
   :type input_ports_order: tuple
   :param round_int: Whether to round the complex numbers to integers.
   :type round_int: bool

   :returns: The S-parameter matrix and the input connection index tuple in the standard S-parameter notation.
   :rtype: tuple


.. py:function:: sax_to_ideal_qutip_unitary(sax_input: piel.types.OpticalTransmissionCircuit, input_ports_order: tuple | None = None)

.. py:function:: verify_sax_model_is_unitary(model: piel.types.OpticalTransmissionCircuit, input_ports_order: tuple | None = None) -> bool

.. py:function:: fock_transition_probability_amplitude(initial_fock_state: Any, final_fock_state: Any, unitary_matrix: jax.numpy.ndarray)

       This function returns the transition probability amplitude between two Fock states when propagating in between
       the unitary_matrix which represents a quantum state circuit.

       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.

       .. math ::

   ewcommand{\ket}[1]{\left|{#1}
   ight
   angle}

       The subunitary :math:`U_{f_1}^{f_2}` is composed from the larger unitary by selecting the rows from the output state
       Fock state occupation of :math:`\ket{f_2}`, and columns from the input :math:`\ket{f_1}`. In our case, we need to select the
       columns indexes :math:`(0,3)` and rows indexes :math:`(1,2)`.

       If we consider a photon number of more than one for the transition Fock states, then the Permanent needs to be
       normalised. The probability amplitude for the transition is described as:

       .. math ::
           a(\ket{f_1}     o \ket{f_2}) =
   rac{    ext{per}(U_{f_1}^{f_2})}{\sqrt{(j_1! j_2! ... j_N!)(j_1^{'}! j_2^{'}! ... j_N^{'}!)}}

       Args:
           initial_fock_state (qutip.Qobj | jnp.ndarray): The initial Fock state.
           final_fock_state (qutip.Qobj | jnp.ndarray): The final Fock state.
           unitary_matrix (jnp.ndarray): The unitary matrix that represents the quantum state circuit.

       Returns:
           float: The transition probability amplitude between the initial and final Fock states.



.. py:function:: convert_to_network_transmission(network: piel.types.FrequencyTransmissionModel) -> piel.types.NetworkTransmission

   This function takes in any supported FrequencyTransmissionModel and provides translation between the different
   domain representations and returns a standard NetworkTransmission class.


