Skip to content

Add Kehl ENDOR pulse sequences#80

Closed
IlyaKuprov wants to merge 27 commits into
mainfrom
talos/kehl-endor-spinach
Closed

Add Kehl ENDOR pulse sequences#80
IlyaKuprov wants to merge 27 commits into
mainfrom
talos/kehl-endor-spinach

Conversation

@IlyaKuprov

@IlyaKuprov IlyaKuprov commented May 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • adds the Kehl ENDOR context and supporting helper routines under experiments/esr_hyperfine/
  • adds two Spinach examples and the MLR09 pulse profile under examples/esr_sol_pulsed/
  • examples now use native Spinach constructor input and call endor_kehl_context() with pulse-sequence handles (@endor_kehl_mims, @endor_kehl_time) in the standard Spinach context style
  • Kehl context data are inferred from the Spinach spin_system; fundamental constants come from spin_system.tols, and nuclear magnetogyric ratios now come directly from Spinach spin() with conversion from rad/s/T to Hz/T by division by 2*pi
  • conceptually distinct Kehl pulse-sequence simulators live in their own files: endor_kehl_mims.m, endor_kehl_time.m, endor_kehl_davies.m, endor_kehl_spinlock.m, endor_kehl_tensor.m, and endor_kehl_cp.m
  • redundant one-call non-sequence helper files have been inlined/removed, including kehl_spin_label.m, kehl_nuc_gamma.m, kehl_mat_to_lbra.m, kehl_lket_to_mat.m, and kehl_spin_ops.m
  • Hilbert-to-Liouville state-vector conversions now call Spinach hilb2liouv(...,'statevec') directly; Liouville-ket-to-Hilbert-matrix recovery now uses direct reshape(vec,sqrt(size(vec,1)),sqrt(size(vec,1)))
  • Kehl sequence kernels now obtain explicit spin operators from Spinach operator() and the initial electron state from Spinach state() through a reduced Spinach operator system
  • the former expt parameter bucket has been removed; sequence-specific pulse/RF/axis setup now appends fields to parameters inside the individual endor_kehl_* sequence files
  • endor_kehl_context.m is now a thin sequence-agnostic wrapper; non-trivial physics helpers were extracted to standalone kehl_* files, including spin-data extraction, EPR orientation selection, pulse-profile scaling, EPR-axis preparation, and line broadening

Validation

  • git diff --cached --check before commit, and clean git status after push
  • static audit: endor_kehl_context.m is 134 lines and contains only the public wrapper, trivial sequence_handle(), and input grumble()
  • static audit: no spinSys, spinOps, kehl_spin_ops, expt, parameters.expt, context_experiment, or cp_axis references remain in Kehl/context/example files
  • static audit: no local physics functions remain in endor_kehl_context.m
  • MATLAB execution smoke after context extraction: Mims, Davies, Spinlock, Tensor, TimeDomain, and CP all finite; result file tmp/spinach_kehl_context_extract_allseq_20260508.mat
  • handle-based isotope-order permutation regression after context extraction:
    • nuclear permutation [1 5 3 2 4], ENDOR [4 3 5]: Mims max_abs 1.3322676295501878e-15, time max_abs 8.3266726846886741e-17
    • electron-moving permutation [5 2 1 4 3], ENDOR [2 5 4]: Mims max_abs 8.8817841970012523e-16, time max_abs 8.3266726846886741e-17

The older references generated before the Spinach isotope/vectorisation/sequence-handle/direct-operator/parameter-field/context-extraction refactors are intentionally superseded by the current handle-based figures above.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e28cb98f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread experiments/esr_hyperfine/kehl_ori_freq.m Outdated
Comment on lines +162 to +163
if spinSys("CS_used")==true
CS_zz(m)=(sin(theta))^2*(cos(phi))^2*CS(3*m-2,1)...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rotate CS tensors for every ENDOR nucleus

In field-domain powder simulations with multiple ENDOR nuclei and CS_used true, CS_zz is filled only once after the preceding for m=1:Ni_ENDOR loop, so m is left at the final nucleus and all earlier nuclei keep a zero chemical-shift contribution. The downstream ENDOR calculations index CS_zz(mm) for each nucleus, so their resonances are computed without their CS shifts in this mode.

Useful? React with 👍 / 👎.

Comment on lines +38 to +39
if isKey(values,'use_Bterm')
opt("Bterm")=values("use_Bterm");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honour the documented Bterm option key

All simulation code reads opt("Bterm"), and the added example populates values('Bterm'), but kehl_set_opt only checks for values('use_Bterm'); consequently a caller setting values('Bterm')=true silently gets opt("Bterm")=false, and none of the B-term branches run. This makes the option ineffective under the key exposed by the rest of the new API.

Useful? React with 👍 / 👎.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Follow-up update for the Kehl ENDOR examples:

  • Replaced example-level inter.coupling.matrix construction with inter.coupling.eigs / inter.coupling.euler pairs.
  • Removed the local local_tensor and local_traceless_tensor helper blocks from the two example files.
  • Used Spinach remtrace.m for the traceless quadrupole tensor.
  • Confirmed the old local rotation matrix is equivalent to euler2dcm([-gamma,-beta,-alpha]), i.e. to the transpose/inverse convention relative to euler2dcm([alpha beta gamma]); all angles now pass radians into euler2dcm through the standard euler fields.
  • Extended the Kehl ENDOR context to accept eigs/euler input as well as direct matrix input when reconstructing the legacy Kehl maps.

