Skip to content

Chapter 3: PHITS Design Authoring Pipeline

In Chapter 2: Scene and Geometry Model, we learned how RAMAL-EBX represents materials, surfaces, cells, transforms, and PHITS settings as an editable scene.

Now we will follow that scene through the PHITS Design Authoring Pipeline.

Imagine importing a PHITS input file containing a shielding model. You want to:

  • Open it in RAMAL-EBX.
  • Edit its geometry and source settings.
  • Keep PHITS sections RAMAL-EBX does not understand.
  • Check that the design is valid.
  • Save both a managed record and a solver-ready input file.

This is the job of the PHITS Design Authoring Pipeline.

The main idea

A Design is the editable scientific input for a simulation.

A useful analogy is a master manuscript:

  • The imported PHITS file is the original manuscript.
  • RAMAL-EBX parses important parts into structured fields.
  • The editor changes the structured fields.
  • Unknown sections remain preserved.
  • The exporter writes a clean PHITS input.
  • A later Case freezes a verified copy.
flowchart LR
    A[PHITS text] --> B[Parse]
    B --> C[Resolve references]
    C --> D[Validate]
    D --> E[Editable Design]
    E --> F[Generate PHITS input]
    F --> G[Managed record and solver file]

The key separation is:

A Design can change. A Case later records one frozen version of that Design.

This prevents a simulation from silently changing because someone edited the original Design afterward.

A central example

Suppose we start with this PHITS input:

[Material]
  mat[1]  100001 2 8016 1  $ Concrete

[Surface]
  10 RPP -50 50 -50 50 -50 50

[Cell]
  20 1 -2.3 -10

[Source]
  s-type = 1
  proj = electron
  e0 = 10

This describes:

  • Material 1: concrete.
  • Surface 10: a box.
  • Cell 20: concrete inside the box.
  • Source: 10 MeV electrons.

After importing it, the user might move the box, change the source energy, or add a tally.

The final output should still be valid PHITS text, while RAMAL-EBX also remembers the structured design.

Why not edit raw text only?

Direct text editing is flexible, but it is difficult to validate.

For example, a text file might contain:

20 1 -2.3 -999

If surface 999 does not exist, the error may only appear when PHITS runs.

RAMAL-EBX instead resolves the reference while importing:

cell 20 → surface 10

The editor can then report missing surfaces, materials, transforms, or cells before export.

Structured data makes the design easier to inspect and safer to change.

The Design state

The main editable state is Scene_State.

It contains geometry, PHITS settings, and editor history:

Scene_State :: struct {
    geometry: Geometry,
    phits: PHITS_State,
    dirty: bool,
    undo_history: [dynamic]^Scene_State,
    redo_history: [dynamic]^Scene_State,
}

geometry comes from Chapter 2.

phits contains settings such as:

  • PHITS run parameters.
  • Source definition.
  • Dose tally.
  • Profile tally.
  • Assessment points.
  • Analysis regions.
  • Imported source text.

The dirty flag tells RAMAL-EBX whether the Design has unsaved changes.

PHITS state

PHITS_State stores both editable settings and document information:

PHITS_State :: struct {
    path: string,
    source_text: string,
    baseline_generated: string,
    revision: u64,
    autosync: bool,
    parameters: PHITS_Parameters,
    source: PHITS_Source,
}

The important fields are:

  • path: where the PHITS input is stored.
  • source_text: the managed text currently owned by the Design.
  • baseline_generated: the last canonical text generated by RAMAL-EBX.
  • parameters: solver settings.
  • source: particle and energy settings.

The baseline allows RAMAL-EBX to tell which parts changed.

Importing a PHITS document

Importing happens in stages:

  1. Create an empty import draft.
  2. Read the input line by line.
  3. Parse known sections.
  4. Store unresolved references temporarily.
  5. Resolve those references.
  6. Validate the result.
  7. Commit it to the active Design.
sequenceDiagram
    participant User
    participant Importer
    participant Draft
    participant Design
    participant Files

    User->>Importer: Open PHITS input
    Importer->>Draft: Parse text
    Draft->>Draft: Resolve references
    Draft->>Design: Commit validated state
    Design->>Files: Save managed record

The draft is important. RAMAL-EBX does not replace the active Design while the imported file is only partially parsed.

The import draft

The temporary import state is represented by Design_PHITS_Import.

