Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧪 Further experimental analysis functionality #132

Merged
merged 9 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include LICENSE
include README.md
include *.md
recursive-include piel *
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 0 additions & 19 deletions docs/examples.rst

This file was deleted.

6 changes: 3 additions & 3 deletions docs/examples/06a_analytical_mzm_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
# name: python3
# ---

# # Analytic Electronic-Photonic Mach-Zehnder Modulator Models
# # Electro-Optic Mach-Zehnder Modulator Design & Analysis
#
# The goal of this example is to explore some of the physics related to both the optical and electronic modelling of mach-zehnder modulators. We will demonstrate how the theory matches some of the numerical implementations used throughout `piel`
# The goal of this example is to explore some of the physics related to both the optical and electronic modelling of mach-zehnder modulators. We will demonstrate how the theory matches some of the numerical implementations used throughout `piel`. We will explore device metrics analysis and also show how this can be used in larger system architecture through `piel`.
#
# The main references of this notebook are:
#
Expand All @@ -29,7 +29,6 @@
import numpy as np
import jax.numpy as jnp
import pandas as pd

# -

# ## Coupler Modelling
Expand Down Expand Up @@ -64,6 +63,7 @@
# \end{equation}
#
# An implementation of this coupler in an integrated photonics platform using `gdsfactory` is shown in the image below. Note that the equation notation above matches that within the `gds` implementation:

from gdsfactory.components import mmi1x2

mmi1x2().plot()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import piel
import piel.experimental as pe
import piel.analysis.signals.time_data as tda
from piel.models.physical.electrical import E8364A, cables
from datetime import datetime
import os