Validation run locally:

  • Rotation equivalence: 0 Frobenius error for the tested Kehl angles.
  • remtrace equivalence: 0 Frobenius error.
  • Spin-system tensor equivalence, old helper matrices vs new eigs/euler input:
    • Mims Zeeman max error: 5.14e-12
    • Mims coupling max error: 1.17e-10
    • Time-domain Zeeman max error: 0
    • Time-domain coupling max error: 1.17e-10
  • Small-orientation ENDOR regression against the saved original-output reference:
    • Mims powder max abs/rel: 2.58e-10 / 2.56e-12
    • time-domain powder max abs/rel: 3.46e-13 / 4.19e-14
  • Existing Kehl smoke/regression suite passed after the change.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Follow-up refactor update:

  • Moved the example-level Kehl experiment setup calls into endor_kehl_context.m:
    • kehl_exp_create
    • kehl_mims_fields / kehl_time_fields / other sequence field helpers selected by the pulse sequence
    • kehl_exp_freq
    • kehl_exp_angle
    • kehl_exp_pulse
    • kehl_cp_def when CP sweep fields are present
  • Removed the public parameters.expt input path from endor_kehl_context.
  • Replaced the separate expt blocks in both example files with named physical parameters.* fields, including frequency, static field, field step, ENDOR resolution/range, pulse timings, RF-field source, EPR sweep, pulse profile, and RF flip angle where used.
  • Kept the internal expt Map only as the legacy data structure passed down to the Kehl calculation internals.

Validation run locally:

  • MATLAB mtree parse on the changed context and both changed examples: passed.
  • Grep check over Kehl example files: no parameters.expt, no Experiment parameters blocks, no direct kehl_exp_* or kehl_*_fields setup calls, no local_tensor/local_traceless_tensor, and no inter.coupling.matrix construction.
  • Small-orientation ENDOR regression against the saved original-output reference using the new parameters.* context API:
    • Mims powder max abs/rel: 2.58e-10 / 2.56e-12
    • time-domain powder max abs/rel: 3.45e-13 / 4.16e-14
  • Targeted bug/regression suite passed, including the frequency-domain no-aux-EPR context path and the field-domain multi-ENDOR CS projection check.
  • Smoke suite and branch-coverage subset passed.
  • Example-parameter smoke using the new example-style parameters.* fields, shaped pulse file, RF flip angle, and Nang=4: passed for both Mims and time-domain examples.
  • git diff --check passed.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Follow-up parameter-pruning update:

  • Removed the redundant example assignments:
    • parameters.inter
    • parameters.epr_spins
    • parameters.epr_quadrupole_matrix
    • parameters.n_spin_systems
    • parameters.dipolar_pairs
  • endor_kehl_context.m now reconstructs the legacy Kehl spin-system map from spin_system after create():
    • electron g tensor from spin_system.inter.zeeman.matrix and basefrqs
    • nuclear CS tensors by inverting Spinach’s nuclear Zeeman conversion
    • hyperfine, quadrupole, and nuclear-nuclear couplings from spin_system.inter.coupling.matrix/(2*pi)
    • EPR-only nuclei as non-ENDOR nuclei with non-zero electron coupling or self-quadrupole coupling
    • dipolar pairs as non-zero couplings among the selected ENDOR nuclei
  • Kept parameters.endor_spins: this is still practically necessary because the spin system contains all nuclei, but not the experiment-level choice of which nuclei are ENDOR-observed and which are only EPR-selection nuclei.
  • Removed inline %#ok<ASGLU> pragmas in the examples by using ignored outputs.

Validation run locally:

  • MATLAB mtree parse on the changed context and both changed examples: passed.
  • Static grep over the changed Kehl examples/context confirmed no public use of parameters.inter, parameters.epr_spins, parameters.epr_quadrupole_matrix, parameters.endor_quadrupole_matrix, parameters.n_spin_systems, parameters.dipolar_pairs, or parameters.expt; examples also have no local_tensor/local_traceless_tensor, inter.coupling.matrix, or Experiment parameters blocks.
  • Small-orientation ENDOR regression against the saved original-output reference using the reduced parameter API:
    • Mims powder max abs/rel: 2.60e-10 / 2.58e-12
    • time-domain powder max abs/rel: 4.37e-13 / 5.28e-14
  • Targeted bug/regression suite passed, including no-aux-EPR frequency-domain context and multi-ENDOR field-domain CS projection.
  • Example-parameter smoke using the reduced API, shaped pulse file, RF flip angle, and Nang=4: passed for both Mims and time-domain examples.
  • Smoke suite and branch-coverage subset passed.
  • git diff --check passed.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Permutation-equivalence test requested in Slack:

  • Built a temporary MATLAB permutation suite that runs the current example-style parameter path with Nang=4 for both Mims and time-domain examples.
  • Tested two nontrivial isotope-order permutations while also remapping all corresponding Zeeman/coupling specifications and parameters.endor_spins:
    • nuclear-only permutation: original spin order [E,19F,1H,1H,14N] → old-index order [1,5,3,2,4], with ENDOR indices remapped to [4,3,5]
    • electron-moving permutation: old-index order [5,2,1,4,3], so the electron is no longer spin 1, with ENDOR indices remapped to [2,5,4]

Results against the unpermuted setup:

  • Nuclear permutation:
    • Mims max abs/rel: 1.33e-15 / 4.81e-16
    • time-domain max abs/rel: 1.11e-16 / 1.11e-16
  • Electron-moving permutation:
    • Mims max abs/rel: 1.33e-15 / 4.81e-16
    • time-domain max abs/rel: 8.33e-17 / 8.33e-17

Result file: tmp/spinach_kehl_permutation_20260508.mat.

No source changes were needed for this test; the current inference from spin_system is invariant under these permutations within roundoff.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Inlining update pushed as e969423.

What changed:

  • Inlined the Kehl ENDOR wrapper/dispatch layer into endor_kehl_context.m; examples now select sequences by name ('mims', 'time') instead of function-handle wrappers.
  • Folded single-call context setup, preparation, orientation, line-broadening, and sequence kernel functions into endor_kehl_context.m as localised implementation code.
  • Removed redundant standalone helper/wrapper files from experiments/esr_hyperfine; the remaining standalone Kehl helper files all have multiple internal call sites in the current tree.
  • Also inlined the one-call CP B-term helpers into the CP kernels before localising those kernels.