Design_PHITS_Import :: struct {
    geometry: Geometry,
    pending_cell_expressions: [dynamic]Cell_Expression,
    parameters: PHITS_Parameters,
    source: PHITS_Source,
    dose_tally: PHITS_Dose_Tally,
}

It also stores transforms, assessment points, analysis regions, and import bookkeeping.

The draft is like a workbench. RAMAL-EBX can reject it without damaging the current Design.

Parsing sections

PHITS uses named sections such as:

[Parameters]
[Source]
[Material]
[Surface]
[Cell]
[T-Track]
[End]

The importer tracks the current section:

section := ""
lines, _ := strings.split(string(data), "\n", context.temp_allocator)

for line in lines {
    if !design_phits_import_parse_line(draft, line, &section) {
        return false
    }
}

Each line is sent to the parser with the current section name.

For example:

  • A line in [Material] is parsed as a material card.
  • A line in [Surface] is parsed as a surface.
  • A line in [Cell] is parsed as a cell expression.

Parsing a material

A material line contains a PHITS material ID and a composition:

mat[1]  100001 2 8016 1  $ Concrete

RAMAL-EBX creates an internal material with both identities:

Material{
    id = id,
    phits_id = mat_id,
    name = strings.clone(name),
    composition = strings.clone(composition),
}

The internal id belongs to RAMAL-EBX.

The phits_id is the number written into PHITS.

This is similar to a laboratory sample having both:

  • An internal database ID.
  • A label printed on the sample container.

Parsing surfaces

A surface line is converted into a structured Surface.

For example:

10 RPP -50 50 -50 50 -50 50

becomes a rectangular parallelepiped:

surface.kind = .RPP
surface.position = {(x0+x1)*0.5, (y0+y1)*0.5, (z0+z1)*0.5}
surface.size = {abs(x1-x0), abs(y1-y0), abs(z1-z0)}

RAMAL-EBX supports recognized kinds such as:

  • RPP
  • SPH
  • RCC
  • TRC
  • P
  • PX
  • PY
  • PZ

It can also preserve unknown surface kinds as Raw.

Preserving raw surfaces

Some PHITS surfaces may not have a full structured representation in RAMAL-EBX.

Instead of deleting them, the importer stores their original parts:

surface.kind = .Raw
surface.raw_kind = strings.clone(fields[1])
surface.raw_parameters = strings.clone(parameters)

This means the editor can preserve a surface even when it cannot manipulate it geometrically.

The principle is:

Unknown does not mean disposable.

Parsing cell expressions

A PHITS cell expression uses signs and references:

-10 20 #30

The meaning is approximately:

  • Inside surface 10.
  • Outside surface 20.
  • Outside cell 30.

RAMAL-EBX parses these terms into structured references:

if strings.has_prefix(token, "#") {
    append(&expression.terms,
        Cell_Expression_Term{kind = .Outside_Cell})
}

A negative surface reference means “inside.” A positive reference means “outside.”

Why cell references are deferred

Cells can refer to other cells that appear later in the file.

For example:

20 1 -2.3 -10 #30
30 1 -2.3 -20

When cell 20 is read, cell 30 may not exist yet.

RAMAL-EBX therefore stores the reference temporarily:

During parsing:
    cell 20 → PHITS cell 30

After parsing:
    cell 20 → internal Cell_ID for cell 30

This is called deferred resolution.

Resolving references

After parsing, RAMAL-EBX resolves PHITS IDs into internal IDs:

ref_cell, ref_ok := cell_find_by_phits(geometry, i32(term.cell))
if !ref_ok {
    return false
}
term.cell = ref_cell

The same idea is used for:

  • Materials.
  • Surfaces.
  • Cells.
  • Transforms.
  • Source restriction regions.

This gives the application a consistent internal graph.

flowchart TD
    A[PHITS number] --> B[Find matching record]
    B --> C[Store internal ID]
    C --> D[Validate relationship]

Validating cell expressions

During resolution, RAMAL-EBX checks that:

  • Every surface exists.
  • Every referenced cell exists.
  • A cell does not exclude itself.
  • Cell references do not form invalid cycles.
  • Material cells have usable volume.
  • Transform references are valid.
if ref_cell == geometry.cells[cell_index].id {
    return false
}

This rejects a cell that tries to exclude itself.

