Metadata-Version: 2.4
Name: pyffag
Version: 0.3.0
Summary: DA-based FFAG accelerator tracking using differential algebra
Author-email: Eremey Valetov <evv@msu.edu>
License: MIT
Project-URL: Repository, https://github.com/evvaletov/pyffag
Keywords: FFAG,accelerator,beam physics,differential algebra,transfer map
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: daceypy>=1.3.0
Requires-Dist: numpy>=1.24
Dynamic: license-file

# pyffag

DA-based FFAG accelerator tracking using differential algebra.

Built on [daceypy](https://pypi.org/project/daceypy/) for arbitrary-order
transfer map computation through FFAG sector magnets via integration of the
exact midplane Hamiltonian.

## Installation

```bash
pip install pyffag
```

## Quick start

```python
import numpy as np
from daceypy import DA
from pyffag import sector_map, compose_sequence, compose_n, tune, twiss
from pyffag.constants import kinetic_to_brho, M_PROTON

# 250 MeV proton FFAG ring: 8 FD doublet cells
DA.init(5, 2)  # DA order 5, 2 variables (x, px)
Brho = kinetic_to_brho(250.0, M_PROTON)

# F magnet: horizontally focusing sector, 25 degrees
F = sector_map([0.9, 2.0], Brho, angle=np.radians(25.0))

# D magnet: horizontally defocusing sector, 20 degrees
D = sector_map([0.9, -3.0], Brho, angle=np.radians(20.0))

cell = compose_sequence([F, D])
ring = compose_n(cell, 8)

print(f"Cell tune: {twiss(cell)['tune']:.4f}")
print(f"Ring tune: {tune(ring):.4f}")
```

## Features

- **Sector tracking** — exact curvilinear Hamiltonian integration through
  sector magnets with polynomial midplane field `By(x) = Σ B_k x^k`. Midplane
  2-DOF (`sector_map`) and 4-DOF (`sector_map_4d`, Maxwell-consistent
  off-midplane expansion).
- **Element maps** — exact drift, thin quadrupole / sextupole / octupole,
  edge kicks.
- **Ring operations** — map composition, `N`-fold composition, closed-orbit
  finding (Newton with DA Jacobian; see below).
- **Optics** — transfer matrix, tune, Twiss (β, α, γ), stability check,
  symplecticity error.
- **Shared lattice format** — JSON schema that defines a sequence of
  elements and drives `pyffag.build_map()`. Portable across pyffag versions
  and, when paired with external cross-validation harnesses, across codes.

## Lattice format (JSON)

```json
{
  "name": "fd_doublet",
  "brho": 2.0,
  "elements": [
    {"type": "sector", "B": [1.0, 2.0], "angle_deg": 15.0},
    {"type": "drift",  "length": 0.3},
    {"type": "sector", "B": [1.0, -1.0], "angle_deg": 15.0},
    {"type": "thin_sext", "kL": 0.05}
  ]
}
```

```python
import pyffag
from daceypy import DA

lattice = pyffag.load("fd_doublet.json")
DA.init(5, 2)
ring = pyffag.build_map(lattice)
print(pyffag.tune(ring), pyffag.twiss(ring))
```

Element types: `sector`, `drift`, `drift_paraxial`, `thin_quad`, `thin_sext`,
`thin_oct`, `edge_kick`. See `pyffag.lattice` for the full schema.

## Closed-orbit finding

```python
from daceypy import DA
from pyffag import sector_map, find_closed_orbit

DA.init(5, 2)
x_co, px_co, converged = find_closed_orbit(
    lambda: sector_map([1.0, 2.0], 2.0, angle=0.4363),
    x_guess=0.0, px_guess=0.0,
)
```

The solver expands the DA map internally about the current guess and uses
the DA Jacobian for a Newton step. Converges in one iteration for linear
maps; quadratic convergence for nonlinear.

## Physics

`sector_map()` integrates the midplane equations of motion in
Frenet-Serret (curvilinear) coordinates with arc length as the independent
variable. Sector magnets have radial edge faces (no edge focusing);
non-radial edges are modelled explicitly via `edge_kick`.

## Integration with danf

For nonlinear normal-form analysis (detuning, chromaticity, complex-basis
NF) use [danf](https://pypi.org/project/danf/):

```python
from danf import NormalForm, chromaticity_1d

nf = NormalForm(ring)
nf.compute()
print(nf.tunes, nf.detuning)
```

## License

MIT
