Skip to content

handle heavy ions#2353

Draft
kanitsch wants to merge 3 commits intomasterfrom
2079-handle-heavy-ions-with-varying-a-z
Draft

handle heavy ions#2353
kanitsch wants to merge 3 commits intomasterfrom
2079-handle-heavy-ions-with-varying-a-z

Conversation

@kanitsch
Copy link
Copy Markdown

No description provided.

@kanitsch kanitsch linked an issue Mar 30, 2026 that may be closed by this pull request
@kanitsch kanitsch marked this pull request as draft March 30, 2026 17:58
@grzanka
Copy link
Copy Markdown
Contributor

grzanka commented Apr 7, 2026

Take a look at following design: https://gist.github.com/grzanka/1c63ccb5921756bd0f0ea93642af627c
does it help ?

# Feature: Unified Particle Type Selector

## User Story
As a researcher configuring a particle simulation, I want a single "Particle type" dropdown/search field that lets me find any supported particle — including all isotopes (stable and unstable) of all elements — by typing natural-language aliases (e.g. "carbon", "C-12", "e-", "proton"), so that I can quickly select my projectile without needing to understand internal ID systems or the distinction between "heavy ions" and other particle types.

---

## Cross-Repo Architecture: UI ↔ Converter Contract

### Current JSON wire format (UI → Converter)

The UI serialises particle data to JSON. Three converter backends consume it:

| Converter | File | How it reads `particle` |
|---|---|---|
| **SHIELD-HIT12A** | `converter/shieldhit/parser.py` `_parse_beam()` | `particle.id` → `JPART0`, `particle.a` → `HIPROJ a`, `particle.z` → `HIPROJ z`. If `id == 25`, writes `HIPROJ` heavy-ion line. |
| **FLUKA** | `converter/fluka/helper_parsers/beam_parser.py` `parse_beam()` | `particle.id` → looks up `PARTICLE_DICT` for FLUKA name. If name is `"HEAVYION"` (id 25), reads `particle.a` and `particle.z` for `HI-PROPE` card. |
| **Geant4** | `converter/geant4/Geant4MacroGenerator.py` `_append_initialization()` | `particle.id` → if `== 25`, emits `/gps/particle ion` + `/gps/ion {z} {a} 0 0`. Otherwise maps to Geant4 particle name. |

### Wire format — UNCHANGED