The geometry checks described in Chapter 2 then verify that the expression can represent a meaningful region.

Parsing source settings

The source section describes the radiation source.

For example:

s-type = 1
proj = electron
e0 = 10
x0 = 0
y0 = 0
z0 = -500

These values become a PHITS_Source:

source.s_type = 1
owned_string_replace(&source.particle, "electron")
source.energy = 10
source.position = {0, 0, -500}

The source may also contain:

  • Direction.
  • Radius.
  • Source region.
  • Transform ID.
  • Pulse normalization.
  • Scan metadata.

Validating the source

A source is valid only if its values make scientific and structural sense.

Examples include:

  • Energy is positive.
  • The particle name is present.
  • Coordinates are finite.
  • Radii are not negative.
  • A referenced region exists.
  • A transform exists.
  • Pulsed settings have valid pulse width and rate.
if !phits_source_valid(draft.source) {
    return false
}

The validation does not choose the scientific experiment for the user. It checks that the declared source can be represented safely and consistently.

Tally and analysis settings

A Design can also contain output instructions.

Examples include:

  • A voxel dose tally.
  • A profile tally.
  • Assessment points.
  • Analysis regions.
  • A multiplier ID.
  • Output units and normalization.

A dose tally might contain:

PHITS_Dose_Tally :: struct {
    enabled: bool,
    target: PHITS_Field_Target,
    mesh_min: [3]f32,
    mesh_max: [3]f32,
    mesh_count: [3]i32,
    particles: string,
    energy_min: f32,
    energy_max: f32,
}

RAMAL-EBX verifies that mesh bounds, energy ranges, particles, and multiplier information are valid.

Importing unknown sections

Not every PHITS section is managed by RAMAL-EBX.

For example, an imported file might contain:

[SomeFutureSection]
  custom_setting = 42

The parser may not understand this section, but the document layer preserves it.

The imported text is divided into blocks:

PHITS_Document_Block :: struct {
    header: string,
    text: string,
}

Each block remembers its header and original text.

This allows unknown sections to survive a save operation.

Managed and unmanaged sections

RAMAL-EBX knows how to regenerate some sections:

[Title]
[Parameters]
[Source]
[Transform]
[Material]
[Surface]
[Cell]
[T-Track]
[End]

These are managed sections.

Other sections remain source-owned unless RAMAL-EBX later learns how to manage them.

phits_document_managed_header :: proc(header: string) -> bool {
    return strings.equal_fold(header, "[Material]") ||
        strings.equal_fold(header, "[Surface]") ||
        strings.equal_fold(header, "[Cell]")
}

The complete implementation recognizes additional managed headers.

Merging imported text

When saving, RAMAL-EBX compares three versions:

  1. The original imported source.
  2. The previous canonical output.
  3. The newly generated output.

Conceptually:

If a section did not change:
    keep the user's original formatting.

If a managed section changed:
    use the new canonical section.

If a section is unknown:
    preserve it.

This is the purpose of phits_document_merge.

flowchart LR
    A[Imported source] --> D[Merge]
    B[Previous generated baseline] --> D
    C[Current generated text] --> D
    D --> E[Managed Design document]

For example, a custom [SomeFutureSection] remains in the saved document, while a changed [Cell] section is regenerated.

Generating canonical PHITS input

The function phits_input_generate writes the structured Design into PHITS syntax.

It writes sections in a predictable order:

[Title]
[Parameters]
[Source]
[Transform]
[Material]
[MatNameColor]
[Surface]
[Cell]
[T-Track]
[End]

A simplified export call looks like this:

phits_input := phits_input_generate(
    &app.design_scene,
    context.temp_allocator,
)

The result is a canonical PHITS document generated from the current structured state.

Generating a material card

A material becomes a PHITS card:

card := fmt.tprintf(
    "  mat[%d]  %s  $ %s",
    material.phits_id,
    material.composition,
    material.name,
)

Additional information may be written afterward, such as:

  • Neutron library overrides.
  • Gas settings.
  • Material reference metadata.

Generating a cell card

A cell expression is converted back into PHITS notation:

case .Inside_Surface:
    fmt.sbprintf(&b, "-%d", surface.phits_id)

case .Outside_Surface:
    fmt.sbprintf(&b, "%d", surface.phits_id)

case .Outside_Cell:
    fmt.sbprintf(&b, "#%d", cutter.phits_id)

For example, an internal expression may become:

-10 20 #30

The internal IDs are translated back into PHITS IDs during export.

Generated world geometry

If the Design does not contain an explicit outer void, RAMAL-EBX may generate default world cards.

These can include:

  • A world boundary surface.
  • A world air material.
  • A default air cell.
  • An outer void cell.

This allows an ordinary geometry design to become a complete solver input.

The generated world must still pass validation. RAMAL-EBX does not silently accept an invalid boundary.

Generated source scan input

RAMAL-EBX can represent a source scan as structured settings:

source.scan_enabled = true
source.scan_axis = .X
source.scan_min_cm = -20
source.scan_max_cm = 20
source.scan_position_count =  nine

The exporter writes the scan as a series of PHITS source entries.

Conceptually:

scan range
    ↓
sample source positions
    ↓
assign quadrature weights
    ↓
write repeated [Source] entries

The source scan metadata is also written as a RAMAL comment so that it can be reconstructed on import.

Generated comments as metadata

Some information is not naturally represented by standard PHITS cards.

RAMAL-EBX stores such information in comments:

$ ramal:assessment-point name=detector x_cm=0 y_cm=20 z_cm=50

Other examples include:

$ ramal:material-reference phits_id=1 id=dry-air
$ ramal:source-scan enabled=1 axis=x ...
$ ramal:region-mean region_id=region-001

These comments are readable by people and understood by RAMAL-EBX during a later import.

Validating before synchronization

Before writing a solver input, RAMAL-EBX validates the Design.

The validation includes:

  • At least one material.
  • Valid world configuration.
  • Valid PHITS parameters.
  • Valid source.
  • Valid tallies.
  • Valid transforms.
  • Unique PHITS IDs.
  • Exportable surfaces.
  • Exportable cells.
  • Valid references.
  • No overlapping material regions.
if !design_phits_ids_validate(app) {
    app.design_scene.phits.last_sync_failed = true
    return false
}

This function performs structural validation before synchronization.

Verifying generated text

After generation, RAMAL-EBX checks that important output actually appears:

if !design_phits_generated_validate(app, phits_input) {
    app.design_scene.phits.last_sync_failed = true
    return false
}

It verifies required sections such as:

[Title]
[Parameters]
[Material]
[Surface]
[Cell]
[Source]
[End]

It also checks that generated materials, surfaces, cells, transforms, and world cards are present.

This is a second safety net:

  • First validate the structured Design.
  • Then verify the generated text.

Saving a Design

Saving creates two related files:

designs/shield-study/
├── shield-study.ramal-design
└── shield-study.inp

The .ramal-design file is the managed record.

It contains the RAMAL-owned document and metadata.

The .inp file is the solver input.

Before writing the solver file, RAMAL-EBX removes RAMAL-only comments:

design_solver_input :: proc(data: string) -> string {
    // Copy lines while skipping "$ ramal:" metadata.
}

This keeps the PHITS file suitable for the solver while retaining richer metadata in the managed record.

The save sequence

The save process is:

  1. Finish pending editor interaction.
  2. Generate canonical PHITS text.
  3. Merge it with preserved source text.
  4. Add the Design title.
  5. Write the managed record.
  6. Atomically replace the solver input.
  7. Mark the Design clean.
  8. Update the source and baseline.
sequenceDiagram
    participant Editor
    participant Generator
    participant Merger
    participant Record
    participant SolverFile

    Editor->>Generator: Generate current PHITS
    Generator->>Merger: Merge with imported source
    Merger->>Record: Write managed document
    Merger->>SolverFile: Write solver-compatible input
    SolverFile-->>Editor: Design is saved

The managed record and solver input are kept together so they describe the same Design.

Atomic file replacement

The solver input is replaced atomically:

storage_replace_atomic(
    app.design_scene.phits.path,
    transmute([]byte)design_solver_input(managed),
)

Atomic replacement means RAMAL-EBX writes a complete temporary file and then swaps it into place.

The user should see either:

  • The previous complete file.
  • The new complete file.

They should not see a half-written PHITS document.

This follows the safe storage principles introduced in Chapter 1.

Loading a saved Design

When reopening a Design, RAMAL-EBX reads the managed record:

record, read_ok := design_record_read(record_path)
if !read_ok {
    return false
}

It then imports and resolves the stored input again:

if !design_phits_import(
    app,
    transmute([]byte)record.input,
    allow_incomplete = true,
) {
    return false
}

The target metadata is restored afterward if it was declared separately in the record.

The Design baseline

After a successful load or save, RAMAL-EBX stores a baseline:

baseline := phits_input_generate(
    &app.design_scene,
    context.temp_allocator,
    allow_incomplete = true,
)

Later, if the user changes a cell or source setting, the new generated document can be compared against this baseline.

This lets the merger decide whether to preserve imported source text or replace a managed section.

Checking whether a Design changed

design_matches_saved_input regenerates the current Design and compares it with the stored record.

The comparison includes:

  • Merged PHITS text.
  • Design title.
  • Target metadata.

If they match, the Design is still equivalent to its saved form.

This is useful when deciding whether reload is safe.

Design and Case are different

A Design is mutable:

Design
├── Can be edited
├── Can be reloaded
├── Can be renamed
├── Can be saved again
└── Can generate new PHITS input

A Case is a frozen snapshot:

Case
├── Copies the Design state
├── Has no editing history
├── Runs from a fixed input
└── Does not change when the Design changes

The scene clone used for a Case copies the scientific state but not undo history:

result.geometry = geometry_clone(&source.geometry)
result.undo_history = make([dynamic]^Scene_State, 0)
result.redo_history = make([dynamic]^Scene_State, 0)

This is the bridge to Chapter 4: Immutable Case, Job, Attempt, and Result Lifecycle.

A complete beginner workflow

To author a Design:

  1. Import a PHITS input or start with an empty scene.
  2. Let RAMAL-EBX parse materials, surfaces, cells, source settings, and tallies.
  3. Resolve PHITS references into internal IDs.
  4. Edit the structured scene.
  5. Run Design validation.
  6. Generate canonical PHITS text.
  7. Merge generated sections with preserved source sections.
  8. Save the managed record and solver input.
  9. Create a Case when the Design is ready to freeze.

The central idea is:

PHITS text
    ↓
structured editable Design
    ↓
validated canonical PHITS
    ↓
managed record + solver input
    ↓
immutable Case

Where this code lives

The main responsibilities are divided across several files.

phits_document.odin handles:

  • PHITS section blocks.
  • Managed section detection.
  • Source preservation.
  • Merging imported and generated text.

phits.odin handles:

  • PHITS state.
  • Source settings.
  • Tallies.
  • Validation.
  • PHITS card generation.
  • Canonical input generation.

design_phits.odin handles:

  • PHITS import parsing.
  • Reference resolution.
  • Design validation.
  • Generated-input verification.
  • Design synchronization.
  • Design save and reload.

design.odin handles:

  • Design records.
  • Design titles.
  • Solver-compatible input.
  • Design catalog entries.
  • Creating, renaming, loading, and deleting Designs.

scene_state.odin handles:

  • Scene initialization.
  • Scene cloning.
  • Clearing geometry and PHITS state.
  • Removing editor history from frozen copies.

A beginner’s mental model

Think of the pipeline as a careful translation workshop:

Imported PHITS document
        ↓
Translator reads known sections
        ↓
References are connected
        ↓
Validator checks the whole design
        ↓
Editor changes structured values
        ↓
Generator writes canonical PHITS
        ↓
Merger restores unknown source sections
        ↓
Managed record and solver file are saved

The structured Design is the working model.

The managed document is the durable authoring record.

The solver input is the PHITS-compatible output.

Conclusion

You learned that:

  • A Design is the editable scientific input for a future simulation.
  • PHITS text is parsed into structured geometry and simulation state.
  • References are resolved from PHITS IDs to internal IDs.
  • Unknown sections and raw geometry can be preserved.
  • Validation checks materials, surfaces, cells, transforms, sources, tallies, and IDs.
  • Canonical PHITS input is generated from the structured Design.
  • Imported text and generated text are merged carefully.
  • Managed records preserve RAMAL metadata.
  • Solver files remove RAMAL-only comments.
  • Atomic replacement protects saved files.
  • A Design remains mutable until it is copied into an immutable Case.

The Design Authoring Pipeline connects human editing with reliable PHITS input generation.

Next, we will examine how a Design becomes an immutable simulation record in Immutable Case, Job, Attempt, and Result Lifecycle.


Generated by AI Codebase Knowledge Builder