Table of Contents

Class SetupAngleEstimation

Namespace
Hi.MillingForces.Training
Assembly
HiNc.dll

Estimates the cutter setup angle from smart-holder moment samples and a known cutting parameter.

Physics: for a cutter whose flutes are all alike, mounting it turned by φ inside the holder changes the spindle-rotation-frame transverse moment series in two ways only — every (Mx, My) vector rotates by φ and the series shifts by φ along the rotation angle. The per-step begin-angle alignment the training pipeline already performs absorbs the shift, so φ survives solely as a constant planar rotation of the model's (Mx, My) against the measured (Mx, My); Mz, Fz and the workpiece-frame forces carry no information about it. The estimate is therefore a rotation-plus-scale (complex gain) Procrustes fit shared by all steps, with the begin-angle hypothesis chosen per step by brute force.

Channels: Mx, My or both are the transverse channels the angle is read from. With both, the fit is the complex-gain fit above; with one, the gain's two unknowns are solved from that channel alone, which suits a cutter with three or more flutes (over a turn one channel sees the transverse moment from N directions). A one- or two-flute cutter's holder-frame moment swings along one line, so on one channel a turned cutter and a lower gain read the same; there the scale is pinned — to 1, the magnitude being known: the holder delivers calibrated N·m and the coefficients have fixed units (the owner, 2026-10-12); or, with Mz selected and usable (its correlation with the model torque at or above MzCorrelationFloor), to the ratio the torque measures, since Mz = R·Ft reads the tangential force's size whatever the setup angle (the owner, the same day: Mx and Mz together; and 2026-10-16: the torque's zero read from the samples between the flutes' pulses, MzZeroGuard_deg) — and the fit reports the two angles mirrored about that line (ScaleFixed), never applied automatically while two fit. Mz, when selected, enters the per-step begin-angle choice, the shared scale and the energy diagnostics (contrast, residual ratio, the energy cap), not the angle itself. Which channels exist and which are used is the user's selection: the estimator never drops a selected channel, at most it warns.

Identifiability: the angle is determined only modulo the flute pitch (360/N for N uniform flutes) and only when the transverse envelope is modulated within a revolution — light side cuts or intermittent contact; a full slot with several flutes engaged at once leaves the objective flat, which Contrast exposes.

public static class SetupAngleEstimation
Inheritance
SetupAngleEstimation
Inherited Members

Fields

MaxAlternations

Upper bound on begin-angle / gain alternations per pass.

public const int MaxAlternations = 30

Field Value

int

MaxTrimRounds

Upper bound on outlier-trim rounds (trim, re-solve, re-score).

public const int MaxTrimRounds = 4

Field Value

int

Properties

EnergyCapOverMedian

A step whose measured energy exceeds this multiple of the median is weighted down to it.

public static double EnergyCapOverMedian { get; set; }

Property Value

double

GridStep_deg

Angle grid step (deg) of the global search; the closed-form refinement makes the estimate sub-grid.

public static double GridStep_deg { get; set; }

Property Value

double

LowContrastThreshold

Objective contrast (see Contrast) below which the estimate is reported as unconstrained.

public static double LowContrastThreshold { get; set; }

Property Value

double

LowSignalEnergyRatio

