Skip to content

Translation symmetry: TranslationGroup + orbit-representative Pauli-sum merging - #180

Open
AlexSchuckert wants to merge 7 commits into
mainfrom
split/1-translation-symmetry
Open

AlexSchuckert wants to merge 7 commits into
mainfrom
split/1-translation-symmetry

Conversation

@AlexSchuckert

Copy link
Copy Markdown
Collaborator

PR 1 of 4 splitting #178 into reviewable chunks (this → CTPP core → symmetry-merged evolution → ledgers). Full development history: branch continuous-time-pauli-propagation.

Adds ppvm_pauli_sum::symmetry (pure Rust, no new dependencies):

  • TranslationGroup: finite abelian permutation groups — 1D chains, 2D/3D tori, multi-leg ladders, or arbitrary generator lists — with lex-min orbit canonicalization, shift counters, and momentum-sector characters χ_k(g).
  • k = 0 merging: canonicalize_pauli_sum (Vec-pair form used by the upcoming adaptive evolution) and symmetry_merge_pauli_sum (PauliSum form). Preserves all G-invariant expectation values for G-commuting dynamics (Theorem 1 of Teng, Chang, Rudolph & Holmes, arXiv:2512.12094).
  • k ≠ 0: canonicalize_pauli_sum_complex (character-weighted projection with 1/|G| normalization) and check_momentum_sector to validate inputs before projecting (silent projection discards physics).

Tests: canonicalization/orbit properties on chains, tori, ladders; merge correctness; momentum-eigenstate round trips; a Trotter end-to-end check that per-step merging matches merge-at-end in the dt → 0 limit.

🤖 Generated with Claude Code

…erging

TranslationGroup (finite abelian permutation groups: 1D chains, 2D/3D
tori, multi-leg ladders, arbitrary generators) with lex-min orbit
canonicalization, shift counters, and momentum-sector characters.
Pauli-sum merging in both sectors: canonicalize_pauli_sum /
symmetry_merge_pauli_sum (k=0, real coefficients) and
canonicalize_pauli_sum_complex (k≠0, character-weighted projection,
1/|G| normalization), plus check_momentum_sector to validate an input
before projecting. Following Teng, Chang, Rudolph & Holmes
(arXiv:2512.12094).

Split 1/4 of the CTPP work (see PR body for the stack); full
development history on branch continuous-time-pauli-propagation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://QuEraComputing.github.io/ppvm/pr-preview/pr-180/

