Skip to content

Core: motor, operating point, limits, sweeps

axfluxmdo.operating_point

Operating point definition.

OperatingPoint dataclass

OperatingPoint(speed_rpm: float, current_rms: float, dc_bus_voltage: float = 48.0, ambient_temp_c: float = 25.0)

A single electrical/mechanical operating condition.

axfluxmdo.limits

Design limit values used to evaluate constraints.

Limits dataclass

Limits(max_winding_temp_c: float = 140.0, max_electrical_freq_hz: float = 1000.0, max_current_density_a_mm2: float = 10.0, max_line_voltage_v: float | None = None, max_core_flux_density_t: float | None = None, max_magnet_temp_c: float | None = None)

Constraint limit values.

None fields are resolved at evaluation time from the motor and operating point: max line voltage from the DC bus (SVPWM, V_dc/sqrt(2)), max core flux from the steel's saturation knee, and max magnet temperature from the magnet grade rating.

axfluxmdo.sweeps

Parameter sweeps over motor design variables.

Sweeps never mutate the input motor: each point is evaluated on a dataclasses.replace variant, the same mechanism later optimization drivers use.

SweepResult dataclass

SweepResult(parameter: str, values: list, results: list[AnalyticalResult])

to_arrays

to_arrays(*fields: str) -> dict[str, np.ndarray]

Extract named result fields (keys of AnalyticalResult.to_dict()) as arrays.

Source code in src/axfluxmdo/sweeps.py
50
51
52
53
54
55
56
def to_arrays(self, *fields: str) -> dict[str, np.ndarray]:
    """Extract named result fields (keys of ``AnalyticalResult.to_dict()``) as arrays."""
    dicts = [r.to_dict() for r in self.results]
    out = {self.parameter: np.asarray(self.values)}
    for name in fields:
        out[name] = np.array([d[name] for d in dicts])
    return out

plot

plot(fields: Sequence[str] = ('torque_nm', 'efficiency', 'core_loss_w', 'winding_temp_c'), show: bool = False)

Plot each field against the swept parameter on a grid of axes.

Source code in src/axfluxmdo/sweeps.py
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
def plot(
    self,
    fields: Sequence[str] = (
        "torque_nm",
        "efficiency",
        "core_loss_w",
        "winding_temp_c",
    ),
    show: bool = False,
):
    """Plot each field against the swept parameter on a grid of axes."""
    import matplotlib.pyplot as plt

    n = len(fields)
    ncols = 2
    nrows = (n + ncols - 1) // ncols
    fig, axes = plt.subplots(nrows, ncols, figsize=(5 * ncols, 3.2 * nrows), squeeze=False)
    data = self.to_arrays(*fields)
    x = data[self.parameter]
    for ax, name in zip(axes.flat, fields, strict=False):
        ax.plot(x, data[name], "o-")
        ax.set_xlabel(self.parameter)
        ax.set_ylabel(name)
        ax.grid(True, alpha=0.3)
    for ax in axes.flat[n:]:
        ax.set_visible(False)
    fig.tight_layout()
    if show:
        plt.show()
    return fig

replace_field

replace_field(motor: AxialFluxMotor, name: str, value) -> AxialFluxMotor

dataclasses.replace supporting one level of dotted paths.

"tolerances.runout_m" replaces the field on the nested frozen dataclass and re-attaches it — the mechanism for sweeping and optimizing manufacturing imperfections.

Source code in src/axfluxmdo/sweeps.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def replace_field(motor: AxialFluxMotor, name: str, value) -> AxialFluxMotor:
    """``dataclasses.replace`` supporting one level of dotted paths.

    ``"tolerances.runout_m"`` replaces the field on the nested frozen
    dataclass and re-attaches it — the mechanism for sweeping and optimizing
    manufacturing imperfections.
    """
    if name.count(".") > 1:
        raise ValueError(
            f"dotted field path {name!r} exceeds one level; only 'outer.inner' "
            "(e.g. 'tolerances.runout_m') is supported"
        )
    if "." not in name:
        return dataclasses.replace(motor, **{name: value})
    outer, inner = name.split(".", 1)
    nested = dataclasses.replace(getattr(motor, outer), **{inner: value})
    return dataclasses.replace(motor, **{outer: nested})

sweep_parameter

sweep_parameter(motor: AxialFluxMotor, op: OperatingPoint, name: str, values: Sequence, model: Model | None = None) -> SweepResult

Evaluate the motor across values of one design field.

name may be a dotted path into a nested dataclass field, e.g. "tolerances.runout_m" (use with model=AnnularModel()).

Source code in src/axfluxmdo/sweeps.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def sweep_parameter(
    motor: AxialFluxMotor,
    op: OperatingPoint,
    name: str,
    values: Sequence,
    model: Model | None = None,
) -> SweepResult:
    """Evaluate the motor across values of one design field.

    ``name`` may be a dotted path into a nested dataclass field, e.g.
    ``"tolerances.runout_m"`` (use with ``model=AnnularModel()``).
    """
    model = model or AnalyticalModel()
    results = [model.evaluate(_replace_field(motor, name, v), op) for v in values]
    return SweepResult(parameter=name, values=list(values), results=results)

sweep_pole_pairs

sweep_pole_pairs(motor: AxialFluxMotor, op: OperatingPoint, pole_pairs: Sequence[int] = tuple(range(4, 24, 2)), model: Model | None = None) -> SweepResult

The SPEC MVP question #1: performance tradeoffs across pole-pair count.

Source code in src/axfluxmdo/sweeps.py
107
108
109
110
111
112
113
114
def sweep_pole_pairs(
    motor: AxialFluxMotor,
    op: OperatingPoint,
    pole_pairs: Sequence[int] = tuple(range(4, 24, 2)),
    model: Model | None = None,
) -> SweepResult:
    """The SPEC MVP question #1: performance tradeoffs across pole-pair count."""
    return sweep_parameter(motor, op, "pole_pairs", list(pole_pairs), model)