Group-scope predicts for MGC combination optimization
Adds a parallel set of Predict instances (groupPredictFlow / Power / Ctrl) that share input curves with the pump's individual predicts but maintain their own operating point. MGC drives these via setGroupOperatingPoint() to evaluate every pump curve at one shared manifold differential during combination optimization, without corrupting each pump's own diagnostic outputs (which track that pump's local sensors). Created lazily on first use so pumps without an MGC parent pay nothing. Pairs with generalFunctions Predict.shareInputsFrom plumbing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -87,6 +87,18 @@ class Machine {
|
||||
}
|
||||
}
|
||||
|
||||
// Group-scope predicts. These are parallel "views" of the same source
|
||||
// curves used by an MGC parent for combination optimization. Created
|
||||
// lazily on the first setGroupOperatingPoint() call so pumps that
|
||||
// never have an MGC parent pay nothing. They share input-curve refs
|
||||
// with the individual predicts (see Predict.shareInputsFrom) but
|
||||
// maintain independent operating-point state, so the pump's own
|
||||
// sensor stream and the MGC's group operating point can coexist.
|
||||
this.groupPredictFlow = null;
|
||||
this.groupPredictPower = null;
|
||||
this.groupPredictCtrl = null;
|
||||
this.groupNCog = 0;
|
||||
|
||||
this.state = new state(stateConfig, this.logger); // Init State manager and pass logger
|
||||
this.errorMetrics = new nrmse(errorMetricsConfig, this.logger);
|
||||
|
||||
@@ -1021,6 +1033,70 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
||||
|
||||
}
|
||||
|
||||
// ---------- Group-scope operating point (MGC parent uses this) ----------
|
||||
//
|
||||
// The pump's individual predicts (predictFlow / predictPower / predictCtrl)
|
||||
// are driven by THIS pump's own pressure sensors via getMeasuredPressure().
|
||||
// For combination optimization an MGC parent needs every pump curve
|
||||
// evaluated at ONE shared operating point (the manifold differential).
|
||||
// Doing that on the individual predicts would corrupt the pump's own
|
||||
// diagnostic outputs. So we keep a parallel set of predicts here that
|
||||
// ONLY the MGC drives via setGroupOperatingPoint(). Pump's individual
|
||||
// outputs are unaffected.
|
||||
|
||||
// Lazily create group-scope predicts that share input curves with the
|
||||
// individual ones. Safe to call multiple times.
|
||||
_ensureGroupPredicts() {
|
||||
if (!this.hasCurve || !this.predictFlow || !this.predictPower || !this.predictCtrl) return;
|
||||
if (this.groupPredictFlow && this.groupPredictPower && this.groupPredictCtrl) return;
|
||||
this.groupPredictFlow = new predict({ shareInputsFrom: this.predictFlow });
|
||||
this.groupPredictPower = new predict({ shareInputsFrom: this.predictPower });
|
||||
this.groupPredictCtrl = new predict({ shareInputsFrom: this.predictCtrl });
|
||||
}
|
||||
|
||||
// External (MGC) API: set the group operating point. Recomputes the
|
||||
// group predicts at the new differential pressure and updates groupNCog.
|
||||
// Does NOT touch this.predictFlow / predictPower / predictCtrl /
|
||||
// this.NCog / this.measurements.
|
||||
setGroupOperatingPoint(downstreamPa, upstreamPa) {
|
||||
this._ensureGroupPredicts();
|
||||
if (!this.groupPredictFlow || !this.groupPredictPower) return;
|
||||
if (!Number.isFinite(downstreamPa) || !Number.isFinite(upstreamPa)) return;
|
||||
const diff = downstreamPa - upstreamPa;
|
||||
if (diff <= 0) return;
|
||||
this.groupPredictFlow.fDimension = diff;
|
||||
this.groupPredictPower.fDimension = diff;
|
||||
if (this.groupPredictCtrl) this.groupPredictCtrl.fDimension = diff;
|
||||
this.groupNCog = this._calcGroupCog();
|
||||
}
|
||||
|
||||
// Power consumption at flow on the group operating point (used by
|
||||
// MGC's marginal-cost refinement). Falls back to the individual
|
||||
// calculation if the group predicts haven't been initialised.
|
||||
groupCalcPower(flow) {
|
||||
if (!this.groupPredictFlow || !this.groupPredictPower || !this.groupPredictCtrl) {
|
||||
return this.inputFlowCalcPower(flow);
|
||||
}
|
||||
this.groupPredictCtrl.currentX = flow;
|
||||
const cCtrl = this.groupPredictCtrl.y(flow);
|
||||
this.groupPredictPower.currentX = cCtrl;
|
||||
return this.groupPredictPower.y(cCtrl);
|
||||
}
|
||||
|
||||
// Mirrors calcCog() but reads from group predicts. Returns the
|
||||
// normalised cog (0..1) — the MGC optimizer uses this for BEP-Gravitation.
|
||||
_calcGroupCog() {
|
||||
if (!this.groupPredictFlow || !this.groupPredictPower) return 0;
|
||||
const powerCurve = this.groupPredictPower.currentFxyCurve[this.groupPredictPower.currentF];
|
||||
const flowCurve = this.groupPredictFlow.currentFxyCurve[this.groupPredictFlow.currentF];
|
||||
if (!powerCurve?.y?.length || !flowCurve?.y?.length) return 0;
|
||||
const { peakIndex } = this.calcEfficiencyCurve(powerCurve, flowCurve);
|
||||
const yMin = this.groupPredictFlow.currentFxyYMin;
|
||||
const yMax = this.groupPredictFlow.currentFxyYMax;
|
||||
if (yMax <= yMin) return 0;
|
||||
return (flowCurve.y[peakIndex] - yMin) / (yMax - yMin);
|
||||
}
|
||||
|
||||
// Function to predict control value for a desired flow
|
||||
calcCtrl(x) {
|
||||
if(this.hasCurve) {
|
||||
|
||||
Reference in New Issue
Block a user