Skip to content
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
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[flake8]
max-line-length = 120
ignore = E203,W503
exclude = .bctds
exclude = .bctds,.venv,website
31 changes: 31 additions & 0 deletions .github/workflows/python_format_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Flake8 Check

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
python-format-check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.12" ]

steps:
- uses: actions/checkout@v3.1.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.3.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
- name: Lint with flake8
run: |
flake8 .
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@
import numpy as np
from tqdm import tqdm


# ONE chip only, which is why there is no sample_id column. Recorded here so the
# fact is not lost; it also goes into metadata.json.
SAMPLE_NAME = "Si_111_native_oxide"
Expand All @@ -178,7 +177,7 @@

AWG_CLOCK_MHZ = 9830.4 # pulse generator's clock -> 1 tick ~= 0.1017 ns
ADC_CLOCK_MHZ = 552.96 # digitiser's clock -> 1 sample ~= 1.8084 ns
N_TIME_SAMPLES = 1000 # how many instants are recorded per ring-down
N_TIME_SAMPLES = 1000 # how many instants are recorded per ring-down

N_PULSE_WIDTHS = len(PULSE_WIDTH_TICKS_LIST) # 42
N_FREQUENCIES = len(FREQUENCY_LIST_MHZ) # 500
Expand Down Expand Up @@ -477,19 +476,27 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):

n_total_rows = N_PULSE_WIDTHS * N_FREQUENCIES * N_TIME_SAMPLES # 21,000,000

print(f"\nFormat : long (one row per time sample)")
print("\nFormat : long (one row per time sample)")
print(f"Sample : {SAMPLE_NAME} ({SAMPLE_MATERIAL})")
print(f"Pulse widths : {N_PULSE_WIDTHS} ({PULSE_WIDTHS_NS[0]:.3f}-"
f"{PULSE_WIDTHS_NS[-1]:.3f} ns, from {PULSE_WIDTH_TICKS_LIST[0]}-"
f"{PULSE_WIDTH_TICKS_LIST[-1]} ticks)")
print(f"Frequencies : {N_FREQUENCIES} ({FREQUENCY_LIST_MHZ[0]}-"
f"{FREQUENCY_LIST_MHZ[-1]} MHz, step 1)")
print(
f"Pulse widths : {N_PULSE_WIDTHS} ({PULSE_WIDTHS_NS[0]:.3f}-"
f"{PULSE_WIDTHS_NS[-1]:.3f} ns, from {PULSE_WIDTH_TICKS_LIST[0]}-"
f"{PULSE_WIDTH_TICKS_LIST[-1]} ticks)"
)
print(
f"Frequencies : {N_FREQUENCIES} ({FREQUENCY_LIST_MHZ[0]}-"
f"{FREQUENCY_LIST_MHZ[-1]} MHz, step 1)"
)
print(f"Time samples : {N_TIME_SAMPLES} per measurement")
print(f"Total rows : {N_PULSE_WIDTHS} x {N_FREQUENCIES} x "
f"{N_TIME_SAMPLES} = {n_total_rows:,}")
print(
f"Total rows : {N_PULSE_WIDTHS} x {N_FREQUENCIES} x "
f"{N_TIME_SAMPLES} = {n_total_rows:,}"
)
print(f"Columns : {COLUMN_NAMES}")
print(f"Memory needed : ~{n_total_rows*len(COLUMN_NAMES)*4/1024**3:.2f} GB "
f"for the array alone\n")
print(
f"Memory needed : ~{n_total_rows * len(COLUMN_NAMES) * 4 / 1024**3:.2f} GB "
f"for the array alone\n"
)

# Make the whole output table up front and fill it in, rather than growing it
# row by row (which would be far slower). float32 = 4 bytes per number, so
Expand Down Expand Up @@ -522,8 +529,10 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
# truncated acquisition, or one of the DERIVED products that sit in the
# same directory as the raw sweeps.
if IQ_matrix.shape != (2, N_FREQUENCIES, N_TIME_SAMPLES):
print(f" WARNING: {filename} has shape {IQ_matrix.shape}, expected "
f"(2, {N_FREQUENCIES}, {N_TIME_SAMPLES}) — skipping")
print(
f" WARNING: {filename} has shape {IQ_matrix.shape}, expected "
f"(2, {N_FREQUENCIES}, {N_TIME_SAMPLES}) — skipping"
)
continue