With both transverse channels selected, the used steps are folded onto one spindle turn at their aligned bins and each channel's synchronous energy (the folded waveform minus its turn-average, so a constant sensor offset does not count) is compared; the weaker channel is named in a low-signal warning when its energy falls below this fraction of the stronger one's. The threshold is set from a shop-floor recording with a dead bending bridge: folded over the steps, the dead channel sits at 0.3–0.4 % of the live one in amplitude (energy about 1e-5; a dead channel of ±0.2 N·m quantised noise folds to 1e-5–2e-5 over 12–20 steps), while its raw per-turn ratio was 1.0–3.8 % in amplitude — too close to call, which is why the measure folds. A live two-flute channel sits near zero only when the holder-frame moment runs along one line; with edge coefficients the moment turns a little within the arc (at a 60° engagement arc the ratio's floor is about 1.7e-3, half the model isotropy), so a live channel falls below 1e-3 only for pure-shear coefficients or an engagement arc of 90° and more — a two-flute warning is rare and its text says what it can mean. The hundredfold margin over the dead channel stays for recordings with few steps. The warning never changes Succeeded and never enters GetApplyRefusal(SetupAngleEstimate, double).

public static double LowSignalEnergyRatio { get; set; }

Property Value

double

MaxScatterToApply_deg

Per-step angle scatter (Scatter_deg) above which an automatic application refuses the estimate. The sim replay scatters about 1°, a 3 dB recording about 2°; the inconclusive 2024 holder recording scattered 19.5° with a flat contrast.

public static double MaxScatterToApply_deg { get; set; }

Property Value

double

MinDeltaToApply_deg

Rotation (deg, folded into ±half the period) below which an automatic application does nothing: the estimate's begin-angle grid is 360/MillingCycleDivisionNum (2° at the training's usual 180), so a residual under a quarter of that is noise — writing it would only rerun the force kernel on every touched step of the tool for no visible change (an armed estimate followed by the explicit form sees such a residual).

public static double MinDeltaToApply_deg { get; set; }

Property Value

double

MirrorPreferenceRatio

The mirrored-frame hypothesis is reported as preferred when its objective falls below this fraction of the plain hypothesis' objective.

public static double MirrorPreferenceRatio { get; set; }

Property Value

double

MzCorrelationFloor

The correlation of the measured torque with the model's (mean removed, pooled over the used steps) below which the torque does not pin the scale of a fixed-scale fit: a torque bridge reading noise correlates at about 1/√N of the N samples used (0.01 on the toy's 5,940), either sign, and would otherwise hand the fit a tiny positive scale that pulls the angle onto the model's moment line; a live bridge correlates near 1 (0.99 on the engine's own recording, 0.94–0.98 on a shop-floor holder). 0.5 is a per-sample ratio of the torque's AC amplitude to its noise of about 0.58; at the floor the pooled ratio's relative standard error is about 1.7/√N over the N samples used with the slope with an intercept, and more with the zero read from the gaps, whose own noise adds √((1−u)(1+u/G)) — u the model torque's mean² over its mean square, G the gap share: about 1.1× on a 61° two-flute cut, 1.7× at the least gap share — and a live bridge in a light cut can fall below it — the warning then says the torque was too noisy, not dead. The same floor gates each step's own ratio. The correlation is taken with the means removed however the scale's zero is read (MzZeroGuard_deg).

public static double MzCorrelationFloor { get; set; }

Property Value

double

MzZeroGuard_deg

The reach (deg of spindle rotation) over which a measured torque sample can still carry a flute's pulse although the model torque at its own angle is zero: a holder sample averages its interval (1 ms is 7.6° at 1270 rpm) and the gather interpolates the samples onto the angle grid, which spreads a pulse edge by up to one more interval. A measured sample is a gap sample — the torque bridge reading its zero while no flute cuts — only when the model torque is zero at its own angle and at every angle within this reach plus one bin on either side; the mean of a step's gap samples is the zero subtracted from its torque before the ratio to the model torque is taken through the origin, which keeps the torque's undistorted mean (a slope with an intercept keeps the harmonics alone, and the torque's harmonics lose more to the sampling than the bending's fundamental: 1° on the engine's own 1 ms recording). Too small a guard lets pulse edges into the zero and reads the scale low; too large a one only costs gap samples, and a step short of them (MzZeroMinSampleShare, MzZeroMinSampleNum; a full slot has none) takes the slope with an intercept, which needs no zero. 15° is about two 1 ms intervals at 1270 rpm (15.2°; the extra bin covers the rest); raise it for a faster spindle or a slower holder.

public static double MzZeroGuard_deg { get; set; }

Property Value

double

MzZeroMinSampleNum

The least count of gap samples for a step's torque zero (MzZeroMinSampleShare).

public static int MzZeroMinSampleNum { get; set; }

Property Value

int

MzZeroMinSampleShare

The least share of a step's instances that must be gap samples, and no fewer than MzZeroMinSampleNum, for the step's torque zero to be read from them (MzZeroGuard_deg); a step with fewer takes the slope with an intercept.