# In this example, we will compare measurement measurements and simulated measurements to understand the performance of a cryogenic-designed EIC-interposer printed-circuit board.
#
Expand Down Expand Up @@ -293,7 +295,7 @@ def calibration_propagation_delay_experiment_instance(
oscilloscope = piel.models.physical.electrical.create_two_port_oscilloscope()
waveform_generator = (
piel.models.physical.electrical.create_one_port_square_wave_waveform_generator(
peak_to_peak_voltage_V=0.5,
peak_to_peak_voltage_V=0.25,
rise_time_s=1,
fall_time_s=1,
frequency_Hz=square_wave_frequency_Hz,
Expand Down Expand Up @@ -328,7 +330,7 @@ def pcb_propagation_delay_experiment_instance(
oscilloscope = piel.models.physical.electrical.create_two_port_oscilloscope()
waveform_generator = (
piel.models.physical.electrical.create_one_port_square_wave_waveform_generator(
peak_to_peak_voltage_V=0.5,
peak_to_peak_voltage_V=0.25,
rise_time_s=1,
fall_time_s=1,
frequency_Hz=square_wave_frequency_Hz,
Expand Down Expand Up @@ -568,17 +570,41 @@ def calibration_propagation_delay_experiment(
fig, ax = (
piel.visual.experimental.propagation.experiment_data.plot_propagation_signals_time(
calibration_propagation_delay_experiment_data,
path="../../_static/img/examples/08a_pcb_interposer_characterisation/calibration_propagation_delay_signals.jpg",
path="../../_static/img/examples/08a_pcb_interposer_characterisation/calibration_propagation_delay_signals_default.jpg",
debug=True,
)
)

# ![calibration_propagation_delay_signals](../../_static/img/examples/08a_pcb_interposer_characterisation/calibration_propagation_delay_signals_default.jpg)

# You can visualise this plot with both a default plotting using the `ExperimentData` metadata or can also also customize this plotting easily.

# +
fig, ax = (
piel.visual.experimental.propagation.experiment_data.plot_propagation_signals_time(
calibration_propagation_delay_experiment_data,
debug=True,
path="../../_static/img/examples/08a_pcb_interposer_characterisation/calibration_propagation_delay_signals.jpg",
figure_title='"Identical-Path" Signal Delay Verification',
create_parameters_tables=False,
axes_subtitle_list=["1 GHz", "3 GHz", "5 GHz", "10 GHz"],
xlabel=piel.types.units.ns,
ylabel=piel.types.units.V,
figure_kwargs={"figsize": (8, 8)},
rising_edges_kwargs={},
)
)

fig.savefig(os.path.join(os.getenv("TAP"), "calibration_propagation_delay_signals.jpg"))
# -

# ![calibration_propagation_delay_signals](../../_static/img/examples/08a_pcb_interposer_characterisation/calibration_propagation_delay_signals.jpg)

fig, ax = (
piel.visual.experimental.propagation.experiment_data.plot_propagation_signals_time(
pcb_propagation_delay_experiment_data,
path="../../_static/img/examples/08a_pcb_interposer_characterisation/pcb_propagation_delay_signals.jpg",
rising_edges_kwargs={},
)
)

Expand All @@ -587,11 +613,13 @@ def calibration_propagation_delay_experiment(

# We can also plot the data related to the metrics extracted from the measurements.

calibration_propagation_delay_experiment_data.data.collection[0].measurements.metrics

fig, ax = (
piel.visual.experimental.propagation.experiment_data.plot_signal_propagation_measurements(
calibration_propagation_delay_experiment_data,
x_parameter="square_wave_frequency_Hz",
measurement_name="delay_ch1_ch2__s_2",
measurement_name="delay_ch1_ch2__s_1",
path="../../_static/img/examples/08a_pcb_interposer_characterisation/calibration_propagation_delay_measurements.jpg",
)
)
Expand All @@ -609,6 +637,8 @@ def calibration_propagation_delay_experiment(

# ![pcb_propagation_delay_measurements](../../_static/img/examples/08a_pcb_interposer_characterisation/pcb_propagation_delay_measurements.jpg)

# However, maybe you want to generate a better quality figure with both metrics and the time-signals.

# #### Automatic Report and Plotting

# One of the nice functionalities provided by `piel.measurement` is that because `Experiment`s and `ExperimentData`s can be serialized. That means their analysis can also be automated at multiple stages of the development flow.
Expand Down Expand Up @@ -637,3 +667,221 @@ def calibration_propagation_delay_experiment(
# ```

# Note that this has some limitations of revalidation and reinstantion of python classes.

# ### Extract Software-Defined Statistics from a `DataTimeSignalData`

# `piel.analysis` also provides some functionality to analyse the corresponding time-data accordingly.
#
# For example, we might want to extract only the rising edge section of one of the measured signals:

calibration_10ghz_dut_waveform = (
calibration_propagation_delay_experiment_data.data.collection[3].dut_waveform
)
calibration_10ghz_dut_measurements = (
calibration_propagation_delay_experiment_data.data.collection[3].measurements
)

calibration_propagation_delay_experiment_data.experiment.parameters

calibration_10ghz_dut_measurements

help(tda.extract_rising_edges)

calibration_10ghz_dut_waveform_rising_edge_list = tda.extract_rising_edges(
signal=calibration_10ghz_dut_waveform,
lower_threshold_ratio=0.1,
upper_threshold_ratio=0.9,
)

# We can, for example, plot all these signals overlaid on top of each other - easily by just offsetting to a base reference time:

offset_calibration_10ghz_dut_waveform_rising_edge_list = tda.offset_time_signals(
calibration_10ghz_dut_waveform_rising_edge_list
)

help(piel.visual.plot.signals.plot_multi_data_time_signal)

piel.visual.plot.signals.plot_multi_data_time_signal(
offset_calibration_10ghz_dut_waveform_rising_edge_list,
xlabel=piel.types.units.ns,
path="../../_static/img/examples/08a_pcb_interposer_characterisation/extracted_rising_edges.jpg",
title="Extracted Rising Edges - Reference",
)

# ![extracted_rising_edges](../../_static/img/examples/08a_pcb_interposer_characterisation/extracted_rising_edges.jpg)

# We can technically also extract some statistical metrics from this data which we could use to compare with the measured statistics:

offset_calibration_10ghz_dut_waveform_rising_edge_metrics = (
tda.extract_statistical_metrics(
offset_calibration_10ghz_dut_waveform_rising_edge_list,
analysis_types="peak_to_peak",
)
)
offset_calibration_10ghz_dut_waveform_rising_edge_metrics.table

# | | Metric | Value |
# |---:|:-------------------|------------:|
# | 0 | Value | 0.241984 |
# | 1 | Mean | 0.241984 |
# | 2 | Min | 0.233234 |
# | 3 | Max | 0.251516 |
# | 4 | Standard Deviation | 0.00642395 |
# | 5 | Count | 17 |

# You can also extract this in a collection form, which is easier to compose with larger more measurements:

offset_calibration_10ghz_dut_waveform_rising_edge_metrics = (
tda.extract_statistical_metrics_collection(
offset_calibration_10ghz_dut_waveform_rising_edge_list,
analysis_types=["peak_to_peak"],
)
)
offset_calibration_10ghz_dut_waveform_rising_edge_metrics.table

# We could now compare this to the metrics the oscilloscope calculated for us previously:

calibration_10ghz_dut_measurements.table.iloc[2]

# | | Metric | Value |
# |---:|:-------------------|:-------------------|
# | 0 | Value | 0.274796879094793 |
# | 1 | Mean | 0.276517693380144 |
# | 2 | Min | 0.02445312536438 |
# | 3 | Max | 0.345625005150214 |
# | 4 | Standard Deviation | 0.0039634275277739 |
# | 5 | Count | 9.91k |

# We can see the measurements are approximately close enough which is pretty cool! I would still trust the device measurements more, but with this functionality it is possible to compare a given waveform to a stastistical output from a machine.

# #### Composing Meaningful Metrics
#
# We might also want to export nice tables of metrics. We can do this through `pandas` and `latex`. Let's make a little metrics collection which we might want to customize:

example_nice_metrics_collection_concatenated = tda.concatenate_metrics_collection(
[
offset_calibration_10ghz_dut_waveform_rising_edge_metrics,
calibration_10ghz_dut_measurements,
]
)
example_nice_metrics_collection_concatenated.table

# | Name | Value | Mean | Min | Max | Standard Deviation | Count | Unit |
# |:-------------|-------------:|-------------:|-------------:|------------:|---------------------:|--------:|:------------|
# | | 0.241984 | 0.241984 | 0.233234 | 0.251516 | 0.00642395 | 17 | Voltage $V$ |
# | delay | -1.23115e-11 | 8.07015e-12 | -7.19126e-10 | 6.7611e-10 | 4.25599e-11 | 9910 | Time $s$ |
# | delay | -1.42048e-11 | -1.3621e-11 | -8.78589e-10 | 1.06648e-09 | 1.61926e-12 | 9910 | Time $s$ |
# | peak_to_peak | 0.274797 | 0.276518 | 0.0244531 | 0.345625 | 0.00396343 | 9910 | Voltage $V$ |
# | peak_to_peak | 0.270969 | 0.276206 | 0.0221875 | 0.359875 | 0.00670219 | 9910 | Voltage $V$ |

# For example, we might want to convert the units so that they're nicer to read:

example_nice_table = piel.analysis.metrics.convert_metric_collection_per_unit(
example_nice_metrics_collection_concatenated,
target_units={piel.types.units.s.name: piel.types.units.ps},
)
example_nice_table.table

# | Name | Value | Mean | Min | Max | Standard Deviation | Count | Unit |
# |:-------------|-----------:|-----------:|-------------:|------------:|---------------------:|--------:|:------------|
# | | 0.241984 | 0.241984 | 0.233234 | 0.251516 | 0.00642395 | 17 | Voltage $V$ |
# | delay | -12.3115 | 8.07015 | -719.126 | 676.11 | 42.5599 | 9910 | Time $ps$ |
# | delay | -14.2048 | -13.621 | -878.589 | 1066.48 | 1.61926 | 9910 | Time $ps$ |
# | peak_to_peak | 0.274797 | 0.276518 | 0.0244531 | 0.345625 | 0.00396343 | 9910 | Voltage $V$ |
# | peak_to_peak | 0.270969 | 0.276206 | 0.0221875 | 0.359875 | 0.00670219 | 9910 | Voltage $V$ |

print(example_nice_table.table.to_latex())

# ### Larger Multi-Variable Analysis

# We have just done some analysis for a single waveform. A more useful metric, say, would be to evaluate the delay metrics for multiple frequencies, or even the rise time accordingly. Maybe let's try to create this specific dataset.
#
# We can start by extracting all the measurements into an `xarray.Dataset` which is designed for multivariable analysis like this. We can always convert back to `pandas` when required.

calibration_propagation_delay_dataset = pe.compose_xarray_dataset_from_experiment_data(
experiment_data=calibration_propagation_delay_experiment_data,
)
calibration_propagation_delay_dataset

x_GHz = (
calibration_propagation_delay_dataset.square_wave_frequency_Hz.values
/ piel.types.units.GHz.base
)

# We can compose our data into nice little variables to use:

# +
delay_mean_ps = (
calibration_propagation_delay_dataset["mean"].sel(metric_name="delay_ch1_ch2__s_2")
/ piel.types.units.ps.base
)

delay_std_deviation_ps = (
calibration_propagation_delay_dataset["standard_deviation"].sel(
metric_name="delay_ch1_ch2__s_2"
)
/ piel.types.units.ps.base
)

# We can plot metrics from our two channels
pk_pk_ch1_mean = calibration_propagation_delay_dataset["mean"].sel(
metric_name="pk-pk_ch1__v"
)
pk_pk_ch1_std_deviation = calibration_propagation_delay_dataset[
"standard_deviation"
].sel(metric_name="pk-pk_ch1__v")
pk_pk_ch2_mean = calibration_propagation_delay_dataset["mean"].sel(
metric_name="pk-pk_ch2__v"
)
pk_pk_ch2_std_deviation = calibration_propagation_delay_dataset[
"standard_deviation"
].sel(metric_name="pk-pk_ch2__v")
# -

# Let's create a custom plot based on all our current analysis:

# +
# Plotting
fig, axs = piel.visual.create_axes_per_figure(
rows=1, columns=2, figsize=(8, 6), constrained_layout=True
)

# Get the standard color cycler
color_cycle = plt.rcParams["axes.prop_cycle"].by_key()["color"]

# Plot Delay Metrics
axs[0].errorbar(
x_GHz, delay_mean_ps, yerr=delay_std_deviation_ps, color=color_cycle[2], capsize=4
)
axs[0].set_xlabel(piel.types.units.GHz.label)
axs[0].set_ylabel("CH1-CH2 \n Time Delay $ps$")


# Plot Peak-2-Peak Metrics
axs[1].errorbar(
x_GHz,
pk_pk_ch1_mean,
yerr=pk_pk_ch1_std_deviation,
label="CH1",
color=color_cycle[0],
capsize=4,
)
axs[1].errorbar(
x_GHz,
pk_pk_ch2_mean,
yerr=pk_pk_ch2_std_deviation,
label="CH2",
color=color_cycle[1],
capsize=4,
)
axs[1].set_ylabel("\n $V_{pp}$ $V$")
axs[1].set_xlabel(piel.types.units.GHz.label)
axs[1].legend(loc="upper right")

fig.savefig(
"../../_static/img/examples/08a_pcb_interposer_characterisation/extracted_time_metrics_combined.jpg"
)
# fig.savefig(os.path.join(os.getenv("TAP"), "calibration_oscilloscope_metrics.jpg"))
# -

# ![extracted_time_metrics_combined](../../_static/img/examples/08a_pcb_interposer_characterisation/extracted_time_metrics_combined.jpg)
Loading
Loading