Contents
Tyres
Fitting tyre data
How the app turns a rig's measured points into a Magic Formula tyre, how close that gets, and how a rig tyre is scaled to the road.
A tyre file is a fit. Somebody ran the tyre on a rig, recorded its forces over a sweep of slip at a few loads, and chose the coefficients that bring the Magic Formula closest to those points. The Tyres tool does the same with rig runs you give it. This page fits a known tyre with it, so the answer can be checked against the truth, and then follows the fitted tyre from the rig's belt to the road.
The rig's points
A cornering rig holds the tyre at a load and sweeps its slip angle, a few times over, at each of a handful of loads. The app sorts the samples into bins by load, camber and slip angle and fits the bins' averages, so a run's thousands of samples become a few hundred points.
The points in Fig. 1 are made rather than measured: the template's own tyre at 450, 900 and 1350 N, every 0.5° of slip angle out to 12°, with a scatter of 1 % of the load added to each. A real rig scatters more and not evenly, but this way the tyre the fit should find is known.
The loads that count are the car's. The app fits over the loads from the car's lightest wheel to its most loaded one on its own g-envelope, unless you type others, and never below 300 N, where a rig's load cell is mostly noise. A curve shaped by loads the car never sees is the wrong curve where it does.
What is minimised
The fit asks for the coefficients that make the formula's side force closest to the points, each point's miss divided by its load:
- the rig's side force in bin , at slip angle and load
- the coefficients being fitted
- the residual, in units of friction coefficient
Dividing by the load makes the residual a friction coefficient, so a miss of 10 N at 450 N counts as much as one of 30 N at 1350 N, and the light loads, where a car's inside wheels live, are not drowned out by the heavy ones.
The residual comes out a shade above the 0.01 of scatter put in: the fit cannot pass through the noise, and the template's curvature differs a little either side of zero, which the seven coefficients below cannot follow. On real data a fit almost never reaches the rig's own scatter, because runs on the same tyre disagree with each other. The Tyres tool places a fit's residual among the fits of a library of rig data, so it can be read as tight or loose against fits of the same kind.
What is fitted
A pure-slip sweep at one camber and one pressure can tell seven coefficients apart. The fit starts from the data itself and keeps each inside a range:
| Coefficient | What it sets | Fitted | Template's tyre |
|---|---|---|---|
| the shape: how far the force falls past its peak | 1.524 | 1.5 | |
| the friction coefficient at the nominal load | 1.813 | 1.8 | |
| its fall with load | −0.186 | −0.2 | |
| , | the curvature near the peak, and its change with load | −0.668, −0.344 | −0.8, −0.4 |
| the cornering stiffness | −36.37 | −34.21 | |
| the load at which the cornering stiffness peaks | 2.118 | 2 |
Three ranges carry physics rather than convenience:
- stays on the side of zero the data's own slope through the origin is on. The far side of zero fits just as well and is a tyre that steers the car the wrong way.
- stays at or below zero, give or take a hair. A noisy set can otherwise report a tyre that grips better the harder it is pressed, and every analysis downstream would then find load transfer free (Load sensitivity).
- When the data turns over, the fitted curve has to peak inside the sweep, within 15 % of the largest slip angle measured. Left free, a fit can put its peak far past the data and invent grip nobody measured. When the data is still climbing at the edge, the peak is not constrained and the app says it is extrapolated.
None of the seven shifts the curve off the origin, so every fit the app makes has no side force at zero slip at any load. A real tyre makes a little, from its ply and its conicity; the Tyres tool fits those offsets only when asked, to measure them, since a tyre that pushes at zero slip upsets the torque-vectoring sums.
The search is Nelder–Mead's, restarted four times from different seeds, keeping the best. Here no coefficient ended on its range and the data turns over, so the peak is measured.
How close it gets
| Load | fitted | true | Off by | fitted | true | Off by |
|---|---|---|---|---|---|---|
| 450 N | 1.906 | 1.905 | 0.01 % | 14 635 N/rad | 14 574 N/rad | 0.41 % |
| 900 N | 1.813 | 1.811 | 0.12 % | 25 271 N/rad | 25 143 N/rad | 0.51 % |
| 1350 N | 1.720 | 1.716 | 0.23 % | 30 876 N/rad | 30 687 N/rad | 0.61 % |
The fit finds the tyre's grip to a few tenths of a per cent and its cornering stiffness to about half a per cent, from points scattered by a whole per cent. Its coefficients are another matter: is −0.67 where the template's is −0.8. The template's file has a nominal load of 950 N where the fit takes the bins' mean, 900 N, and it carries a curvature term the fit leaves at zero. Different coefficients make nearly the same curve. Compare tyres by their curves, and at the loads you run, not coefficient by coefficient.
Which model
The Tyres tool fits four models. They differ in what they can represent, whatever the data:
| Model | Coefficients | Cannot represent |
|---|---|---|
| Simple | 8 | camber, the aligning moment, pressure; its combined slip is a friction ellipse |
| MF 5.2 | about 19 | pressure; camber enters as a shift of the slip angle |
| MF 6.1 | about 19 | pressure; camber enters through its own stiffness |
| MF 6.1 + pressure | about 27 | nothing more in the app, which runs every tyre at its nominal pressure |
The fit here is MF 6.1, and of its coefficients only the seven above, since the data has one camber and no drive or brake runs. "Compare models" fits every model to the same bins and puts each residual beside its count of coefficients. A model with eight more coefficients and the same residual has fitted the rig's afternoon, not the tyre. The pressure model earns its place another way: without it a fit has to choose one pressure and discard the runs at the others.
From the rig to the road
A rig runs the tyre on a belt of abrasive paper, not on the road it will race on, and the tyre's grip and stiffness differ between the two. Two factors take the fit to the road, one for the peak and one for the slope:
- the grip scale, rig to road; it scales the drive direction's peak too
- the stiffness scale, rig to road; it scales the slip stiffness too
follows from the other two, so the curve keeps its shape and each end lands where it is put. Fig. 2 is the template's tyre at 0.7 and 0.85, values chosen to show the two apart, not a rule. How far each falls depends on the surface, the tyre and its temperature, and nothing ties one to the other, which is why there are two factors and not one on the whole force. Set them from your own data. The Calibration tool can set the grip scale from the car's logs; the stiffness scale you set yourself.
What the model assumes
- Pure slip only. Combined slip, the aligning moment and the relaxation length come from other runs: drive and brake runs at a slip angle, the moment channel, step-steer tests. A fit with no data for them says so and names what stands in.
- Load dependence is the Magic Formula's: straight in the friction coefficient, a sine of an arctangent in the cornering stiffness. A tyre that bends otherwise leaves a residual no coefficient can take up.
- The rig's slip angle, load and force are what it says they are. A rig with an offset in its slip angle shifts every fit by the same amount.
- One factor per direction takes the rig to the road, at every load and slip.
In the app
Tyres › Fit takes rig runs in four steps: Runs, what the rig measured; Channels, how it was read; Fit, the model over the data; Keep, into the library and onto the car. Fit sets the model, the load window and the offsets, and "Compare models" fits all four. Its "Fitted data" charts draw the fit over the bins in the file's own sense, so the template's side force is negative there for positive slip.
In the car's setup, under "Tyres", "Grip scale (rig → road)" and "Stiffness scale (rig → road)" are equation (2)'s two factors. Both default to 1, the tyre as its file states it.
Check it yourself
example_rig.m holds the rig points of Fig. 1. The script fits them with Octave's fminsearch, from the same start, and compares the residual, and at the three loads with the app's to 0.01 %. Two searches on the same residual land on the same curve to a tolerance, not to the last digit, so this page is checked as Close. The rig-to-road scaling is exact.
>> fitting_tyre_data this script PaceDynamics template's tyre PCY1 1.5237 1.5237 1.5000 PDY1 1.8127 1.8127 1.8000 PDY2 -0.1857 -0.1857 -0.2000 PEY1 -0.6685 -0.6685 -0.8000 PEY2 -0.3440 -0.3440 -0.4000 PKY1 -36.3747 -36.3747 -34.2105 PKY2 2.1185 2.1185 2.0000 (the template's tyre has a nominal load of 950 N, the fit 900 N, and p_Ey3 = 0.1 that the fit leaves at zero, so its coefficients are not the fit's to find) Quantity This script PaceDynamics Diff ----------------------------------------------------------------------------- Residual, rms over the load [-] 0.010876 0.010876 1.4e-13% Fitted mu_y at 450 N [-] 1.90551 1.90551 6.1e-09% Fitted mu_y at 900 N [-] 1.81265 1.81265 4.9e-09% Fitted mu_y at 1350 N [-] 1.71979 1.71979 5e-09% Fitted C_alpha at 450 N [N/rad] 14634.6 14634.6 5.3e-08% Fitted C_alpha at 900 N [N/rad] 25270.6 25270.6 1.4e-08% Fitted C_alpha at 1350 N [N/rad] 30875.5 30875.5 9.4e-08% On the road: mu_y, x 0.7 [-] 1.25998 1.25998 0% On the road: C_alpha, x 0.85 [N/rad] 22095.5 22095.5 3.3e-14% All 9 quantities agree with PaceDynamics (largest difference 9.4e-08 %).
The script
%% Fitting tyre data: a Magic Formula fitted to rig points, and scaled to the road % % Companion to the documentation page "Fitting tyre data". Runs unchanged in GNU Octave % (7 or later) and MATLAB. Run: % % >> fitting_tyre_data % % A rig measures a tyre's side force at a few loads over a sweep of slip angle. A fit % chooses the coefficients that bring the formula closest to those points, in the least- % squares sense, with each point's error divided by its load: % % minimise sqrt( mean( ((F_y(alpha, F_z; p) - F_y,rig) / F_z)^2 ) ) % % over the seven coefficients a pure-slip, one-camber sweep can tell apart: the shape % p_Cy1, the friction p_Dy1 and its fall with load p_Dy2, the curvature p_Ey1 and p_Ey2, % and the cornering stiffness p_Ky1 and the load at its peak p_Ky2. % % The points in example_rig.m are made from the template's own tyre with a seeded scatter % of 1 % of the load added, so there is a known answer. The app fits them with its own % search (Nelder-Mead, four restarts); this script with fminsearch, from the same start. % Two searches on the same residual land on the same curve to a stated tolerance, not to % the last digit, so this page is checked as Close. % % Then the rig-to-road scaling, which is exact: lambda_mu y scales the peak and lambda_Ky % the cornering stiffness, and the formula's B = K / (C D) keeps the curve's shape. % % Reference: H. B. Pacejka, Tyre and Vehicle Dynamics, 3rd ed. (Butterworth-Heinemann, % 2012), ch. 4.3.2 and 4.3.3. % % Part of the PaceDynamics documentation, https://pacedyn.dev/docs % SPDX-License-Identifier: MIT [t, app] = example_tyre(); [rig, fit] = example_rig(); deg = pi / 180; TOL = 1e-4; % Close: two searches agree to 0.01 % %% The rig's points, one column each na = numel(rig.alphaDeg); Fz = kron(rig.loads(:), ones(na, 1)); alpha = repmat(rig.alphaDeg(:) * deg, numel(rig.loads), 1); Fy = reshape(rig.fy', [], 1); %% A blank tyre, as the fitter starts one % Every coefficient the sweep cannot determine is zero, the scaling factors 1, and the % nominal load is the points' mean load. b = t; for k = {'PEY3', 'PEY4', 'PHY1', 'PHY2', 'PVY1', 'PVY2', 'PVY3', 'PVY4', 'PDY3', 'PKY3', 'PKY5', 'PKY6', 'PKY7'} b.(k{1}) = 0; end b.PKY4 = 2; b.FNOMIN = fit.fnomin; %% The start, as the app takes it from the points % The friction from the highest |F_y| / F_z, the cornering stiffness from the slope of % F_y / F_z through the origin within 2 deg, on the side of zero the data is on. D = max(abs(Fy) ./ max(1, Fz)); small = abs(alpha) > 1e-5 & abs(alpha) <= 2 * deg; K = sum(alpha(small) .* Fy(small) ./ max(1, Fz(small))) / sum(alpha(small) .^ 2); keys = {'PCY1', 'PDY1', 'PDY2', 'PEY1', 'PEY2', 'PKY1', 'PKY2'}; x0 = [1.4, D, -0.1, -0.5, 0, sign(K) * max(5, abs(K)), 1.8]; %% The fit cost = @(x) pd_fy_rms(x, b, keys, Fz, alpha, Fy); opts = optimset('MaxFunEvals', 40000, 'MaxIter', 40000, 'TolX', 1e-12, 'TolFun', 1e-14); x = fminsearch(cost, x0, opts); x = fminsearch(cost, x, opts); % restarted once from where it stopped for k = 1:numel(keys), b.(keys{k}) = x(k); end fprintf('\n %-6s %12s %12s %16s\n', '', 'this script', 'PaceDynamics', 'template''s tyre'); for k = 1:numel(keys) fprintf(' %-6s %12.4f %12.4f %16.4f\n', keys{k}, x(k), fit.coefficients.(keys{k}), t.(keys{k})); end fprintf('\n (the template''s tyre has a nominal load of %g N, the fit %g N, and p_Ey3 = %g\n', t.FNOMIN, fit.fnomin, t.PEY3); fprintf(' that the fit leaves at zero, so its coefficients are not the fit''s to find)\n'); %% What the fitted tyre does, at each rig load mu = zeros(1, numel(rig.loads)); Ca = zeros(1, numel(rig.loads)); for i = 1:numel(rig.loads) F = rig.loads(i); Fy_at = @(a) getfield(pd_mf(b, F, a, 0), 'Fy0'); mu(i) = pd_peak(@(a) Fy_at(a * deg), 45, 1) / max(1, F); e = 0.1 * deg; Ca(i) = abs((Fy_at(e) - Fy_at(-e)) / (2 * e)); end %% Rig to road: scale the peak and the slope, each by its own factor s = t; s.LMUY = t.LMUY * app.road.grip; s.LKY = t.LKY * app.road.stiffness; Fy_road = @(a) getfield(pd_mf(s, s.FNOMIN, a, 0), 'Fy0'); mu_road = pd_peak(@(a) Fy_road(a * deg), 45, 1) / max(1, s.FNOMIN); e = 0.1 * deg; Ca_road = abs((Fy_road(e) - Fy_road(-e)) / (2 * e)); %% Against the app pd_compare('Residual, rms over the load [-]', cost(x), fit.rmsMu, TOL); for i = 1:numel(rig.loads) pd_compare(sprintf('Fitted mu_y at %4g N [-]', rig.loads(i)), mu(i), fit.muY(i), TOL); end for i = 1:numel(rig.loads) pd_compare(sprintf('Fitted C_alpha at %4g N [N/rad]', rig.loads(i)), Ca(i), fit.corneringStiffness(i), TOL); end pd_compare(sprintf('On the road: mu_y, x %g [-]', app.road.grip), mu_road, app.road.muY); pd_compare(sprintf('On the road: C_alpha, x %g [N/rad]', app.road.stiffness), Ca_road, app.road.corneringStiffness); pd_compare();
Download: fitting_tyre_data.m · example_rig.m · example_tyre.m · pd_mf.m · pd_peak.m · pd_fy_rms.m · pd_compare.m. The scripts are MIT licensed.
Further reading
- H. B. Pacejka, Tyre and Vehicle Dynamics, 3rd ed. (Butterworth-Heinemann, 2012), ch. 4.3.2, on the Magic Formula's coefficients, and ch. 4.3.3, on the scaling factors.
- W. F. Milliken and D. L. Milliken, Race Car Vehicle Dynamics (SAE, 1995), ch. 2, on tyre testing.
- J. C. Lagarias, J. A. Reeds, M. H. Wright and P. E. Wright, "Convergence properties of the Nelder–Mead simplex method in low dimensions", SIAM Journal on Optimization 9 (1998), 112–147.