Skip to content

Config Module

TOML-based configuration module for pymagnet.

Provides a declarative interface to define magnet configurations, run field calculations, and generate plots from TOML files.

Usage

from pymagnet.config import run, load, validate

Run a full simulation from a TOML file

result = run("my_config.toml")

result["results"] is a list of dicts, one per [[grid]]/[[plot]] pair

Each contains: points, field, figure (if plot enabled)

Load and validate only (no calculation)

config = load("my_config.toml")

Validate and get error list

errors = validate("my_config.toml")

load(toml_path)

Load and validate a TOML configuration file.

Parameters:

Name Type Description Default
toml_path str | Path

Path to the TOML file.

required

Returns:

Type Description
SimulationConfig

SimulationConfig dataclass.

Raises:

Type Description
FileNotFoundError

If the file does not exist.

ValueError

If validation fails.

Source code in src/pymagnet/config/_loader.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def load(toml_path: str | Path) -> SimulationConfig:
    """Load and validate a TOML configuration file.

    Args:
        toml_path: Path to the TOML file.

    Returns:
        SimulationConfig dataclass.

    Raises:
        FileNotFoundError: If the file does not exist.
        ValueError: If validation fails.
    """
    toml_path = Path(toml_path)
    if not toml_path.exists():
        raise FileNotFoundError(f"Configuration file not found: {toml_path}")

    with open(toml_path, "rb") as f:
        raw = tomllib.load(f)

    config = _build_config(raw, toml_path.parent)

    errors = validate_config(config)
    if errors:
        msg = "Configuration validation failed:\n" + "\n".join(
            f"  - {e}" for e in errors
        )
        raise ValueError(msg)

    return config

run(toml_path, *, output_dir=None)

Load config, create magnets, calculate fields, and generate plots.

Each [[grid]]/[[plot]] pair produces one entry in the results lists.

Parameters:

Name Type Description Default
toml_path str | Path

Path to TOML configuration file.

required
output_dir str | Path | None

Output directory for saved figures. If None, figures are saved relative to the current working directory.

None

Returns:

Type Description
dict with keys

config: SimulationConfig magnets: list of magnet instances results: list of dicts, one per grid/plot pair, each containing: points, field, figure (if plot enabled), saved_to (if saved) force: dict with force/torque (if force enabled)

Source code in src/pymagnet/config/_runner.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def run(
    toml_path: str | Path, *, output_dir: str | Path | None = None
) -> dict[str, Any]:
    """Load config, create magnets, calculate fields, and generate plots.

    Each [[grid]]/[[plot]] pair produces one entry in the results lists.

    Args:
        toml_path: Path to TOML configuration file.
        output_dir: Output directory for saved figures. If None, figures are
            saved relative to the current working directory.

    Returns:
        dict with keys:
            config: SimulationConfig
            magnets: list of magnet instances
            results: list of dicts, one per grid/plot pair, each containing:
                points, field, figure (if plot enabled), saved_to (if saved)
            force: dict with force/torque (if force enabled)
    """
    config = load(toml_path)

    result: dict[str, Any] = {"config": config}

    # Resolve output directory
    out_path: Path | None = None
    if output_dir is not None:
        out_path = Path(output_dir)

    # Build magnets
    magnets = build_magnets(config)
    result["magnets"] = magnets

    # Process each grid/plot pair, grouping by figure_group where set
    results_list: list[dict[str, Any]] = []

    # Identify figure groups for 3D slice plots
    group_indices: dict[str, list[int]] = {}
    for i, pc in enumerate(config.plots):
        if pc.figure_group:
            group_indices.setdefault(pc.figure_group, []).append(i)

    processed: set[int] = set()

    for i, (gc, pc) in enumerate(zip(config.grids, config.plots, strict=False)):
        if i in processed:
            continue

        entry: dict[str, Any] = {}

        if config.dimension == "2D":
            points, field = _calculate_field_2d(gc)
            entry["points"] = points
            entry["field"] = field

            if pc.enabled and field is not None:
                fig = _plot_2d(pc, points, field)
                entry["figure"] = fig
                if pc.save_fig:
                    path = _save_figure_2d(fig, pc, i, config.name, out_path)
                    entry["saved_to"] = str(path)
        elif pc.figure_group and len(group_indices.get(pc.figure_group, [])) > 1:
            # Grouped 3D slices: render multiple grid/plot pairs onto one figure
            member_indices = group_indices[pc.figure_group]
            processed.update(member_indices)
            fig = _plot_3d_grouped(config, member_indices)
            entry["figure"] = fig
            # Save using first member's plot config
            first_pc = config.plots[member_indices[0]]
            if first_pc.save_fig:
                path = _save_figure_3d(
                    fig, first_pc, member_indices[0], config.name, out_path
                )
                entry["saved_to"] = str(path)
        else:
            if pc.enabled:
                fig, points, field = _plot_3d(gc, pc)
                entry["figure"] = fig
                entry["points"] = points
                entry["field"] = field
                if pc.save_fig:
                    path = _save_figure_3d(fig, pc, i, config.name, out_path)
                    entry["saved_to"] = str(path)
            else:
                points, field = _calculate_field_3d(gc)
                entry["points"] = points
                entry["field"] = field

        results_list.append(entry)

    result["results"] = results_list

    # Force calculation
    if config.force.enabled:
        result["force"] = _calculate_force(config, magnets)

    return result

validate(toml_path)

Validate a TOML configuration file without running it.

Parameters:

Name Type Description Default
toml_path str | Path

Path to the TOML file.

required

Returns:

Type Description
list[str]

List of error messages. Empty list means valid.

Source code in src/pymagnet/config/__init__.py
32
33
34
35
36
37
38
39
40
41
42
def validate(toml_path: str | Path) -> list[str]:
    """Validate a TOML configuration file without running it.

    Args:
        toml_path: Path to the TOML file.

    Returns:
        List of error messages. Empty list means valid.
    """
    config = load(toml_path)
    return validate_config(config)