Validation:

  • mtree parse over remaining Kehl/context/example .m files: pass.
  • Static whitespace/diff check: pass (git diff --cached --check before commit).
  • Standalone-helper call audit: 14 standalone Kehl files remain; no zero/single-call standalone Kehl helper files left.
  • Regression against the saved pre-inline reference: worst Mims powder difference 2.603002258183551e-10 absolute, 2.580553290302762e-12 relative; time-domain worst difference 4.3876013933186186e-13 absolute, 5.3016149334537227e-14 relative; sweep axes unchanged.
  • Spin-permutation regression after inlining:
    • nuclear permutation [1 5 3 2 4], ENDOR spins [4 3 5]: Mims 1.3322676295501878e-15, time 8.3266726846886741e-17 max absolute difference.
    • electron permutation [5 2 1 4 3], ENDOR spins [2 5 4]: Mims 1.3322676295501878e-15, time 1.1102230246251565e-16 max absolute difference.

Note: I started the full unthrottled graphical example functions as an extra check, but they were still running after several minutes; I stopped that run and used the reduced regression/permutation suites above as the acceptance tests for this commit.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Refactoring update pushed as 0b5fecd.

What changed:

  • endor_kehl_context.m no longer declares the fundamental constants explicitly. It now builds its small legacy-compatibility constant map from spin_system.tols: H=2*pi*hbar, K_B=kbol/H, MU_B=muB, GE=freeg*muB/H, and CONST1=GE/1e10.
  • The legacy nuclear gyromagnetic ratios remain in kehl_nuc_gamma.m, not in endor_kehl_context.m, because those are isotope-database values rather than spin_system.tols fundamental constants.
  • Removed localpar; the received parameters structure is extended in place with seq_name, assumptions, constants, expt, spinSys, spinOps, paramsENDOR, paramsEPR, and epr.
  • Sequence/local child calls now use compact signatures such as kehl_spinlock_rlx(spin_system,parameters) and kehl_tensor_calc(spin_system,parameters) rather than passing long localpar.* argument lists. The child functions unpack the required legacy data from parameters locally.

Validation:

  • mtree parse over remaining Kehl/context/example .m files: pass.
  • git diff --check: pass.
  • Static audit: localpar hits in endor_kehl_context.m = 0; explicit fundamental constant literals there = 0; no long Kehl local-function signatures remain in endor_kehl_context.m.
  • Context small-orientation run completed and saved tmp/spinach_kehl_context_tols_params_success_20260508.mat.
  • Spin-permutation regression completed and saved tmp/spinach_kehl_permutation_tols_params_legacygn_20260508.mat:
    • nuclear permutation [1 5 3 2 4], ENDOR [4 3 5]: Mims max abs 8.8817841970012523e-16, time max abs 8.3266726846886741e-17.
    • electron-moving permutation [5 2 1 4 3], ENDOR [2 5 4]: Mims max abs 1.3322676295501878e-15, time max abs 8.3266726846886741e-17.

Numerical note:

  • The old equivalence comparison to the pre-spin_system.tols explicit-constant reference is no longer exact by construction. With the legacy nuclear gamma values retained, axes and Larmor frequencies remain unchanged, but amplitudes shift slightly because CONST1/fundamental constants now come from spin_system.tols: Mims worst max abs 6.290724808479808e-4, rel 6.2364719630477824e-6; time-domain worst max abs 4.508835079519713e-5, rel 5.4481037011389805e-6. I did not mask this as a pass; the old strict equivalence gate correctly fails after the requested constant-source change.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Reference test figures updated from the current spin_system.tols implementation.

What I changed:

  • Regenerated the current-code small-orientation reference as tmp/spinach_kehl_current_reference_20260508.mat from the present branch head.
  • Reran the same context suite into tmp/spinach_kehl_context_current_rerun_20260508.mat and compared it against the regenerated reference.
  • Updated the PR body so its validation section now cites the current-code reference figures rather than the superseded hand-declared-constant reference.

Validation:

  • Current-code rerun vs regenerated reference: worst max_abs 2.8421709430404007e-14, worst max_rel 2.8176768946348999e-16; axes and Larmor frequencies unchanged.
  • Previous post-refactor spin-permutation gate remains the relevant invariance check:
    • nuclear permutation [1 5 3 2 4], ENDOR [4 3 5]: Mims max_abs 8.8817841970012523e-16, time max_abs 8.3266726846886741e-17.
    • electron-moving permutation [5 2 1 4 3], ENDOR [2 5 4]: Mims max_abs 1.3322676295501878e-15, time max_abs 8.3266726846886741e-17.

No source-code changes were needed for this update.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Update pushed as 475eb22.

What changed:

  • Removed experiments/esr_hyperfine/kehl_spin_label.m.
  • Removed experiments/esr_hyperfine/kehl_nuc_gamma.m.
  • Replaced the Kehl nuclear-gamma helper calls with direct Spinach spin() calls in endor_kehl_context.m, converting gamma from rad/s/T to Hz/T as gamma/(2*pi) before forming Larmor frequencies.
  • Kept the only needed legacy isotope alias at the call site: 2D normalises to Spinach 2H inside kehl_spin_ops.

Validation:

  • git diff --check -- .: passed.
  • Static audit over Kehl/context/example files: no kehl_spin_label or kehl_nuc_gamma references remain.
  • MATLAB mtree parse over remaining Kehl/context/example .m files: passed.
  • MATLAB alias probe: legacy 2D input to kehl_spin_ops produced isotope 2H.
  • Regenerated the current-code small-orientation reference after the Spinach spin() gamma change: tmp/spinach_kehl_spin_gamma_reference_20260508.mat.
  • Reran the context suite into tmp/spinach_kehl_spin_gamma_rerun_20260508.mat; comparison against the regenerated reference gave worst max_abs 2.8421709430404007e-14, worst max_rel 2.8176800295990996e-16; axes and Larmor frequencies reproduced exactly between reference and rerun.
  • Permutation regression saved to tmp/spinach_kehl_permutation_spin_gamma_20260508.mat:
    • nuclear permutation [1 5 3 2 4], ENDOR [4 3 5]: Mims max_abs 1.3322676295501878e-15, time max_abs 8.3266726846886741e-17.
    • electron-moving permutation [5 2 1 4 3], ENDOR [2 5 4]: Mims max_abs 1.3322676295501878e-15, time max_abs 8.3266726846886741e-17.

