Skip to content

Fix TSE and EM propulsion regressions - #86

Open
triet228 wants to merge 8 commits into
mainfrom
bugs
Open

triet228 wants to merge 8 commits into
mainfrom
bugs

Conversation

@triet228

@triet228 triet228 commented Sep 16, 2026

Copy link
Copy Markdown
Member

Closes #85

TSE connections and sizing

ProcessPropArch keeps every propeller directly connected to an engine, so PropAnalysis sums their thrust demands. It also propagates each propeller demand through every upstream path when computing engine hybrid coefficients; a direct engine no longer hides one reached through a cable.

% Propagate each propeller's demand through every upstream path. A direct
% engine connection must not hide a second engine reached through a cable.
for iprop = PropIdx
PowerVector(iprop) = SLSPower(iprop - nsrc);
Pout = PropulsionPkg.PowerFlow(PowerVector, Arch', LamDwn, EtaDwn, -1, 1.0e-06);
ActiveEngines = EngIdx(Pout(EngIdx) > 0);
Preq(ActiveEngines) = Preq(ActiveEngines) + Pout(ActiveEngines);
PSLS(ActiveEngines) = PSLS(ActiveEngines) + SLSPower(iprop - nsrc);
PowerVector(iprop) = 0;
end
% loop through all propellers to find directly connected gas turbine engines
for iprop = PropIdx
% find gas turbine engines
GTEIdx = find(Arch(:, iprop) > 0 & ID == 1 & itrn);
% check for an index
if (~isempty(GTEIdx))
% remember the index
for igte = GTEIdx(:)'
WhichProp{igte - nsrc}(end + 1) = iprop;
end

iprop = WhichProp{HasEng(ieng)};
% check if the engine is connected to a propeller
if (~isempty(iprop))
% get the thrust requirement from the propeller
TEng = sum(Tout(ibeg:iend, iprop), 2);
else
% there is no connection, assume idle thrust and that there is

The sizing loop uses the current engine's motor supplement and its actual graph index when locating its fan. This supports engines interleaved with other transmitters.

Engine = EngineModelPkg.TurbofanNonlinearSizing(Aircraft.Specs.Propulsion.Engine, Psupp(ieng(jeng)));

ifan = find((Arch(ieng(jeng)+nsrc, idx) == 1) & Prop);

Motor supplement accounting

For a parallel target, motor credit comes from target demand and its downstream motor share. This avoids counting full motor input at each target or applying the split twice. Fan efficiency applies only to fan targets. When engines share a target, credit follows their downstream shares.

Driving = find((Arch(:, icomp) > 0)' & (TrnType == 1));
% a shared target needs both an engine and a motor contribution
Helping = find((Arch(:, icomp) > 0)' & (TrnType == 0));
if (isempty(Driving) || isempty(Helping))
continue;
end
% Downstream splits describe fractions of the target demand. Motor
% power at the target already includes path losses in Preq, so using
% motor input power here would apply the same split a second time.
MotorPower = Preq(:, icomp) .* sum(Lambda(icomp, Helping));
% Only a fan target needs the engine fan-efficiency conversion.
TargetEfficiency = 1;
if (TrnType(icomp) == 2)
TargetEfficiency = EtaFan;
end
% When engines share the target, allocate the motor credit in
% proportion to their own contributions to that target.
EngineShares = Lambda(icomp, Driving);
if (sum(EngineShares) <= 0)
EngineShares = ones(size(EngineShares));
end
Psupp(:, Driving) = Psupp(:, Driving) + ...
MotorPower .* TargetEfficiency .* ...
(EngineShares ./ sum(EngineShares));

Independent parallel targets

RecomputeSplits determines each target's downstream shares from power delivered over its incoming edges. Separate targets can share an engine without being forced into one helper group. It checks that operational split parameters can represent those shares.

OperUps = Aircraft.Specs.Propulsion.PropArch.OperUps;
OperDwn = Aircraft.Specs.Propulsion.PropArch.OperDwn;
EtaUps = Aircraft.Specs.Propulsion.PropArch.EtaUps;
% Collect each parallel target's incoming edges. An engine can contribute
% to several targets, so its helpers cannot be treated as one group.
Source = [];
Target = [];
for itarget = nsrc + (1:length(TrnType))
Parents = find(Arch(:, itarget))';
Transmitters = Parents(Parents > nsrc & Parents <= nsrc + length(TrnType));
Engine = Transmitters(TrnType(Transmitters - nsrc) == 1);
Motor = Transmitters(TrnType(Transmitters - nsrc) == 0);
if (~isempty(Engine) && ~isempty(Motor))
Source = [Source, Parents];
Target = [Target, repmat(itarget, 1, length(Parents))];
end
end
if (isempty(Source) || nsplit == 0)
return
end
ncomp = size(Arch, 1);
DownIndex = sub2ind([ncomp, ncomp], Target, Source);
UpIndex = sub2ind([ncomp, ncomp], Source, Target);
for ipoint = find(idx)'
Active = Pav(ipoint, Target) > 0;
if (~any(Active))
continue
end
Current = LamDwn(ipoint, :);
UpMatrix = PropulsionPkg.EvalSplit(OperUps, LamUps(ipoint, :));
DownMatrix = PropulsionPkg.EvalSplit(OperDwn, Current);
EdgePower = Pav(ipoint, Source(Active))';
TargetPower = Pav(ipoint, Target(Active))';
UpFraction = UpMatrix(UpIndex(Active));
UpEfficiency = EtaUps(UpIndex(Active));
Desired = EdgePower .* UpFraction(:) .* UpEfficiency(:) ./ TargetPower;
Baseline = DownMatrix(DownIndex(Active));
Baseline = Baseline(:);
Sensitivity = zeros(sum(Active), nsplit);
for isplit = 1:nsplit
Perturbed = Current;
Perturbed(isplit) = Perturbed(isplit) + 0.01;
NewMatrix = PropulsionPkg.EvalSplit(OperDwn, Perturbed);
NewFraction = NewMatrix(DownIndex(Active));
Sensitivity(:, isplit) = (NewFraction(:) - Baseline) / 0.01;
end
Change = Sensitivity \ (Desired(:) - Baseline(:));
Updated = Current + Change';
NewMatrix = PropulsionPkg.EvalSplit(OperDwn, Updated);
Actual = NewMatrix(DownIndex(Active));
if (any(~isfinite(Updated)) || any(abs(Actual(:) - Desired(:)) > 1.0e-06))
error("FAST:EdgePowerInconsistent", ...
"Edge power is not consistent. Your power splits are over-constrained.");
end
LamDwn(ipoint, :) = Updated;

Validation

PropulsionPkg.TestEngineMotorRegressions() passes all 33 checks, including parameterized 3-, 5-, and 8-TSE connection, motor-credit, interleaved-sizing, and mixed-depth-path cases.

function [Success] = TestEngineMotorRegressions()

@triet228 triet228 self-assigned this Sep 16, 2026
@triet228 triet228 added the bug Something isn't working label Sep 16, 2026
@triet228
triet228 added this pull request to stack #89 September 16, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TSE and EM bugs

1 participant