Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ else if (!result.IsCancelled)
- `RawValues` 和 `Scores` 始终保持公开形状:六个雷达轴加
`fitted_constant`;不可用项为 `null`。
- 只有七维 raw 全部成功时才生成 `FittedConstant` 和映射后的 score。
- 拟合输出在模块内部限位;上下限由 `RegressionBetaModel` 的
`MinimumFittedConstant` / `MaximumFittedConstant` 常量配置,当前为 0~18。
- `partial` 会保留已完成的特征,但不会生成拟合定数。


Expand Down Expand Up @@ -266,6 +268,8 @@ else if (!result.IsCancelled)
axes plus `fitted_constant`; unavailable entries are `null`.
- `FittedConstant` and mapped scores are produced only when all seven raw
features succeed.
- Predictions are clamped inside the module using `MinimumFittedConstant` and
`MaximumFittedConstant` in `RegressionBetaModel` (currently 0 and 18).
- `partial` preserves completed feature results but does not produce a fitted
constant.
- `fitted_constant` is identity-mapped and is not on the radar axes' 0-250
Expand Down
6 changes: 5 additions & 1 deletion Runtime/Regression/RegressionBetaModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ namespace MajRadar.Regression;

public sealed class RegressionBetaModel
{
// Output limits can be tuned independently of the fitted coefficients.
internal const double MinimumFittedConstant = 0;
internal const double MaximumFittedConstant = 18;

public IReadOnlyList<string> InputFeatures => RadarFeatureNames.ModelInputOrder;

public double Predict(IReadOnlyList<double> rawValues)
Expand All @@ -32,7 +36,7 @@ public double Predict(IReadOnlyList<double> rawValues)
result += coefficients[coefficientIndex++] * normalized[left] * normalized[right];
if (coefficientIndex != coefficients.Length || double.IsNaN(result) || double.IsInfinity(result))
throw new InvalidOperationException("Regression prediction is invalid.");
return result;
return Math.Clamp(result, MinimumFittedConstant, MaximumFittedConstant);
}

public double Predict(IReadOnlyDictionary<string, double> rawFeatures)
Expand Down
9 changes: 7 additions & 2 deletions Tests~/MajRadar.Tests/AnalysisAndRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,14 +360,19 @@ public void FeatureFailuresAreIsolatedInsideRadarAnalyzer()

[Theory]
[MemberData(nameof(RegressionVectors))]
public void EmbeddedRegressionMatchesFrozenPythonVectors(double[] raw, double expected)
public void EmbeddedRegressionMatchesPythonVectorsWithinOutputLimits(double[] raw, double expected)
{
var actual = new RegressionBetaModel().Predict(raw);
Assert.Equal(expected, actual, 9);
Assert.Equal(Math.Clamp(expected,
RegressionBetaModel.MinimumFittedConstant,
RegressionBetaModel.MaximumFittedConstant), actual, 9);
}

public static IEnumerable<object[]> RegressionVectors()
{
// Unbounded Python oracle outputs exercise both ends of the configured range.
yield return Vector(new[] { 8.0, 15.0, 20000.0, 10.0, 3.0, 10.0, 1.0 }, -136224.7610705242);
yield return Vector(new[] { 8.0, 15.0, 20.0, 10.0, 3.0, 10.0, 1000.0 }, 435992.9429645393);
yield return Vector(new[] { 6.727708533441772, 10.213333333333333, 0.0, 3.500000000000045, 2.3435703486210016, 5.223136339994041, 0.32687803060913356 }, 13.18826234659077);
yield return Vector(new[] { 5.3448652850017435, 9.373333333333333, 5.151860505357921, 3.8688372913206157, 1.527537949608422, 3.4463156768134153, 0.5098107142089721 }, 13.04867082557079);
yield return Vector(new[] { 6.556341447489688, 9.728309501643864, 2.7754445459480404, 4.146342997793008, 1.977721867042476, 10.365354504785971, 0.276398788581657 }, 13.335359785629928);
Expand Down
Loading