The older references made with legacy Kehl nuclear gamma values are now superseded by the Spinach isotope database path.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Update pushed as a7c4aa3.

What changed:

  • Removed experiments/esr_hyperfine/kehl_mat_to_lbra.m.
  • Replaced every kehl_mat_to_lbra(...).' state-vector conversion in endor_kehl_context.m with direct Spinach hilb2liouv(...,'statevec') calls.
  • Replaced the kehl_relax_t2 index-vector conversion with find(hilb2liouv(O,'statevec')).
  • This removes the previous sparse/full conversions and the wrapper-level transposes from the state-vector path.

Validation:

  • git diff --check -- .: passed.
  • Static audit over Kehl/context/example files: no kehl_mat_to_lbra or mat_to_lbra references remain.
  • MATLAB mtree parse over remaining Kehl/context/example .m files: passed.
  • MATLAB state-vector probe: hilb2liouv(A,'statevec') equals A(:) exactly.
  • MATLAB kehl_relax_t2 probe: returned a finite 4x4 relaxation superoperator on a two-level test operator.
  • Regenerated the current-code small-orientation reference after direct Liouville vectorisation: tmp/spinach_kehl_lbra_reference_20260508.mat.
  • Reran the context suite into tmp/spinach_kehl_lbra_rerun_20260508.mat; comparison against the regenerated reference gave worst max_abs 2.8421709430404007e-14, worst max_rel 2.8176800295990996e-16; axes and Larmor frequencies reproduced exactly between reference and rerun.
  • Permutation regression saved to tmp/spinach_kehl_permutation_lbra_20260508.mat:
    • nuclear permutation [1 5 3 2 4], ENDOR [4 3 5]: Mims max_abs 1.3322676295501878e-15, time max_abs 1.1102230246251565e-16.
    • electron-moving permutation [5 2 1 4 3], ENDOR [2 5 4]: Mims max_abs 1.3322676295501878e-15, time max_abs 8.3266726846886741e-17.

I did not keep a compatibility wrapper; the helper is gone and all remaining state-vector conversions call Spinach directly.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Update pushed as 56bf93e.

What changed:

  • Removed experiments/esr_hyperfine/kehl_lket_to_mat.m.
  • Replaced all kehl_lket_to_mat(...) call sites in endor_kehl_context.m with direct reshape(vec,sqrt(size(vec,1)),sqrt(size(vec,1))) matrix recovery.
  • No compatibility wrapper remains.

Validation:

  • git diff --check -- .: passed.
  • Static audit over Kehl/context/example files: no kehl_lket_to_mat or lket_to_mat references remain.
  • MATLAB mtree parse over remaining Kehl/context/example .m files: passed.
  • MATLAB reshape probe: direct reshape(vec,sqrt(size(vec,1)),sqrt(size(vec,1))) recovers the Hilbert matrix exactly for a square Liouville vector.
  • Regenerated the current-code small-orientation reference after direct reshape replacement: tmp/spinach_kehl_lket_reference_20260508.mat.
  • Reran the context suite into tmp/spinach_kehl_lket_rerun_20260508.mat; comparison against the regenerated reference gave worst max_abs 1.4210854715202004e-14, worst max_rel 1.4088400147995498e-16; axes and Larmor frequencies reproduced exactly between reference and rerun.
  • Permutation regression saved to tmp/spinach_kehl_permutation_lket_20260508.mat:
    • nuclear permutation [1 5 3 2 4], ENDOR [4 3 5]: Mims max_abs 1.3322676295501878e-15, time max_abs 8.3266726846886741e-17.
    • electron-moving permutation [5 2 1 4 3], ENDOR [2 5 4]: Mims max_abs 8.8817841970012523e-16, time max_abs 5.5511151231257827e-17.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Update pushed as 23f02e3.

I corrected the architecture boundary I misunderstood earlier.

What changed:

  • Extracted the conceptually distinct pulse-sequence simulators back out of endor_kehl_context.m into separate files:
    • endor_kehl_mims.m
    • endor_kehl_time.m
    • endor_kehl_davies.m
    • endor_kehl_spinlock.m
    • endor_kehl_tensor.m
    • endor_kehl_cp.m
  • endor_kehl_context.m now calls the selected pulse sequence via the standard Spinach experiment signature pulse_sequence(spin_system,parameters,H,R,K).
  • The Kehl examples now pass handles into the context: @endor_kehl_mims and @endor_kehl_time.
  • String selectors are still mapped internally for backwards compatibility, but the examples now use the normal handle style.

Validation:

  • git diff --check -- .: passed.
  • MATLAB mtree parse over remaining Kehl/context/example .m files: passed.
  • Static audit: endor_kehl_context.m has no local kehl_mims/time/davies/spinlock/tensor/cp sequence-kernel definitions.
  • Static audit: six separate endor_kehl_* pulse-sequence files exist, and both Kehl examples call the context with function handles.
  • Handle-based current-code reference regenerated as tmp/spinach_kehl_handle_reference_20260508.mat.
  • Handle-based rerun saved as tmp/spinach_kehl_handle_rerun_20260508.mat; comparison gave worst max_abs 2.8421709430404007e-14, worst max_rel 2.8176800295990991e-16; axes and Larmor frequencies reproduced exactly between reference and rerun.
  • Handle-based permutation regression saved as tmp/spinach_kehl_permutation_handle_20260508.mat:
    • nuclear permutation [1 5 3 2 4], ENDOR [4 3 5]: Mims max_abs 1.3322676295501878e-15, time max_abs 1.1102230246251565e-16.
    • electron-moving permutation [5 2 1 4 3], ENDOR [2 5 4]: Mims max_abs 1.3322676295501878e-15, time max_abs 1.1102230246251565e-16.

