axfluxmdo.models¶
Fidelity layers 1–2 plus losses, thermal, constraints, and efficiency maps.
axfluxmdo.models.analytical ¶
Fast analytical sizing model (Phase 1, Layer 1).
Electromagnetic formulation¶
The air-gap field comes from the magnet load line (materials.magnetic),
its fundamental being B1 = (4/pi) * B_g * sin(alpha_m * pi / 2). The
fundamental flux per pole over the annulus is
Phi_p = (2/pi) * B1 * A_g / (2p) = B1 * A_g / (pi * p)
and the peak phase flux linkage lambda = k_w * N * Phi_p. Torque and back-EMF are BOTH derived from this same flux linkage (sinusoidal machine, currents aligned with EMF / MTPA for a surface-PM machine):
E_rms = omega_e * lambda / sqrt(2) (equivalently 4.44 f N k_w Phi_p)
T = m * p * lambda * I_rms / sqrt(2)
so the power identity m * E_rms * I_rms == T * omega_m holds to machine
precision by construction. The SPEC's shear-stress form
T = (2 pi sigma_t / 3)(r_o^3 - r_i^3) is the equivalent integral formulation;
it differs from the flux-linkage form only by the geometry factor
(2/3)(r_o^3 - r_i^3) / (r_m (r_o^2 - r_i^2)) (~1.09 for the reference motor).
The result reports shear_stress_pa as the average shear implied by the
computed torque, T / ((2 pi / 3)(r_o^3 - r_i^3)).
Phase-1 simplifications (documented, lifted in later phases): slotless field (Carter factor 1 unless supplied), single air gap, magnets evaluated at ambient + 40 C (NOT coupled to the solved winding temperature — estimate the magnet thermal path separately for temperature-sensitive designs), inductive voltage drop neglected, zero mechanical loss by default.
AnalyticalResult
dataclass
¶
AnalyticalResult(torque_nm: float, back_emf_v_rms: float, electrical_frequency_hz: float, airgap_flux_density_t: float, shear_stress_pa: float, phase_resistance_ohm: float, current_density_a_mm2: float, copper_loss_w: float, core_loss_w: float, mechanical_loss_w: float, output_power_w: float, input_power_w: float, efficiency: float, winding_temp_c: float, mass_kg: float, mass_breakdown: dict[str, float], constraints: list[ConstraintRecord])
Evaluated performance of one motor at one operating point (SI + named units).
to_dict ¶
to_dict() -> dict[str, float]
Flat scalar dict for sweeps/DataFrames; keys match constraint names.
Source code in src/axfluxmdo/models/analytical.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | |
AnalyticalModel ¶
AnalyticalModel(limits: Limits | None = None, carter_factor: float = 1.0)
Layer-1 analytical sizing model.
carter_factor multiplies the magnetic air gap in the load line (default
1.0 = slotless). The Phase-4 FEA validation found the uncorrected load
line OVERESTIMATES the gap field (about -11% on the under-magnet mean and
-7% on the fundamental for the reference motor, from inter-magnet leakage
and fringing), and measured an effective k_C = 1.44 for the slotted
variant — measure your own with
:func:axfluxmdo.validation.measured_carter_factor and pass it here for
corrected predictions.
Source code in src/axfluxmdo/models/analytical.py
199 200 201 | |
build_constraints ¶
build_constraints(motor: AxialFluxMotor, op: OperatingPoint, limits: Limits, *, winding_temp_c: float, f_e_hz: float, current_density_a_mm2: float, back_emf_v_rms: float, phase_resistance_ohm: float, b_yoke_t: float, magnet_temp_c: float) -> list[ConstraintRecord]
The Phase-1 constraint set, shared by all fidelity layers.
None limits resolve from the motor/operating point (see Limits).
Voltage: V_required = sqrt(3)(E + IR) vs V_dc/sqrt(2) (SVPWM line-line
fundamental). The INDUCTIVE drop I*X_L is neglected — fine at low
electrical frequency, but materially optimistic when f_e is high AND the
voltage margin is tight (e.g. ~100 uH at several kRPM adds tens of
volts). Measure or estimate winding inductance before trusting a
near-binding voltage constraint at high speed.
Source code in src/axfluxmdo/models/analytical.py
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 | |
axfluxmdo.models.annular_2p5d ¶
2.5D annular slice model (Phase 2, Layer 2).
The disk machine is split into n_slices radial annuli; the Phase-1
flux-linkage chain is evaluated per slice and summed:
dA_k = pi * (r_{k+1}^2 - r_k^2) (exact annulus areas)
dlambda_k = k_w * N * B1(r_k) * dA_k / (pi * p)
lambda = fsum(dlambda_k)
T = m*p*lambda*I/sqrt(2), E_rms = p*omega_m*lambda/sqrt(2)
Because torque and EMF come from the same summed flux linkage, the power
identity mEI == T*omega and the energy balance hold at any slice count, and
with radius-uniform parameters the model reproduces AnalyticalModel to
machine precision (n_slices=1 matches on every to_dict() key — the
parity tests pin this).
Radius dependence enters through: the local axisymmetric gap (offset +
coning from motor.tolerances), the local magnet arc (magnet_shape ==
"rectangular" gives alpha(r) = min(1, alpha_m * r_m / r)), the local pole
pitch in the yoke flux proxy (low pole counts saturate first at the outer
radius), optional edge fringing, and the 1/r current loading. Runout enters
as analytic circumferential averages of the load line
(:mod:axfluxmdo.materials.magnetic); note the convexity consequence — mean
torque slightly increases with runout, the penalty being the 1/rev
modulation reported as torque_ripple_proxy and the axial pull
axial_force_n. Rotor tilting moment is not modeled in Phase 2.
Copper resistance and the thermal network stay lumped (single RC), per SPEC.
AnnularResult
dataclass
¶
AnnularResult(torque_nm: float, back_emf_v_rms: float, electrical_frequency_hz: float, airgap_flux_density_t: float, shear_stress_pa: float, phase_resistance_ohm: float, current_density_a_mm2: float, copper_loss_w: float, core_loss_w: float, mechanical_loss_w: float, output_power_w: float, input_power_w: float, efficiency: float, winding_temp_c: float, mass_kg: float, mass_breakdown: dict[str, float], constraints: list[ConstraintRecord], torque_ripple_proxy: float, axial_force_n: float, n_slices: int, slice_radii_m: ndarray, slice_airgap_b_t: ndarray, slice_b1_t: ndarray, slice_torque_nm: ndarray, slice_shear_pa: ndarray, slice_yoke_b_t: ndarray, slice_current_loading_a_m: ndarray)
Bases: AnalyticalResult
AnalyticalResult plus imperfection metrics and per-slice field/torque profiles.
to_dict ¶
to_dict() -> dict[str, float]
Phase-1 keys (stable interface) plus the Phase-2 scalars.
Source code in src/axfluxmdo/models/annular_2p5d.py
78 79 80 81 82 83 | |
AnnularModel ¶
AnnularModel(limits: Limits | None = None, n_slices: int = 32, edge_fringe_length_m: float = 0.0, k_bearing: float = 0.0, k_windage: float = 0.0, carter_factor: float = 1.0)
Layer-2 annular slice model.
Parameters¶
n_slices : torque/EMF are exact at any count (the flux-linkage sum is
additive); only core loss and the saturation constraint are
discretized, and both are smooth in radius — 32 slices is well within
0.1 % of converged.
edge_fringe_length_m : optional flux derating length at the annulus edges,
B1 = (1-exp(-(r-r_i)/L))(1-exp(-(r_o-r)/L)). Zero (default) disables
it and preserves exact Phase-1 parity.
k_bearing, k_windage : mechanical loss coefficients (see
:func:~axfluxmdo.models.losses.mechanical_loss), default zero.
carter_factor : multiplies the magnetic gap in the load line (default 1.0
= slotless). Phase-4 FEA measured k_C = 1.44 for the slotted reference
motor; see :func:axfluxmdo.validation.measured_carter_factor.
Source code in src/axfluxmdo/models/annular_2p5d.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
axfluxmdo.models.efficiency_map ¶
Efficiency map over the speed-torque plane.
The Layer-1/2 models have no saturation, so torque is exactly linear in
current at fixed geometry (flux linkage is independent of current and speed;
the magnet temperature is the fixed Phase-1/2 assumption). One probe
evaluation yields torque-per-amp, then each grid cell is a single
model.evaluate at the inverted current. Cells whose result violates any
constraint are masked NaN, with the first violated constraint recorded.
EfficiencyMap
dataclass
¶
EfficiencyMap(speeds_rpm: ndarray, torques_nm: ndarray, efficiency: ndarray, current_rms_a: ndarray, copper_loss_w: ndarray, core_loss_w: ndarray, winding_temp_c: ndarray, feasible: ndarray, binding_constraint: ndarray)
Gridded results over (torque, speed); arrays are (n_torque, n_speed).
compute_efficiency_map ¶
compute_efficiency_map(motor: AxialFluxMotor, base_op: OperatingPoint, *, max_speed_rpm: float, max_torque_nm: float, n_speed: int = 40, n_torque: int = 40, model: Model | None = None) -> EfficiencyMap
Evaluate the motor over a speed-torque grid (bus voltage and ambient from base_op).
Source code in src/axfluxmdo/models/efficiency_map.py
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 | |
axfluxmdo.models.losses ¶
Loss components and mass rollup — small pure functions reused by all models.
phase_resistance ¶
phase_resistance(motor: AxialFluxMotor, temp_c: float) -> float
DC phase resistance at winding temperature T: rho(T) * N * L_turn / A_cond.
Source code in src/axfluxmdo/models/losses.py
9 10 11 12 | |
copper_loss ¶
copper_loss(phases: int, current_rms: float, resistance_ohm: float) -> float
Total winding copper loss P_cu = m * I_rms^2 * R_phase.
Source code in src/axfluxmdo/models/losses.py
15 16 17 | |
steinmetz_core_loss ¶
steinmetz_core_loss(motor: AxialFluxMotor, f_hz: float, b_peak_t: float) -> float
Stator core loss: specific Steinmetz loss times stator core mass.
Source code in src/axfluxmdo/models/losses.py
20 21 22 | |
mechanical_loss ¶
mechanical_loss(omega_m_rad_s: float, k_bearing: float = 0.0, k_windage: float = 0.0) -> float
Bearing + windage placeholder: k_b * omega + k_w * omega^3 (defaults zero).
Fidelity item for Phase 2; the field exists so the efficiency rollup and result schema do not change later.
Source code in src/axfluxmdo/models/losses.py
25 26 27 28 29 30 31 | |
stator_core_mass ¶
stator_core_mass(motor: AxialFluxMotor) -> float
Stator yoke lamination mass including stacking factor.
Source code in src/axfluxmdo/models/losses.py
34 35 36 | |
core_flux_density_proxy ¶
core_flux_density_proxy(motor: AxialFluxMotor, b_gap_t: float) -> float
Peak stator-yoke flux density proxy for core loss and saturation checks.
Half of one pole's air-gap flux returns through the yoke cross-section (active_length * stator_core_thickness * stacking), giving
B_yoke = B_g * alpha_m * tau_p / (2 * t_core * stacking)
Source code in src/axfluxmdo/models/losses.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
mass_rollup ¶
mass_rollup(motor: AxialFluxMotor) -> dict[str, float]
Component masses in kg; 'total' includes the structure factor.
Source code in src/axfluxmdo/models/losses.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
axfluxmdo.models.thermal_rc ¶
Steady-state lumped thermal network (single winding-to-ambient resistance).
Copper loss rises linearly with winding temperature through rho(T), so the steady-state fixed point
T_w = T_amb + R_theta * (P_cu(T_w) + P_other)
P_cu(T) = P_cu_ref * (1 + alpha * (T - T_ref))
has the closed-form solution
T_w = [T_amb + R_theta * (P_cu_ref * (1 - alpha * T_ref) + P_other)]
/ [1 - R_theta * P_cu_ref * alpha]
Thermal runaway occurs when the denominator is non-positive: each kelvin of temperature rise adds more than a kelvin's worth of extra copper loss.
solve_winding_temperature ¶
solve_winding_temperature(p_cu_ref_w: float, ref_temp_c: float, alpha_per_c: float, p_other_w: float, r_theta_k_per_w: float, ambient_c: float) -> ThermalSolution
Closed-form steady-state winding temperature with R(T) coupling.
p_cu_ref_w: copper loss evaluated at ref_temp_c. p_other_w: other losses deposited in the winding node (e.g. a fraction of core loss), assumed temperature-independent.
Source code in src/axfluxmdo/models/thermal_rc.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
solve_winding_temperature_iterative ¶
solve_winding_temperature_iterative(p_cu_ref_w: float, ref_temp_c: float, alpha_per_c: float, p_other_w: float, r_theta_k_per_w: float, ambient_c: float, iterations: int = 50) -> float
Fixed-point iteration reference implementation (used to verify the closed form).
Source code in src/axfluxmdo/models/thermal_rc.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
axfluxmdo.models.constraints ¶
Constraint evaluation records.
Constraint names deliberately match the flat keys of
AnalyticalResult.to_dict() (e.g. winding_temp_c) so that Phase 3's
optimize_pareto can parse SPEC-style constraint strings like
"winding_temp_c < 140" against result dictionaries.
make_upper_bound ¶
make_upper_bound(name: str, value: float, limit: float) -> ConstraintRecord
Build a '<=' constraint record with normalized margin.
Source code in src/axfluxmdo/models/constraints.py
32 33 34 35 36 37 38 39 40 41 | |