I_matrix = IQ_matrix[0] # shape (500, 1000) — I for every frequency
Expand Down Expand Up @@ -575,17 +584,17 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
with open(cols_path, "w", encoding="utf-8") as fh:
json.dump(build_column_info(), fh, indent=2)

print(f"\nDone!")
print("\nDone!")
print(f" Shape : ({row_idx:,}, {len(COLUMN_NAMES)}) — rows x columns")
print(f" Pickle : {pkl_path} ({os.path.getsize(pkl_path)/1024**2:.1f} MB)")
print(f" Pickle : {pkl_path} ({os.path.getsize(pkl_path) / 1024**2:.1f} MB)")
print(f" metadata : {meta_path}")
print(f" column_info : {cols_path}")

# ── Sanity checks ─────────────────────────────────────────────────────────
# These do not change the data. They re-derive facts we already expect to be
# true and shout if any of them is not, so that a silently broken conversion
# (a missing file, a wrong axis, a rounding trap) cannot pass unnoticed.
print(f"\nRunning sanity checks ...")
print("\nRunning sanity checks ...")
errors = []

# Did every file arrive? 42 files x 500 frequencies x 1000 instants should
Expand All @@ -596,15 +605,17 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
f"— likely caused by missing .npy files"
)
else:
print(f" [PASS] Row count: {row_idx:,} = {N_PULSE_WIDTHS} pulse widths x "
f"{N_FREQUENCIES} frequencies x {N_TIME_SAMPLES} samples")
print(
f" [PASS] Row count: {row_idx:,} = {N_PULSE_WIDTHS} pulse widths x "
f"{N_FREQUENCIES} frequencies x {N_TIME_SAMPLES} samples"
)

n_nan = int(np.sum(np.isnan(data_final)))
n_inf = int(np.sum(np.isinf(data_final)))
if n_nan > 0 or n_inf > 0:
errors.append(f" [FAIL] Data contains {n_nan} NaN and {n_inf} Inf values")
else:
print(f" [PASS] No NaN or Inf values in dataset")
print(" [PASS] No NaN or Inf values in dataset")

# Are the measured numbers the right size? What comes BACK from the chip is a
# few thousand a.u.; the 30000 number is how hard we SHOUT, not what we hear.
Expand All @@ -620,8 +631,10 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
f"I=[{I_min:.0f}, {I_max:.0f}], Q=[{Q_min:.0f}, {Q_max:.0f}]"
)
else:
print(f" [PASS] I/Q readout range: I=[{I_min:.1f}, {I_max:.1f}], "
f"Q=[{Q_min:.1f}, {Q_max:.1f}]")
print(
f" [PASS] I/Q readout range: I=[{I_min:.1f}, {I_max:.1f}], "
f"Q=[{Q_min:.1f}, {Q_max:.1f}]"
)