GitHub reports no PR checks configured for the branch.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Updated PR #80 with commit 332bde4 (Move Kehl experiment fields into parameters).

This slice removes the expt bucket entirely from the Kehl PR files. Generic physical fields are appended to parameters by endor_kehl_context.m; sequence-specific RF/pulse/axis setup now lives inside the corresponding endor_kehl_* sequence file via the sequence preparation path. The context wrapper no longer contains the large sequence-specific switch block and is sequence-agnostic apart from generic handle normalisation.

Validation:

  • git diff --check -- experiments/esr_hyperfine examples/esr_sol_pulsed
  • static grep: no expt, parameters.expt, context_experiment, or cp_axis references remain
  • static grep: no spinSys, spinOps, or kehl_spin_ops regressions
  • MATLAB mtree: Kehl/context/example files plus kehl_endor_axis.m and kehl_rf_policy.m
  • compact all-sequence smoke: Mims, Davies, Spinlock, Tensor, TimeDomain, and CP finite (tmp/spinach_kehl_no_expt_allseq_final_20260508.mat)
  • permutation regression (tmp/spinach_kehl_no_expt_permutation_final_20260508.mat): nuclear permutation Mims/time max_abs 8.88e-16 / 8.33e-17; electron-moving permutation Mims/time max_abs 1.33e-15 / 1.11e-16

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Updated PR #80 with commit 6221822 (Extract Kehl context physics helpers).

This pulls the non-trivial physics/helper subfunctions out of endor_kehl_context.m into standalone kehl_* files. The context file is now a thin 134-line wrapper containing only the public context function, a trivial string/function-handle normaliser, and input validation. Physics code now lives in standalone helpers, including spin-data extraction, EPR axis setup, field/frequency-domain orientation selection, pulse-profile scaling, magnetic-quantum-number table construction, and line broadening.

Validation:

  • git diff --cached --check
  • static audit: endor_kehl_context.m has only three function definitions: endor_kehl_context, sequence_handle, and grumble
  • static audit: no local physics functions remain in endor_kehl_context.m
  • static audit: no spinSys, spinOps, kehl_spin_ops, expt, parameters.expt, context_experiment, or cp_axis regressions
  • compact all-sequence smoke: Mims, Davies, Spinlock, Tensor, TimeDomain, and CP finite (tmp/spinach_kehl_context_extract_allseq_20260508.mat)
  • permutation regression (tmp/spinach_kehl_context_extract_permutation_20260508.mat): nuclear permutation Mims/time max_abs 1.33e-15 / 8.33e-17; electron-moving permutation Mims/time max_abs 8.88e-16 / 8.33e-17

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Updated the Kehl ENDOR refactor with commit 004a498d6bcbcab38a2392a67e2c8daf0e627e28.

What changed:

  • Added experiments/esr_hyperfine/kehl_free_ham.m, which writes the selected Kehl ENDOR interactions into a temporary Spinach spin-system object and assembles the static Hilbert-space Hamiltonian via assume, dictum, hamiltonian, and orientation.
  • Replaced the repeated manual Hfree_p construction blocks in endor_kehl_mims.m, endor_kehl_davies.m, endor_kehl_spinlock.m, endor_kehl_tensor.m, endor_kehl_time.m, and endor_kehl_cp.m with calls to that helper.
  • Preserved the legacy special cases: separated-spin-system index mapping, CP offset updates, Bterm full/secular NQI behaviour, and the first-nucleus dipolar correction.

Validation:

  • git diff --cached --check passed before commit.
  • Kehl helper component probes reproduced the old manual Hamiltonian to about 1e-8 absolute error for both Bterm=true and Bterm=false, including the separated-spin-system branch.
  • Six-sequence smoke/regression run (mims, davies, spinlock, tensor, time, cp) completed with finite outputs.
  • Baseline-vs-refactor output comparison against commit 6221822 gave overall max absolute difference 1.7763568394002505e-15 and max relative difference 5.3448456598104155e-16.

I left the small RF-frame Hcorr additions as explicit operator sums for now; they are sweep-dependent frame corrections rather than the large static molecular Hamiltonian blocks that hamiltonian.m/orientation.m naturally assemble.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Implemented the Hamiltonian-basis refactor requested here.

Summary:

  • Added kehl_ham_basis.m: builds the Kehl static Hamiltonian through Spinach once as [I,Q]=hamiltonian(spin_system) and caches the process-local basis by spin system, interaction tensors, active flags, term map, and dipolar mode.
  • kehl_free_ham.m now assembles each orientation as I + orientation(Q,euler_angles) + 2*pi*v_off_S*Sz, with only the legacy dipolar correction left as cheap per-orientation tensor rotation/operator reuse.
  • kehl_ori_field.m / kehl_ori_freq.m now pass the selected Euler angles through EPR("euler_sel"); all Kehl sequence kernels pass those angles into kehl_free_ham.
  • Added kehl_operator_basis.m and switched the sequence kernels, kehl_rho0.m, and kehl_offsets.m to cached electron/nuclear operators, diagonal-frame relaxation operators, the default Lz state, identity, and RF sum operators instead of rebuilding operators/states repeatedly.
  • Fixed the helper primary function names in kehl_coupling_matrix.m and kehl_matrix_from_cell.m.

