Skip to content

axfluxmdo.materials

axfluxmdo.materials.magnetic

Magnetic material properties: NdFeB magnet grades and electrical steel.

Steinmetz coefficients for M-19 29-gauge are tuned to reproduce typical manufacturer data (AK Steel / Proto Laminations): roughly 1.4-1.6 W/kg at 60 Hz, 1.5 T. The classical (two-term) Steinmetz model is used:

P_v = k_h * f * B**alpha + k_e * f**2 * B**2    [W/kg]

which assumes sinusoidal flux. Coefficients vary noticeably between sources and gauges; a unit test pins the 60 Hz / 1.5 T point to the datasheet band.

MagnetMaterial dataclass

MagnetMaterial(name: str, remanence_t: float, mu_r: float = 1.05, temp_coeff_br_per_c: float = -0.0012, density_kg_m3: float = 7500.0, max_operating_temp_c: float = 80.0)

Sintered permanent-magnet material (NdFeB unless noted).

remanence_at

remanence_at(temp_c: float) -> float

Temperature-derated remanence Br(T) = Br20 * (1 + alpha_Br * (T - 20)).

Source code in src/axfluxmdo/materials/magnetic.py
30
31
32
def remanence_at(self, temp_c: float) -> float:
    """Temperature-derated remanence Br(T) = Br20 * (1 + alpha_Br * (T - 20))."""
    return self.remanence_t * (1.0 + self.temp_coeff_br_per_c * (temp_c - 20.0))

ElectricalSteel dataclass

ElectricalSteel(name: str, density_kg_m3: float, k_h: float, alpha: float, k_e: float, b_sat_t: float = 1.6, stacking_factor: float = 0.95, mu_r: float = 4000.0)

Non-oriented electrical steel lamination with Steinmetz loss coefficients.

Loss model (per unit mass): P_v = k_h * f * Balpha + k_e * f2 * B**2.

core_loss_w_per_kg

core_loss_w_per_kg(f_hz: float, b_peak_t: float) -> float

Specific core loss at electrical frequency f and peak flux density B.

Source code in src/axfluxmdo/materials/magnetic.py
59
60
61
62
63
def core_loss_w_per_kg(self, f_hz: float, b_peak_t: float) -> float:
    """Specific core loss at electrical frequency f and peak flux density B."""
    if f_hz <= 0.0 or b_peak_t <= 0.0:
        return 0.0
    return self.k_h * f_hz * b_peak_t**self.alpha + self.k_e * f_hz**2 * b_peak_t**2

airgap_flux_density

airgap_flux_density(magnet: MagnetMaterial, magnet_thickness: float, air_gap: float, magnet_temp_c: float = 20.0, carter_factor: float = 1.0) -> float

Open-circuit air-gap flux density from the magnet load line.

Series magnetic circuit of a surface magnet over an air gap (slotless; Carter factor defaults to 1.0 in Phase 1, parameterized for later):

B_g = Br(T) * t_m / (t_m + mu_r * k_C * g)

Approaches Br(T) as g -> 0 (with mu_r -> 1), and falls off as the gap grows.

Source code in src/axfluxmdo/materials/magnetic.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def airgap_flux_density(
    magnet: MagnetMaterial,
    magnet_thickness: float,
    air_gap: float,
    magnet_temp_c: float = 20.0,
    carter_factor: float = 1.0,
) -> float:
    """Open-circuit air-gap flux density from the magnet load line.

    Series magnetic circuit of a surface magnet over an air gap (slotless;
    Carter factor defaults to 1.0 in Phase 1, parameterized for later):

        B_g = Br(T) * t_m / (t_m + mu_r * k_C * g)

    Approaches Br(T) as g -> 0 (with mu_r -> 1), and falls off as the gap grows.
    """
    if magnet_thickness <= 0.0:
        raise ValueError("magnet_thickness must be positive")
    if air_gap < 0.0:
        raise ValueError("air_gap must be non-negative")
    br = magnet.remanence_at(magnet_temp_c)
    return br * magnet_thickness / (magnet_thickness + magnet.mu_r * carter_factor * air_gap)

airgap_flux_density_runout_mean

airgap_flux_density_runout_mean(magnet: MagnetMaterial, magnet_thickness: float, mean_gap: float, runout_amp: float, magnet_temp_c: float = 20.0, carter_factor: float = 1.0) -> float

Circumferential mean of B over a 1/rev runout g(theta) = g_mean + d*cos(theta).

The load line B(g) = Brt_m/(t_m + mu_rk_C*g) is a Moebius function of g, so the theta-average is analytic:

<B> = Br*t_m / sqrt((t_m + mu_r*k_C*g_mean)^2 - (mu_r*k_C*d)^2)

B(g) is convex in g, so by Jensen's inequality >= B(g_mean): runout slightly increases the mean flux density (and torque); its real penalties are the 1/rev torque/force modulation. Reduces exactly to :func:airgap_flux_density at runout_amp = 0.