public static double MzZeroMinSampleShare { get; set; }

Property Value

double

RotationSignConvention

Sign relating the estimate to the tool's MillingTool.SetupAngle_deg: the setup angle to enter is current + RotationSignConvention · Angle_deg. Pinned by the sim replay test (a cutter exported at a known setup angle must be recovered with this sign).

public static int RotationSignConvention { get; set; }

Property Value

int

Methods

Estimate(IEnumerable<KeyValuePair<int, List<List<TrainingSample>>>>, double[], SampleFlag, int, double, double, IProgress<IMessage>, CancellationToken)

Estimates the setup angle from gathered angle-hypothesis samples (the GatherSampleByAngle structure: hypothesis list b at instance i pairs the model row of angle (b+i) mod N with the measured value at instance i). The gather always carries both transverse model rows — a one-channel fit still reads the complex model P = Px + i·Py, since Im(g·P) mixes them; which measured channels enter is sampleFlags.

public static SetupAngleEstimate Estimate(IEnumerable<KeyValuePair<int, List<List<TrainingSample>>>> stepIndexToAngleSamples, double[] paraVector, SampleFlag sampleFlags, int fluteNum, double period_deg, double outlierRatio, IProgress<IMessage> messageProgress, CancellationToken cancellationToken)

Parameters

stepIndexToAngleSamples IEnumerable<KeyValuePair<int, List<List<TrainingSample>>>>

Gathered samples per step.

paraVector double[]

The known cutting parameter as an element vector (the gather's template order).

sampleFlags SampleFlag

Channels to use: Mx, My or both are the transverse channels the angle is read from (a single channel suits a cutter with three or more flutes; on a one- or two-flute cutter one channel is read with the scale pinned, to 1 or to the ratio Mz measures, and gives two mirrored angles, ScaleFixed). Mz, when selected, enters the per-step begin-angle choice, the shared scale and the energy diagnostics (contrast, residual ratio, the energy cap), not the angle itself. Force channels are ignored.

fluteNum int

Flute count of the cutter; 0 when unknown (a one-channel fit is then attempted and says so).

period_deg double

Ambiguity period of the angle (360/N for uniform fluting).

outlierRatio double

Fraction of the worst-fitting steps trimmed before the final estimate.

messageProgress IProgress<IMessage>

Message sink.

cancellationToken CancellationToken

Cancellation token.

Returns

SetupAngleEstimate

GetApplyRefusal(SetupAngleEstimate, double)

The gate of an automatic application (the online command): the reason estimate must not be written to the tool, or null when it may. A failed estimate, a contrast below LowContrastThreshold (the cut does not constrain the angle), a scatter above maxScatter_deg (the steps disagree), a preferred mirror (the sensor axes look swapped, not rotated — never flagged for two flutes), a fixed-scale estimate (ScaleFixed, one channel on a one- or two-flute cutter) whose channel reads more than the model can give along its line (no angle makes the magnitudes agree) or nearly nothing (a dead bridge reads the same), or that has two candidate angles (the preference between them rests on the model moment's share off its line, which the pulses as sampled distort by more: on the engine's own 1 ms recording it went either way), a one-channel estimate without a per-step scatter (no step alone could separate the rotation from the gain, so the steps cannot be checked against each other), and a one- or two-flute estimate on both transverse channels of which one folded to almost nothing against the other (LowSignalChannel, the SetupAngle-Channel–LowSignalFewFlutes warning: with so few flutes the data cannot tell a live channel reading near zero from a dead bridge, no mirror check is possible, and a dead bridge taken as data reads a false angle – 152.5° and 143.9° on a shop-floor holder with a dead bending bridge, 2026-10-05) are refused; an explicit EstimateSetupAngle(.., applyToTool: true) is the operator's own decision and does not go through here.

public static string GetApplyRefusal(SetupAngleEstimate estimate, double maxScatter_deg)

Parameters

estimate SetupAngleEstimate

The estimate to gate.

maxScatter_deg double

Largest scatter let through.

Returns

string

The refusal, or null when the estimate passes.