Validation run locally:

  • MATLAB parse marker over changed Kehl files: KEHL_HAM_REFACTOR_PARSE_OK (MATLAB still hits the known exit-time allocator crash afterwards on this host).
  • Direct Hamiltonian equivalence against the legacy kehl_free_ham implementation: all tested Frobenius differences <= 2.11e-7 absolute / ~machine precision relative for full, Bterm, dipolar, single-spin, and mapped-term cases.
  • Compact all-sequence smoke (mims, davies, spinlock, tensor, time, cp): all finite.
  • RF/operator-cache refactor vs immediately prior cached-Hamiltonian output: max absolute difference 0 over all smoke outputs.
  • Permutation checks: nuclear permutation MIMS max 1.33e-15, time max 7.81e-18; electron permutation MIMS max 6.12e-13, time max 8.67e-18.

Commit: bc6eb8b (Cache Kehl Hamiltonian orientation basis).

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Hardening pass pushed in c59238a.

Summary:

  • Standardised the Kehl ENDOR files to Spinach house style: headers, spacing, indentation, final grumble helpers, loop-index names, and removal of stale debug/comment fragments.
  • Consolidated sequence-local validation and kept the large sequence kernels explicit rather than over-abstracting them.
  • Reworked kehl_free_ham/kehl_ham_basis to cache Spinach Cartesian operators while assembling the legacy Kehl Hamiltonian form exactly for the selected orientation; added kehl_orient_terms for the orientation projections. This removes the previous risk of accidentally introducing B-term hyperfine components when Bterm=false.

Validation:

  • tmp/kehl_ham_basis_equivalence.m: KEHL_HAM_BASIS_EQUIV_OK.
  • Current Spinach API gates: example refactor, all-sequence direct-ops smoke, permutation-handle suite, and context-handle suite all reached KEHL_CURRENT_GATES_OK.
  • Original GitLab Kehl comparison with constants patched to current Spinach precision: KEHL_ORIGINAL_PRECISE_COMPARE_OK; max amplitude relative error about 2.2e-13, and coordinate differences at double-roundoff scale (7.45e-9 absolute on Hz axes).
  • Original GitLab Kehl comparison with stock Kehl constants shows only the expected constants-driven shifts; representative max relative amplitude shifts were about 7.4e-6 for Mims and 6.4e-6 for time-domain.
  • House-style checker over 43 Kehl files: 0 errors. MATLAB checkcode parse scan: 0 parse issues.

Note: local MATLAB on this host intermittently reports a known allocator crash after printing successful completion during some -batch exits; the successful validation markers above were emitted before that exit-time crash where it occurred.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Full-system Kehl ENDOR refactor pushed in 20a0558.

Summary:

  • Removed the ad-hoc reduced-spin-system machinery completely from the PR source.
  • Deleted experiments/esr_hyperfine/kehl_spin_system.m and experiments/esr_hyperfine/kehl_infer_epr_spins.m.
  • Removed n_spin_systems, operator_spin_system, spin_map, term_map, use_dipolar, and the associated reduced electron+nucleus branches from the Kehl ENDOR path.
  • Sequence kernels now use the incoming full Spinach spin_system for operators and propagators throughout.
  • Simplified kehl_operator_basis.m, kehl_ham_basis.m, kehl_free_ham.m, kehl_offsets.m, kehl_rho0.m, and the RF B-term helpers to full-system signatures.
  • Added kehl_offset_step.m so degenerate full-system offset manifolds produce finite propagation timesteps rather than infinities.

Validation run locally:

  • Static grep: no removed reduction identifiers remain in the PR source.
  • House-style checker over 42 current Kehl files: 0 errors.
  • MATLAB checkcode parse scan: 0 issues.
  • Compact two-spin all-sequence smoke: KEHL_FULL_SYSTEM_ALLSEQ_OK.
  • Five-spin full-system single-crystal all-sequence smoke: KEHL_FULL_SYSTEM_5SPIN_SMOKE_OK.
  • Five-spin frequency-domain powder Mims probe: KEHL_FULL_SYSTEM_FREQ_PROBE_OK.

I did not run the full unthrottled powder examples in this pass; after removing the reduced subsystem branch, the full state-space versions are expected to be much heavier. The reduced full-system gates above exercise the all-sequence and frequency-domain paths without reintroducing Kehl's own state-space reduction.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Spinach-relaxation refactor pushed in fcabac9.

Summary:

  • Removed Kehl-local relaxation-superoperator construction from the ENDOR path.
  • Deleted kehl_relax_t1.m, kehl_relax_t2.m, and kehl_rf_bterm_rlx.m.
  • Removed the local *_rlx sequence branches; the pulse-sequence files now dispatch to Liouville-space branches only when the context passes a non-empty R.
  • endor_kehl_context.m now builds R centrally when parameters.Relax=true, using Spinach relaxation.m with the t1_t2 theory.
  • Because Spinach t1_t2 is implemented in sphten-liouv, the context builds a complete spherical-tensor Liouville copy for relaxation, calls relaxation, then transforms the result to the Zeeman-Liouville representation used by the Kehl kernels via sphten2zeeman.
  • The Kehl rates now map into Spinach r1_rates / r2_rates: electron T1e=4e-3 s, T2e=5e-6 s; nuclear T1n=Inf, T2n=3e-3 s, matching the original Kehl example defaults. Infinite times are mapped to zero rates.
  • kehl_rf_bterm.m now handles both Hilbert-space and relaxation-space RF B-term propagation through the same helper, using the supplied R when present.

Validation run locally:

  • Static grep: no kehl_relax_t*, _rlx, kehl_rf_bterm_rlx, RT1*, RT2*, T2dq*, or old Rho-relaxation fields remain in the PR source.
  • House-style checker over 39 current Kehl files: 0 errors.
  • MATLAB checkcode parse scan: 0 issues.
  • Non-relaxed compact all-sequence smoke: KEHL_RELAX_REFACTOR_NONREL_ALLSEQ_OK.
  • Relaxed compact all-sequence smoke using Spinach R: KEHL_SPINACH_RELAX_ALLSEQ_OK.
  • Five-spin relaxed Mims smoke using Spinach R: KEHL_SPINACH_RELAX_5SPIN_MIMS_OK.