for col_idx, name, expected in [
(0, "pulse widths", N_PULSE_WIDTHS),
Expand All @@ -642,8 +655,10 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
if n_at == 0:
errors.append(" [FAIL] frequency_MHz == 4254 matches no rows after upcast")
else:
print(f" [PASS] frequency_MHz exact under float64 upcast "
f"({n_at:,} rows at 4254 MHz)")
print(
f" [PASS] frequency_MHz exact under float64 upcast "
f"({n_at:,} rows at 4254 MHz)"
)

ts_col = data_final[:, 2]
ts_min, ts_max = float(ts_col.min()), float(ts_col.max())
Expand All @@ -663,26 +678,30 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
cols_ok = list(reloaded["columns"]) == COLUMN_NAMES
del reloaded
if not same:
errors.append(f" [FAIL] Pickle round-trip: reloaded data differs from memory")
errors.append(" [FAIL] Pickle round-trip: reloaded data differs from memory")
elif not cols_ok:
errors.append(f" [FAIL] Pickle round-trip: columns differ from memory")
errors.append(" [FAIL] Pickle round-trip: columns differ from memory")
else:
print(f" [PASS] Pickle round-trip: reloaded array matches exactly")
print(" [PASS] Pickle round-trip: reloaded array matches exactly")

if errors:
print(f"\n {len(errors)} sanity check(s) FAILED:")
for e in errors:
print(e)
else:
print(f"\n All sanity checks passed.")
print("\n All sanity checks passed.")

# ── CSV ───────────────────────────────────────────────────────────────────
if write_csv_file:
print(f"\nWriting CSV to {csv_path} ...")
print(f" (expect roughly {n_total_rows*55/1024**3:.1f} GB and a few minutes)")
print(
f" (expect roughly {n_total_rows * 55 / 1024**3:.1f} GB and a few minutes)"
)
write_csv(data_final, COLUMN_NAMES, csv_path)
print(f" CSV : {csv_path} "
f"({os.path.getsize(csv_path)/1024**2:.1f} MB, {row_idx:,} rows)")
print(
f" CSV : {csv_path} "
f"({os.path.getsize(csv_path) / 1024**2:.1f} MB, {row_idx:,} rows)"
)


# ─────────────────────────────────────────────────────────────────────────────
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
PULSE_WIDTH_TICKS = 308
AWG_CLOCK_MHZ = 9830.4 # pulse generator's clock -> 1 tick ~= 0.1017 ns
ADC_CLOCK_MHZ = 552.96 # digitiser's clock -> 1 sample ~= 1.8084 ns
N_TIME_SAMPLES = 1000 # how many instants are recorded per ring-down
N_TIME_SAMPLES = 1000 # how many instants are recorded per ring-down

PULSE_WIDTH_NS = (PULSE_WIDTH_TICKS / AWG_CLOCK_MHZ) * 1000 # ~= 31.33 ns

Expand Down Expand Up @@ -348,7 +348,10 @@ def build_column_info() -> dict:
return {
"dataset": "experiment_3_amplitude_control",
"format": "long (one row per time sample)",
"n_rows": len(SAMPLES) * len(SPACING_LIST_NS) * len(AMPLITUDE_LIST) * N_TIME_SAMPLES,
"n_rows": len(SAMPLES)
* len(SPACING_LIST_NS)
* len(AMPLITUDE_LIST)
* N_TIME_SAMPLES,
"n_columns": len(COLUMN_NAMES),
"schema_rule": (
"The filename holds everything constant within a file (sample, pulse "
Expand Down Expand Up @@ -527,14 +530,18 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
n_amps = len(AMPLITUDE_LIST)
n_total_rows = n_samples * n_spacings * n_amps * N_TIME_SAMPLES # 1,800,000

print(f"\nFormat : long (one row per time sample)")
print("\nFormat : long (one row per time sample)")
print(f"Samples : {n_samples} ({SAMPLE_NAMES})")
print(f"Spacings : {n_spacings} ({SPACING_LIST_NS} ns)")
print(f"Amplitudes : {n_amps} ({AMPLITUDE_LIST[0]}-{AMPLITUDE_LIST[-1]}, "
f"step 300)")
print(
f"Amplitudes : {n_amps} ({AMPLITUDE_LIST[0]}-{AMPLITUDE_LIST[-1]}, "
f"step 300)"
)
print(f"Time samples : {N_TIME_SAMPLES} per measurement")
print(f"Total rows : {n_samples} x {n_spacings} x {n_amps} x "
f"{N_TIME_SAMPLES} = {n_total_rows:,}")
print(
f"Total rows : {n_samples} x {n_spacings} x {n_amps} x "
f"{N_TIME_SAMPLES} = {n_total_rows:,}"
)
print(f"Columns : {COLUMN_NAMES}\n")

# Make the whole output table up front and fill it in, rather than growing it
Expand Down Expand Up @@ -638,17 +645,17 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
with open(cols_path, "w", encoding="utf-8") as fh:
json.dump(build_column_info(), fh, indent=2)

print(f"\nDone!")
print("\nDone!")
print(f" Shape : ({row_idx:,}, {len(COLUMN_NAMES)}) — rows x columns")
print(f" Pickle : {pkl_path} ({os.path.getsize(pkl_path)/1024**2:.1f} MB)")
print(f" Pickle : {pkl_path} ({os.path.getsize(pkl_path) / 1024**2:.1f} MB)")
print(f" metadata : {meta_path}")
print(f" column_info : {cols_path}")

# ── Sanity checks ─────────────────────────────────────────────────────────
# These do not change the data. They re-derive facts we already expect to be
# true and shout if any of them is not, so that a silently broken conversion
# (a missing file, a wrong axis, a rounding trap) cannot pass unnoticed.
print(f"\nRunning sanity checks ...")
print("\nRunning sanity checks ...")
errors = []

# Did every file arrive? 18 files x 100 amplitudes x 1000 instants should
Expand All @@ -659,15 +666,17 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
f"— likely caused by missing .npy files"
)
else:
print(f" [PASS] Row count: {row_idx:,} = {n_samples} samples x {n_spacings} "
f"spacings x {n_amps} amplitudes x {N_TIME_SAMPLES} samples")
print(
f" [PASS] Row count: {row_idx:,} = {n_samples} samples x {n_spacings} "
f"spacings x {n_amps} amplitudes x {N_TIME_SAMPLES} samples"
)

n_nan = int(np.sum(np.isnan(data_final)))
n_inf = int(np.sum(np.isinf(data_final)))
if n_nan > 0 or n_inf > 0:
errors.append(f" [FAIL] Data contains {n_nan} NaN and {n_inf} Inf values")
else:
print(f" [PASS] No NaN or Inf values in dataset")
print(" [PASS] No NaN or Inf values in dataset")

# Are the measured numbers the right size? What comes BACK from the chip is a
# few thousand a.u.; the 30000 in amplitude_arb is how hard we SHOUT, not
Expand All @@ -683,8 +692,10 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
f"I=[{I_min:.0f}, {I_max:.0f}], Q=[{Q_min:.0f}, {Q_max:.0f}]"
)
else:
print(f" [PASS] I/Q readout range: I=[{I_min:.1f}, {I_max:.1f}], "
f"Q=[{Q_min:.1f}, {Q_max:.1f}]")
print(
f" [PASS] I/Q readout range: I=[{I_min:.1f}, {I_max:.1f}], "
f"Q=[{Q_min:.1f}, {Q_max:.1f}]"
)

for col_idx, name, expected in [
(COL["sample_id"], "sample IDs", n_samples),
Expand All @@ -707,10 +718,12 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
spacing_64 = data_final[:, COL["spacing_ns"]].astype(np.float64)
n_at_10 = int((spacing_64 == 10).sum())
if n_at_10 == 0:
errors.append(f" [FAIL] spacing_ns == 10 matches no rows after float64 upcast")
errors.append(" [FAIL] spacing_ns == 10 matches no rows after float64 upcast")
else:
print(f" [PASS] spacing_ns exact under float64 upcast "
f"({n_at_10:,} rows at 10 ns)")
print(
f" [PASS] spacing_ns exact under float64 upcast "
f"({n_at_10:,} rows at 10 ns)"
)

ts_col = data_final[:, COL["timestamp_ns"]]
ts_min, ts_max = float(ts_col.min()), float(ts_col.max())
Expand All @@ -727,25 +740,27 @@ def build_dataset(data_dir: str, output_dir: str, write_csv_file: bool = True):
# — i.e. that nothing was lost or corrupted in the writing.
reloaded = load_pickle(pkl_path)
if not np.array_equal(reloaded["data"], data_final):
errors.append(f" [FAIL] Pickle round-trip: reloaded data differs from memory")
errors.append(" [FAIL] Pickle round-trip: reloaded data differs from memory")
elif list(reloaded["columns"]) != COLUMN_NAMES:
errors.append(f" [FAIL] Pickle round-trip: columns differ from memory")
errors.append(" [FAIL] Pickle round-trip: columns differ from memory")
else:
print(f" [PASS] Pickle round-trip: reloaded array matches exactly")
print(" [PASS] Pickle round-trip: reloaded array matches exactly")

if errors:
print(f"\n {len(errors)} sanity check(s) FAILED:")
for e in errors:
print(e)
else:
print(f"\n All sanity checks passed.")
print("\n All sanity checks passed.")

# ── CSV ───────────────────────────────────────────────────────────────────
if write_csv_file:
print(f"\nWriting CSV to {csv_path} ...")
write_csv(data_final, COLUMN_NAMES, csv_path)
print(f" CSV : {csv_path} "
f"({os.path.getsize(csv_path)/1024**2:.1f} MB, {row_idx:,} rows)")
print(
f" CSV : {csv_path} "
f"({os.path.getsize(csv_path) / 1024**2:.1f} MB, {row_idx:,} rows)"
)


# ─────────────────────────────────────────────────────────────────────────────
Expand Down
Loading
Loading