```jsonc
// beam.particle in project JSON
{
  "id": 25,            // int — SHIELD-HIT ID (25 = generic heavy ion)
  "name": "Carbon-12", // string — human-readable, now resolved isotope name
  "a": 12,             // int — mass number
  "z": 6               // int — atomic/charge number
}

Key rule: every heavy ion gets id: 25 on the wire. Light particles keep their dedicated IDs (1, 2, 3, …, 24, 26). The converter already relies solely on id + a + z; the name field is only used for comments. No converter changes are required for the beam.

Scoring filters — small converter fix needed

converter/shieldhit/parser.py parse_scoring_filter() maps particle.idPARTICLE_DICT[id]["filter"] rules. For heavy-ion filters the converter must also be able to emit Z == z, A == a rules. Currently only the id lookup path exists.

  • Converter change needed: parse_scoring_filter() should, when particle.id == 25, emit [("Z", "==", particle.z), ("A", "==", particle.a)] instead of looking up PARTICLE_DICT[25]["filter"] (which doesn't have a "filter" key today). This is a ~5-line change.
  • Geant4 change needed: Geant4MacroGenerator._append_quantity() should handle id == 25 in filter particle lists using ion-syntax.

Design decision: catalogue in UI, PARTICLE_DICT in converter

The UI owns the isotope catalogue (display names, aliases, abundance, sortPriority). The converter owns the per-simulator PARTICLE_DICT mappings. They communicate via the existing JSON wire format. No need to move the catalogue to the converter.


Data Model (UI repo: yaptide/ui)

1. ParticleEntry interface

// src/types/ParticleCatalogue.ts

export interface ParticleEntry {
  /** Display name shown after selection, e.g. "Carbon-12", "Electron", "Proton" */
  displayName: string;

  /**
   * All strings a user may type to find this entry.
   * Matching is case-insensitive substring against every alias.
   */
  aliases: readonly string[];

  /** SHIELD-HIT12A / backend particle id */
  id: number;

  /** Mass number (nucleons). Required for baryonic particles. */
  a?: number;

  /** Atomic / charge number. Required for baryonic particles. */
  z?: number;

  /**
   * Natural isotopic abundance as a percentage (0–100).
   * Source: IUPAC 2021 "Isotopic Compositions of the Elements" / NUBASE2020.
   * Undefined for non-baryonic particles (electron, photon, pions, muons, etc.)
   * and for particles with dedicated IDs (proton id:2, deuteron id:21, etc.)
   * where "abundance" is not a meaningful concept.
   *
   * Used to:
   *  - Derive "most abundant isotope" per element for bold + ★ rendering.
   *  - Sort isotopes of the same element (highest abundance first).
   *  - Optionally display in a tooltip.
   */
  abundance?: number;

  /**
   * Sort priority for the unfiltered dropdown list.
   * Lower number = appears earlier. Default assumed 10 if omitted.
   *
   * Tier definitions:
   *   0  = Everyday workhorse beams (Proton, Carbon-12)
   *   1  = Very common (Neutron, Electron, Positron, Photon, Helium-4/Alpha)
   *   2  = Frequently used ions & composites (Deuteron, Triton, He-3,
   *         Nitrogen-14, Oxygen-16, Neon-20, Argon-40, Iron-56)
   *   3  = Notable special-purpose (U-235, U-238, C-14, Pb-208, Si-28)
   *  10  = All other isotopes and rarely used particles (default)
   */
  sortPriority?: number;

  /**
   * Which simulators support this particle.
   * Used by getParticlesForSimulator() to filter the catalogue.
   */
  simulators: readonly SimulatorType[];
}

2. Derived helper: "most abundant" detection

isMostAbundant is no longer stored — it is computed at runtime from abundance:

/**
 * Returns true if this particle has the highest natural abundance
 * among all catalogue entries sharing the same Z value.
 * Returns false for non-isotope particles (abundance === undefined).
 */
export function isMostAbundantIsotope(
  particle: ParticleEntry,
  catalogue: readonly ParticleEntry[]
): boolean {
  if (particle.abundance === undefined || particle.z === undefined) return false;
  const maxAbundance = Math.max(
    ...catalogue
      .filter(p => p.z === particle.z && p.abundance !== undefined)
      .map(p => p.abundance!)
  );
  return particle.abundance === maxAbundance;
}

This result can be memoized per catalogue build for rendering performance.

3. Master particle catalogue (PARTICLE_CATALOGUE)

A single readonly ParticleEntry[] exported from src/types/ParticleCatalogue.ts.

Non-ion particles (with dedicated SHIELD-HIT IDs)

displayName id a z sortPriority aliases simulators
Proton 2 1 1 0 ["proton", "p", "protium", "hydrogen-1", "H-1", "hydrogen", "1H"] SH, FLUKA, G4
Neutron 1 1 0 1 ["neutron", "n"] SH, FLUKA, G4
Photon 3 (G4) 1 ["photon", "gamma", "γ"] G4
Electron (G4) 4 (G4) 1 ["electron", "e-"] G4
Electron (FLUKA) 26 1 ["electron", "e-"] FLUKA
Positron 5 (G4) 1 ["positron", "e+"] G4
Alpha (G4) 6 (G4) 4 2 1 ["alpha", "helium-4", "He-4", "4He"] G4
Helium-4 24 4 2 1 ["helium-4", "He-4", "4He", "alpha"] SH, FLUKA
Deuteron 21 2 1 2 ["deuteron", "d", "deuterium", "hydrogen-2", "H-2", "2H"] SH, FLUKA
Triton 22 3 1 2 ["triton", "t", "tritium", "hydrogen-3", "H-3", "3H"] SH, FLUKA
Helium-3 23 3 2 2 ["helium-3", "He-3", "3He"] SH, FLUKA
Pion π- 3 10 ["pion pi-", "pion-", "π-"] SH, FLUKA
Pion π+ 4 10 ["pion pi+", "pion+", "π+"] SH, FLUKA
Anti-proton 7 1 1 10 ["anti-proton", "antiproton", "p-bar"] SH, FLUKA
Kaon κ- 8 10 ["kaon k-", "kaon-", "κ-"] SH, FLUKA
Kaon κ+ 9 10 ["kaon k+", "kaon+", "κ+"] SH, FLUKA
Kaon κ0 10 10 ["kaon k0", "kaon0", "κ0"] SH, FLUKA
Kaon κ~ 11 10 ["kaon k~", "kaon~", "κ~"] SH, FLUKA
Muon µ- 15 10 ["muon mu-", "muon-", "µ-"] SH, FLUKA, G4
Muon µ+ 16 10 ["muon mu+", "muon+", "µ+"] SH, FLUKA, G4
Pion π- (G4) 9 (G4) 10 ["pion pi-", "pion-", "π-"] G4
Pion π+ (G4) 10 (G4) 10 ["pion pi+", "pion+", "π+"] G4

Note: Particles shared across simulators with different IDs (e.g. Electron is id:26 in FLUKA but id:4 in Geant4) exist as separate entries, disambiguated by simulators.

Heavy-ion isotopes (all get id: 25)

All isotopes from Li (Z=3) through U (Z=92), including both stable and commonly-used unstable isotopes. Each entry has abundance from IUPAC data and sortPriority based on research usage frequency.

Example entries:

// sortPriority 0 — everyday workhorse
{
  displayName: "Carbon-12",
  aliases: ["carbon-12", "C-12", "12C", "carbon"],
  id: 25, a: 12, z: 6,
  abundance: 98.93,
  sortPriority: 0,
  simulators: [SimulatorType.SHIELDHIT, SimulatorType.FLUKA, SimulatorType.GEANT4]
},

// sortPriority 2 — frequently used
{
  displayName: "Oxygen-16",
  aliases: ["oxygen-16", "O-16", "16O", "oxygen"],
  id: 25, a: 16, z: 8,
  abundance: 99.757,
  sortPriority: 2,
  simulators: [SimulatorType.SHIELDHIT, SimulatorType.FLUKA, SimulatorType.GEANT4]
},

// sortPriority 3 — notable special-purpose
{
  displayName: "Uranium-235",
  aliases: ["uranium-235", "U-235", "235U"],
  id: 25, a: 235, z: 92,
  abundance: 0.72,
  sortPriority: 3,
  simulators: [SimulatorType.SHIELDHIT, SimulatorType.FLUKA, SimulatorType.GEANT4]
},

// sortPriority 10 (default) — everything else
{
  displayName: "Carbon-13",
  aliases: ["carbon-13", "C-13", "13C"],
  id: 25, a: 13, z: 6,
  abundance: 1.07,
  sortPriority: 10,
  simulators: [SimulatorType.SHIELDHIT, SimulatorType.FLUKA, SimulatorType.GEANT4]
},

Complete sortPriority assignments for heavy-ion isotopes:

sortPriority Isotopes
0 C-12
2 N-14, O-16, Ne-20, Ar-40, Fe-56
3 C-14, Si-28, Ca-40, Pb-208, U-235, U-238
10 All remaining isotopes

Alias rules for isotopes:

  • "{element}-{A}" lowercase (e.g. "carbon-12")
  • "{Sym}-{A}" (e.g. "C-12")
  • "{A}{Sym}" (e.g. "12C")
  • If this is the most abundant isotope of its element (derived from abundance), also the bare element name (e.g. "carbon")
  • Full element name always included (e.g. "carbon-12" contains "carbon" as substring, ensuring match)

Deduplication with light particles:

  • Proton (id:2) covers H-1. No separate id:25, a:1, z:1 entry.
  • Deuteron (id:21) covers H-2. No separate id:25, a:2, z:1.
  • Triton (id:22) covers H-3. No separate id:25, a:3, z:1.
  • He-3 (id:23) covers He-3. No separate id:25, a:3, z:2.
  • He-4/Alpha (id:24/id:6) covers He-4. No separate id:25, a:4, z:2.
  • Heavy-ion isotopes start at Li-6 (id:25, a:6, z:3).

4. Helper functions

// src/types/ParticleCatalogue.ts

/** Return particles available for a given simulator, pre-sorted by sortPriority */
export function getParticlesForSimulator(sim: SimulatorType): readonly ParticleEntry[];

/** Lookup by (id, a, z) — used for deserialisation */
export function findParticleByIdAZ(
  id: number, a?: number, z?: number, sim?: SimulatorType
): ParticleEntry | undefined;

/** True if particle is a heavy ion (id === 25) */
export function isHeavyIon(p: ParticleEntry): boolean;

/**
 * Filter particles by query string against aliases (case-insensitive substring).
 * Returns results sorted by: sortPriority ASC, then Z ASC, then abundance DESC.
 */
export function filterParticles(
  query: string, particles: readonly ParticleEntry[]
): ParticleEntry[];

/**
 * Sort comparator for particle list.
 * Primary:  sortPriority ASC (lower = first)
 * Secondary: Z ASC (lighter elements first)
 * Tertiary:  abundance DESC (most abundant isotope first)
 */
export function compareParticles(a: ParticleEntry, b: ParticleEntry): number;

/**
 * Returns true if this particle is the most naturally abundant isotope
 * for its element (same Z) in the given catalogue subset.
 */
export function isMostAbundantIsotope(
  particle: ParticleEntry, catalogue: readonly ParticleEntry[]
): boolean;

5. Catalogue is closed

No manual A/Z input. The user must pick from the catalogue dropdown. Missing isotopes require a code change to PARTICLE_CATALOGUE.


Inputs

Input Type Constraints
Particle type Single autocomplete text field Must resolve to exactly one ParticleEntry. Free text for searching; selection from dropdown only.

Behavior

Dropdown / Search

  1. User sees a single field labelled "Particle type".
  2. On focus or typing, a filterable dropdown appears with all particles for the current simulator.
  3. Filtering: case-insensitive substring match against all aliases of each ParticleEntry. E.g.:
    • "elec" → Electron
    • "C-1" → Carbon-12, Carbon-13, Carbon-14
    • "carbon" → all carbon isotopes (C-12 first, bold)
    • "p" → Proton (and any others matching "p")
    • "12C" → Carbon-12
    • "H-2" → Deuteron (not a heavy-ion entry, because H-2 is deduplicated to deuteron)
  4. Ordering (uses compareParticles()):
    • Primary: sortPriority ascending — Proton (0), C-12 (0) at top, rare isotopes (10) at bottom.
    • Secondary: z ascending — lighter elements before heavier.
    • Tertiary: abundance descending — most abundant isotope of an element first.
  5. Rendering:
    • Each option shows displayName.
    • If isMostAbundantIsotope(entry, currentList) → rendered in bold + ★ indicator.
    • Non-bold items use normal weight.
  6. User selects → onChange fires with full ParticleEntry.

Default (unfiltered) dropdown appearance

When the user focuses the field without typing:

  Proton                                   ← sortPriority 0
  Carbon-12                           ★    ← sortPriority 0, abundance 98.93%
  Neutron                                  ← sortPriority 1
  Electron                                 ← sortPriority 1
  Helium-4                            ★    ← sortPriority 1
  …
  Deuteron                                 ← sortPriority 2
  Nitrogen-14                         ★    ← sortPriority 2, abundance 99.63%
  Oxygen-16                           ★    ← sortPriority 2, abundance 99.76%
  Iron-56                             ★    ← sortPriority 2, abundance 91.75%
  …
  Carbon-14                                ← sortPriority 3, abundance 0 (trace/unstable)
  Uranium-238                         ★    ← sortPriority 3, abundance 99.27%
  Uranium-235                              ← sortPriority 3, abundance 0.72%
  …
  Lithium-7                           ★    ← sortPriority 10, abundance 92.41%
  Lithium-6                                ← sortPriority 10, abundance 7.59%
  Beryllium-9                         ★    ← sortPriority 10, abundance 100%
  Boron-11                            ★    ← sortPriority 10, abundance 80.1%
  Boron-10                                 ← sortPriority 10, abundance 19.9%
  … (remaining isotopes by Z, then abundance)

After Selection

  1. The displayName is shown in the input field.
  2. If isHeavyIon(selected), an info row appears below: A = {a}, Z = {z} (read-only, informational).
  3. The old separate "Element", "Isotope", editable Z, editable A fields are removed.
  4. Energy unit toggle (MeV vs MeV/nucl) appears when a > 1 or id === 25.

Serialisation (UI → JSON → Converter)

  1. Beam.toSerialized() writes:
    "particle": { "id": 25, "name": "Carbon-12", "a": 12, "z": 6 }
    name carries the resolved displayName. Fields abundance and sortPriority are not serialised — they are UI-only metadata.
  2. Beam.fromSerialized() calls findParticleByIdAZ(id, a, z) to restore the full ParticleEntry. Falls back to raw data if not found (legacy/forward compat).
  3. ParticleFilter and GeantScoringFilter serialisation follows the same pattern.

Edge Cases

  1. Unknown particle in loaded file: If findParticleByIdAZ returns undefined, preserve raw data. Display "{name} (id={id})" with a ⚠ warning icon.
  2. Simulator switch: If the selected particle is not in the new simulator's list, reset to Proton.
  3. Legacy "Heavy ions" name: fromSerialized matches by (id=25, a, z) regardless of name value — handles old project files that say "Heavy ions".
  4. Unstable isotopes with 0% abundance: Entries like C-14, Th-232, U-235 have abundance values from NUBASE2020 (trace amounts or 0). They still appear in the catalogue and can be searched by alias. They will never get bold/★ rendering (they are never the most abundant).

Output

  • Single MUI Autocomplete component replaces old ParticleSelect.
  • Dropdown pre-sorted by sortPriorityzabundance, putting commonly-used particles at top.
  • Bold + ★ for the most abundant isotope of each element (derived from abundance).
  • After selection: read-only A/Z info line for heavy ions.
  • Serialised JSON backward-compatible (id: 25 for heavy ions, dedicated IDs for light particles).
  • abundance and sortPriority never appear in serialised JSON — they are UI-only.
  • All three converter backends continue to work without wire format changes.

Acceptance Criteria

Data Layer

  • PARTICLE_CATALOGUE contains all non-ion particles from current COMMON_PARTICLE_TYPES, FLUKA_PARTICLE_TYPES, GEANT4_PARTICLE_TYPES.
  • PARTICLE_CATALOGUE contains isotopes from Li (Z=3) through U (Z=92) with id: 25, including both stable and commonly-used unstable isotopes (C-14, U-235, Th-232, etc.).
  • Proton, Deuteron, Triton, He-3, He-4 are not duplicated as id: 25 entries.
  • Every isotope entry has aliases: "{element}-{A}", "{Sym}-{A}", "{A}{Sym}".
  • Bare element name alias (e.g. "carbon") maps only to the most abundant isotope of that element.
  • Proton aliases: "proton", "p", "protium", "hydrogen-1", "H-1", "hydrogen", "1H".
  • Deuteron aliases: "deuteron", "d", "deuterium", "hydrogen-2", "H-2", "2H".
  • Electron aliases: "electron", "e-".
  • Positron aliases: "positron", "e+".

Abundance Data

  • Every isotope entry has an abundance value sourced from IUPAC 2021 / NUBASE2020.
  • abundance is a number between 0 and 100 (percentage).
  • For each element (same Z), exactly one isotope has the highest abundance — this is the one that renders bold + ★.
  • Unstable isotopes not in IUPAC stable tables have abundance: 0 (or the trace value if listed).
  • Non-baryonic particles (electron, photon, pions, muons, kaons) have abundance: undefined.
  • Non-ion light particles with dedicated IDs (proton, deuteron, etc.) have abundance: undefined.

Sort Priority

  • Proton has sortPriority: 0.
  • Carbon-12 has sortPriority: 0.
  • Neutron, Electron, Positron, Photon, Helium-4/Alpha have sortPriority: 1.
  • Deuteron, Triton, He-3, N-14, O-16, Ne-20, Ar-40, Fe-56 have sortPriority: 2.
  • U-235, U-238, C-14, Pb-208, Si-28 have sortPriority: 3.
  • All other isotopes and rarely used non-ion particles default to sortPriority: 10.
  • getParticlesForSimulator() returns a list pre-sorted by compareParticles().

Simulator Scoping

  • getParticlesForSimulator(SHIELDHIT) returns COMMON particles + heavy-ion isotopes (no Geant4-only particles).
  • getParticlesForSimulator(GEANT4) returns Geant4 particles + heavy-ion isotopes (no SHIELD-HIT-only kaons, etc.).
  • getParticlesForSimulator(FLUKA) returns COMMON particles + Electron (id:26) + heavy-ion isotopes.

UI — Beam Configuration

  • Only one field labelled "Particle type" is shown (no Element, Isotope, Z, A editable fields).
  • Typing "elec" filters to show Electron.
  • Typing "carbon" shows all carbon isotopes; C-12 appears first and is bold + ★.
  • Typing "C-12" or "12C" matches Carbon-12.
  • Typing "p" matches Proton.
  • Typing "H-2" matches Deuteron.
  • Selecting Carbon-12 shows read-only info: A = 12, Z = 6.
  • Selecting Proton does not show A/Z info.
  • Selecting a particle with a > 1 or id === 25 shows MeV/nucl toggle.
  • Opening the dropdown without typing shows Proton and C-12 at the very top (sortPriority 0).

UI — Particle Filters

  • SHIELD/FLUKA ParticleFilterConfiguration uses the unified selector with all particles (including heavy-ion isotopes selected from dropdown).
  • Geant4 GeantScoringFilterConfiguration multi-select uses the same ParticleEntry catalogue scoped to Geant4.
  • Selecting a heavy-ion isotope in a particle filter serialises { id: 25, name: "Carbon-12", a: 12, z: 6 }.

Serialisation & Backward Compatibility

  • Saving beam with Carbon-12 → particle: { id: 25, name: "Carbon-12", a: 12, z: 6 }.
  • Saving beam with Proton → particle: { id: 2, name: "Proton", a: 1, z: 1 }.
  • abundance and sortPriority are never written to serialised JSON.
  • Loading legacy { id: 25, name: "Heavy ions", a: 12, z: 6 } resolves to Carbon-12.
  • Loading unknown (id, a, z) degrades gracefully — displays raw name, no crash.
  • Converter SHIELD-HIT parser continues to emit JPART0 25 + HIPROJ 12 6 for Carbon-12.
  • Converter FLUKA parser continues to emit HEAVYION + HI-PROPE 6 12 for Carbon-12.
  • Converter Geant4 generator continues to emit /gps/particle ion + /gps/ion 6 12 0 0 for Carbon-12.

Converter — Scoring Filter Fix (small change in yaptide/converter)

  • converter/shieldhit/parser.py parse_scoring_filter(): when particle.id == 25, emit [("Z", "==", particle.z), ("A", "==", particle.a)] instead of failing on missing "filter" key.
  • converter/geant4/Geant4MacroGenerator.py _append_quantity(): when filter particle id == 25, emit ion-syntax filter.

Code Quality

  • Old COMMON_PARTICLE_TYPES, FLUKA_PARTICLE_TYPES, GEANT4_PARTICLE_TYPES replaced by getParticlesForSimulator().
  • No hardcoded === 25 checks for showing/hiding UI fields — use isHeavyIon().
  • HEAVY_ION_LIST from PR handle heavy ions #2353 refactored into catalogue with proper aliases, abundance, and sortPriority.
  • All commented-out dead code from PR handle heavy ions #2353 removed.
  • No any types for particle data flowing through components.
  • Unit tests for filterParticles() alias matching (at least 10 cases).
  • Unit tests for findParticleByIdAZ() including legacy name fallback.
  • Unit tests for getParticlesForSimulator() per simulator.
  • Unit tests for compareParticles() sort order.
  • Unit tests for isMostAbundantIsotope().

Implementation Plan

Phase 1 — Data Layer (yaptide/uisrc/types/)

  1. Create src/types/ParticleCatalogue.ts:
    • Define ParticleEntry interface with abundance and sortPriority.
    • Build PARTICLE_CATALOGUE array with all entries, aliases, IUPAC abundance values, and sortPriority tiers.
    • Export helpers: getParticlesForSimulator(), findParticleByIdAZ(), isHeavyIon(), filterParticles(), compareParticles(), isMostAbundantIsotope().
  2. Update src/types/Particle.ts:
    • Re-export ParticleEntry as Particle (backward-compat type alias).
    • Remove/deprecate COMMON_PARTICLE_TYPES, FLUKA_PARTICLE_TYPES, GEANT4_PARTICLE_TYPES.

Phase 2 — Selector Component (yaptide/uisrc/ThreeEditor/components/Select/)

  1. Rewrite ParticleSelect.tsx:
    • Props: particles: readonly ParticleEntry[], value: ParticleEntry | null, onChange: (entry: ParticleEntry) => void.
    • MUI Autocomplete with custom filterOptions calling filterParticles().
    • Custom renderOption: bold + ★ when isMostAbundantIsotope() returns true.
    • Options pre-sorted by compareParticles().

Phase 3 — Beam Configuration (yaptide/uisrc/ThreeEditor/components/Sidebar/)

  1. Update BeamConfiguration.tsx:
    • Replace supportedParticles construction with getParticlesForSimulator(currentSimulator).
    • Remove editable Z/A number fields.
    • Add read-only A/Z info line for isHeavyIon(selected).
    • Replace shouldShowEnergyUnit logic with isHeavyIon() helper + a > 1 check.

Phase 4 — Particle Filters (yaptide/ui)

  1. Update ParticleFilterConfiguration.tsx:
    • Use unified selector with full particle list (remove old filter(p => p.id !== 25)).
    • On selection, serialise full { id, name, a, z }.
  2. Update GeantScoringFilterConfiguration.tsx:
    • Multi-select from getParticlesForSimulator(GEANT4).

Phase 5 — Models & Serialisation (yaptide/ui)

  1. Update Beam.ts:
    • Change particleData type to ParticleEntry.
    • toSerialized(): emit only { id, name: displayName, a, z } — strip abundance, sortPriority, aliases, simulators.
    • fromSerialized(): resolve via findParticleByIdAZ(particle.id, particle.a, particle.z).
  2. Update ParticleFilter.ts:
    • Extend ParticleFilterJSON to include optional a and z.
    • Same serialisation/deserialisation pattern.
  3. Update GeantScoringFilter.ts:
    • FilterData.particleTypes becomes ParticleEntry[].

Phase 6 — Converter Fix (yaptide/converter)

  1. converter/shieldhit/parser.pyparse_scoring_filter():
    • When particle.id == 25, emit [("Z", "==", z), ("A", "==", a)].
  2. converter/geant4/Geant4MacroGenerator.py_append_quantity():
    • When filter particle id == 25, handle ion filter.
  3. Add converter tests for heavy-ion filter serialisation.

Phase 7 — Cleanup & Tests

  1. Remove all dead/commented code from PR handle heavy ions #2353.
  2. Add unit tests per acceptance criteria above.
  3. Add integration/snapshot tests for ParticleSelect rendering.

Files Changed (Summary)

yaptide/ui

File Action
src/types/ParticleCatalogue.ts NEW — catalogue, ParticleEntry, helpers, abundance data
src/types/Particle.ts Simplify — re-export from catalogue, remove static arrays
src/ThreeEditor/components/Select/ParticleSelect.tsx Rewrite — alias filtering, bold/★ from isMostAbundantIsotope(), sorted by compareParticles()
src/ThreeEditor/components/Sidebar/…/BeamConfiguration.tsx Update — single field, info line, remove Z/A inputs, use isHeavyIon()
src/ThreeEditor/components/Sidebar/…/ParticleFilterConfiguration.tsx Update — use unified selector with all particles including isotopes
src/ThreeEditor/components/Sidebar/…/GeantScoringFilterConfiguration.tsx Update — use catalogue
src/ThreeEditor/Simulation/Physics/Beam.ts Update — ParticleEntry type, strip UI-only fields in toSerialized(), resolve in fromSerialized()
src/ThreeEditor/Simulation/Scoring/ParticleFilter.ts Update — extend JSON type with a/z, resolve on deserialisation
src/ThreeEditor/Simulation/Scoring/GeantScoringFilter.ts Update — ParticleEntry[] in filter data

yaptide/converter

File Action
converter

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.

Handle Heavy Ions with varying A, Z

2 participants