I did not rerun the full unthrottled powder examples in this slice; the targeted gates above cover the changed relaxation path without launching the heavy full-state-space powder jobs.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Native Zeeman-Liouville refactor pushed in ee1e8b5.

Summary:

  • Ported the Kehl ENDOR pulse-sequence path to native zeeman-liouv operation.
  • endor_kehl_context.m now switches the pulse-sequence spin system to zeeman-liouv; the Kehl examples now request zeeman-liouv explicitly.
  • Removed the duplicated Hilbert-space non-relaxed sequence branches and kept one Liouville-space branch per sequence.
  • Removed manual hilb2liouv(...), state-vector stretching, reshape(...), and trace-detection logic from the sequence kernels.
  • operator.m / state.m now provide Liouville-space superoperators and state vectors directly for sequence propagation and detection.
  • Deleted kehl_diag_ops.m; the old diagonal-frame Hilbert relaxation support is no longer used.
  • Reworked kehl_operator_basis.m, kehl_ham_basis.m, kehl_free_ham.m, kehl_rho0.m, and kehl_rf_bterm.m around native Zeeman-Liouville superoperators and state vectors.

Implementation note:

  • EPR orientation/transition selection still uses a temporary Hilbert-space copy internally because that path diagonalises an EPR transition Hamiltonian to select resonances. The pulse-sequence propagation path itself is now native zeeman-liouv and no longer performs manual Hilbert-to-Liouville conversions.

Validation run locally:

  • Static grep: no manual hilb2liouv, reshape/trace detection, _liouv branch names, kehl_diag_ops, or old diagonal-relaxation variables remain in the Kehl PR source.
  • House-style checker over 38 current Kehl files: 0 errors.
  • MATLAB checkcode parse scan: 0 issues.
  • Native Zeeman-Liouville compact all-sequence smoke: KEHL_ZEEMAN_LIOUV_ALLSEQ_OK.
  • Relaxed compact all-sequence smoke using Spinach R: KEHL_SPINACH_RELAX_ALLSEQ_OK.
  • Five-spin full-system all-sequence smoke: KEHL_FULL_SYSTEM_5SPIN_SMOKE_OK.
  • Five-spin frequency-domain powder Mims probe: KEHL_FULL_SYSTEM_FREQ_PROBE_OK.

I did not rerun full unthrottled powder examples in this slice; the reduced full-system gates above cover the changed Liouville execution path without launching the heavy full powder jobs.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Kehl ENDOR Liouville EPR-selection refactor pushed in 433b63c (Port Kehl EPR selection to Liouville space).

What changed:

  • Removed the temporary Hilbert-space spin_system copy and the extra basis() call from endor_kehl_context.m; the context now requires and uses the incoming zeeman-liouv system throughout.
  • Added kehl_epr_transitions.m, which solves the EPR field/frequency transition-selection problem in Liouville space using the existing Spinach operator() / state() machinery.
  • Rewired kehl_ori_field.m, kehl_ori_freq.m, and kehl_offsets.m to use the Liouville transition selector instead of Hilbert-space transition Hamiltonians.
  • Kept relaxation on the same Zeeman-Liouville system via Spinach lindblad rates, avoiding a separate spherical-tensor/basis rebuild path.
  • Hardened cache keys in kehl_operator_basis.m / kehl_ham_basis.m for complete zeeman-liouv bases that do not carry bas.basis.

Validation:

  • Static audit: no create(), basis(), hilb2liouv, zeeman-hilb, sphten2zeeman, kehl_spin_system, or operator_spin_system references remain in the Kehl experiment path; the two top-level Kehl examples each have exactly one create() and one basis() call and request zeeman-liouv.
  • House-style checker over the 7 changed working-tree files: 0 errors.
  • MATLAB checkcode over the 7 changed Kehl files: 0 issues.
  • Five-spin frequency-domain powder Mims Liouville transition smoke: KEHL_LIOUV_TRANSITION_SMOKE_OK.
  • Compact two-spin all-sequence smoke, non-relaxed and relaxed, for Mims/Davies/Spinlock/Tensor/Time/CP: KEHL_LIOUV_ALLSEQ_SMOKE_OK.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Kehl operator-basis removal update pushed in 57fbfc78cf3b3be4f53a731d75479585b8a736ad.

Changes:

  • Deleted experiments/esr_hyperfine/kehl_operator_basis.m.
  • Deleted the associated Hamiltonian-basis cache experiments/esr_hyperfine/kehl_ham_basis.m.
  • Replaced all Kehl ops.* / ham.* cached access with direct operator(...) / state(...) requests in the sequence kernels, kehl_rho0.m, and kehl_free_ham.m.
  • Rechecked that no Kehl helper/cache references remain, and that the Kehl path has no zeeman-hilb, hilb2liouv, extra create() or extra basis() path for this machinery.