Source code in src/axfluxmdo/materials/magnetic.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def airgap_flux_density_runout_mean(
    magnet: MagnetMaterial,
    magnet_thickness: float,
    mean_gap: float,
    runout_amp: float,
    magnet_temp_c: float = 20.0,
    carter_factor: float = 1.0,
) -> float:
    """Circumferential mean of B over a 1/rev runout g(theta) = g_mean + d*cos(theta).

    The load line B(g) = Br*t_m/(t_m + mu_r*k_C*g) is a Moebius function of g,
    so the theta-average is analytic:

        <B> = Br*t_m / sqrt((t_m + mu_r*k_C*g_mean)^2 - (mu_r*k_C*d)^2)

    B(g) is convex in g, so by Jensen's inequality <B> >= B(g_mean): runout
    slightly *increases* the mean flux density (and torque); its real penalties
    are the 1/rev torque/force modulation. Reduces exactly to
    :func:`airgap_flux_density` at runout_amp = 0.
    """
    a = magnet_thickness + magnet.mu_r * carter_factor * mean_gap
    d = magnet.mu_r * carter_factor * runout_amp
    if d >= a:
        raise ValueError("runout closes the air gap (runout_amp too large)")
    br = magnet.remanence_at(magnet_temp_c)
    return br * magnet_thickness / math.sqrt(a**2 - d**2)

airgap_flux_density_runout_extremes

airgap_flux_density_runout_extremes(magnet: MagnetMaterial, magnet_thickness: float, mean_gap: float, runout_amp: float, magnet_temp_c: float = 20.0, carter_factor: float = 1.0) -> tuple[float, float]

(B at the tightest gap g_mean - d, B at the widest gap g_mean + d).

Source code in src/axfluxmdo/materials/magnetic.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def airgap_flux_density_runout_extremes(
    magnet: MagnetMaterial,
    magnet_thickness: float,
    mean_gap: float,
    runout_amp: float,
    magnet_temp_c: float = 20.0,
    carter_factor: float = 1.0,
) -> tuple[float, float]:
    """(B at the tightest gap g_mean - d, B at the widest gap g_mean + d)."""
    b_tight = airgap_flux_density(
        magnet, magnet_thickness, mean_gap - runout_amp, magnet_temp_c, carter_factor
    )
    b_wide = airgap_flux_density(
        magnet, magnet_thickness, mean_gap + runout_amp, magnet_temp_c, carter_factor
    )
    return b_tight, b_wide

airgap_b_squared_runout_mean

airgap_b_squared_runout_mean(magnet: MagnetMaterial, magnet_thickness: float, mean_gap: float, runout_amp: float, magnet_temp_c: float = 20.0, carter_factor: float = 1.0) -> float

Circumferential mean of B^2 over a 1/rev runout (for axial magnetic pull).

= Br^2t_m^2(t_m + mu_rk_Cg_mean) / ((t_m + mu_rk_Cg_mean)^2 - (mu_rk_Cd)^2)^(3/2)

Source code in src/axfluxmdo/materials/magnetic.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def airgap_b_squared_runout_mean(
    magnet: MagnetMaterial,
    magnet_thickness: float,
    mean_gap: float,
    runout_amp: float,
    magnet_temp_c: float = 20.0,
    carter_factor: float = 1.0,
) -> float:
    """Circumferential mean of B^2 over a 1/rev runout (for axial magnetic pull).

    <B^2> = Br^2*t_m^2*(t_m + mu_r*k_C*g_mean)
            / ((t_m + mu_r*k_C*g_mean)^2 - (mu_r*k_C*d)^2)^(3/2)
    """
    a = magnet_thickness + magnet.mu_r * carter_factor * mean_gap
    d = magnet.mu_r * carter_factor * runout_amp
    if d >= a:
        raise ValueError("runout closes the air gap (runout_amp too large)")
    br = magnet.remanence_at(magnet_temp_c)
    return br**2 * magnet_thickness**2 * a / (a**2 - d**2) ** 1.5

axfluxmdo.materials.electrical

Electrical conductor properties.

Conductor dataclass

Conductor(name: str, resistivity_20c_ohm_m: float, temp_coeff_per_c: float, density_kg_m3: float)

Winding conductor material.

resistivity

resistivity(conductor: Conductor, temp_c: float) -> float

Resistivity at temperature T: rho(T) = rho_20 * (1 + alpha * (T - 20)).

Source code in src/axfluxmdo/materials/electrical.py
23
24
25
def resistivity(conductor: Conductor, temp_c: float) -> float:
    """Resistivity at temperature T: rho(T) = rho_20 * (1 + alpha * (T - 20))."""
    return conductor.resistivity_20c_ohm_m * (1.0 + conductor.temp_coeff_per_c * (temp_c - 20.0))