# `qlat.field_selection` — Lattice Site Selection and Shuffle Plans
Source: `qlat/qlat/field_selection.pyx`
> **Note:** Update this document when updating the source file.
## Outline
1. [Overview](#overview)
2. [`PointsSelection` Class](#pointsselection-class)
- [Constructors](#constructors)
- [Properties](#properties)
- [Indexing and Iteration](#indexing-and-iteration)
- [Set Operations](#set-operations)
- [Serialization and I/O](#serialization-and-io)
- [MPI Broadcast](#mpi-broadcast)
- [Hashing and Pickle](#hashing-and-pickle)
3. [`FieldSelection` Class](#fieldselection-class)
- [Constructors](#fieldselection-constructors)
- [Properties](#fieldselection-properties)
- [Selection Modification](#selection-modification)
- [Set Operations](#fieldselection-set-operations)
- [Indexing and Iteration](#fieldselection-indexing-and-iteration)
- [I/O and Pickle](#fieldselection-io-and-pickle)
4. [`SelectedShufflePlan` Class](#selectedshuffleplan-class)
- [Constructors](#selectedshuffleplan-constructors)
- [Properties](#selectedshuffleplan-properties)
- [Shuffle Methods](#shuffle-methods)
5. [Module-Level Functions](#module-level-functions)
6. [Examples](#examples)
---
## Overview
`field_selection` provides the mechanism for working with subsets of lattice
sites in a distributed (MPI) environment. Two complementary selection classes
are defined:
- **`PointsSelection`** — a list of global coordinates (`xg`) representing
selected sites. Supports multiple distribution types: Global (`"g"`),
Full (`"f"`), Local (`"l"`), Random (`"r"`), and Other (`"o"`).
- **`FieldSelection`** — a per-site rank array stored as a `Field`. A site is
selected if its rank is `>= 0`. Provides efficient lookup from global
coordinate to local index.
**`SelectedShufflePlan`** builds communication plans for redistributing
selected points between different distribution types (e.g., Local to Global,
Local to Random, or time-slice shuffles). It is used internally by
`SelectedField` and `SelectedPoints` operations.
```python
import qlat as q
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
psel = q.PointsSelection(total_site)
psel.set_rand(total_site, 10, q.RngState("seed"))
print(psel.n_points) # 10
print(psel.points_dist_type) # "g"
fsel = q.FieldSelection(psel, q.Geometry(total_site))
print(fsel.n_elems) # 10
q.end_with_mpi()
```
---
## `PointsSelection` Class
A selection of lattice sites represented as a list of 4-D global coordinates.
### Constructors
### `PointsSelection()`
Create an empty, uninitialized selection.
### `PointsSelection(total_site: Coordinate)`
Create a zero-length selection with the given total lattice dimensions.
| Parameter | Type | Description |
|---|---|---|
| `total_site` | `Coordinate` | Total lattice dimensions |
### `PointsSelection(total_site, xg_arr)`
Create from an explicit list of coordinates.
| Parameter | Type | Description |
|---|---|---|
| `total_site` | `Coordinate` | Total lattice dimensions |
| `xg_arr` | `int`, `Coordinate`, `numpy.ndarray`, or `list` | If `int`, treated as `n_points`; if `Coordinate`, a single point; if `ndarray`, shape `(n_points, 4)`; if `list` of `Coordinate` |
### `PointsSelection(total_site, xg_arr, points_dist_type)`
Same as above but also set the distribution type.
| Parameter | Type | Description |
|---|---|---|
| `points_dist_type` | `str` | One of `"g"`, `"f"`, `"l"`, `"r"`, `"o"` |
### `PointsSelection(geo: Geometry)`
Create a full selection (all sites) with `points_dist_type = "f"`.
### `PointsSelection(fsel: FieldSelection)`
Create a local selection from a `FieldSelection` (`points_dist_type = "l"`).
### `PointsSelection(fsel, ssp)`
Create by shuffling a `FieldSelection` according to a `SelectedShufflePlan`.
### `PointsSelection(psel: PointsSelection)`
Copy constructor.
### `PointsSelection(psel, ssp, is_reverse=False)`
Create by shuffling an existing `PointsSelection` according to a
`SelectedShufflePlan`.
---
### Properties
### `points_dist_type -> str`
Get or set the distribution type. Values:
- `"g"` — Global: all points known on every node.
- `"f"` — Full: every lattice site selected.
- `"l"` — Local: points distributed across nodes (each node holds its local subset).
- `"r"` — Random: points randomly distributed across nodes.
- `"o"` — Other.
### `total_site -> Coordinate`
Get or set the total lattice dimensions.
### `geo -> Geometry`
A `Geometry` constructed from `total_site` with no halo expansion.
### `n_points -> int`
Number of selected points on this node.
### `xg_arr -> numpy.ndarray`
The global coordinates of all selected points as a `(n_points, 4)` array of
`int32`. Setting this property accepts an `int` (number of points), a single
`Coordinate`, an `ndarray`, or a `list` of coordinates.
---
### Indexing and Iteration
### `__getitem__(idx)` / `__setitem__(idx, val)`
Index into the underlying `xg_arr` via `numpy.asarray`.
### `__iter__()`
Iterate over selected points, yielding `Coordinate` objects.
### `__len__()`
Return `n_points`.
### `coordinate_from_idx(idx: int) -> Coordinate`
Return the global coordinate at linear index `idx`.
---
### Set Operations
### `intersect(fsel: FieldSelection) -> PointsSelection`
Return a new `PointsSelection` containing only points that are also selected
by `fsel`.
### `is_containing(sel_small) -> bool`
Return `True` if every point in `sel_small` (a `PointsSelection` or
`FieldSelection`) is contained in `self`.
### `is_containing_psel(psel_small: PointsSelection) -> bool`
Check containment for a `PointsSelection`.
### `is_containing_fsel(fsel_small: FieldSelection) -> bool`
Check containment for a `FieldSelection`.
---
### Serialization and I/O
### `save(path: str, *, is_sync_node=True)`
Save to file. When `is_sync_node=True`, writes via synchronized I/O
(`save_points_selection_info`); otherwise writes per-node
(`save_points_selection`).
### `load(path: str, geo=None, *, is_sync_node=True)`
Load from file. For `.lati` files, `geo` is optional (total_site is stored
in the file). For other formats, `geo` is required.
### `save_str() -> bytes`
Serialize to a byte string (only node 0 returns data).
### `load_str(content: bytes)`
Deserialize from a byte string (only node 0 needs the data; result is
broadcast).
### `to_lat_data() -> LatDataInt`
Convert to `LatDataInt` format.
### `from_lat_data(ld: LatDataInt)`
Load from `LatDataInt`.
---
### MPI Broadcast
### `bcast(root=0) -> PointsSelection`
Broadcast the selection from `root` to all nodes using native MPI broadcast.
### `bcast_via_ld(root=0) -> PointsSelection`
Broadcast via `LatDataInt` serialization (alternative implementation).
---
### Hashing and Pickle
### `hash_sha256() -> str`
Return a SHA-256 hash that is consistent across all nodes. For `"g"`
distribution, returns the local hash if all nodes agree; otherwise hashes
the gathered data from all nodes.
### `__getstate__` / `__setstate__`
Pickle support. Only works correctly on a single node or when all nodes hold
the same data.
---
## `FieldSelection` Class
A site-level selection stored as a rank field. Each local site has a rank
value: `rank >= 0` means selected, `rank == -1` means not selected.
### Constructors
### `FieldSelection()`
Create an empty, uninitialized selection.
### `FieldSelection(geo: Geometry, rank=-1)`
Create a uniform selection. `rank=0` selects all sites; `rank=-1` (default)
selects no sites.
### `FieldSelection(psel: PointsSelection, geo=None)`
Create from a `PointsSelection`. Requires `psel.points_dist_type in ["l", "f", "g"]`.
---
### Properties
### `geo -> Geometry`
The geometry of the underlying rank field.
### `total_site -> Coordinate`
The total lattice dimensions.
### `n_elems -> int`
Number of selected elements on this node.
---
### Selection Modification
### `update()`
Rebuild internal indices from the `f_rank` field. Must be called after
modifying `f_rank` directly (e.g., via buffer view).
### `set_empty(geo: Geometry)`
Set an empty selection (all ranks = -1) with the given geometry.
### `set_uniform(geo: Geometry, val=0)`
Set a uniform selection. `val=0` selects all sites; `val=-1` deselects all.
### `set_rand(total_site: Coordinate, n_per_tslice: int, rs: RngState)`
Randomly select `n_per_tslice` sites per time slice.
### `set_rand_psel(total_site, n_per_tslice, rs, psel=None)`
Same as `set_rand`, then additionally include all points from `psel`.
### `add_psel(psel: PointsSelection, rank_psel=...)`
Add points from a `PointsSelection` to the selection. Points already selected
with a lower rank keep their existing rank.
### `add_fsel(fsel: FieldSelection)`
Add points from another `FieldSelection`. Points already selected with a
lower rank keep their existing rank.
---
### Set Operations
### `intersect_with(fsel: FieldSelection)`
Modify `self` to contain only sites also selected by `fsel`. More efficient
if `self` is smaller.
### `intersect(fsel: FieldSelection) -> FieldSelection`
Return a new `FieldSelection` containing only sites selected by both.
Does not modify `self`.
### `is_containing(sel_small) -> bool`
Return `True` if every point in `sel_small` is contained in `self`.
### `is_containing_psel(psel_small) -> bool` / `is_containing_fsel(fsel_small) -> bool`
Specialized containment checks.
### `to_psel() -> PointsSelection`
Convert to a Global-distribution `PointsSelection`.
### `to_psel_local() -> PointsSelection`
Convert to a Local-distribution `PointsSelection`.
---
### Indexing and Iteration
### `__getitem__(idx)` / `__setitem__(idx, val)`
Index into the rank array via `numpy.asarray`. Modifying values requires
calling `update()` afterwards.
### `__iter__()`
Iterate over selected sites, yielding `Coordinate` objects.
### `__len__()`
Return `n_elems`.
### `idx_from_coordinate(xg: Coordinate) -> int`
Look up the local index for a global coordinate. Returns the index in the
selection (not the field index).
### `coordinate_from_idx(idx: int) -> Coordinate`
Return the global coordinate for the selected element at index `idx`.
---
### I/O and Pickle
### `save(path: str) -> int`
Write the selection to a file. Returns total bytes written.
### `load(path: str) -> int`
Read the selection from a file. Returns total bytes read.
### `__getstate__` / `__setstate__`
Pickle support. Only works on a single node.
---
## `SelectedShufflePlan` Class
Builds an MPI communication plan for redistributing `SelectedPointsChar`
data between different point distribution types. The plan is reusable and
can shuffle forward or in reverse.
### Constructors
### `SelectedShufflePlan()`
Create an empty (identity) plan.
### `SelectedShufflePlan("l_from_g", psel_src, root)`
Shuffle from Global to Local distribution. `root` is the node that holds
the full global point list.
### `SelectedShufflePlan("l_from_g", psel_src_list, root_list)`
Batch version: multiple point selections with corresponding root nodes.
### `SelectedShufflePlan("g_from_l", psel_src, root, geo)`
Shuffle from Local to Global distribution.
### `SelectedShufflePlan("g_from_l", psel_src_list, root_list, geo_src_list)`
Batch version.
### `SelectedShufflePlan("r_from_l", psel_src, geo, rs)`
Shuffle from Local to Random distribution.
### `SelectedShufflePlan("r_from_l", psel_src_list, geo_src_list, rs)`
Batch version.
### `SelectedShufflePlan("dist_r_from_l", psel_src, geo, rs, id_node_list)`
Shuffle from Local to Random, restricted to nodes in `id_node_list`.
### `SelectedShufflePlan("t_slice_from_l", psel_src_list, geo_src_list)`
Shuffle from Local to Local by time slice (transpose data across time
slices).
### `SelectedShufflePlan("dist_t_slice_from_l", psel_src, geo, num_field)`
Distributed time-slice shuffle. Matches `"t_slice_from_l"` for use cases
like spatial smearing where propagators and gauge fields need compatible
shuffles.
---
### Properties
### `points_dist_type_send -> str`
Distribution type of the source data.
### `points_dist_type_recv -> str`
Distribution type of the destination data.
### `num_selected_points_send -> int`
Number of points on the send side.
### `num_selected_points_recv -> int`
Number of points on the receive side.
### `psel_send_list -> list` / `psel_recv_list -> list`
Lists of `PointsSelection` objects for send and receive sides.
### `geo_send_list -> list` / `geo_recv_list -> list`
Lists of `Geometry` objects for send and receive sides (computed lazily).
### `fsel_send_list -> list` / `fsel_recv_list -> list`
Lists of `FieldSelection` objects derived from the point selections
(computed lazily; only populated for Local/Full distribution types).
---
### Shuffle Methods
### `shuffle(sp_src: SelectedPointsChar, *, is_reverse=False) -> SelectedPointsChar`
Shuffle a single `SelectedPointsChar` using this plan. If `is_reverse=True`,
shuffles in the reverse direction.
### `shuffle_list(sp_src_list, *, is_reverse=False) -> list`
Shuffle a list of `SelectedPointsChar` objects.
### `shuffle_sp(cls, src, *, is_reverse=False)`
Shuffle a single field object (`SelectedPointsBase`, `SelectedFieldBase`, or
`FieldBase`). Converts to `SelectedPointsChar` internally, shuffles, and
converts back to type `cls`.
### `shuffle_sp_list(cls, src_list, *, is_reverse=False)`
Shuffle a list of field objects.
---
## Module-Level Functions
### `mk_xg_field(geo: Geometry) -> FieldInt`
Create a `FieldInt` containing the global coordinate index for each local
site.
### `get_psel_single(total_site, xg=None) -> PointsSelection`
Return a cached single-point selection. If `xg` is `None`, uses
`[-1, -1, -1, -1]`.
### `get_psel_tslice(total_site, *, t_dir=3) -> PointsSelection`
Return a cached time-slice selection. For `t_dir=3`, selects all spatial
sites at each time value. For `t_dir=2`, selects along the z-direction.
### `is_matching_fsel(fsel1, fsel2) -> bool`
Check if two `FieldSelection` objects have matching selection patterns.
---
## Examples
### Creating a PointsSelection
```python
import qlat as q
import numpy as np
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
# From a numpy array of coordinates
xg_arr = np.array([[0, 0, 0, t] for t in range(8)], dtype=np.int32)
psel = q.PointsSelection(total_site, xg_arr)
print(f"points_dist_type: {psel.points_dist_type}") # "g"
print(f"n_points: {psel.n_points}") # 8
# From a single coordinate
psel_single = q.PointsSelection(total_site, q.Coordinate([1, 2, 3, 4]))
print(f"n_points: {psel_single.n_points}") # 1
q.end_with_mpi()
```
### Creating a FieldSelection
```python
import qlat as q
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
geo = q.Geometry(total_site)
# Select all sites
fsel_all = q.FieldSelection(geo, 0)
print(f"n_elems: {fsel_all.n_elems}") # 512
# Select no sites
fsel_none = q.FieldSelection(geo)
print(f"n_elems: {fsel_none.n_elems}") # 0
# Random selection
rs = q.RngState("test")
fsel_rand = q.FieldSelection()
fsel_rand.set_rand(total_site, 4, rs)
print(f"n_elems: {fsel_rand.n_elems}") # 4 * 8 = 32
q.end_with_mpi()
```
### Using PointsSelection with a Geometry
```python
import qlat as q
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
geo = q.Geometry(total_site)
# Full selection (every site)
psel_full = q.PointsSelection(geo)
print(f"points_dist_type: {psel_full.points_dist_type}") # "f"
print(f"n_points: {psel_full.n_points}") # 512
q.end_with_mpi()
```
### Time-Slice Selection
```python
import qlat as q
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
# Get a selection of all spatial sites per time slice
psel_tslice = q.get_psel_tslice(total_site)
print(f"n_points: {psel_tslice.n_points}") # 8 (one entry per time slice)
q.end_with_mpi()
```
### Intersecting Selections
```python
import qlat as q
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
geo = q.Geometry(total_site)
# Create a random field selection
rs = q.RngState("seed")
fsel = q.FieldSelection()
fsel.set_rand(total_site, 2, rs)
# Create a global point selection
psel = q.PointsSelection(total_site)
psel.set_rand(total_site, 20, q.RngState("seed2"))
# Intersect: keep only points in psel that are also in fsel
psel_intersect = psel.intersect(fsel)
print(f"psel n_points: {psel.n_points}")
print(f"fsel n_elems: {fsel.n_elems}")
print(f"psel_intersect n_points: {psel_intersect.n_points}")
q.end_with_mpi()
```
### Building a Shuffle Plan
```python
import qlat as q
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
geo = q.Geometry(total_site)
# Create a local point selection from a random field selection
rs = q.RngState("test")
fsel = q.FieldSelection()
fsel.set_rand(total_site, 2, rs)
psel_local = q.PointsSelection(fsel)
# Build a shuffle plan: local -> global
ssp = q.SelectedShufflePlan("g_from_l", psel_local, 0, geo)
print(f"send dist type: {ssp.points_dist_type_send}")
print(f"recv dist type: {ssp.points_dist_type_recv}")
q.end_with_mpi()
```
### Saving and Loading a PointsSelection
```python
import qlat as q
q.begin_with_mpi([[1, 1, 1, 1]])
total_site = q.Coordinate([4, 4, 4, 8])
rs = q.RngState("seed")
psel = q.PointsSelection(total_site)
psel.set_rand(total_site, 4, rs)
# Save to file
psel.save("/tmp/test_psel.lati")
# Load from file
psel_loaded = q.PointsSelection()
psel_loaded.load("/tmp/test_psel.lati", q.Geometry(total_site))
assert psel == psel_loaded
q.end_with_mpi()
```