Validation run:

  • check_kehl_house_style_current.py -> checked 37 files, errors 0
  • MATLAB checkcode on the touched Kehl files -> KEHL_CHECKCODE_DONE issues=19 nonparfor=0 (remaining messages are existing parfor broadcast warnings)
  • Static search for kehl_operator_basis|kehl_ham_basis|ops.|ham.|operator_cache|basis_cache|operator_key|ham_key under experiments/esr_hyperfine -> none
  • Static search for create(, basis(, hilb2liouv, zeeman-hilb, sphten2zeeman, kehl_spin_system, operator_spin_system in Kehl files -> none
  • MATLAB all-sequence Liouville smoke -> KEHL_LIOUV_ALLSEQ_SMOKE_OK
  • MATLAB transition selector smoke -> KEHL_LIOUV_TRANSITION_SMOKE_OK

Confession / gates not run:

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Kehl unused-signature cleanup pushed in 6d713d2422dc739f6889de0bcdbdcf91b377b1fb.

Changes:

  • Re-inspected Kehl function signatures with grumble-only argument use ignored.
  • Removed unused K from the Kehl pulse-sequence signatures and context calls.
  • Removed unused spin_system from kehl_endor_axis(...) and kehl_prep_epr(...), plus their callers.
  • Reduced kehl_rho0(...) to the two values it actually uses: spin_system and parameters.
  • Removed now-dead local selector/unpack variables that only existed to feed the old kehl_rho0(...) call.

Validation run:

  • Custom unused-signature pass over Kehl files, ignoring grumble(...) calls as use -> no remaining findings.
  • Static stale-reference pass for H,R,K, K, old kehl_endor_axis(spin_system,...), old kehl_prep_epr(spin_system,...), and old long kehl_rho0(...) calls -> none.
  • git diff --check -> clean.
  • check_kehl_house_style_current.py -> checked 37 files, errors 0.
  • MATLAB checkcode on touched Kehl files -> KEHL_CHECKCODE_DONE issues=23 nonparfor=0 (remaining messages are existing parfor broadcast warnings only).
  • MATLAB all-sequence Liouville smoke -> KEHL_LIOUV_ALLSEQ_SMOKE_OK.
  • MATLAB transition selector smoke -> KEHL_LIOUV_TRANSITION_SMOKE_OK.

Confession / gates not run:

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Kehl initial-state helper removal pushed in e3d2209956707d3e3865b0384a9639834cddd5c8.

Changes:

  • Replaced every kehl_rho0(spin_system,parameters) call with the direct Spinach call:
    rho0=-state(spin_system,'Lz',parameters.electron_spin_idx);
  • Deleted experiments/esr_hyperfine/kehl_rho0.m.

Validation run:

  • Static search for kehl_rho0 under Kehl experiment/example files -> none.
  • Static search confirmed the five sequence kernels now assign rho0 directly through state(...).
  • git diff --check -> clean.
  • check_kehl_house_style_current.py -> checked 36 files, errors 0.
  • Custom unused-signature pass over Kehl files, ignoring grumble(...) calls -> no findings.
  • MATLAB checkcode on the five touched sequence files -> KEHL_CHECKCODE_DONE issues=18 nonparfor=0 (remaining messages are existing parfor broadcast warnings only).
  • MATLAB all-sequence Liouville smoke -> KEHL_LIOUV_ALLSEQ_SMOKE_OK.
  • MATLAB transition selector smoke -> KEHL_LIOUV_TRANSITION_SMOKE_OK.

Confession / gates not run:

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Implemented the Kehl unit-normalisation pass requested in Slack.

Commit: 5ef695054672ea06ea9abf6b54c421a112951a62 (Normalise Kehl ENDOR units)

Summary:

  • Removed the central legacy unit-migration block from kehl_context_fields.m; the context now expects caller-supplied SI/angular units directly.
  • Renamed Kehl public parameter fields away from unit-suffixed legacy names: mw_freq, static_field, field_step, endor_res, endor_range, pulse_times, epr_freq_*, cp_start, cp_range, rf_flip_angle, and rf_nutations.
  • Propagated rad/s conventions through EPR transition selection, offsets, ENDOR axes, RF nutation fields, CP offsets, B-term RF propagation, line broadening, and examples.
  • Kept chemical shifts as ppm in the context and converted them to angular-frequency offsets only at Hamiltonian assembly.
  • Removed CONST1, pulse_width, *_hz, *_mhz, *_ghz, *_ns, field_t, and pulse_times_s usage from the Kehl context/sequences.
  • Updated the powder examples to pass angular frequencies and seconds directly.

Validation:

  • git diff --check passed.
  • MATLAB checkcode sweep over the touched Kehl files completed; remaining messages are pre-existing-style parfor broadcast/preallocation warnings, not syntax errors.
  • MATLAB all-sequence smoke passed for Mims, Davies, spinlock, tensor, time-domain, and CP ENDOR with relaxation both off and on: KEHL_LIOUV_ALLSEQ_SMOKE_OK.
  • MATLAB frequency-domain Liouville transition smoke passed: KEHL_LIOUV_TRANSITION_SMOKE_OK.
  • Stale legacy-unit name scan found no remaining Kehl references to *_hz, *_mhz, *_ghz, *_ns, field_t, pulse_times_s, rf_nutation_freqs, CONST1, or pulse_width except the substring field_tol in a tolerance helper name.

@IlyaKuprov

Copy link
Copy Markdown
Owner Author

Implemented the follow-up Kehl ENDOR refactor in a91344f (Refactor Kehl sequence setup and relaxation).

Summary:

  • removed the context-level sequence preflight call; endor_kehl_context() now calls the pulse sequence exactly once
  • moved sequence-specific derived-parameter setup into the normal sequence call path
  • added kehl_sequence_context() so EPR selection happens after the sequence has defined its own excitation parameters
  • removed parameters.Relax, parameters.T1e, parameters.T2e, parameters.T1n, parameters.T2n, and the Kehl-local relaxation-rate construction path
  • examples now request canonical Spinach t1_t2 relaxation with inter.r1_rates / inter.r2_rates next to the spin-system specification
  • Kehl context requests R from Spinach exactly once and then passes it through unchanged to the sequence code

Validation:

  • MATLAB checkcode sweep on the touched Kehl files: only pre-existing parfor broadcast warnings
  • KEHL_LIOUV_ALLSEQ_SMOKE_OK
  • KEHL_LIOUV_TRANSITION_SMOKE_OK
  • Kehl house-style gate: checked 37 files, errors 0
  • static grep confirms no parameters.T1*, parameters.T2*, parameters.Relax, legacy sequence parameters mode, or local Lindblad-rate construction remains in the Kehl/example paths

@IlyaKuprov IlyaKuprov closed this May 9, 2026
@IlyaKuprov IlyaKuprov deleted the talos/kehl-endor-spinach branch May 9, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant