# Instructions for AI assistants using PiP
All AI assistants working with Physics in Parallel must read this document
before proposing or changing an integration. It applies to choosing and using
PiP APIs, adapting an existing scientific project, and explaining numerical
behavior. Follow the consuming project's instructions as well.
## 1. Understand the application before editing
Identify the model, units, scalar types, state layout, timestep or update rule,
randomness, boundary conditions, and observable outputs. Read the relevant
source and existing tests. Separate established requirements from assumptions;
ask about scientific choices that cannot be inferred from the project.
PiP is a Rust numerical library. The application owns its science, configuration,
experiment orchestration, files, checkpoints, aggregation, and visualization.
A Workflow integration can provide some of that application infrastructure, but
PiP itself does not execute JSON study descriptions or parameter sweeps.
Do not promise that adopting PiP eliminates model code. The benefit is reusing
numerical machinery and reducing the code and context an assistant must manage.
## 2. Start with the smallest public API
| Arrays and linear algebra | `prelude::basic`, `math` | [Containers](guide/4-tensors-matrices-and-vectors.md) |
| Explicit random values | `rng`, basic sampling exports | [Randomness](guide/5-randomness-and-sampling.md) |
| Boundaries, lattices, pairs | `space`, basic exports | [Spatial tools](guide/6-spaces-lattices-and-pairs.md) |
| Particle models | `prelude::models`, `models` | [Particle state](guide/7-particle-state.md) and [dynamics](guide/8-forces-integration-and-thermostats.md) |
| Data interchange | Validated Serde implementations | [Serialization](guide/9-serialization-and-numpy.md) |
| Execution policy | `threading` and the caller's Rayon pool | [Parallelism](guide/10-parallelism-and-performance.md) |
| Custom numerical engines | Independent `prelude::advanced` | [Extensions](guide/11-advanced-extensions.md) |
The preludes are independent. Import only what is needed. Domain roots are the
authoritative API; private modules are not extension points. Consult the
[API index](reference/api-index.md) and source signatures before generating calls.
Do not invent convenience methods, legacy aliases, or generalized adapters.
## 3. Respect dependency and ownership boundaries
Use the published package `physics_in_parallel` with the exact alpha version
chosen by the consuming project. These guides target `=4.1.1-alpha`. Do not
replace a public dependency with an unpublished checkout or a path override.
Do not modify upstream crates without the user's explicit permission.
Keep application policy in the application. A missing file-layout helper or
study configuration type does not justify extending the numerical library.
Prefer a small application adapter with explicit input/output contracts.
For a PiP library change, inspect repository instructions, validate the complete
change, and successfully push the commit and any release tag before publishing.
Then update downstream dependencies to the online version. Do not silently yank
older releases as part of a patch.
## 4. Preserve shapes, backends, and scalar semantics
- Choose `Backend::Dense` or `Backend::Sparse` explicitly; PiP does not silently
switch representation according to occupancy.
- `Matrix` uses row/column coordinates. `VectorList::zeros(dim, count, backend)`
has logical shape `[count, dim]`; do not reverse its constructor arguments.
- Allocating arithmetic preserves the receiver's backend. Reusable-output
methods preserve the supplied output's backend.
- Distinguish elementwise multiplication from matrix multiplication.
- Validate dimensions, indexing, scalar width, and integer overflow assumptions.
Do not downcast values just to make an example compile.
- Dense uninitialized builders require all elements to be written before
completion. Sparse unwritten entries represent zero.
- Raw sparse iteration is not a stable ordering. If order is scientifically
relevant, use the public operation's documented order or sort explicitly.
Do not change storage, traversal, reduction, or conversion order under a claim
that an implementation is automatically equivalent. Preserve the required
numerical contract and measure memory and time separately.
## 5. Make randomness explicit
Record the RNG method and seed. Use `ResolvedRng::new` for specified seeds and
only request entropy when the application explicitly calls for a fresh run.
There is no implicit entropy fallback.
For indexed operations, preserve the documented index/sweep coordinates and
method compatibility. An identical seed alone does not establish equivalence
when the method, sampling distribution, draw order, or indexing changes.
Retain stateful stochastic objects across steps when their counters are part of
the stream, including a Langevin thermostat. Reconstructing one each iteration
can restart its stream.
Do not promise bitwise equality across arbitrary CPUs, operation orderings, or
methods. State the actual repeatability requirement and test it with the selected
API and execution configuration.
## 6. Preserve spatial and particle invariants
Validate geometry, axis order, spacing, boundary behavior, and particle selection.
A lattice initialization RNG and its realized values are distinct information;
keep what the project needs to replay or inspect a run.
Neighbor lists use nonperiodic raw Euclidean distances. Wrapping coordinates
does not implement minimum-image periodic neighbor distances. Do not use that
combination as a periodic algorithm without an application implementation that
actually supplies the required geometry.
Rebuild a neighbor list after positions change and before querying it. Arbitrary
position edits are not automatically tracked. Reuse its buffers through the
available rebuild/collection APIs when doing so preserves this contract.
Use the canonical particle template and attributes when they match the model.
Use `set_mass` to preserve mass and inverse-mass consistency, and the public
alive/rigid setters for those flags. Validate custom attributes and selection
rather than assuming every object participates in every operation.
## 7. Treat dynamics as scientific choices
Forces accumulate into acceleration. Clear acceleration once before assembling
the force contributions for a step, then apply all intended contributions.
Do not clear it between forces or accidentally carry it into the next step.
Keep the chosen integrator, timestep, force order, boundary order, thermostat
order, and observation interval. Explicit and semi-implicit Euler are different
methods. Combining operations does not automatically produce a higher-order
splitting. Explain a deliberate scientific change before implementing it.
Spring and power-law networks are keyed by particle pairs. Insertion can replace
an existing law and returns its previous value; handle duplicates according to
the project's intent. Bulk updates validate transactionally, and malformed or
duplicate serialized network entries are rejected.
Zero inverse mass can be meaningful in force application. Thermostats and
kinetic observations require valid positive finite inverse mass for the
particles they include. Do not infer identical admissibility rules across APIs.
Retain thermostat state. Its amplitude-validation failure leaves velocities and
the stochastic counter unchanged; propagate errors rather than fabricating an
output or retrying with altered physical parameters.
## 8. Use the caller's thread pool
PiP uses the active Rayon pool and never creates or resizes one. Identify the
pool owner, especially inside Workflow or another runner. Configure one shared
pool before concurrent work begins.
`set_max_threads` limits worker participation requested by a PiP operation. It
is not a scheduler-wide admission limit. Configure it once at startup rather
than changing process-wide policy in concurrent cases. Separate pools can
oversubscribe the machine.
Set release profiles in the consuming workspace root. SIMD dispatch and portable
fallbacks are internal. Do not add CPU-specific flags or modify floating-point
semantics without a deployment requirement and measurements.
## 9. Keep serialization and persistence separate
Use PiP's validated Serde representations directly. Applications own filenames,
atomic writes, checkpoint lifecycle, metadata, schema migration, and recovery.
A successful decode establishes representation validity, not scientific units,
equilibrium, or suitability for a new experiment.
The tensor-family wire format is schema version 2. A crate version is not a
wire-schema version. Do not edit a saved schema number to bypass validation.
PiP does not read 3.x or 4.0.0-alpha.1 tensor formats. Convert old data with the
version that wrote it and validate the resulting scientific values.
The optional `python/numpy_support.py` reads supported direct PiP JSON. It does
not read Workflow raw/NPY manifests. Sparse input materializes a dense NumPy
array; use its element limit and account for memory. The limit is not a bound on
JSON parser memory. Preserve integer precision, including object dtype where
needed, and do not claim a zero-copy conversion.
## 10. Validate correctness before performance
Compile and run the smallest representative example. Compare numerical results
with an independent reference or a captured baseline that includes provenance.
Choose tolerances from the method and observable; do not enlarge them merely to
make a migration pass.
Check the properties relevant to the model: shapes and backend, conserved
quantities where applicable, expected boundary behavior, stochastic replay,
selection, finite values, malformed inputs, and serialization round trips.
For stochastic algorithm changes, distinguish deterministic replay from
statistical agreement and use the appropriate evidence.
Tensor sums and dot products use documented logical ordering. Particle kinetic
observations use fixed reduction blocks for large inputs; those reductions can
round differently from a single sequential sum. Consult
[numerical behavior](PERFORMANCE.md#numerical-behavior) before promising equality.
Measure performance in release mode on representative sizes, recording hardware,
thread count, backend, compiler settings, and what work was timed. Include
allocation and conversion costs when they occur in the real application. Keep
historical [benchmark evidence](BENCHMARKS.md) distinct from new measurements.
## Migrate an existing project to PiP
This procedure covers a general-purpose Rust scientific project adopting PiP.
Upgrading an existing PiP dependency is covered separately in
[release migration notes](RELEASES.md).
### Step 1: Capture the scientific baseline
Read the model and entry points. Record units, shapes, scalar types, equations,
update order, boundary conditions, seeds/methods, observables, and current
execution policy. Save one small deterministic run and one representative case.
Record the source revision, dependency lockfile, input, and expected comparisons.
### Step 2: Inventory reusable infrastructure
Classify each component as application science, application policy, or numerical
machinery that matches an actual PiP API. Use this map as a starting point:
| Handwritten dense/sparse arrays | `Tensor`, `Matrix`, `VectorList` | Shape, axis order, scalar type, backend, arithmetic semantics. |
| Random initialization and sampling | Explicit RNG and sampling APIs | Method, seed, draw/index order, distribution. |
| Boundary or lattice bookkeeping | Public spatial types | Geometry, spacing, coordinate order, boundary physics. |
| Particle columns and state flags | `PhysObj` template and attributes | Meaning, units, mass pair, participation rules. |
| Matching force laws | Spring or power-law networks | Parameters, pair identity, signs, accumulation. |
| Matching time integration | Public integrators/thermostats | Method, timestep, splitting, retained stochastic state. |
| Per-component thread pools | Caller-owned shared Rayon pool | Resource budget and concurrent work requirements. |
| Ad hoc numerical encoding | Validated Serde plus application persistence | Precision, schema provenance, recovery policy. |
A conceptual resemblance is insufficient. Read signatures and numerical
contracts. Keep an existing algorithm when PiP has no matching public primitive
or replacing it would change the intended physics.
### Step 3: Choose one bounded integration
Start with a container calculation, initialization stage, or small model kernel.
Add the published exact dependency and import the smallest API layer. Keep the
application's configuration and entry points stable while comparing results.
Avoid broad compatibility frameworks and wholesale rewrites.
### Step 4: Adapt state deliberately
Translate shapes and storage once at the boundary. Validate data before entering
the numerical loop. Preserve mass/inverse-mass invariants and explicit flags.
Use reusable outputs when available and appropriate. Do not add repeated
array-to-vector-to-array conversions inside hot loops.
If changing layout or backend, test that migration separately from changing the
physical algorithm. Save enough provenance to identify which change affected a
result.
### Step 5: Preserve randomness and dynamics
Carry the seed, RNG method, and stream/index coordinates into the new component.
Keep persistent stochastic state alive across steps. Compare deterministic
operations before investigating stochastic differences.
Preserve force accumulation, integration order, timestep, boundary behavior,
and selection. Do not add a thermostat, change Euler variants, or introduce
periodic neighbors merely because an example uses a related operation.
### Step 6: Integrate execution and persistence
Locate the existing pool owner and reuse that pool; configure PiP's participation
cap at startup. Keep workflow scheduling and experiment-level concurrency with
the application or runner.
Use Serde at the existing persistence boundary. Keep application metadata and
schema-conversion policy outside PiP. Convert old records through a deliberate
migration, retaining the originals until validated. Update any Python reader to
the correct data format, accounting for dense allocation and integer precision.
### Step 7: Compare science, then cost
Run the baseline comparison and relevant invariants. Explain expected rounding
or stream differences with evidence. Exercise malformed input and any recovery
path affected by the integration. Test the actual selected thread configuration.
Only after correctness checks pass, compare release-mode time and memory at
representative problem sizes. Include conversion costs. Keep a replacement only
when its behavior and tradeoffs meet the project's requirements.
### Step 8: Complete the cutover
Replace subsequent components one at a time using the same checks. Remove
obsolete duplicate machinery after the replacement is validated. Commit the
manifest and lockfile, update user instructions and data migration notes, and
record the new baseline.
Summarize which code now uses PiP, which scientific/application components remain
project-owned, what was tested, and any unresolved numerical or performance
limits. A successful build alone is not a completed scientific migration.
## Final review checklist
- Public APIs and exact published dependency verified; no unauthorized upstream edits.
- Shapes, scalar precision, backends, seeds, and physics retained or explicitly changed.
- One identified pool owner; persistence and orchestration owned by the application.
- Relevant examples and numerical comparisons pass with stated tolerances.
- README, configuration instructions, and migration notes describe the actual result.
Continue with the [numbered guide](README.md) or
[complete simulation](guide/3-first-simulation.md).