summaryrefslogtreecommitdiff
path: root/TapTogglingAlgos/MarginLock.m
diff options
context:
space:
mode:
authorDavit Grigoryan <[email protected]>2026-04-09 12:25:24 -0700
committerDavit Grigoryan <[email protected]>2026-04-22 02:37:26 -0700
commit14ca50edb249d5f4ef621903259d955deb3a2fc3 (patch)
treea1798764b97c2c351ea9c8ab1422886e9488d18b /TapTogglingAlgos/MarginLock.m
public release v1.0HEADmain
Diffstat (limited to 'TapTogglingAlgos/MarginLock.m')
-rw-r--r--TapTogglingAlgos/MarginLock.m138
1 files changed, 138 insertions, 0 deletions
diff --git a/TapTogglingAlgos/MarginLock.m b/TapTogglingAlgos/MarginLock.m
new file mode 100644
index 0000000..c54ae25
--- /dev/null
+++ b/TapTogglingAlgos/MarginLock.m
@@ -0,0 +1,138 @@
+classdef MarginLock < handle
+ % Version: 0.1
+ % Description:
+ % If the smallest registered COM is above 3dB for some period,
+ % then assume it is safe to turn off the most "insignificant"
+ % tap (which now is, kind of, the smallest one)
+ %
+ % However, if smallest COM drops below 3dB for some period,
+ % then activate the fallback mechanism and turn on the recently
+ % disabled tap and terminate the algorithm
+
+ properties
+ lastDisabledTap
+ internalFFETapsMask
+ lastUpdateTime
+ reachedHighErr
+
+ cooldownPeriod
+ errCollectionPeriod
+
+ initialErrCounter
+ maxErr
+ runningMeanErr
+ errCounter
+ cooldownCounter
+ reachedHighCOM
+ reducedErrSeqCounter
+ increasedErrSeqCounter
+ end
+
+ methods
+ function obj = MarginLock(LMSUpdateObj)
+ obj.lastDisabledTap = -1;
+ obj.internalFFETapsMask = LMSUpdateObj.FFE_TapsMask;
+ obj.lastUpdateTime = LMSUpdateObj.StartFFEAdaptTime * LMSUpdateObj.WindowSize;
+ obj.reachedHighErr = 0;
+
+ obj.cooldownPeriod = 2;
+ obj.errCollectionPeriod = 10;
+
+ obj.initialErrCounter = 0;
+ obj.maxErr = 0;
+ obj.runningMeanErr = 0;
+ obj.errCounter = 0;
+ obj.cooldownCounter = 0;
+ obj.reachedHighCOM = 0;
+ obj.reducedErrSeqCounter = 0;
+ obj.increasedErrSeqCounter = 0;
+ end
+
+ function FFETapsMask = GetFFETapsMask(obj, LMSUpdateObj)
+ initialTogglingStart = LMSUpdateObj.LearningRatesAdjustTimes(1) + 8;
+ canStartAlgo = LMSUpdateObj.IterCount > initialTogglingStart * LMSUpdateObj.WindowSize;
+ errVecSize = max(size(LMSUpdateObj.ErrorVec));
+ inCooldown = obj.cooldownCounter > 0;
+
+ dlevDiff1 = LMSUpdateObj.Dlev_0p5 - LMSUpdateObj.Dlev_0p16;
+ dlevDiff2 = LMSUpdateObj.Dlev_0p16 - LMSUpdateObj.Dlev_m0p16;
+ dlevDiff3 = LMSUpdateObj.Dlev_m0p16 - LMSUpdateObj.Dlev_m0p5;
+ minDlevDiff = min([dlevDiff1 dlevDiff2 dlevDiff3]);
+ margin = minDlevDiff / 2;
+
+ % if eye is not open yet, monitor when high enough COM is
+ % reached
+ if canStartAlgo && ~obj.reachedHighCOM && ~obj.reachedHighErr
+ obj.maxErr = max(obj.maxErr, max(abs(LMSUpdateObj.ErrorVec)));
+ obj.initialErrCounter = obj.initialErrCounter + 1;
+ end
+
+ % since maximum error can not be used very accurately for
+ % monitoring high COM, the threshold for initial COM becomes 4dB
+ if canStartAlgo && ~obj.reachedHighCOM && ~obj.reachedHighErr
+ if obj.initialErrCounter >= 3 * LMSUpdateObj.WindowSize
+ approxMinCOM = 20*log10(margin / obj.maxErr);
+
+ if approxMinCOM >= 4
+ obj.reachedHighCOM = 1;
+ end
+
+ obj.initialErrCounter = 0;
+ obj.maxErr = 0;
+ end
+ end
+
+ if canStartAlgo && ~inCooldown && obj.reachedHighCOM && ~obj.reachedHighErr
+ obj.runningMeanErr = (sum(abs(LMSUpdateObj.ErrorVec)) + obj.runningMeanErr * obj.errCounter * errVecSize) / ((obj.errCounter + 1) * errVecSize);
+ obj.errCounter = obj.errCounter + 1;
+ end
+
+ if canStartAlgo && inCooldown && ~obj.reachedHighErr
+ obj.cooldownCounter = obj.cooldownCounter - 1;
+ end
+
+ weights = abs(LMSUpdateObj.TapWeights);
+
+ % Take into account the neighboring taps as well
+ weights_padded = [0 weights 0];
+ weights_avg = (weights_padded(1:end-2) + 2*weights_padded(2:end-1) + weights_padded(3:end)) / 3;
+ weights_avg(~obj.internalFFETapsMask) = inf;
+
+ [~, minTapIndex] = min(weights_avg);
+
+ if obj.errCounter >= obj.errCollectionPeriod * LMSUpdateObj.WindowSize && ~obj.reachedHighErr
+ std = obj.runningMeanErr * sqrt(pi / 2);
+ %std = std * 1.05; % due to ISI, samples are not all iid, so std is within +-5% with 95% CI
+ possibleMaxErr = 4.75 * std; % trying to find COM over 1e6 symbols with 90% confidence => using 5std
+ statMinCOM = 20*log10(margin / possibleMaxErr);
+
+ if statMinCOM >= 3
+ % Stop the LMS adaptation of FFE and DFE taps
+ LMSUpdateObj.setLearningRates(0, 0, LMSUpdateObj.MuDlev);
+
+ obj.internalFFETapsMask(minTapIndex) = 0;
+ obj.lastDisabledTap = minTapIndex;
+ obj.lastUpdateTime = LMSUpdateObj.IterCount;
+
+ % cooldown for some LMS iterations so that other taps can
+ % adapt to the recently turned off tap and then start
+ % measuring the error distribution
+ obj.cooldownCounter = obj.cooldownPeriod * LMSUpdateObj.WindowSize;
+ else
+ obj.reachedHighErr = 1;
+ if obj.lastDisabledTap ~= -1
+ obj.internalFFETapsMask(obj.lastDisabledTap) = 1;
+ end
+
+ % to make sure it throws an error in case tried being accessed
+ obj.lastDisabledTap = -1;
+ end
+
+ obj.errCounter = 0;
+ obj.runningMeanErr = 0;
+ end
+
+ FFETapsMask = obj.internalFFETapsMask;
+ end
+ end
+end \ No newline at end of file