Built to branch gh-pages at 2026-09-28 13:57 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Rust-only symmetry layer to ppvm-pauli-sum for translation-invariant (and momentum-resolved) Pauli-sum canonicalization/merging, intended to support upcoming continuous-time Pauli propagation work (#178 split).

Changes:

  • Introduces ppvm_pauli_sum::symmetry with TranslationGroup plus orbit-canonicalization utilities.
  • Adds real (k=0) and complex/momentum-sector merging/canonicalization helpers, along with momentum-sector validation.
  • Exposes the new module from ppvm-pauli-sum’s public API and adds a comprehensive test suite for canonicalization/merge behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
crates/ppvm-pauli-sum/src/symmetry.rs New symmetry module: translation group model, orbit canonicalization, (momentum) merging, and tests.
crates/ppvm-pauli-sum/src/lib.rs Exposes the new symmetry module publicly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 16, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.

Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
@david-pl

david-pl commented Jul 20, 2026 •

Copy link
Copy Markdown
Collaborator

@AlexSchuckert I think this is a pretty good boiled down PR here. I just saw that the single file here was pretty large.

codex suggested some semantic split, see below.

Codex output:

symmetry.rs is approaching 1,000 lines, and I think there is a worthwhile semantic split here beyond simply moving code around. I would suggest preserving the existing ppvm_pauli_sum::symmetry::* API through re-exports, but organizing the implementation as:

symmetry/
├── mod.rs       # module docs and public re-exports
├── group.rs     # TranslationGroup, constructors, orbit/canonicalization
├── merge.rs     # k=0 Vec/PauliSum orbit merging
├── momentum.rs  # characters, complex projection, sector validation/error
└── tests.rs     # current tests

The group versus reduction boundary is already quite clear in the current file. I think momentum also merits its own module because canonicalize_pauli_sum_complex is a normalized sector projection, not merely the complex-coefficient equivalent of the plain merge; colocating it with character, check_momentum_sector, and SectorCheckError would make that semantic distinction easier to maintain.

I would not split the individual lattice constructors into separate files—the 1D/2D/3D/ladder constructors are cohesive parts of TranslationGroup. Also, about a third of the current file is tests, so moving those out alone would make the production code substantially easier to scan.

Copilot AI review requested due to automatic review settings July 20, 2026 10:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (6)

crates/ppvm-pauli-sum/src/symmetry.rs:101

  • from_generators accepts orders that can be zero. That leads to division/modulo by zero later (e.g. character divides by orders[g], and mixed-radix decoding uses % o). Add an upfront order > 0 validation per generator.
    pub fn from_generators(n_qubits: usize, perms: Vec<Vec<u32>>, orders: Vec<u32>) -> Self {
        assert_eq!(perms.len(), orders.len(), "perms and orders must match");
        for (g, perm) in perms.iter().enumerate() {

crates/ppvm-pauli-sum/src/symmetry.rs:219

  • TranslationGroup::order() multiplies generator orders with product(), which can silently overflow usize in release builds. That can make order() wrong and can even feed 1.0 / order in momentum merging. Use checked multiplication and fail fast on overflow.
    /// Total group order: `Π orders[g]`.
    pub fn order(&self) -> usize {
        self.orders.iter().map(|&o| o as usize).product()
    }

crates/ppvm-pauli-sum/src/symmetry.rs:133

  • The convenience constructors can panic on zero dimensions (e.g. chain_1d(0) does (q + 1) % n). Add explicit assert!(...) guards so invalid sizes fail with a clear message.
    /// 1D chain of `n` sites with periodic boundary conditions.
    /// Single generator: cyclic shift by one site.
    pub fn chain_1d(n: usize) -> Self {
        let perm: Vec<u32> = (0..n).map(|q| ((q + 1) % n) as u32).collect();
        Self::from_generators(n, vec![perm], vec![n as u32])
    }

crates/ppvm-pauli-sum/src/symmetry.rs:152

  • torus_2d(0, ly) / torus_2d(lx, 0) will panic due to modulo-by-zero in the permutation builders. Add explicit dimension assertions so the API fails fast with a clear message.
    /// 2D `lx × ly` torus, qubit at `(i, j)` indexed as `j*lx + i`.
    /// Two generators: x-shift (i → i+1 mod lx) and y-shift (j → j+1 mod ly).
    pub fn torus_2d(lx: usize, ly: usize) -> Self {
        let n = lx * ly;
        let perm_x: Vec<u32> = (0..n)
            .map(|q| {
                let (i, j) = (q % lx, q / lx);
                (j * lx + (i + 1) % lx) as u32
            })
            .collect();
        let perm_y: Vec<u32> = (0..n)
            .map(|q| {
                let (i, j) = (q % lx, q / lx);
                (((j + 1) % ly) * lx + i) as u32
            })
            .collect();
        Self::from_generators(n, vec![perm_x, perm_y], vec![lx as u32, ly as u32])
    }

crates/ppvm-pauli-sum/src/symmetry.rs:187

  • torus_3d has the same modulo-by-zero hazard as torus_2d when any dimension is zero. Add explicit assertions for lx, ly, and lz > 0.
    /// 3D `lx × ly × lz` torus, qubit at `(i, j, k)` indexed as
    /// `k*lx*ly + j*lx + i`.
    pub fn torus_3d(lx: usize, ly: usize, lz: usize) -> Self {
        let n = lx * ly * lz;
        let perm_x: Vec<u32> = (0..n)
            .map(|q| {
                let i = q % lx;
                let j = (q / lx) % ly;
                let k = q / (lx * ly);
                (k * lx * ly + j * lx + (i + 1) % lx) as u32
            })
            .collect();
        let perm_y: Vec<u32> = (0..n)
            .map(|q| {
                let i = q % lx;
                let j = (q / lx) % ly;
                let k = q / (lx * ly);
                (k * lx * ly + ((j + 1) % ly) * lx + i) as u32
            })
            .collect();
        let perm_z: Vec<u32> = (0..n)
            .map(|q| {
                let i = q % lx;
                let j = (q / lx) % ly;
                let k = q / (lx * ly);
                (((k + 1) % lz) * lx * ly + j * lx + i) as u32
            })
            .collect();
        Self::from_generators(
            n,
            vec![perm_x, perm_y, perm_z],
            vec![lx as u32, ly as u32, lz as u32],
        )
    }

crates/ppvm-pauli-sum/src/symmetry.rs:204

  • ladder(l, n_legs) can panic when l == 0 due to (j + 1) % l, and n_legs == 0 produces a degenerate 0-qubit group. Add input validation for clarity and to avoid modulo-by-zero.
    /// Multi-leg ladder: `l` sites along the chain × `n_legs` legs.
    /// Single generator: cyclic shift along the chain direction (all
    /// legs simultaneously). Qubit at `(leg, j)` indexed as
    /// `leg * l + j`. No translation along the leg axis (legs are
    /// distinguished).
    pub fn ladder(l: usize, n_legs: usize) -> Self {
        let n = l * n_legs;
        let perm: Vec<u32> = (0..n)
            .map(|q| {
                let leg = q / l;
                let j = q % l;
                (leg * l + (j + 1) % l) as u32
            })
            .collect();
        Self::from_generators(n, vec![perm], vec![l as u32])
    }

Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
Comment thread crates/ppvm-pauli-sum/src/symmetry.rs Outdated
This implements the split suggested in
#180 (comment)
and fixes some issues that surfaced during the split or from copilot
findings.

Should be merged before #181 

cc @AlexSchuckert

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings September 1, 2026 14:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Review details
  • Files reviewed: 14/14 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +321 to +325
} => write!(
f,
"input not in target momentum sector: orbit rep {rep} expected c={expected:?}, \
but orbit member {offending_pauli} (shift {shift:?}) has c={actual:?}"
),
Keep main's #214 clippy fixes (arg structs, type annotations) over the

Co-authored-by: Cursor <cursoragent@cursor.com>
#193 #[allow] workarounds on the five overlapping tableau files.
Copilot AI review requested due to automatic review settings September 1, 2026 14:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

crates/ppvm-pauli-sum/src/symmetry/momentum.rs:325

  • SectorCheckError::CoefficientMismatch's Display message is misleading: it says the orbit representative "expected c=…" but expected is the expected coefficient for the offending orbit member (after applying the sector relation). This makes debugging confusing.
            Self::CoefficientMismatch {
                rep,
                offending_pauli,
                expected,
                actual,
                shift,
            } => write!(
                f,
                "input not in target momentum sector: orbit rep {rep} expected c={expected:?}, \
                 but orbit member {offending_pauli} (shift {shift:?}) has c={actual:?}"
            ),
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +158 to +176
/// Returns `Ok(())` on pass; `Err(SectorCheckError)` on fail with the
/// offending orbit-rep, expected coefficient, and actual coefficient.
///
/// Use this on a user-supplied initial state before feeding it to a
/// phase-aware merging pipeline — silently projecting a wrongly-typed
/// input throws away meaningful physics.
pub fn check_momentum_sector<A, S, const R: bool>(
basis: &[PauliWord<A, S, R>],
coeffs: &[Complex<f64>],
group: &TranslationGroup,
k_modes: &[i32],
tol: f64,
) -> Result<(), SectorCheckError<A, S, R>>
where
A: PauliStorage,
S: BuildHasher + Clone + Default + HashFinalize,
{
assert_eq!(basis.len(), coeffs.len());
assert_eq!(k_modes.len(), group.n_generators());
david-pl added a commit that referenced this pull request Sep 2, 2026
## Summary
- Same kind of cleanup as #193 did for #180: split the 1300-line
`ppvm-lindblad` `lib.rs` / `LindbladSpec` impl into `word`, `algebra`,
`spec`, `basis`, `step`, and `tests`, keeping the public
`ppvm_lindblad::*` API.
- Collapses the timed/untimed `pc_step` copy, demotes `ppvm-pauli-sum`
to a dev-dependency, drops unused `approx`, strips 182-forward
`orbit_rep` doc links, and forwards `admit_basis` / `tau_add` on the
Python string `pc_step`.
- **Base is #181 (`split/2-ctpp-core`) on purpose.** #193 replaces #180
and is not an ancestor of #181; retargeting this at #193 now would mix
the whole CTPP crate into the diff. After #193 merges and #181 is
rebased onto it, this PR rebases with #181.

## Test plan
- [x] `cargo test -p ppvm-lindblad` (6 passed)
- [x] `cargo clippy -p ppvm-lindblad --all-targets -- -D warnings`
- [x] `cargo check -p ppvm-python-native`
- [ ] Rebase onto #181 after #181 is retargeted onto #193


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
david-pl added a commit that referenced this pull request Sep 28, 2026
Adds the general GKSL form
`D*(O) = Σ_nm K_nm (A_n† O A_m − ½{A_n† A_m, O})` to `ppvm-lindblad`,
alongside the existing jump-operator form, with Python bindings, tests
and benchmarks.

Replaces #179, which was stacked on the pre-split #178 and could not be
reopened usefully: its branch had never been rebased, so its diff
against
the split stack was 160 files and reverted work that #180–#182 had since
landed. This branch is the same feature ported onto `split/3` and
cleaned
up; the original commits are preserved on `kossakowski-dissipator`.

Stacked on #182 — **review after that one settles**, see the note below.

## What's here

| Commit | |
|---|---|
| `7b52cf6b` | core dissipator: `kossakowski.rs`, `PairShape`,
`add_kossakowski` |
| `e6266b52` | superradiance + dipolar-relaxation benchmarks, profiling
example |
| `dc8c6234` | `Lindbladian(..., kossakowski=(ops, K))` Python surface |
| `b52d22cc` | dense-reference and orbit-representative tests |

Conjugate pairs `(n,m)`/`(m,n)` are folded into a single upper-triangle
entry — both sandwiches produce the same output words with conjugate
phase and the action keeps only the real part — halving the pair count.
`PairShape` distinguishes the diagonal entry from a folded off-diagonal
one and carries the term list only the folded case needs, so the
off-diagonal-only path is unreachable on a diagonal pair.

Against the equivalent eigenmode-jump decomposition on a superradiant
chain, the pair-matrix path costs 27.7 ms vs 103 ms at n=10 and 96 ms vs
1.71 s at n=30: the per-string action scales with the number of nonzero
`K_nm` entries instead of carrying an extra factor of `M`.

## Differences from #179

Ported into the `spec`/`basis`/`step`/`algebra` layout `split/2` and
`split/3` introduced, rather than appended to `lib.rs`:

- New `kossakowski.rs` module. `spec.rs` grows 65 lines, not 520.
- `add_kossakowski` (147 lines) split into `validate_k` / `parse_ops` /
  `compile_pair` / `compile`; the ~90-line action arm became
  `Pair::accumulate` + the one-sided and both-sided halves.
- `PairShape` enum replaces an `off_diag` bool that drove three separate
  branches in the hot path. Benchmarked both shapes: the deltas
  (+2.4% / −1.3% / +2.2%, n30 p=0.17) are smaller than the drift on the
  `eigenmode_*` control benchmarks (+3.4% to +6.6%) in the same run, so
  this is noise, not a regression.
- **7 clippy `allow`s removed, 0 added.** `split/3` had none in
  `ppvm-lindblad`; three `needless_range_loop` became iterator loops and
  four `type_complexity` became two named aliases.
- `precompute_ldagger_l` generalized to `precompute_adag_b(a, b)`, moved
  to `algebra.rs` beside `PauliTerm` and a named `COEFF_DROP_TOL` (was a
  `1e-14` literal in five places).
- Dropped a dead `let _ = i;`, and row-length errors now report which
row.
- `support_mask` lifted out of the pair loop, where it was being rebuilt
  per iteration.
- Deleted the `k_ops` field — `LindbladSpec` state that never outlived
  the constructor.
- Did not port the original's `:func:`/`:meth:` docstrings; `split/3`
  had already converted those to backticks per AGENTS.md.

## One thing to check

`test_orbit_dissipative.py` failed on arrival. `d8313e0e` on `split/3`
("orbit-rep evolution on stabilized orbits") made rep coefficients
member
coefficients on *every* orbit, where stabilized orbits were previously
divided by `|G|`; the tests encoded the old behaviour and `c_I` came out
exactly 6× the asserted value on a 6-site ring.

I rewrote the reconstruction as `Σ |orbit(rep)| · c_rep` and updated the
identity-bookkeeping expectation to `coeff_I`. The new values match the
closed form exactly. **If `d8313e0e` was not meant to change that
convention, the bug is there and not in these tests** — worth confirming
while reviewing #182.

## Verification

- `cargo test -p ppvm-lindblad` — 8 passed
- `pytest test/lindblad/` — 35 passed, 1 skipped
- `cargo clippy -p ppvm-lindblad --all-targets` — zero warnings
- pre-commit green on all four commits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Alexander Schuckert <schuckertalexander@gmail.com>
…olution (#181)

**PR 2 of 4** splitting #178 (stacked on #180). Full history: branch
[`continuous-time-pauli-propagation`](https://github.com/QuEraComputing/ppvm/tree/continuous-time-pauli-propagation).

The core CTPP method: direct Heisenberg-picture evolution `O ←
exp(dt·L*) O` of Pauli observables under a Lindbladian, on an adaptively
truncated Pauli-string basis. Exact in `dt` within the working basis —
no Trotter splitting; the only approximation is truncation.

- **`ppvm-lindblad` crate**: `LindbladSpec` precompiles Hermitian-Pauli
jumps (fast diagonal path) and general complex Pauli-sum jumps
(σ±/amplitude damping via a precomputed L†L expansion). `pc_step` =
two-hop leakage enrichment + predictor/corrector matrix-free exponential
(cached-CSC columns fed to `quspin-expm`, MIT-licensed pin;
Al-Mohy–Higham Taylor partition selection with a truncation-matched
relaxed table). Truncation policy in one `PcStepConfig`: `max_basis`
rank cap (primary dial), `admit_basis` working-set bound (displacement
truncation), `drop_tol` prune, `tau_add` admission filter.
- **Python**: `ppvm.Lindbladian` with a numpy-array hot path plus
string-keyed convenience API; `mimalloc` as the extension's global
allocator (~50% peak-RSS reduction on the allocation-heavy adaptive
paths); two jupytext demos.
- **Tests**: action/generator/leakage against dense-Liouvillian and
Pauli-table references; adaptive-evolution convergence to a closed
bilinear solution; `pc_step` pinned against `numpy.linalg.eig`; O(dt³)
per-step scaling of the predictor-corrector.

Benchmarks backing the design choices (190× vs Trotter at L=21 matched
accuracy; external cross-validation vs Begušić & Chan, PRX Quantum 6,
020302 at L=41): ledgers arrive in PR 4 of the stack.

The translation-symmetric (momentum-sector) variant follows in PR 3.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: David Plankensteiner <david-pl@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: David Plankensteiner <dplankensteiner@quera.com>
Copilot AI review requested due to automatic review settings September 28, 2026 13:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let terms = self.compute_action_terms(p, s1, s2, lm);
let mut out = Vec::with_capacity(terms.len());
for (w, v) in terms.iter() {
let h = word_hash(w);
Comment on lines +230 to +231
let p_slice = p.as_slice()?;
let p_word = word_from_codes::<C>(p_slice).map_err(map_err)?;
Comment on lines +122 to +123
let inner = core_sym::TranslationGroup::try_from_generators(n_qubits, perms, orders)
.map_err(|e| PyValueError::new_err(e.to_string()))?;
Comment on lines +47 to +49
def _basis(basis_arr: npt.ArrayLike) -> np.ndarray:
return np.ascontiguousarray(basis_arr, dtype=np.uint8)

Comment on lines +127 to +132
// A non-Hermitian K is not a valid GKSL pair matrix and would produce an
// action that does not preserve Hermiticity.
let tol = HERMITICITY_TOL * max_abs.max(1.0);
for (n, row_n) in k.iter().enumerate() {
for (m, k_nm) in row_n.iter().enumerate().skip(n) {
if (k_nm - k[m][n].conj()).norm() > tol {
Comment on lines +38 to +41
let mut mags: Vec<f64> = merged.values().map(|v| v.mag()).collect();
let k = room.min(mags.len() - 1);
let cutoff = nth_largest(&mut mags, k);
merged.retain(|_, v| v.mag() >= cutoff);
Comment thread Cargo.toml
"crates/ppvm-pauli-word",
"crates/ppvm-pauli-sum",
"crates/ppvm-sym",
"crates/ppvm-lindblad",
Copilot AI review requested due to automatic review settings September 28, 2026 13:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Comment on lines +441 to +442
let exact = permutation_order(&perms[generator], generator);
if declared != exact {
Comment on lines +457 to +460
let order = checked_group_order(&orders);
let phase_modulus = orders.iter().fold(1usize, |acc, &value| {
checked_lcm(acc, value as usize, "character phase modulus")
});
Comment on lines +150 to +154
assert_eq!(
table.len(),
self.order(),
"character table does not belong to this group"
);
Comment on lines +92 to +94
retain_in_place(basis, coeffs, |w, c| {
protected_set.contains(w) || c.mag() >= cutoff
});
Comment on lines +61 to +65
# Optional: only the `demo/` scripts use it (`expm_multiply` for the reference
# matrix exponential). Runtime ppvm and the test suite have no scipy dep.
demo = [
"scipy>=1.13",
]

This branch has not been deployed

No deployments
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.

3 participants