Skip to content

Add new options to genesis4_par_to_data: wrap, z0, equal_weights, cut…#106

Open
ChristopherMayes wants to merge 4 commits intomasterfrom
genesis4_particles_features
Open

Add new options to genesis4_par_to_data: wrap, z0, equal_weights, cut…#106
ChristopherMayes wants to merge 4 commits intomasterfrom
genesis4_particles_features

Conversation

@ChristopherMayes
Copy link
Owner

@ChristopherMayes ChristopherMayes commented Jun 20, 2025

This adds new options to genesis4_par_to_data.

  • wrap: enables modulo-wrapping of theta
  • z0: starting z-position offset
  • slices: extract specific slice indices
  • equal_weights: resample particles to uniform charge
  • cutoff: skip sub-electron macroparticles
  • rng: user-supplied or seeded random number generator

ParticleGroup now uses that to load Genesis4 particles directly with these options:

image image image

This comment was marked as resolved.

Copy link
Contributor

Copilot AI left a comment

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 6 out of 9 changed files in this pull request and generated 11 comments.


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

Comment on lines +608 to +609
sample = round(s_spacing / ds_slice) # This should be an integer

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

sample = round(slicespacing / slicelength) is used as the upper bound for rng.integers(0, sample, ...), but there is no validation that the ratio is close to an integer and >= 1. If the ratio is non-integer (or < 0.5), rounding can silently change the intended smear behavior or make sample 0 and crash. Consider validating the ratio (e.g., np.isclose(...)) and raising a clear error when it's not a positive integer.

Suggested change
sample = round(s_spacing / ds_slice) # This should be an integer
# Validate that the slice spacing / length ratio is a positive integer
if ds_slice == 0:
raise ValueError("Genesis4 parameter 'slicelength' must be non-zero.")
ratio = s_spacing / ds_slice
if ratio <= 0:
raise ValueError(
f"Genesis4 parameters 'slicespacing' and 'slicelength' must define a positive ratio, "
f"got slicespacing={s_spacing!r}, slicelength={ds_slice!r} (ratio={ratio!r})."
)
sample_float = ratio
sample = int(round(sample_float)) # This should be an integer
if sample < 1 or not np.isclose(sample_float, sample, rtol=1e-6, atol=1e-12):
raise ValueError(
"Genesis4 parameter ratio 'slicespacing' / 'slicelength' must be a positive integer; "
f"got slicespacing={s_spacing!r}, slicelength={ds_slice!r}, "
f"ratio={sample_float!r}, rounded integer={sample!r}."
)

Copilot uses AI. Check for mistakes.
Comment on lines +641 to +648
current = pdata.current # I * s_spacing/c = Q
n1 = len(pdata.x)

# Convert current to weight (C)
# I * s_spacing/c = Q
# Single charge
q1 = current * s_spacing / c_light / n1

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

n1 = len(pdata.x) is used as a divisor when computing q1 = current * s_spacing / c_light / n1, but there is no guard for n1 == 0. If a slice group exists with zero macroparticles, this will raise a ZeroDivisionError before any filtering. Add an explicit check for empty slices and skip or error with a clear message.

Copilot uses AI. Check for mistakes.
Comment on lines +919 to +922
equal_weights: bool = False,
cutoff: float = 1.6e-19,
rng: Optional[np.random.Generator] = None,
) -> "ParticleGroup":
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

rng is documented as accepting a "Generator or seed", but the type annotation is Optional[np.random.Generator]. Since this is a public API, consider widening the type (e.g., to include int/np.random.BitGenerator) or adjusting the docstring so the signature and behavior match.

