Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions docs/python/types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
sidebar_position: 5
---

# Input types (Python / NumPy) and numerical precision

`pyamtrack` is a Python interface to the C/C++ **libamtrack** library. Many functions accept either a **single number** or a **set of numbers** (a Python list or a NumPy array). Internally, most continuous physical quantities are computed using C/C++ **double precision** (`double`).
Copy link
Contributor

Choose a reason for hiding this comment

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

set of numbers : set is something that is not ordered, I would rather say "vector"/"matrix".

Copy link
Contributor

Choose a reason for hiding this comment

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

I would also avoid here mentioning the double precision stuff.

what I'd expect here is a few sentences like "The aim of this page is to familiarize the user with the input/output datatypes for functions available in libamtrack...."


This page explains what you can pass to `pyamtrack` functions and how to avoid the most common numerical pitfalls—especially when using `numpy.float32`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't expose the numpy.float32 so much, rather put it at the end as a separate header 1 entry.


---

## Quick recommendations (safe defaults)

If you only read one section, read this:

- For **single values** (energy, LET, etc.): use Python `float`
Copy link
Contributor

Choose a reason for hiding this comment

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

scientists like exponential notation, mentioning number like 1e-3 would be nice as well

(example: `150.0`, not `np.float32(150)`).
- For **arrays of values**: use NumPy arrays with `dtype=np.float64`.
Copy link
Contributor

Choose a reason for hiding this comment

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

See:

>>> np.array([10.0, 15.]).dtype
dtype('float64')

by default we get float64.

Its safe to recommend np.array and give such example.

- For **IDs** (material IDs, model IDs): use Python `int` or integer NumPy arrays (`np.int32` / `np.int64`).
- Avoid `numpy.float32` / `dtype=np.float32` for inputs to physics calculations unless you really know you can tolerate reduced precision.
Copy link
Contributor

Choose a reason for hiding this comment

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

that should go to bottom of the page


---

## 1) What inputs are accepted?

Most numeric functions accept these input forms:

### A. A single number
Use when you want one result.

```python
import pyamtrack
pyamtrack.converters.beta_from_energy(150.0)
```

### B. A list of numbers
Use when you want results for multiple values.

```python
import pyamtrack
energies = [50.0, 100.0, 150.0]
pyamtrack.stopping.electron_range(energies, material=1, model="tabata")
```

### C. A NumPy array (`ndarray`)
Recommended for performance and clarity, especially for longer vectors.

```python
import numpy as np
import pyamtrack

energies = np.asarray([50.0, 100.0, 150.0], dtype=np.float64)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend numpy array when plotting is needed and you need hundreds of values, like:

energies_MeV = np.linspace(start=10, stop=1000, num=500)

using numpy array just for few numbers is weird.

You can mention plotting, its frequent use case

pyamtrack.stopping.electron_range(energies, material=1, model="tabata")
```

**Note:** Some functions require NumPy input arrays to be **1‑dimensional** (a simple vector).

---

## 2) Continuous values vs IDs (different “kinds” of inputs)
Copy link
Contributor

Choose a reason for hiding this comment

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

Continous vs discrete ?


In `pyamtrack`, it helps to think of inputs in two categories:

### A. Continuous physical quantities (use float64)
Examples: energies, ranges, stopping power values, etc.

- Recommended dtype for arrays: **`np.float64`**
- Recommended type for scalars: Python **`float`**

### B. Integer identifiers (use integers)
Examples: `material` ID, sometimes model ID.

- Recommended dtype for arrays: **`np.int32`** or **`np.int64`**
- Recommended type for scalars: Python **`int`**

Example (material IDs as an integer array):

```python
import numpy as np
import pyamtrack

energies = np.asarray([50.0, 100.0, 150.0], dtype=np.float64)
materials = np.asarray([1, 1, 1], dtype=np.int32)
Copy link
Contributor

Choose a reason for hiding this comment

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

here for materials I'd use simple list, not an array.

I'd also use the opportunity to say to the user that you can mix types, like one argument being np.array, another list and another string.


pyamtrack.stopping.electron_range(energies, material=materials, model="tabata")
Copy link
Contributor

Choose a reason for hiding this comment

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

do we support strings as model ?

```

---

## 3) Why `numpy.float32` is not recommended

You will often see NumPy arrays created with `dtype=np.float32` to save memory. However, for `pyamtrack` this can be a bad default.

### What happens
Even though the C/C++ code uses `double`, if you store inputs as `float32`:

- the values are already rounded to about **7 significant digits**,
- and converting that float32 value to C/C++ `double` **cannot recover the lost precision**.

So you can get slightly different results compared to float64 inputs.

### What to do instead
Prefer `float64`:

```python
import numpy as np

# Not recommended for numerically sensitive inputs
x32 = np.asarray([0.1, 0.2, 0.3], dtype=np.float32)

# Recommended
x64 = np.asarray([0.1, 0.2, 0.3], dtype=np.float64)

# If you receive float32 from elsewhere, convert early:
x = np.asarray(x32, dtype=np.float64)
```

---

## 4) NumPy dtype vs C type (quick reference)

This is a short summary based on the NumPy documentation section
“Relationship Between NumPy Data Types and C Data Types”.
([numpy.org](https://numpy.org/doc/stable/user/basics.types.html?utm_source=openai))

| Recommended NumPy dtype | NumPy “C-like” name | Rough C type | Notes |
|---|---|---|---|
| `np.int32` | *(no single alias on every platform)* | usually `int32_t` | fixed width (portable) |
| `np.int64` | `np.longlong` | `long long` | fixed width (portable) |
| `np.float32` | `np.single` | `float` | ~7 significant digits |
| `np.float64` | `np.double` | `double` | ~15–16 significant digits |

---

## 5) “One value” vs “many values”: how results are returned

Many functions behave like this:

- If you pass a **single value**, you get a **single value** back (Python `float`).
- If you pass a **list** or **NumPy array**, you get a **vector of results** back (often a NumPy array).
Copy link
Contributor

Choose a reason for hiding this comment

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

always a numpy array ?


Example:

```python
import numpy as np
import pyamtrack

print(pyamtrack.converters.beta_from_energy(150.0)) # scalar -> scalar

arr = np.asarray([50.0, 100.0, 150.0], dtype=np.float64)
print(pyamtrack.converters.beta_from_energy(arr)) # array -> array
```

---

## 6) Practical checklist for users

Before running calculations, quickly check:

1. Are my **continuous values** stored as `float64`?
- `np.asarray(x, dtype=np.float64)`
2. Are my **ID-like inputs** (materials/models) integers?
- `np.asarray(ids, dtype=np.int32)` or plain `int`
3. Am I accidentally using `float32` because of upstream data loading?
- If yes: upcast early to `float64`.

---
Loading