---
myst:
  html_meta:
    description: "Learn why ScalarField slicing in GWexpy preserves 4D structure, how metadata survives indexing, and when squeeze() is safe."
---

# Scalar Field Slicing Guide (Why 4D is Preserved)

:::{note}
**Who should read this page?**
Refer to this guide if you use `ScalarField` and have the following questions:
- Why does `field[0]` remain 4D instead of becoming a 1D array?
- Why do I get `Shape Mismatch` errors during indexing operations?
- When is it safe to use `squeeze()`, and when should it be avoided?
:::

This guide explains why `ScalarField` **always maintains its 4-dimensional structure** during indexing operations. This differs from the standard behavior of NumPy or GWpy and is designed as an "invariant" to ensure the integrity of multidimensional physical data.

(scalarfield-slicing-4d-persistence)=
## Why Always Maintain "4D"?

A `ScalarField` represents a physical "field" over four axes: `(axis 0, x, y, z)`. Axis 0 is the **time axis** — or the **frequency axis** after a time-domain FFT (`fft_time()`) — while axes 1–3 are the **spatial axes** `x, y, z`, each of which can be transformed to its k-space counterpart `kx, ky, kz` (`fft_space()`). So a concrete field is laid out as `(t, x, y, z)`, `(f, x, y, z)`, `(f, kx, ky, kz)`, and so on. The reasons for not reducing dimensions (Rank Loss) like NumPy does are based on the **Four Pillars of Persistence**:

1.  **Domain Protection**: Reducing dimensions causes the loss of metadata associated with those axes (e.g., `t0`/`f0` for axis 0, `dx`/`dy`/`dz` for the spatial axes), making it impossible to map back to the original physical space.
2.  **FFT/Transformation Consistency**: Always being 4D allows immediate multidimensional operations like `fft()`, `ifft()`, and `spatial_filter()` on any slice.
3.  **Stream Processing Safety**: The number of dimensions remains constant when passing objects between functions, improving program robustness.
4.  **Broadcast Consistency**: Dimensional alignment (`reshape`) intent becomes explicit in the code because the object is always 4D.

(scalarfield-slicing-comparison-diagram)=
### NumPy vs GWexpy Slicing Behavior

![4D Dimension Persistence comparison between NumPy and GWexpy](../../_static/images/scalarfield_slicing_4d_persistence.png)

| Operation Example | Result Shape (NumPy) | Result Shape (Field) | Physical Meaning |
| :--- | :---: | :---: | :--- |
| `field[5]` | 3D (Rank Loss) | **4D (1, X, Y, Z)** | Snapshot at a single axis-0 (time) index (metadata preserved) |
| `field[:, 3, 3, 3]` | 1D (Rank Loss) | **4D (T, 1, 1, 1)** | Time-series at a fixed spatial point `(x, y, z)` (metadata preserved) |
| `field[10:20]` | 4D (Maintained) | **4D (10, X, Y, Z)** | Axis-0 (time) interval extraction |

---

:::{caution}
**Critical Warning: Using `.squeeze()`**
When you call `squeeze()` to reduce dimensions, **all metadata (coordinate information) associated with the removed axes is completely lost.**
Since you can no longer reconstruct the correct physical axes for operations like `field.fft()`, we strongly recommend using `squeeze()` only at the "final output stage," such as for plotting or saving to CSV.
:::

---

(scalarfield-slicing-practical-examples)=
## Practical Operation Examples

### 1. Slicing Behavior

- Purpose: show that axis metadata stays attached after indexing
- Input: a `ScalarField` with shape `(100, 50, 10, 10)`
- Output: `snapshot` and `plane` objects that keep singleton axes

```python
from gwexpy.fields import ScalarField
import numpy as np

# (t, x, y, z) = (100, 50, 10, 10)
field = ScalarField(np.zeros((100, 50, 10, 10)), ...)

# Extract a snapshot at a specific time
snapshot = field[50]
# Shape becomes (1, 50, 10, 10), preserving the time-axis metadata.

# Extract a spatial cross-section (x-y plane at a fixed z)
plane = field[:, :, :, 2]
# Shape becomes (100, 50, 10, 1), preserving the z-axis metadata.
```

### 2. When to Reduce Dimensions (`squeeze`)

If you intentionally want to treat the data as 1D or 2D (e.g., for plotting or input to an external library), explicitly call `.squeeze()`.

- Purpose: reduce dimensions only at the final output step
- Input: a sliced `ScalarField`
- Output: a 1D array suitable for plotting or external libraries

```python
# Get time-series at a specific spatial point for plotting
point_ts = field[:, 2, 5, 5]      # (100, 1, 1, 1)
actual_ts = point_ts.squeeze(axis=(1, 2, 3))    # (100,) - compatible with TimeSeries
```

Specifying `axis=` makes it explicit which singleton axes you are removing and reduces the risk of dropping a dimension you still need.

### 3. Broadcasting Considerations

Since `ScalarField` is always 4D, you must match the shape when adding or subtracting NumPy arrays.

- Purpose: avoid shape mismatch during broadcasting
- Input: a `ScalarField` plus a 1D calibration array
- Output: an explicit `reshape(...)` that documents intent

```python
# ❌ Bad Example: Attempting to add a 1D array directly
field + np.array([1, 2, 3])  # Shape mismatch

# ✅ Good Example: Reshaping to the correct dimensions
calibration = np.array([1, 2, 3]).reshape(3, 1, 1, 1) # three coefficients along axis 0 (time/frequency)
field + calibration
```

The `reshape(3, 1, 1, 1)` form means "these 3 values lie along axis 0 (the time/frequency axis), and the same values are broadcast across the x, y, and z axes."

---

(scalarfield-slicing-faq)=
## FAQ

### Q: Isn't it inconvenient for 1D calculations if it's always 4D?
**A:** `ScalarField` is designed for handling data that has spatial and temporal extent as a "field." If you are working with simple, single-channel time-series data, we recommend using the `TimeSeries` class from the start.

### Q: What happens if I extract a scalar like `ScalarField[0, 0, 0, 0]`?
**A:** If all indices are scalars, a standard Python scalar or NumPy scalar is returned.

(scalarfield-slicing-next-to-read)=
## Next to Read

- [ScalarField introduction tutorial](field_scalar_intro)
- [Field module API reference](../../reference/api/fields)
- [Numerical stability](../../explanation/numerical_stability.md) - Precision management for 4D operations
- [Architecture and Data Flow](../../explanation/architecture.md) - Where the Field API fits in the broader design