Copilot uses AI. Check for mistakes.
"cell_type": "markdown",
"metadata": {},
"source": [
"Normally Genesis4 populates the same number of particles in each slice, with each slice having its own weight (internally the `current`). In some cases it's simpler to have particles that have the same weight, so `ParticleGroup` further has an `equal_weights` option that samples particles from the slices according to the relative weights. Notice that there are fewer particles in this case, but that the projected densities are roughtly the same as above."
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Typo in notebook text: "roughtly" → "roughly".

Suggested change
"Normally Genesis4 populates the same number of particles in each slice, with each slice having its own weight (internally the `current`). In some cases it's simpler to have particles that have the same weight, so `ParticleGroup` further has an `equal_weights` option that samples particles from the slices according to the relative weights. Notice that there are fewer particles in this case, but that the projected densities are roughtly the same as above."
"Normally Genesis4 populates the same number of particles in each slice, with each slice having its own weight (internally the `current`). In some cases it's simpler to have particles that have the same weight, so `ParticleGroup` further has an `equal_weights` option that samples particles from the slices according to the relative weights. Notice that there are fewer particles in this case, but that the projected densities are roughly the same as above."

Copilot uses AI. Check for mistakes.
Comment on lines +465 to +469
# Allow for opening a file
if isinstance(h5, (str, Path)):
assert os.path.exists(h5), f"File does not exist: {h5}"
h5 = File(h5, "r")

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

genesis4_parfile_scalars (and genesis4_parfile_slice_groups) open an HDF5 file when given a path, but never close it. Since these helpers return plain Python objects, they can safely use a context manager (with File(...):) when opening internally to avoid leaking file descriptors in long-running processes.

Copilot uses AI. Check for mistakes.
Comment on lines +627 to +633
for sname in snames:
ix = int(sname[5:]) # Extract slice index

# Skip missing
if sname not in h5:
continue

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

ix = int(sname[5:]) assumes every group name in snames is of the form sliceXXXXXX. Since genesis4_parfile_slice_groups() currently returns all root keys except a small allow/deny list, any unexpected group name (e.g. non-slice metadata groups) would cause a ValueError before the later checks. Filtering snames to only those starting with "slice" (or guarding the int(...) conversion) would make this more robust.

Copilot uses AI. Check for mistakes.
Comment on lines +714 to +721
# Stack
x = np.hstack(xs)
px = np.hstack(pxs)
y = np.hstack(ys)
py = np.hstack(pys)
gamma = np.hstack(gammas)
z = np.hstack(zs)
weight = np.hstack(weights)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Before stacking with np.hstack, there is no check that any slices were actually loaded. If all requested slices are missing or skipped (e.g., due to cutoff), np.hstack(xs) will raise ValueError: need at least one array to concatenate without explaining the cause. Consider detecting the empty case and raising a clearer error (or returning an empty ParticleGroup if that's supported).

Copilot uses AI. Check for mistakes.
"outputs": [],
"source": [
"P = ParticleGroup(data=genesis4_par_to_data(\"data/genesis4.par.h5\"))\n",
"# ParticleGroup has a `smear` option that shifts particles in each slice by a random integer multiple of the slice spacing, so that the overall distrubution is smoother while preserving the bunching characteristics:"
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Typo in notebook text: "distrubution" → "distribution".

Suggested change
"# ParticleGroup has a `smear` option that shifts particles in each slice by a random integer multiple of the slice spacing, so that the overall distrubution is smoother while preserving the bunching characteristics:"
"# ParticleGroup has a `smear` option that shifts particles in each slice by a random integer multiple of the slice spacing, so that the overall distribution is smoother while preserving the bunching characteristics:"

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
genesis4 genesis4.in
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

run.sh is missing a shebang and will rely on the caller invoking it with bash. Add #!/usr/bin/env bash (and optionally set -euo pipefail) to make it executable and less error-prone when used as a standalone script.

Copilot uses AI. Check for mistakes.
Comment on lines +968 to +978
data = genesis4_par_to_data(
h5=h5,
smear=smear,
wrap=wrap,
z0=z0,
slices=slices,
equal_weights=equal_weights,
cutoff=cutoff,
rng=rng,
)
return cls(data=data)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

This introduces a new public constructor (ParticleGroup.from_genesis4) with multiple options (smear/wrap/slices/equal_weights/cutoff/rng), but there are currently no unit tests covering this behavior (there are existing ParticleGroup tests in tests/test_particlegroup.py). Adding at least a smoke test that loads docs/examples/data/genesis4/end.par.h5 (and checks particle count / total charge invariants for a couple option combinations) would help prevent regressions.

Copilot uses AI. Check for mistakes.
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.

2 participants