Every coil of electrical steel ships with a mill certificate, and the number everyone reads first is core loss: something like P1.7/50 = 1.05 W/kg. Transformer designers build loss budgets on it and purchase orders are written against it.
That number isn't a material constant. It comes out of a short chain of arithmetic applied to four instrument readings from a 100-year-old test rig called the Epstein frame. Once you've written that chain out in code, a lot of supplier arguments get easier to follow, so that's what this post does.
The rig in one paragraph
An Epstein frame is four coil bobbins arranged in a 25 cm square. You cut the steel into strips (280–320 mm long, 30 mm wide), split them into four equal bundles, and slide one bundle into each bobbin. The bundles overlap at the corners so the flux has a closed path, and the whole thing behaves like the core of an unloaded transformer. A primary winding magnetises it. A secondary winding senses the induced voltage. A power meter on the secondary side measures what the core is eating. The method is standardised as IEC 60404-2 (ASTM A343 in North America).
What comes off the bench is four readings:
| Reading | Symbol | What it is |
|---|---|---|
| Frequency | f | 50 or 60 Hz |
| Average rectified secondary voltage | U2,avg | Sets the flux density |
| RMS secondary voltage | U2,rms | Used to check the waveform |
| Power meter reading | Pm | Power drawn, before corrections |
Everything on the certificate is derived from those four numbers plus the sample's mass and dimensions.
The model
from dataclasses import dataclass DENSITY = 7650 # kg/m3, conventional density for grain-oriented steel PATH_M = 0.94 # conventional magnetic path length of a 25 cm Epstein frame FF = 1.111 # form factor of a pure sine wave (rms / average rectified) @dataclass(frozen=True) class EpsteinSample: n_strips: int length_m: float width_m: float thickness_m: float @property def mass_kg(self): return self.n_strips * self.length_m * self.width_m * self.thickness_m * DENSITY @property def area_m2(self): # cross-section of ONE limb of the square return self.mass_kg / (4 * self.length_m * DENSITY) @property def active_mass_kg(self): # only the steel on the 0.94 m conventional path counts return self.mass_kg * PATH_M / (4 * self.length_m) @dataclass(frozen=True) class Reading: f_hz: float u2_avg_v: float u2_rms_v: float p_meter_w: float def form_factor(r): return r.u2_rms_v / r.u2_avg_v def peak_polarisation_t(s, r, n2=700): # Faraday's law for a sinusoidal flux: U_avg = 4 * f * N2 * A * J_peak return r.u2_avg_v / (4 * r.f_hz * n2 * s.area_m2) def specific_loss_w_per_kg(s, r, ri_ohm, n1=700, n2=700): # the meter also measures the power burned in its own voltage circuit p_instruments = (FF * r.u2_avg_v) ** 2 / ri_ohm p_core = (n1 / n2) * r.p_meter_w - p_instruments return p_core / s.active_mass_kg Three lines in there do most of the work.
area_m2 is computed from mass, not from a micrometer. Strip thickness varies along the coil and the coating adds a few microns, so the standard backs the cross-section out of the weighed mass and a conventional density. That's why the scale in an Epstein lab is as important as the power analyser.
active_mass_kg uses 0.94 m, not the real strip length. The four bundles add up to 4 × 280 mm = 1.12 m of steel, but the corners overlap and the flux takes a shortcut. The standard fixes the effective path at 0.94 m by convention. It's an approximation, and we'll see below what that costs.
p_instruments is subtracted before dividing. The power meter's voltage circuit is connected to the secondary winding, so it measures its own consumption along with the core's. With 10 kΩ of instrument resistance that's about 16 mW. That sounds tiny until you notice the whole core only draws about 370 mW.
The default 700 turns on both windings is the usual winding for the 25 cm frame. If your frame is wound differently, pass the real numbers in.
Running it
A sample of 0.27 mm grain-oriented steel, 24 strips of 280 × 30 mm, driven at 50 Hz:
s = EpsteinSample(n_strips=24, length_m=0.280, width_m=0.030, thickness_m=0.27e-3) r = Reading(f_hz=50, u2_avg_v=11.57, u2_rms_v=12.85, p_meter_w=0.384) print(f"sample mass {s.mass_kg * 1000:.1f} g") print(f"cross-section {s.area_m2 * 1e6:.1f} mm2") print(f"active mass {s.active_mass_kg * 1000:.1f} g") print(f"form factor {form_factor(r):.3f}") print(f"peak J {peak_polarisation_t(s, r):.3f} T") print(f"P1.7/50 {specific_loss_w_per_kg(s, r, ri_ohm=10_000):.3f} W/kg") sample mass 416.4 g cross-section 48.6 mm2 active mass 349.5 g form factor 1.111 peak J 1.700 T P1.7/50 1.051 W/kg So this sample would be certified at 1.05 W/kg at 1.7 T, 50 Hz. A grade like 27Q120 guarantees a maximum of 1.20 W/kg, so the coil passes with room to spare.
Two sanity checks are worth building into any real version of this:
- Form factor. It should be 1.111 within about 1%. If the supply waveform is distorted, the flux isn't sinusoidal and the loss number is wrong, no matter how carefully you did everything else. Reject the reading; don't correct it.
- Mass. The standard wants at least about 240 g and a strip count that's a multiple of four, so all four limbs carry the same steel.
Where the 0.94 m bites
The conventional path length is the method's best-known simplification. Studies of the effective path length put it somewhere between 0.90 and 0.96 m, depending on grade, flux density and frequency. Feed the same readings through with different path lengths:
for lm in (0.90, 0.94, 0.96): active = s.mass_kg * lm / (4 * s.length_m) p_core = r.p_meter_w - (FF * r.u2_avg_v) ** 2 / 10_000 print(f"{lm:.2f} m -> {p_core / active:.3f} W/kg") 0.90 m -> 1.098 W/kg 0.94 m -> 1.051 W/kg 0.96 m -> 1.030 W/kg That's roughly a ±4% spread from one convention. It's a big part of why two labs testing the same coil can disagree by a few percent, and why a single sheet tester (IEC 60404-3), which has no corners, lands a few percent away from Epstein on the same steel. Neither is wrong. They're different conventions, which is why a good certificate states the method it used. I wrote up the procedure, the standards and the Epstein-vs-SST comparison in more detail in how core loss is measured on the Epstein frame.
Bonus: comparing a US datasheet with a European one
North American grades are often quoted in W/lb at 60 Hz. European and Chinese grades are quoted in W/kg at 50 Hz. People compare them directly all the time, and it's wrong twice over:
W_PER_LB_TO_W_PER_KG = 2.2046 def to_w_per_kg_50hz(w_per_lb_60hz, ratio_60_50=1.32): # 1.32 is a typical 60/50 Hz loss ratio for grain-oriented steel at 1.7 T; # non-oriented steel is closer to 1.27. Use measured values when you have them. return w_per_lb_60hz * W_PER_LB_TO_W_PER_KG / ratio_60_50 print(f"{to_w_per_kg_50hz(0.60):.2f} W/kg") # 1.00 print(f"{to_w_per_kg_50hz(0.70):.2f} W/kg") # 1.17 The unit conversion is exact. The frequency ratio isn't: it depends on the steel family, thickness and flux density, so treat it as a planning number, not a guarantee. If you want the common GB, EN, JIS and AISI grades lined up on the same basis, there's a grade equivalents table on our site.
What the code leaves out
- Air-flux compensation. Some of the flux linking the secondary winding goes through air, not steel. Real frames cancel it with a mutual inductor, so the model assumes U2,avg is already compensated.
- Sample preparation. Shearing work-hardens the cut edges and raises loss. Whether the strips were stress-relief annealed changes the result, especially for non-oriented steel.
- Built cores. The Epstein number is measured on flat, low-stress strips. An assembled transformer core with joints, cutting stress and clamping always loses more. The ratio, called the building factor, runs from about 1.0 for wound cores to around 1.3 for stacked cores. That's the topic of my previous post.
Wrapping up
- A core loss figure is four instrument readings, a weighed mass and two conventions (0.94 m and 7650 kg/m³).
- Check the form factor before you trust any reading.
- An Epstein number and an SST number can differ by a few percent for the same coil, so ask which method the certificate used.
- Convert units and frequency before comparing a W/lb@60 Hz grade with a W/kg@50 Hz one.
If you've written software around other test rigs, whether that's materials, battery cyclers or RF, I'd like to hear how you handled the "convention vs physics" parts in the comments.
Disclosure: I work at GOES Steel, an electrical steel supplier in Wuxi, China, where core loss is tested on every coil before it ships. The example readings above are illustrative, chosen to be typical of a 0.27 mm grain-oriented sample. The code is free to use.

