Compare commits

1 Commits

Author SHA1 Message Date
Silas Oettinghaus
18ccaf8c12 duobinary cleanup (currently it is a mess) 2026-02-23 09:17:26 +01:00
606 changed files with 7158 additions and 43320 deletions

4
.gitignore vendored
View File

@@ -23,7 +23,3 @@ codegen/
.mat
# Local test dashboard status tracking
Tests/.last_test_run.json
Tests/reports/

12
.vscode/launch.json vendored
View File

@@ -1,12 +0,0 @@
{
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "matlab",
"name": "Debug MATLAB"
}
]
}

View File

@@ -1,344 +0,0 @@
# IM/DD Simulation Framework Overview
This file is for agents that encounter this repository for the first time.
Its goal is to explain what the codebase is for, where the main building
blocks live, and how a typical IM/DD workflow is assembled.
It is not a testing guide. The focus here is the simulation framework itself.
## What This Repository Is
This repository is a MATLAB-based simulation and analysis framework for
IM/DD optical communication systems.
At a high level, the codebase supports:
- generation of digital symbol and bit streams
- electrical transmitter modeling
- electro-optical modulation
- optical channel modeling
- photodetection and receiver front-end processing
- digital signal processing and sequence detection
- metric evaluation such as BER, GMI, AIR, EVM, and SNR
- project-specific simulation scripts and experimental analysis workflows
The codebase is not just one monolithic simulator. It is a toolkit of
reusable classes plus many project scripts that assemble those classes in
different ways.
## Mental Model
The easiest way to understand the repository is as a staged signal chain:
1. Information signal generation
2. Symbol mapping and pulse shaping
3. AWG / electrical drive path
4. Optical modulation
5. Fiber / channel propagation
6. Receiver front-end
7. Synchronization and DSP
8. Performance evaluation
The classes in `Classes/` implement the reusable blocks.
The scripts in `projects/` wire those blocks together into concrete systems.
## Suggested First File To Read
For a compact end-to-end example, start here:
- `projects/IMDD_base_system/minimal_example.m`
That file shows a reduced but representative full workflow:
- `PAMsource`
- `AWG`
- `EML`
- `Fiber`
- `Amplifier`
- `Photodiode`
- `Filter`
- `Scope`
- matched filtering
- synchronization
- equalization / MLSE
If you need to understand “how the system is intended to be used”, this is
one of the best entry points.
## Top-Level Structure
### `Classes/`
This is the core reusable framework.
Most simulation building blocks live here.
Subfolders are organized by role:
- `00_signals`
Base signal classes and core signal behavior.
This is the semantic foundation used by most of the rest of the codebase.
- `01_transmit`
Digital TX-side blocks such as bit/symbol generation, mapping, pulse
shaping, and AWG-related functionality.
- `02_etc`
General-purpose support blocks, especially filtering and amplification.
- `02_optical`
Optical-domain components such as modulators, multiplexing, fiber/channel
propagation, and related physical effects.
- `03_electrical`
Electrical-domain helper blocks and traces.
- `03_receive`
RX-side front-end blocks such as photodiodes and scopes/ADC behavior.
- `04_DSP`
Equalizers, timing recovery, postfilters, coding blocks, and sequence
detection such as MLSE.
- `05_Lab`
Instrument-control classes for real hardware.
These are not just simulations. They can talk to reachable devices.
- `DataBaseHandler`
Query/filter/result structures and database-facing helper classes.
- `Warehouse_class`
More specialized storage / plotting / result-handling infrastructure.
### `Functions/`
This holds free functions and workflow helpers that are not packaged as
classes.
Important subareas:
- `EQ_structures`
Higher-level DSP driver functions such as `ffe`, `vnle_postfilter_mlse`,
and related end-stage evaluation helpers.
- `Metrics`
BER, EVM, GMI, AIR, SNR, and related performance calculations.
- `EQ_visuals`
Diagnostic and visualization helpers for equalizers and result analysis.
- `channel_structures`
Channel-model helpers.
- `Job_Processing`
Scriptable processing helpers for bigger job/result workflows.
- `Theory`
Supporting theory calculations and one-off analytic utilities.
### `Datatypes/`
This contains enums and small type definitions used throughout the codebase.
Examples:
- normalization modes
- power notation
- filter types
- modulation / adaptation enums
When a class constructor takes a symbolic mode value, it is often defined here.
### `projects/`
This is where the reusable framework gets turned into concrete systems,
experiments, and studies.
The `projects/` folder is broad and includes:
- minimal examples
- paper-specific studies
- lab analysis scripts
- offline DSP pipelines
- experiment-specific workflows
These scripts are often the best place to understand intended usage patterns.
### `Tests/`
This contains the current MATLAB unit/integration test framework.
It validates parts of the reusable framework, not the full meaning of the
repository.
Use it as a quality tool, not as the primary documentation source for what
the simulation does.
## Core Signal Classes
The repository uses class-based signal objects instead of raw arrays whenever
possible.
Key signal classes:
- `Signal`
Base class with common signal behavior, metadata, logbook, arithmetic,
normalization, resampling, plotting, and utility methods.
- `Informationsignal`
Discrete/digital information-level representation.
- `Electricalsignal`
Electrical-domain signal representation.
- `Opticalsignal`
Optical-domain signal representation including optical metadata such as
wavelength and ASE-related quantities.
These classes are central because many other blocks accept or return them.
## Typical Workflow Composition
A representative IM/DD chain often looks like this:
1. `PAMsource`
Creates bits, mapped symbols, and shaped digital transmit signals.
2. `AWG`
Applies DAC-like processing, upsampling, quantization, and optional
filtering.
3. `EML`
Converts the electrical drive signal to an optical signal.
4. `Fiber`
Applies optical channel propagation.
5. `Amplifier`
Sets optical power / gain and can manipulate ASE handling.
6. `Photodiode`
Performs square-law detection and creates an electrical RX signal.
7. `Filter`
Applies electrical filtering.
8. `Scope`
Models ADC / sampling / quantization / optional RX-side LPF behavior.
9. `Pulseformer` in matched-filter mode
Used again on the RX side as matched filtering.
10. `tsynch`
Synchronizes the RX signal against transmitted symbols.
11. DSP blocks
Examples:
- `FFE`
- `EQ`
- `Postfilter`
- `MLSE`
- timing-recovery variants
12. Metrics
Performance is evaluated via helper functions and result structures.
## Two Important Layers
There are two distinct abstraction levels in the repo:
### 1. Block-level classes
Examples:
- `PAMsource`
- `Pulseformer`
- `AWG`
- `EML`
- `Fiber`
- `Photodiode`
- `Scope`
- `FFE`
- `MLSE`
These are the reusable simulation primitives.
### 2. Workflow-level functions/scripts
Examples:
- `projects/.../minimal_example.m`
- `Functions/EQ_structures/ffe.m`
- `Functions/EQ_structures/vnle_postfilter_mlse.m`
These assemble the primitives into practical runs and performance outputs.
When debugging behavior, it matters which layer you are in:
- if a signal object has the wrong shape or `fs`, look at block-level classes
- if BER/GMI pipelines behave unexpectedly, also inspect workflow-level DSP helpers
## Where To Look For What
If you want to understand:
- base signal semantics:
read `Classes/00_signals/*`
- transmitter generation:
read `Classes/01_transmit/*`
- optical propagation:
read `Classes/02_optical/*`
- receiver modeling:
read `Classes/03_receive/*`
- equalization and detection:
read `Classes/04_DSP/*` and `Functions/EQ_structures/*`
- metric definitions:
read `Functions/Metrics/*`
- intended end-to-end usage:
read `projects/IMDD_base_system/minimal_example.m`
## How To Approach The Repo As A New Agent
A good first-pass reading order is:
1. `projects/IMDD_base_system/minimal_example.m`
2. `Classes/00_signals/Signal.m`
3. `Classes/01_transmit/PAMsource.m`
4. `Classes/01_transmit/Pulseformer.m`
5. `Classes/01_transmit/AWG.m`
6. `Classes/02_optical/EML.m`
7. `Classes/02_optical/Fiber.m`
8. `Classes/03_receive/Photodiode.m`
9. `Classes/03_receive/Scope.m`
10. `Functions/EQ_structures/ffe.m`
11. `Functions/EQ_structures/vnle_postfilter_mlse.m`
12. `Classes/04_DSP/Equalizer/FFE.m`
13. `Classes/04_DSP/Sequence Detection/MLSE.m`
This gives both the architectural view and the runtime path.
## Practical Safety Note
Do not casually execute or test `Classes/05_Lab/*`.
Those are lab-device control classes, not harmless simulations.
In this environment, reachable device IPs can exist, and executing those
classes may change instrument set points and interfere with active experiments.
Treat `05_Lab` as operational code, not as a normal simulation subfolder.
## Final Summary
The repository is best understood as:
- a reusable class library for IM/DD system building blocks
- plus workflow helpers for DSP and analysis
- plus many project scripts that instantiate those blocks for concrete studies
If you are lost, do not start from `Tests/`.
Start from the minimal project workflow, then map each stage back to the
relevant class folder in `Classes/`.

View File

@@ -26,11 +26,11 @@ classdef Informationsignal < Signal
end
function pow = power(obj)
pow = mean(abs(obj.signal.^2),"all") ;
end
% function pow = power(obj)
%
% pow = mean(abs(obj.signal.^2),"all") ;
%
% end
end

View File

@@ -24,9 +24,9 @@ classdef Signal
obj.signal = signal;
obj.signal = obj.signal;
obj.fs = options.fs;
%
% [~,obj.gitSHA] = system('git rev-parse HEAD');
% [~,obj.gitStatus] = system('git status --porcelain');
[~,obj.gitSHA] = system('git rev-parse HEAD');
[~,obj.gitStatus] = system('git status --porcelain');
% [~,obj.gitPatch] = system('git diff');
%%% Stuff for Logbook %%%
@@ -172,9 +172,9 @@ classdef Signal
hold on;
if isempty(options.color)
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1);
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', 'none', 'LineStyle','-', 'MarkerSize', 0.1);
else
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1,'Color',options.color);
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', 'none', 'LineStyle','-', 'MarkerSize', 0.1,'Color',options.color);
end
% 2 c)
% - xlabel if not already here: time in readable format (1 ms and not 1e-3 s)
@@ -200,15 +200,12 @@ classdef Signal
%% Add signals from one signal to another, the first object will sustain
function Sum = plus(X,y)
if isa(X,'Signal') && isa(y,'Signal')
if isa(y,'Signal')
Sum = X;
Sum.signal = X.signal + y.signal;
elseif isa(X,'Signal') && isnumeric(y)
elseif isnumeric(y)
Sum = X;
Sum.signal = X.signal + y;
elseif isnumeric(X) && isa(y,'Signal')
Sum = y;
Sum.signal = X + y.signal;
end
end
@@ -216,42 +213,24 @@ classdef Signal
%% Add signals from one signal to another, the first object will sustain
function Diff = minus(X,y)
if isa(X,'Signal') && isa(y,'Signal')
if isa(y,'Signal')
Diff = X;
Diff.signal = X.signal - y.signal;
elseif isa(X,'Signal') && isnumeric(y)
elseif isnumeric(y)
Diff = X;
Diff.signal = X.signal - y;
elseif isnumeric(X) && isa(y,'Signal')
Diff = y;
Diff.signal = X - y.signal;
end
end
function Product = times(X,y)
if isa(X,'Signal') && isa(y,'Signal')
if isa(y,'Signal')
Product = X;
Product.signal = X.signal .* y.signal;
elseif isa(X,'Signal') && isnumeric(y)
elseif isnumeric(y)
Product = X;
Product.signal = X.signal .* y;
elseif isnumeric(X) && isa(y,'Signal')
Product = y;
Product.signal = X .* y.signal;
end
end
function Product = mtimes(X,y)
if (isa(X,'Signal') && isnumeric(y) && isscalar(y)) || ...
(isnumeric(X) && isscalar(X) && isa(y,'Signal'))
Product = times(X,y);
else
error('Signal:mtimes:UnsupportedOperands', ...
'Use element-wise .* for Signal multiplication, or scalar * Signal for scaling.');
end
end
@@ -346,8 +325,7 @@ classdef Signal
else
[p, q] = rat(options.fs_out / options.fs_in);
obj.signal = resample(obj.signal,p,q,options.n,options.beta);
obj.signal = resample(obj.signal,options.fs_out,options.fs_in,options.n,options.beta);
desc = ['resample signal from ', num2str(options.fs_in*1e-9), ' GHz to ', num2str(options.fs_out*1e-9), ' GHz' ];
@@ -368,13 +346,10 @@ classdef Signal
options.displayname = "";
options.color = [];
options.linestyle = '-';
options.HandleVisibility (1,1) string {mustBeMember(options.HandleVisibility, ["on","off"])} = "on";
options.normalizeToNyquist = 0;
options.normalizeToSamplingRate = 0;
options.addDCoffset = 0;
options.normalizeToDC = 0;
options.normalizeTo0dB = 0;
options.show_onesided = false;
options.max_num_lines = []; % Leave empty or omit to disable line rotation
options.fft_length = [];
% --- NEW options ---
@@ -387,10 +362,7 @@ classdef Signal
end
useSamplingRateAxis = options.normalizeToSamplingRate ~= 0;
useRadPerSampleAxis = options.normalizeToNyquist ~= 0 && ~useSamplingRateAxis;
if ~useRadPerSampleAxis && ~useSamplingRateAxis
if options.normalizeToNyquist == 0
[p_lin,f_Hz] = pwelch(obj.signal, hanning(options.fft_length), ...
options.fft_length/2, options.fft_length, ...
obj.fs, "centered", "power", "mean");
@@ -399,11 +371,11 @@ classdef Signal
[p_lin,f_rad] = pwelch(obj.signal, hanning(options.fft_length), ...
options.fft_length/2, options.fft_length, ...
"centered", "power", "mean");
% In normalized modes, pwelch returns rad/sample centered on 0.
% Divide by 2*pi for the f/fs axis where Nyquist is 0.5.
% In normalized mode, pwelch returns rad/sample centered on 0.
% We'll keep f_rad for the x-axis in that mode.
end
p_lin = movmean(p_lin,10);
% p_lin = movmean(p_lin,4);
if options.normalizeTo0dB
p_lin = p_lin ./ max(p_lin);
@@ -415,7 +387,7 @@ classdef Signal
end
% --- If requested, build wavelength axis from frequency offset ---
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
if options.useWavelengthAxis && options.normalizeToNyquist == 0
c = physconst('LightSpeed'); % [m/s]
lambda0_m = options.lambda0_nm*1e-9; % center wavelength [m]
f_c = c / lambda0_m; % carrier frequency [Hz]
@@ -428,36 +400,21 @@ classdef Signal
% assign axis
x_vec = lambda_nm(:);
x_label = "Wavelength [nm]";
dc_axis = f_Hz;
% Sort to ensure axis is ascending
[x_vec, sortIdx] = sort(x_vec, 'ascend');
p_dbm = p_dbm(sortIdx, :);
dc_axis = dc_axis(sortIdx);
else
% Frequency or normalized axes
if ~useRadPerSampleAxis && ~useSamplingRateAxis
if options.normalizeToNyquist == 0
x_vec = f_GHz;
x_label = "Frequency in GHz";
dc_axis = f_GHz;
elseif useSamplingRateAxis
x_vec = f_rad ./ (2*pi);
x_label = "Normalized Frequency f/fs";
dc_axis = x_vec;
else
x_vec = f_rad; % normalized frequency in rad/sample
x_label = "Normalized Frequency [rad/sample]";
dc_axis = x_vec;
x_label = "Normalized Frequency";
end
end
if options.show_onesided
keep_idx = dc_axis >= 0;
x_vec = x_vec(keep_idx);
dc_axis = dc_axis(keep_idx);
p_dbm = p_dbm(keep_idx, :);
end
figure(options.fignum);
ax = gca;
hold on
@@ -465,7 +422,7 @@ classdef Signal
p_dbm = p_dbm+options.addDCoffset;
if options.normalizeToDC
[~,min_idx]=min(abs(dc_axis));
[~,min_idx]=min(abs(f_GHz));
pow_at_dc = p_dbm(min_idx);
p_dbm = p_dbm-pow_at_dc;
end
@@ -473,9 +430,9 @@ classdef Signal
for s = 1:min(size(p_dbm))
if isempty(options.color)
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'LineStyle', options.linestyle, 'HandleVisibility', options.HandleVisibility);
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1);
else
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'Color', options.color,'LineStyle',options.linestyle, 'HandleVisibility', options.HandleVisibility);
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'Color', options.color,'LineStyle',options.linestyle);
end
end
@@ -491,25 +448,15 @@ classdef Signal
% Axis labels and limits
xlabel(x_label);
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
if options.useWavelengthAxis && options.normalizeToNyquist == 0
xlim([min(x_vec) max(x_vec)]);
else
if ~useRadPerSampleAxis && ~useSamplingRateAxis
if options.normalizeToNyquist == 0
% Keep your existing freq handling (you can fine-tune as needed)
% xlim([-128 128]); % example for 256 GSa/s if desired
xlim([min(x_vec) max(x_vec)]);
elseif useSamplingRateAxis
if options.show_onesided
xlim([0, 0.5]);
else
xlim([-0.5, 0.5]);
end
else
if options.show_onesided
xlim([0, pi]);
else
xlim([-pi, pi]);
end
xlim([-pi, pi]);
end
end
@@ -526,10 +473,13 @@ classdef Signal
end
y_margin = 0.05 * y_range;
ylim([y_min - y_margin, y_max + y_margin]);
yticks(-200:10:200);
grid on;
ylim([y_min - y_margin, y_max + y_margin]);
% Set ticks automatically, avoid overpopulation
try
yticks(round(linspace(y_min, y_max, min(10, max(4, ceil(y_range/10))))));
end
grid on;
% Add legend if not already present
if isempty(get(gca, 'Legend'))
@@ -812,18 +762,11 @@ classdef Signal
pkpos = sort(pkpos);
if isempty(pks)
warning(['Error in findpeaks, ususally the seuqnece is too short. No Peaks detected']);
return
end
if max(p) < 0.3 || median(w) > 15
%median(w) > 15 part means reject if the detected correlation peaks are too broad. That can be sensible: a true sync peak should often be sharp.
warning(['Error in findpeaks, ususally the seuqnece is too short. max(p) = ',num2str(max(p)),'; median(w)=',num2str(median(w)),'']);
return
end
sequenceFound = 1;
% if mean(w) > 15 || mean(p) > 15
% return
% else
% sequenceFound = 1;
% end
if options.debug_plots
figure(121212);clf
@@ -860,10 +803,6 @@ classdef Signal
S{c}.logbook = [];
end
if ~isempty(S)
obj = S{1};
end
else
%do nothing when shifts are negative or there are none...
@@ -986,10 +925,9 @@ classdef Signal
M
options.fignum = 100;
options.displayname = "";
options.mode = 1; %1= histogram method; 2= intuitive "line based" eye
end
mode = options.mode;
mode = 1;
histpoints = 2048; %% verticale resolution
histpoints = floor(histpoints/2)*2+1; %% to have the eye digram centered around one point make the vertical resolution uneven
@@ -1028,9 +966,8 @@ classdef Signal
col = cbrewer2('Set1',2);
for n=1:1000
hold on
plot(eye_mat(:,n),'LineStyle','-','LineWidth',0.1,'Color',col(2,:));
plot(eye_mat(:,n),'LineStyle',':','LineWidth',0.1,'Color',col(2,:));
end
xlabel('Samples','Interpreter','latex')
ylabel('Amplitude of Signal','Interpreter','latex');
xlim([0 histpoints_horizontal])
@@ -1038,39 +975,8 @@ classdef Signal
elseif mode == 1
% generate eye diagram using histogram
finite_eye = eye_mat(isfinite(eye_mat));
if isempty(finite_eye)
finite_eye = sig(isfinite(sig));
end
amp_min = min(finite_eye);
amp_max = max(finite_eye);
amp_center = (amp_max + amp_min) / 2;
amp_span = amp_max - amp_min;
if amp_span == 0
amp_span = max(abs(amp_center),1);
end
amp_margin = 0.08 * amp_span;
maxA = amp_center + amp_span/2 + amp_margin;
minA = amp_center - amp_span/2 - amp_margin;
if ~isa(obj,'Opticalsignal') && minA < 0 && maxA > 0
targetStep = max(abs([minA maxA])) / 2;
if targetStep > 0
stepMagnitude = 10^floor(log10(targetStep));
normalizedStep = targetStep / stepMagnitude;
if normalizedStep <= 1
tickStep = stepMagnitude;
elseif normalizedStep <= 2
tickStep = 2 * stepMagnitude;
elseif normalizedStep <= 5
tickStep = 5 * stepMagnitude;
else
tickStep = 10 * stepMagnitude;
end
axisLimit = 2 * tickStep;
maxA = axisLimit;
minA = -axisLimit;
end
end
maxA = max(sig(100:end-100))*1.3;
minA = min(sig(100:end-100))*1.3;
% maxA = 0.12;
% minA = -0.08;
@@ -1078,7 +984,6 @@ classdef Signal
difference= maxA-minA;
data_ind_y=round((eye_mat-minA)/difference*(histpoints-1)) +1;
data_ind_y = min(max(data_ind_y,1),histpoints);
for n=1:size(data_ind_y,1)
nn=histcounts(data_ind_y(n,:),1:histpoints+1);
@@ -1104,19 +1009,19 @@ classdef Signal
if isa(obj,'Opticalsignal')
title(['Optical Eye ',options.displayname])
ylabel("Power in mW");
yTickValues = linspace(maxA.*1e3,minA.*1e3,5);
y_tickstring = string(linspace(maxA.*1e3,minA.*1e3,6));
min_ = min(abs(obj.signal(100:end-100)).^2);
max_ = abs(max(obj.signal(100:end-100)).^2);
elseif isa(obj,'Electricalsignal')
title(['Electrical Eye ',options.displayname])
ylabel("Voltage in V");
yTickValues = linspace(maxA,minA,5);
y_tickstring = string(linspace(maxA,minA,6));
min_ = min(obj.signal(100:end-100));
max_ = abs(max(obj.signal(100:end-100)));
else
title(['Digital Eye ',options.displayname])
ylabel("Digital Signal Amplitude");
yTickValues = linspace(maxA,minA,5);
y_tickstring = string(linspace(maxA,minA,6));
min_ = min(obj.signal(100:end-100));
max_ = abs(max(obj.signal(100:end-100)));
end
@@ -1157,14 +1062,7 @@ classdef Signal
hist_interest_smoth = smooth(hist_interest,20);
a = scatter(hist_interest_smoth+posxall,1:length(hist_interest_smoth),4,'.','MarkerEdgeColor','red');
minPeakDistance = max(10, floor(histpoints / (2*M)));
minPeakProminence = max(3, 0.05 * max(hist_interest_smoth));
[pk,loc] = findpeaks(hist_interest_smoth, ...
"MinPeakDistance",minPeakDistance, ...
"NPeaks",M, ...
"MinPeakProminence",minPeakProminence, ...
"SortStr","descend");
loc = sort(loc);
[pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",10,"NPeaks",M,"MinPeakHeight",30,"MinPeakProminence",10);
scatter(posxall,loc,'red','Marker','x','LineWidth',2);
@@ -1247,13 +1145,12 @@ classdef Signal
end
yTickPositions = linspace(1,histpoints,numel(yTickValues));
yticks(yTickPositions);
yticklabels(sprintfc('%.2f', yTickValues));
yticks(linspace(0,histpoints,6));
y_tickstring = sprintfc('%.2f', y_tickstring);
yticklabels(y_tickstring);
xTickValues = linspace(0, 2/fsym, 6) .* 1e12;
xticks(linspace(1,histpoints_horizontal,numel(xTickValues)))
x_tickstring = sprintfc('%.2f', xTickValues);
xticks(linspace(0,histpoints_horizontal,6))
x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12);
xticklabels(x_tickstring);
%

View File

@@ -254,7 +254,7 @@ classdef ChannelFreqResp < handle
xlim([0.2 .5*max(obj.faxis)*1e-9]); grid on;
%%% plot for publication
figure(1996);
figure(101);
hold all;
box on;
title('Magnitude Freq. Response');

View File

@@ -462,49 +462,11 @@ classdef PAMmapper
end
function [out, levels] = splitByReferenceLevels(obj, data_in, reference_in, options)
% Split received samples by the transmitted/reference PAM level.
% Unlike separate_pamlevels, this does not decide the RX level.
arguments
obj
data_in
reference_in
options.levels = []
options.tolerance (1,1) double {mustBeNonnegative} = 0
end
data = obj.toNumericVector(data_in);
reference = obj.toNumericVector(reference_in);
if numel(data) ~= numel(reference)
error("PAMmapper:LengthMismatch", ...
"data_in and reference_in must have the same number of samples.");
end
if isempty(options.levels)
levels = unique(reference);
else
levels = options.levels(:).';
end
out = NaN(numel(levels), numel(reference));
for levelIdx = 1:numel(levels)
if options.tolerance == 0
levelMask = reference == levels(levelIdx);
else
levelMask = abs(reference - levels(levelIdx)) <= options.tolerance;
end
out(levelIdx, levelMask) = data(levelMask);
end
end
function [Signal_out] = quantize(obj,Signal_in,options)
arguments
obj
Signal_in Signal
options.custom_const = [];
obj
Signal_in
options.custom_const = []
end
if isempty(options.custom_const)
@@ -542,133 +504,10 @@ classdef PAMmapper
end
function [ax, mapping_table] = plotBitMapping(obj)
[constellation, bitmap] = obj.getConstellationBitMapping();
constellation = constellation .* obj.get_scaling;
bit_labels = string(cellstr(char(bitmap + '0')));
if obj.M == 6
[~, order] = sortrows([-constellation(:,2), constellation(:,1)]);
constellation = constellation(order, :);
bit_labels = bit_labels(order);
mapping_table = table(constellation(:,1), constellation(:,2), bit_labels, ...
'VariableNames', {'symbol_1', 'symbol_2', 'bits'});
fig = figure("Name", sprintf("PAM-%d Bit Mapping", obj.M));
ax = axes(fig);
scatter(ax, constellation(:,1), constellation(:,2), 60, "filled");
hold(ax, "on");
grid(ax, "on");
box(ax, "on");
x_step = min(diff(unique(constellation(:,1))));
y_step = min(diff(unique(constellation(:,2))));
text(ax, constellation(:,1), constellation(:,2) + 0.18*y_step, bit_labels, ...
"HorizontalAlignment", "center", "FontName", "Consolas");
xlabel(ax, "Symbol 1");
ylabel(ax, "Symbol 2");
title(ax, sprintf("PAM-%d Mapping", obj.M));
axis(ax, "equal");
xticks(ax, unique(constellation(:,1)));
yticks(ax, unique(constellation(:,2)));
xlim(ax, [min(constellation(:,1)) - 0.8*x_step, max(constellation(:,1)) + 1.4*x_step]);
ylim(ax, [min(constellation(:,2)) - 0.6*y_step, max(constellation(:,2)) + 0.9*y_step]);
else
constellation = constellation(:);
mapping_table = table(constellation, bit_labels, ...
'VariableNames', {'symbol', 'bits'});
fig = figure("Name", sprintf("PAM-%d Bit Mapping", obj.M));
ax = axes(fig);
scatter(ax, constellation, zeros(size(constellation)), 60, "filled");
hold(ax, "on");
grid(ax, "on");
box(ax, "on");
text(ax, constellation, 0.08*ones(size(constellation)), bit_labels, ...
"HorizontalAlignment", "center", "FontName", "Consolas");
xlabel(ax, "Symbol");
title(ax, sprintf("PAM-%d Mapping", obj.M));
yticks(ax, []);
ylim(ax, [-0.2 0.2]);
xticks(ax, constellation);
if numel(constellation) > 1
x_step = min(diff(unique(constellation)));
else
x_step = 1;
end
xlim(ax, [min(constellation) - 0.8*x_step, max(constellation) + 0.8*x_step]);
end
if nargout == 0
disp(mapping_table);
end
end
function bitmap = showBitMapping(obj)
[~, bitmap] = obj.getConstellationBitMapping();
end
bitmap = obj.demap((obj.levels ./ obj.scaling)');
function [Gp, details] = graypenalty(obj)
[constellation, bit_labels] = obj.getConstellationBitMapping();
num_points = size(constellation, 1);
dist2 = inf(num_points);
for idx = 1:num_points
delta = constellation - constellation(idx, :);
dist2(idx, :) = sum(delta.^2, 2).';
end
dist2(1:num_points+1:end) = inf;
min_dist2 = min(dist2(:));
is_neighbor = abs(dist2 - min_dist2) < 1e-12;
hamming_dist = zeros(num_points);
for idx = 1:num_points
hamming_dist(idx, :) = sum(bit_labels ~= bit_labels(idx, :), 2).';
end
Gp = sum(hamming_dist(is_neighbor)) / nnz(is_neighbor);
if nargout > 1
details = struct( ...
"constellation", constellation, ...
"bit_labels", bit_labels, ...
"neighbor_mask", is_neighbor, ...
"nearest_distance", sqrt(min_dist2));
end
end
function [constellation, bitmap] = getConstellationBitMapping(obj)
if obj.M == 6
constellation = obj.thresholds ./ obj.scaling;
bitmap = obj.demap(reshape(constellation.', [], 1));
bitmap = reshape(bitmap, 5, []).';
else
constellation = (obj.levels ./ obj.scaling)';
bitmap = obj.demap(constellation);
end
end
end
methods (Access = private)
function values = toNumericVector(~, signalLike)
if isa(signalLike, "Signal")
values = signalLike.signal;
else
values = signalLike;
end
values = values(:).';
end
end

View File

@@ -136,7 +136,6 @@ classdef PAMsource
%%%%%% Duobinary %%%%%%%%%%%
% this is translation from user input to db_mode which was added later... "precode" und "encode" sind auch besser zu verstehen an dieser stelle daher hab ichs gelassen
if obj.db_precode
obj.duobinary_mode = db_mode.db_precoded;
end
@@ -149,13 +148,10 @@ classdef PAMsource
case db_mode.no_db
case db_mode.db_precoded
% symbols = Duobinary().precode(symbols);
symbols = Partialresponse().precode(symbols);
symbols = Duobinary().precode(symbols);
case db_mode.db_encoded
% symbols = Duobinary().precode(symbols);
% symbols = Duobinary().encode(symbols);
symbols = Partialresponse().precode(symbols);
symbols = Partialresponse().encode(symbols);
symbols = Duobinary().precode(symbols);
symbols = Duobinary().encode(symbols);
end

View File

@@ -1,65 +1,12 @@
classdef Signalgenerator
%SIGNALGENERATOR Generate simple information-level test sequences.
%
% This class creates Informationsignal objects for quick simulations,
% mapper checks, and theory scripts. Use the "form" option to select
% the generated sequence type.
%
% Examples:
%
% % Sine wave
% sig = Signalgenerator( ...
% "form", signalform.sine, ...
% "length", 1024, ...
% "fs", 1000, ...
% "fsig", 50).process();
%
% % Deterministic random bit matrix, 2 bits per row
% bits = Signalgenerator( ...
% "form", signalform.random, ...
% "length", 1024, ...
% "dimension", 2, ...
% "randkey", 3).process();
%
% % PRMS bit matrix for PAM-4 mapping. M selects the mapper-ready bit
% % shape. If length is omitted, the output length is derived from
% % order, as in the old PAMsource.
% bits = Signalgenerator( ...
% "form", signalform.prms, ...
% "M", 4, ...
% "order", 7).process();
% symbols = PAMmapper(4, 0).map(bits);
%
% % PRMS for PAM-6: 5 bits map to 2 PAM-6 symbols. M = 6 returns
% % the 1-D bit vector expected by PAMmapper.
% bits = Signalgenerator( ...
% "form", signalform.prms, ...
% "M", 6, ...
% "order", 7).process();
% symbols = PAMmapper(6, 0).map(bits);
%
% % Map, demap, and check BER
% mapper = PAMmapper(8, 0);
% bits = Signalgenerator( ...
% "form", signalform.prms, ...
% "length", 1024, ... % explicit output length override
% "dimension", 3, ...
% "order", 5).process();
% symbols = mapper.map(bits);
% rxBits = mapper.demap(symbols);
% [checkedBits, errors, ber] = calc_ber(rxBits, bits.signal);
%NAME Summary of this class goes here
% Detailed explanation goes here
properties(Access=public)
form
length
fs
fsig
dimension
M
order
randkey
skip
bruijn
end
@@ -70,15 +17,9 @@ classdef Signalgenerator
arguments
options.form signalform = signalform.sine
options.length double = []
options.length double = 1024
options.fs double = 1000 %Hz sampling
options.fsig double = 50 % Hz fundamental frex e.g. of the sine or sawtooth
options.dimension double = 1
options.M double = []
options.order double = 7
options.randkey double = 0
options.skip double = 0
options.bruijn logical = false
end
%
@@ -89,15 +30,6 @@ classdef Signalgenerator
end
end
if ~isempty(obj.M)
obj.validate_pam_format();
obj.dimension = obj.mapper_bit_dimension();
end
if isempty(obj.length)
obj.length = obj.default_length();
end
end
function signalclass_out = process(obj)
@@ -137,15 +69,9 @@ classdef Signalgenerator
% Generate sine wave
signal = A * sin(2*pi*f*t);
case signalform.noise
s = RandStream('twister','Seed',obj.randkey);
signal = randn(s, 1, obj.length);
case signalform.random
signal = obj.build_random_data();
case signalform.prms
signal = obj.build_prms_data();
end
signal = obj.format_for_mapper(signal);
end
@@ -156,361 +82,6 @@ classdef Signalgenerator
% Cant be seen from outside! So put all your functions here that can/
% shall not be called from outside
function signal = build_random_data(obj)
arguments(Input)
obj
end
s = RandStream('twister','Seed',obj.randkey);
signal = randi(s, [0 1], obj.length, obj.dimension);
end
%BUILD_PRMS_DATA Generate pseudo-random multi-level sequence bits.
%
% The output is a binary matrix with size:
% obj.length x obj.dimension
%
% Each row is one generated bit group / symbol time. Each column is
% one parallel PRMS bit stream. For conventional PAM formats this
% means:
% PAM-2: dimension = 1
% PAM-4: dimension = 2
% PAM-8: dimension = 3
%
% PAM-6 is special in this codebase: the mapper consumes 5 bits and
% maps them to two PAM-6 symbols. Prefer M = 6 to generate the 1-D
% vector expected by PAMmapper(6, ...).map(...). The legacy
% dimension = 5 path still returns the unflattened bit matrix.
%
% Relevant Signalgenerator options:
% length Number of generated PRMS bit groups / rows.
% dimension Number of parallel bit streams per row.
% M Optional PAM format. When set, dimension is selected
% automatically and PAM-6 output is mapper-ready.
% order User-facing sequence order, matching the old
% PAMsource behavior. If length is omitted, it sets the
% output length. Internally it is converted to the PRMS
% order per stream.
% Default output length is 2^(order-1) for all mapper
% shapes. PAM-6 still reduces the internal PRMS order
% to keep the register length manageable.
% skip Number of PRMS symbols to advance before output. This
% is useful when different blocks should use shifted
% sections of the same deterministic sequence.
% bruijn If true, enables de Bruijn-style zero-symbol insertion
% as in the legacy MOVE-IT generator.
%
% The implementation mirrors the legacy MOVE-IT/prms_c register
% logic in plain MATLAB, so no mex build is required.
function signal = build_prms_data(obj)
arguments(Input)
obj
end
obj.validate_prms_parameters();
state.dimension = obj.dimension;
state.order = obj.internal_prms_order();
state.periodicity = state.dimension * state.order;
state.bl = obj.length;
state.bruijn = obj.bruijn;
state.bruijn_counter = -1;
state.srgtaps = Signalgenerator.srgtap_masks(state.periodicity);
mat_table = Signalgenerator.build_prms_mapping(state.dimension);
state.mat_table = mat_table * 2.^(0:state.dimension-1).';
state.reg_mask = 2^state.periodicity - 1;
state.data_mask = 2^(state.periodicity - 1);
tmp_reg = 2^49 - 1;
tmp_reg = Signalgenerator.advance_prbs_register(tmp_reg, state.srgtaps, 1, state.reg_mask);
state.reg = zeros(state.dimension, 1);
state.reg(1) = tmp_reg;
modulo_mask = 2^state.dimension - 1;
offset = (2^state.periodicity - 1) / modulo_mask;
for n = 2:state.dimension
tmp_reg = Signalgenerator.advance_prbs_register(tmp_reg, state.srgtaps, offset, state.reg_mask);
state.reg(n) = tmp_reg;
end
if obj.skip > 0
state = Signalgenerator.advance_prms_state(state, obj.skip);
end
[data_out, ~] = Signalgenerator.generate_prms_block(state, state.bl);
signal = data_out.';
end
function validate_prms_parameters(obj)
if obj.length < 1 || fix(obj.length) ~= obj.length
error("Signalgenerator:InvalidLength", ...
"PRMS length must be a positive integer.");
end
if obj.dimension < 1 || fix(obj.dimension) ~= obj.dimension
error("Signalgenerator:InvalidDimension", ...
"PRMS dimension must be a positive integer.");
end
if obj.order < 1 || fix(obj.order) ~= obj.order
error("Signalgenerator:InvalidOrder", ...
"PRMS order must be a positive integer.");
end
if obj.skip < 0 || fix(obj.skip) ~= obj.skip
error("Signalgenerator:InvalidSkip", ...
"PRMS skip must be a nonnegative integer.");
end
if obj.dimension * obj.internal_prms_order() > 48
error("Signalgenerator:UnsupportedOrder", ...
"PRMS dimension * internal PRMS order must be <= 48.");
end
end
function validate_pam_format(obj)
if ~ismember(obj.M, [2 4 6 8 16])
error("Signalgenerator:InvalidPAMFormat", ...
"M must be one of 2, 4, 6, 8, or 16.");
end
end
function length = default_length(obj)
switch obj.form
case {signalform.random, signalform.prms}
if obj.is_pam6_shape()
% PAM-6 consumes 5 input bits for 2 output symbols.
% To keep the symbol count aligned with the other
% PAM formats, reduce the number of generated rows
% before flattening to the mapper-ready bit vector.
length = 2^max(0, obj.order - 2);
else
length = 2^max(0, obj.order - 1);
end
otherwise
length = 1024;
end
if obj.form == signalform.prms && length > 2^20
error("Signalgenerator:AutoLengthTooLarge", ...
"Auto-derived PRMS length would be %d samples, which is too large for an implicit default. Pass an explicit length if you really want a longer sequence.", ...
length);
end
end
function prms_order = internal_prms_order(obj)
bits_per_symbol = obj.prms_parallel_width();
prms_order = max(1, floor(obj.order / bits_per_symbol));
end
function width = prms_parallel_width(obj)
if ~isempty(obj.M)
width = obj.mapper_bit_dimension();
else
width = obj.dimension;
end
end
function dimension = mapper_bit_dimension(obj)
if obj.M == 6
dimension = 5;
else
dimension = log2(obj.M);
end
end
function tf = is_pam6_shape(obj)
tf = (~isempty(obj.M) && obj.M == 6) || (isempty(obj.M) && obj.dimension == 5);
end
function signal = format_for_mapper(obj, signal)
if ~isempty(obj.M) && obj.M == 6
signal = reshape(signal.', [], 1);
signal = signal(1:end - mod(numel(signal), 5));
end
end
end
methods (Static, Access=private)
function state = advance_prms_state(state, number_symbols)
[~, state] = Signalgenerator.generate_prms_block(state, number_symbols);
end
function [data_out, state] = generate_prms_block(state, block_length)
data_out = zeros(state.dimension, block_length);
counter = 0;
while counter < block_length
if state.bruijn == 2
counter = counter + 1;
state.bruijn = 1;
continue;
end
for n = 1:state.dimension
prms_tmp_value = false;
for m = 1:state.dimension
prms_tmp_value = xor(prms_tmp_value, ...
state.reg(m) >= state.data_mask && ...
bitand(state.mat_table(m), 2^(n-1)) ~= 0);
end
data_out(n, counter + 1) = prms_tmp_value;
end
for n = 1:state.dimension
state.reg(n) = Signalgenerator.advance_prbs_register( ...
state.reg(n), state.srgtaps, 1, state.reg_mask);
end
counter = counter + 1;
if state.bruijn == 1
if state.bruijn_counter < 0
if any([~sum(data_out(:, counter)) state.order == 1])
state.bruijn_counter = state.bruijn_counter - 1;
if any([state.bruijn_counter == -state.order state.order == 1])
state.bruijn_counter = 2^state.periodicity - 1;
state.bruijn = 2;
end
else
state.bruijn_counter = -1;
end
else
state.bruijn_counter = state.bruijn_counter - 1;
if state.bruijn_counter == 0
state.bruijn_counter = 2^state.periodicity - 1;
state.bruijn = 2;
end
end
end
end
end
function reg = advance_prbs_register(reg, tap_masks, number_steps, reg_mask)
for idx = 1:number_steps
feedback = false;
for tap_idx = 1:numel(tap_masks)
if tap_masks(tap_idx) ~= 0
feedback = xor(feedback, bitand(reg, tap_masks(tap_idx)) ~= 0);
end
end
reg = bitand(reg * 2 + double(feedback), reg_mask);
end
end
function mat_table = build_prms_mapping(dimension)
if dimension > 2
bchpolynomial = zeros(1, dimension + 1);
bchpolynomial([1 Signalgenerator.srgtaps(dimension) + 1]) = 1;
betatable = fliplr(Signalgenerator.cyclgen_local(2^dimension - 1, bchpolynomial).');
elseif dimension == 2
betatable = [0 1; 1 0; 1 1];
else
betatable = 1;
end
binvec = 2.^(dimension-1:-1:0);
modulo_mask = 2^dimension - 1;
betatable_sort_forward = sum(repmat(binvec, 2^dimension - 1, 1) .* betatable, 2);
[~, betatable_sort_inverse] = sort(betatable_sort_forward);
betatable_sort_inverse = betatable_sort_inverse - 1;
dividend = zeros(dimension + 1, 2);
dividend([dimension - Signalgenerator.srgtaps(dimension) + 1 end], 1) = 1;
h = zeros(dimension, 2);
for digit = 1:dimension
h(digit, 1:2) = dividend(digit, 1:2);
dividend(digit, 1:2) = [0 0];
newdiv_binary = xor( ...
betatable(dividend(digit + 1, 2) + 1, :) * dividend(digit + 1, 1), ...
betatable(rem(h(digit, 2) + 1, modulo_mask) + 1, :) * h(digit, 1));
dividend(digit + 1, 1) = sum(newdiv_binary) > 0;
if (digit ~= dimension) && dividend(digit + 1, 1)
dividend(digit + 1, 2) = dividend(digit + 1, 1) * ...
betatable_sort_inverse(sum(newdiv_binary .* binvec));
else
dividend(digit + 1, 2) = 0;
end
end
mat_table = betatable(h(:, 2) + 1, :);
end
function code = cyclgen_local(columns, polynomial)
rows = log2(columns + 1);
code = zeros(rows, columns);
code(1, 1) = 1;
for s = 2:columns
code(:, s) = [0; code(1:end-1, s-1)];
if code(end, s-1)
code(:, s) = rem(code(:, s) + polynomial(1:end-1).', 2);
end
end
end
function taps = srgtaps(inx)
data = {[1] ...
[2 1] ...
[3 1] ...
[4 1] ...
[5 2] ...
[6 1] ...
[7 1] ...
[8 7 2 1] ...
[9 4] ...
[10 3] ...
[11 2] ...
[12 10 2 1] ...
[13 8 5 3] ...
[14 12 11 1] ...
[15 1] ...
[16 15 12 10] ...
[17 3] ...
[18 7] ...
[19 10 9 3] ...
[20 3] ...
[21 2] ...
[22 1] ...
[23 5] ...
[24 11 5 2] ...
[25 3] ...
[26 23 15 13] ...
[27 23 22 17] ...
[28 3] ...
[29 2] ...
[30 27 10 9] ...
[31 3] ...
[32 16 7 2] ...
[33 13] ...
[34 17 12 8] ...
[35 2] ...
[36 11] ...
[37 22 14 2] ...
[38 27 6 5] ...
[39 4] ...
[40 29 27 23] ...
[41 3] ...
[42 34 31 30] ...
[43 27 22 5] ...
[44 39 35 18] ...
[45 39 28 4] ...
[46 40 31 18] ...
[47 5] ...
[48 19 9 1]};
taps = data{inx};
end
function masks = srgtap_masks(inx)
taps = Signalgenerator.srgtaps(inx);
masks = zeros(1, 4);
masks(1:numel(taps)) = 2.^(taps - 1);
end
end
end

View File

@@ -46,7 +46,7 @@ classdef Optical_Demultiplex < handle
if isempty(obj.wavelengthplan)
obj.wavelengthplan = signalclass_in.lambda; %meter
else
if all(500 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500) %check if given in nm
if all(500e-9 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500e-9) %check if given in nm
obj.wavelengthplan = obj.wavelengthplan.*1e-9;
end
end

View File

@@ -1,82 +0,0 @@
classdef CTLE < handle
properties(Access=public)
Aac_dB
Adc_dB
f_p1
f_p2
plot
end
methods(Access=public)
function obj = CTLE(options)
arguments
options.Aac_dB = 0;
options.Adc_dB = -6;
options.f_p1 = 1.5e9;
options.f_p2 = 5e9;
options.plot = 0;
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
end
function data_out = process(obj, data_in)
x = data_in.signal(:);
Fs = data_in.fs;
N = length(x);
% CTLE Transfer Function Parameters
A_ac = 10^(obj.Aac_dB/20);
A_dc = 10^(obj.Adc_dB/20);
w1 = 2*pi*obj.f_p1;
w2 = 2*pi*obj.f_p2;
% Frequency Domain Conversion
X = fft(x);
% Generating Frequency Axis In The Range Of [-Fs/2,Fs/2]
f_fft = (0:N-1).' * (Fs/N);
f_signed = f_fft;
idxNeg = f_signed > Fs/2;
f_signed(idxNeg) = f_signed(idxNeg) - Fs;
f_pos = abs(f_signed);
w_pos = 2*pi*f_pos;
s_pos = 1j*w_pos;
% Calculate CTLE Transfer Function
H_pos = A_ac*w2 .* (s_pos + (A_dc/A_ac)*w1) ./ ((s_pos + w1).*(s_pos + w2));
% Enforce H(-f) = conj(H(f)) For Negative Bins
H = H_pos;
H(idxNeg) = conj(H_pos(idxNeg));
% Apply CTLE
Y = H .* X;
% Calculate Time Domain Signal
y = ifft(Y, 'symmetric');
data_out = data_in;
data_out.signal = y;
data_out.fs = Fs;
% Plot
if obj.plot
% plot only positive frequencies up to Fs/2
k = 1:floor(N/2)+1;
fplot = f_fft(k);
Hplot = H(k);
figure;
semilogx(fplot, 20*log10(abs(Hplot)+1e-15));
grid on; xlabel('frequency (Hz)'); ylabel('magnitude (dB)');
title('CTLE magnitude on FFT grid');
end
end
end
end

View File

@@ -1,130 +0,0 @@
classdef Electrical_Hybrid < handle
properties(Access=public)
file_path
plot = 0
% If true: perform digital residual-echo cancellation using known TX
cancel_echo = 1
% If true: in addition return v_hyb and v_echo_est
return_intermediates = 1
end
methods(Access=public)
function obj = Electrical_Hybrid(options)
arguments
options.file_path = ''
options.plot = 0
options.cancel_echo = 1
options.return_intermediates = 1
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
end
function [v_fe_rec, v_hyb, v_echo_est] = process(obj, v_ne_tx, v_fe_tx)
% v_ne_tx : Near-End TX signal object (Port 1)
% v_fe_tx : Far-End TX signal object (Port 2)
%
% Output:
% v_hyb : physical hybrid differential output (Port4 - Port3)
% v_echo_est : estimated residual echo due to local TX only
% v_fe_rec : v_hyb - v_echo_est (if cancel_echo enabled), else v_hyb
% --- Load S-Parameters (.s4p) ---
net = sparameters(obj.file_path);
f_s = net.Frequencies(:);
S4 = net.Parameters;
% Extract needed S-parameters for differential output:
% V3 = S31*V1 + S32*V2
% V4 = S41*V1 + S42*V2
% Vhyb = V4 - V3 = (S41-S31)*V1 + (S42-S32)*V2
S31_s = squeeze(S4(3,1,:));
S41_s = squeeze(S4(4,1,:));
S32_s = squeeze(S4(3,2,:));
S42_s = squeeze(S4(4,2,:));
% --- Time-domain signals ---
x1 = v_ne_tx.signal(:);
x2 = v_fe_tx.signal(:);
if length(x2) ~= length(x1)
error('Near-end and far-end signals must have the same length.');
end
N = length(x1);
Fs = v_ne_tx.fs;
% --- Use zero padding to avoid circular convolution artifacts ---
Nfft = 2^nextpow2(2*N); % robust choice
% --- FFT ---
V1 = fft(x1, Nfft);
V2 = fft(x2, Nfft);
% --- Frequency axis for interpolation (signed then abs) ---
f_fft = (0:Nfft-1).' * (Fs/Nfft);
f_signed = f_fft;
idxNeg = f_signed > Fs/2;
f_signed(idxNeg) = f_signed(idxNeg) - Fs; % (-Fs/2, Fs/2]
f_pos = abs(f_signed);
% --- Interpolate S-parameters onto f_pos ---
S31 = interp1(f_s, S31_s, f_pos, 'linear', 'extrap');
S41 = interp1(f_s, S41_s, f_pos, 'linear', 'extrap');
S32 = interp1(f_s, S32_s, f_pos, 'linear', 'extrap');
S42 = interp1(f_s, S42_s, f_pos, 'linear', 'extrap');
% Hermitian symmetry for real time-domain response:
% For negative frequencies enforce conj symmetry.
S31(idxNeg) = conj(S31(idxNeg));
S41(idxNeg) = conj(S41(idxNeg));
S32(idxNeg) = conj(S32(idxNeg));
S42(idxNeg) = conj(S42(idxNeg));
% --- Physical hybrid differential output ---
% Vhyb = (S41-S31)*V1 + (S42-S32)*V2
He = (S41 - S31); % residual echo transfer from local TX
Hr = (S42 - S32); % transfer from far-end TX to output
V_hyb = He .* V1 + Hr .* V2;
% --- Residual echo estimate (digital canceller model) ---
V_echo = He .* V1;
% --- Back to time-domain (take first N samples after padding) ---
v_hyb_full = ifft(V_hyb, 'symmetric');
v_echo_full = ifft(V_echo, 'symmetric');
v_hyb = v_hyb_full(1:N);
v_echo_est = v_echo_full(1:N);
% --- Optional cancellation ---
if obj.cancel_echo
v_fe_rec = v_hyb - v_echo_est;
else
v_fe_rec = v_hyb;
end
if ~obj.return_intermediates
v_hyb = [];
v_echo_est = [];
end
% --- Bring output in the correct form ---
v_fe_rec = Informationsignal(v_fe_rec,"fs",v_fe_tx.fs);
v_hyb = Informationsignal(v_hyb,"fs",v_fe_tx.fs);
v_echo_est = Informationsignal(v_echo_est,"fs",v_fe_tx.fs);
if obj.plot
rfplot(net)
end
end
end
end

View File

@@ -1,31 +0,0 @@
classdef Electrical_Trace < handle
properties(Access=public)
file_path
end
methods(Access=public)
function obj = Electrical_Trace(options)
arguments(Input)
options.file_path = 'C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\COM Test\Mellitzz\TA_6002_6003_FX_B6_C6_B7_C7_Terminated.s4p'
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
end
function [data_out,timing_error] = process(obj, data_in)
S =
end
end
end

View File

@@ -1,114 +0,0 @@
classdef Electrical_Trace_BiDi < handle
properties(Access=public)
file_path
fsym
rolloff
K_over
plot
test
S_test
end
methods(Access=public)
function obj = Electrical_Trace_BiDi(options)
arguments(Input)
options.file_path = 'C:\Users\magf\Desktop\Desktop\MATLAB-Zeugs\COM Test\Mellitzz\TA_6002_6003_FX_B6_C6_B7_C7_Terminated.s4p'
options.fsym = 0;
options.rolloff = 0;
options.K_over = 1;
options.plot = 0;
options.test = 0;
options.S_test = [0,1;1,0];
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
end
function [b_1,b_2] = process(obj, a_1, a_2)
% Rx Signal Calculation Using S-Paramters For a 2-Port Network
% [B_1(f); B_2(f)] = [S_11(f), S_12(f); S_21(f), S_22(f)] * [A_1(f); A_2(f)]
% Initialize Rx Time Domain Signals
b_1 = a_2;
b_2 = a_1;
% Loading and Extract S-Paramters
net = sparameters(obj.file_path);
f_s = net.Frequencies(:);
if ~obj.test
S2 = net.Parameters(1:2, 1:2, :);
else
S2 = repmat(obj.S_test,1,1,size(f_s,1));
end
S11_s = squeeze(S2(1,1,:));
S12_s = squeeze(S2(1,2,:));
S21_s = squeeze(S2(2,1,:));
S22_s = squeeze(S2(2,2,:));
% Setup Time Domain Signals
x1 = a_1.signal(:);
x2 = a_2.signal(:);
N = length(x1);
assert(length(x2)==N, 'a_1 and a_2 must have same length');
% Extract Time Domain Signal Frequencies
if obj.fsym == 0 && obj.rolloff == 0 && obj.K_over == 1
Fs = a_1.fs;
else
Fs = (1+obj.rolloff)*obj.fsym;
end
% Calculate Frequency Domain Signals
A1 = fft(x1);
A2 = fft(x2);
% Calculate Frequency Axis [-Fs/2,...,Fs/2]
f_fft = (0:N-1).' * (Fs/N);
f_signed = f_fft;
idxNeg = f_signed > Fs/2;
f_signed(idxNeg) = f_signed(idxNeg) - Fs; % Now in (-Fs/2, Fs/2]
% Absolute Value Used for Interpolation
f_pos = abs(f_signed);
% Interpolate S-Parameters onto f_pos
S11_p = interp1(f_s, S11_s, f_pos, 'linear', 'extrap');
S12_p = interp1(f_s, S12_s, f_pos, 'linear', 'extrap');
S21_p = interp1(f_s, S21_s, f_pos, 'linear', 'extrap');
S22_p = interp1(f_s, S22_s, f_pos, 'linear', 'extrap');
% Apply Hermitian Symmetry: S(-f) = conj(S(f))
S11 = S11_p; S12 = S12_p; S21 = S21_p; S22 = S22_p;
S11(idxNeg) = conj(S11_p(idxNeg));
S12(idxNeg) = conj(S12_p(idxNeg));
S21(idxNeg) = conj(S21_p(idxNeg));
S22(idxNeg) = conj(S22_p(idxNeg));
% Compute Rx Frequency Domain Signals
B1 = S11 .* A1 + S12 .* A2;
B2 = S21 .* A1 + S22 .* A2;
% Calculate Rx Time Domain Signals
b_1.signal = ifft(B1, 'symmetric');
b_2.signal = ifft(B2, 'symmetric');
if obj.plot
rfplot(net)
end
end
end
end

View File

@@ -1,210 +0,0 @@
classdef Partialresponse
%PARTIALRESPONSE Generalized symbol-domain partial-response coding.
properties
M = []
order = 1
end
methods
function obj = Partialresponse(options)
arguments
options.M = [];
options.order = 1;
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
end
function signal = precode(obj, signal, options)
arguments
obj
signal
options.M = []
end
[data, issignal, signalclass] = obj.unpackSignal(signal);
M = obj.resolveM(data, options.M);
a = obj.amplitudeToIndex(data, M);
h = arrayfun(@(k) nchoosek(obj.order, k), 0:obj.order);
u = zeros(size(a));
state = zeros(1, obj.order);
start_idx = 1;
if obj.order == 1
% Match the legacy Duobinary class exactly: keep the first
% precoded symbol at zero state and start the recursion at k=2.
start_idx = 2;
end
for k = start_idx:numel(a)
u(k) = mod(a(k) - sum(h(2:end).*state), M);
state = [u(k) state(1:end-1)];
end
data_out = obj.indexToPamAmplitude(u, M);
signal = obj.packSignal(data_out, issignal, signalclass);
end
function signal = encode(obj, signal, options)
arguments
obj
signal
options.M = []
end
[data, issignal, signalclass] = obj.unpackSignal(signal);
M = obj.resolveM(data, options.M);
u = obj.amplitudeToIndex(data, M);
h = arrayfun(@(k) nchoosek(obj.order, k), 0:obj.order);
y = zeros(size(u));
state = zeros(1, obj.order);
center = ((M - 1) * sum(h)) / 2;
for k = 1:numel(u)
pr_state = [u(k) state];
y(k) = sum(h .* pr_state) - center;
state = pr_state(1:end-1);
end
y = y ./ obj.encodedScaling(M, obj.order);
signal = obj.packSignal(y, issignal, signalclass);
end
function signal = decode(obj, signal, options)
arguments
obj
signal
options.M = []
end
[data, issignal, signalclass] = obj.unpackSignal(signal);
M = obj.resolveEncodedM(data, options.M);
h = arrayfun(@(k) nchoosek(obj.order, k), 0:obj.order);
idx_to_amp = nan(1, (M-1)*sum(h) + 1);
center = ((M - 1) * sum(h)) / 2;
scaling = obj.encodedScaling(M, obj.order);
for n = 0:(M^(obj.order+1)-1)
state = zeros(1, obj.order+1);
tmp = n;
for k = 1:numel(state)
state(k) = mod(tmp, M);
tmp = floor(tmp/M);
end
y_idx = sum(h .* state);
idx_to_amp(y_idx + 1) = (y_idx - center) / scaling;
end
alphabet = unique(idx_to_amp);
a = zeros(size(data));
for k = 1:numel(data)
[~, pos] = min(abs(data(k) - alphabet));
y_idx = find(idx_to_amp == alphabet(pos), 1) - 1;
a(k) = mod(y_idx, M);
end
data_out = obj.indexToPamAmplitude(a, M);
signal = obj.packSignal(data_out, issignal, signalclass);
end
end
methods (Access=private)
function [data, issignal, signalclass] = unpackSignal(~, signal)
issignal = isa(signal, 'Signal');
if issignal
signalclass = signal;
data = signal.signal;
else
signalclass = [];
data = signal;
end
data = double(data(:));
end
function signal = packSignal(~, data, issignal, signalclass)
if issignal
signalclass.signal = data;
signal = signalclass;
else
signal = data;
end
end
function M = resolveM(obj, data, M)
if isempty(M)
M = obj.M;
end
if isempty(M)
M = numel(unique(round(data, 12)));
end
end
function M = resolveEncodedM(obj, data, M)
if isempty(M)
M = obj.M;
end
if isempty(M)
I = numel(unique(round(data, 12)));
if obj.order == 1
M = (I + 1) / 2;
else
error('Partialresponse:NeedM', ...
'Specify M when decoding higher-order partial-response signals.');
end
end
end
function a = amplitudeToIndex(obj, data, M)
levels = obj.pamLevels(M);
scaling = obj.pamScaling(M);
amp = round(data(:) * scaling);
a = (amp + (M - 1)) / 2;
end
function data = indexToPamAmplitude(obj, a, M)
scaling = obj.pamScaling(M);
data = (2*a(:) - (M - 1)) / scaling;
end
function levels = pamLevels(~, M)
levels = -(M-1):2:(M-1);
end
function scaling = pamScaling(~, M)
try
mapper = PAMmapper(M, 0);
scaling = mapper.scaling;
catch ME
error('Partialresponse:UnsupportedM', ...
'Unsupported PAM order for Partialresponse: %s', ME.message);
end
end
function scaling = encodedScaling(~, M, order)
h = arrayfun(@(k) nchoosek(order, k), 0:order);
center = ((M - 1) * sum(h)) / 2;
y = zeros(M^(order+1), 1);
for n = 0:(numel(y)-1)
state = zeros(1, order+1);
tmp = n;
for k = 1:numel(state)
state(k) = mod(tmp, M);
tmp = floor(tmp/M);
end
y(n + 1) = sum(h .* state) - center;
end
scaling = sqrt(mean(y.^2));
end
end
end

View File

@@ -1,394 +0,0 @@
classdef Copy_of_VNLE < handle
% Implementation of plain and simple FFE.
% 1) Training mode (stable performance when you use NLMS)
% 2) Decision directed mode
% Eq = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0,"order",[25,2,2],"sps",2,"decide",1);
% Somehow it is not possible to use only 1 nonlinear order
properties
sps % usually 2
order
e
e_dc
error
len_tr
mu_tr
epochs_tr
mu_dd
epochs_dd
mu_dc
constellation
decide
save_debug = 0;
debug_struct
optmize_mus = 0;
mu_optimization
mu_optimization_iter = 0;
x_norm
ce
ie2
ie3
end
methods
function obj = Copy_of_VNLE(options)
arguments(Input)
options.sps = 2;
options.order = [15,2,2];
options.len_tr = 4096;
options.mu_tr = 0;
options.epochs_tr = 5;
options.mu_dd = 1e-5;
options.epochs_dd = 5;
options.mu_dc = 0;
options.decide = false;
options.save_debug = 0;
options.optmize_mus = 0;
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
obj.error = 0;
obj.e_dc = 0;
end
function [X,N] = process(obj, X, D)
% actual processing of the signal (steps 1. - 3.)
% 1 normalize RMS
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
obj.x_norm = obj.calcPowerNormalization(X.signal);
obj.ce = obj.calcVNLEMemoryLength(obj.order);
[obj.ie2,obj.ie3] = obj.calcIndiceVectors(obj.order);
obj.e = zeros( sum(obj.ce) ,1);
obj.e_dc = 0;
if obj.optmize_mus
obj.optimizeMus(X.signal,D.signal);
obj.e = zeros(sum(obj.ce),1);
obj.e_dc = 0;
end
% Training Mode
training = 1;
showviz = 0;
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
% Decision Directed Mode
N = X.length;
training = 0;
showviz = 0;
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training,showviz);
% Output Signal
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
lbdesc = [num2str(obj.order),' tap FFE'];
X = X.logbookentry(lbdesc); % append to logbook
N = X;
N = X - D;
end
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
arguments
obj
x
d
mu
epochs
N
training
showviz
end
if all(mu == mu(1))
% mu = mu(1);
mu = diag(ones(1,sum(obj.ce))*mu(1));
else
mu = diag([ones(1,obj.ce(1))*mu(1) ...
ones(1,obj.ce(2))*mu(2) ...
ones(1,obj.ce(3))*mu(3) ]);
end
x = [zeros(floor(obj.order(1)/2),1); x; zeros(obj.order(1),1)];
if showviz
f = figure(111);
subplot(2,2,1:2);
hold on
a = scatter(1:numel(x),x,1,'.');
a2 = scatter(1,1,1,'.');
a3 = scatter(1,1,2,'.');
a4 = xline(1);
ylim([-3 3])
xlim([0 length(x)]);
subplot(2,2,3:4)
c = stem(obj.e);
ylim([-1 1])
drawnow
end
for epoch = 1 : epochs
symbol = 0;
for sample = 1 : obj.sps : N
symbol = symbol+1;
% x_in = x(obj.order(1)+sample+(obj.sps-1):-1:sample+obj.sps);
x_in = x(obj.order(1)+sample-1:-1:sample);
x_in = obj.calcVNLENonlinVecs(x_in,obj.ie2,obj.ie3,obj.order,obj.x_norm);
y(symbol,1) = obj.e_dc + obj.e.' * x_in; % Calculating output of LMS __ * |
if training
err = y(symbol) - d(symbol); % Instantaneous error
else
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); % decision for closest constellation point
d_hat(symbol,1) = obj.constellation(symbol_idx);
err = y(symbol) - d_hat(symbol); % Instantaneous error
end
if ~all(mu==0,'all') %mu has not only zeros
obj.e = obj.e - ( (mu * x_in) * err ) ; % Weight update rule of LMS
else
normalizationfactor = (x_in.' * x_in);
obj.e = obj.e - err * x_in / normalizationfactor; % Weight update rule of NLMS
end
if obj.mu_dc ~= 0
obj.e_dc = obj.e_dc - obj.mu_dc * err;
end
if mod(sample,100) == 1 && showviz
a2.XData = 1:2*numel(y);
a2.YData = repelem(y, 2);
a3.XData = 1:2*numel(d_hat);
a3.YData = repelem(d_hat, 2);
a4.Value = sample;
% b.YData = x(symbol:symbol+500);
c.YData = obj.e;
drawnow;
end
obj.error(epoch,symbol) = err * err'; % Instantaneous square error
if obj.save_debug
obj.debug_struct.error(epoch,symbol) = err * err';
if training
obj.debug_struct.error_tr(epoch,symbol) = err * err';
end
end
end
end
end
function optimizeMus(obj,x,d)
mu_range = [1e-5, 1e-2];
mu_dc_range = [1e-5, 1e-1];
vars = [optimizableVariable("mu_tr",mu_range,"Transform","log"), ...
optimizableVariable("mu_dd",mu_range,"Transform","log")];
optimize_mu_dc = obj.mu_dc ~= 0;
if optimize_mu_dc
vars = [vars, optimizableVariable("mu_dc",mu_dc_range,"Transform","log")];
end
obj.mu_optimization_iter = 0;
obj.mu_optimization = bayesopt(@(p)obj.muObjective(p,x,d),vars, ...
"MaxObjectiveEvaluations",10, ...
"AcquisitionFunctionName","expected-improvement-plus", ...
"IsObjectiveDeterministic",false, ...
"Verbose",0, ...
"PlotFcn",[]);
obj.mu_tr = obj.mu_optimization.XAtMinObjective.mu_tr;
obj.mu_dd = obj.mu_optimization.XAtMinObjective.mu_dd;
if optimize_mu_dc
obj.mu_dc = obj.mu_optimization.XAtMinObjective.mu_dc;
end
objective_db = 10*log10(obj.mu_optimization.MinObjective);
if optimize_mu_dc
fprintf("\nVNLE mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_dc,obj.mu_optimization.MinObjective,objective_db);
else
fprintf("\nVNLE mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_optimization.MinObjective,objective_db);
end
end
function objective = muObjective(obj,params,x,d)
old_debug = obj.save_debug;
old_mu_dc = obj.mu_dc;
obj.save_debug = 1;
optimize_mu_dc = ismember("mu_dc",string(params.Properties.VariableNames));
if optimize_mu_dc
obj.mu_dc = params.mu_dc;
end
obj.e = zeros(sum(obj.ce),1);
obj.e_dc = 0;
obj.debug_struct = struct();
obj.equalize(x,d,params.mu_tr,obj.epochs_tr,obj.len_tr,1,0);
obj.equalize(x,d,params.mu_dd,obj.epochs_dd,numel(x),0,0);
objective = mean(obj.debug_struct.error(end,:),"omitnan");
if ~isfinite(objective)
objective = inf;
end
objective_db = 10*log10(objective);
obj.mu_optimization_iter = obj.mu_optimization_iter + 1;
if optimize_mu_dc
fprintf("\rVNLE mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,params.mu_dc,objective,objective_db);
else
fprintf("\rVNLE mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,objective,objective_db);
end
obj.save_debug = old_debug;
obj.mu_dc = old_mu_dc;
end
%% Functions needed During Adaption
function x_in_vnle_format = calcVNLENonlinVecs(~,x_in_block,I_2,I_3,N_,norm_)
% These are the second and third order input signal products of the VNLE EQ
% h1 x_in(k-n1) + h2 x_in(k-n1)*x_in(k-n2) + h3 x_in(k-n1)*x_in(k-n2)*x_in(k-n3)
l1=length(x_in_block);
l2=length(I_2);
l3=length(I_3);
final_length = l1+l2+l3;
x_in_vnle_format = zeros(final_length,1);
idx = l1;
x_in_vnle_format(1:idx) = x_in_block;
if N_(2) > 0
delta_2 = round((N_(1)-N_(2)) / 2);
input_vec_se = x_in_block(delta_2:end) / norm_(2); %TODO normalization step
% Extract columns from I_2
col1 = input_vec_se(I_2(:,1));
col2 = input_vec_se(I_2(:,2));
x2 = col1 .* col2;
x_in_vnle_format(idx+1:idx+l2) = x2;
end
if N_(3) > 0
delta_3 = round((N_(1)-N_(3))/2);
input_vec_th = x_in_block(delta_3:end) / norm_(3);
% Extract columns from I_3
col1 = input_vec_th(I_3(:,1));
col2 = input_vec_th(I_3(:,2));
col3 = input_vec_th(I_3(:,3));
% Perform matrix multiplication
x3 = col1 .* col2 .* col3;
idx = idx+l2;
x_in_vnle_format(idx+1:idx+l3) = x3;
end
end
%% Functions needed for Preparation
function [C] = calcVNLEMemoryLength(~,N)
%calculates the memory length of VNLE
C = zeros(size(N));
for o = 1:numel(N)
switch o
case 1
C(o) = N(o);
case 2
C(o) = N(o)*(N(o)+1) / 2;
case 3
C(o) = N(o)*(N(o)+1)*(N(o)+2) / 6;
end
end
end
function [indvec2nd, indvec3rd] = calcIndiceVectors(~,N)
% Init vectors of 2nd and 3rd order coefficient indices ->
% yield combination with
indvec2nd=[];
indvec3rd=[];
for o = 2:numel(N)
n = N(o);
v = 1:n; % Ursprünglicher Vektor
row = 1;
% Schleifen zur Generierung des Indize Vektors
switch o
case 2
indvec2nd = zeros(n*(n+1)/2, o);
for i = 1:n
for j = i:n
indvec2nd(row, :) = [v(i) v(j)];
row = row + 1;
end
end
case 3
indvec3rd = zeros(n*(n+1)*(n+2)/6, 3);
for i = 1:n
for j = i:n
for k = j:n
indvec3rd(row, :) = [v(i) v(j) v(k)];
row = row + 1;
end
end
end
end
end
end
function powerNorm = calcPowerNormalization(~,v)
powerNorm(1) = sqrt(mean(abs(v ).^2));
powerNorm(2) = sqrt(mean(abs(v.^2).^2));
powerNorm(3) = sqrt(mean(abs(v.^3).^2));
end
end
end

View File

@@ -480,7 +480,7 @@ classdef EQ < handle
output_vec_weighted(m) = xbar;
fb_sym = xbar;
% output_vec(m) = xbar;
output_vec(m) = xbar;
end
if obj.Nb(1) > 0

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,351 @@
classdef FFE_DCremoval_adaptive_mu < handle
% Implementation of plain and simple FFE.
% 1) Training mode (stable performance when you use NLMS)
% 2) Decision directed mode
% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0);
properties
sps % usually 2
order
e
e_tr
error
len_tr
mu_tr
epochs_tr
mu_dd
epochs_dd
mu_dc
dc_buffer_len
adaptive_mu_mode
ffe_buffer_len
smoothing_buffer_length
smoothing_buffer_update
constellation
decide
end
methods
function obj = FFE_DCremoval_adaptive_mu(options)
arguments(Input)
options.sps = 2;
options.order = 15;
options.len_tr = 4096;
options.mu_tr = 0;
options.epochs_tr = 5;
options.mu_dd = 1e-5;
options.epochs_dd = 5;
options.mu_dc = 0.05;
options.dc_buffer_len = 1;
options.ffe_buffer_len = 1;
options.adaptive_mu_mode = 1;
options.smoothing_buffer_length = 0;
options.smoothing_buffer_update = 0;
options.decide = false;
end
assert(options.dc_buffer_len>0);
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
obj.e = zeros(obj.order,1);
obj.error = 0;
obj.dc_buffer_len = floor(obj.dc_buffer_len);
end
function [X,Noi] = process(obj, X, D)
% actual processing of the signal (steps 1. - 3.)
% 1 normalize RMS
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
% if obj.smoothing_buffer_length > 0
% % Apply A1 filter smoothing
% % Calculate the moving sum with the window size N1
% moving_sum = movsum(X.signal, [obj.smoothing_buffer_length,0]);
%
% % Initialize the output smoothed signal
% X.signal = X.signal - (1 / obj.smoothing_buffer_length) * moving_sum;
% end
% Training Mode
training = 1;
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training);
obj.e_tr = obj.e;
% Decision Directed Mode
N = X.length;
training = 0;
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training);
% Output Signal
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
lbdesc = [num2str(obj.order),' tap FFE'];
X = X.logbookentry(lbdesc); % append to logbook
Noi = X - D;
end
function [y,d_hat] = equalize(obj, x, d, mu_lms, epochs, N, training)
% Equalize with adaptive DC-removal, VSS, and parallel-buffered DC updates
% Added: FFE gradient buffering in DD mode (error buffer) with update every obj.dc_buffer_len symbols
arguments
obj
x
d
mu_lms % LMS step-size (or 0 for NLMS)
epochs % number of training/DD epochs
N % number of samples to process
training % boolean flag: true->training mode, false->DD mode
end
if isempty(obj.e)
obj.e = zeros(obj.order,1);
end
% Zero-padding for filter memory
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
% Initialize storage
numSymbols = ceil(N/obj.sps);
y = zeros(numSymbols,1);
d_hat = zeros(numSymbols,1);
err = NaN(numSymbols,numel(obj.constellation));
e_dc_save= zeros(numSymbols,1);
% DC-adaptation parameters
P_err = 0; % running error power
alpha = 0.98; % forgetting factor for error power
err_prev = 0; % previous error sample for VSS correlation
gamma_dc = 1e-6; % meta step-size for DC VSS
mu_min = 1e-6; % lower bound for mu_dc
mu_max = 3e-1; % upper bound for mu_dc
% DC removal buffer
L = obj.dc_buffer_len; % buffer length
e_dc_buf = NaN(L,1);
e_dc_est = 0;
% FFE gradient buffer (DD mode only)
L_grad = obj.ffe_buffer_len; % buffer length
if ~training
% each column holds one past gradient of length obj.order
grad_buf = NaN(obj.order, L_grad);
end
smth_buffer = zeros(1, obj.smoothing_buffer_length);
smth_mean = 0;
% Main loop
for epoch = 1:epochs
s = 0;
for sample = 1:obj.sps:N
s = s + 1;
if obj.smoothing_buffer_length > 0
smth_buffer = circshift(smth_buffer,1,2);
smth_buffer(1) = x(sample);
if mod(s, obj.smoothing_buffer_update) == 0
smth_mean = mean(smth_buffer);
end
x(sample:sample+obj.sps-1) = x(sample:sample+obj.sps-1)-smth_mean;
end
U = x(obj.order+sample-1:-1:sample);
%-- 1) filter output with DC correction
y(s) = e_dc_est + obj.e.'*U;
%-- 2) decision
if training
[~, idx] = min(abs(d(s) - obj.constellation));
else
[~, idx] = min(abs(y(s) - obj.constellation));
end
d_hat(s) = obj.constellation(idx);
%-- 3) error
e_val = y(s) - d_hat(s);
if epoch == epochs
err(s,idx) = e_val;
true_err(s,idx) = y(s) - d(s);
end
%-- 4) tap-weight update: training immediate, DD buffered
if training
% immediate update (LMS or NLMS)
if mu_lms ~= 0
obj.e = obj.e - mu_lms * e_val * U;
else
normU = (U.'*U) + eps;
obj.e = obj.e - e_val * U / normU;
end
else
if 0
% buffer gradient
if mu_lms ~= 0
grad = e_val * U;
else
normU = (U.'*U) + eps;
grad = e_val * U / normU;
end
% shift and insert
grad_buf = circshift(grad_buf, 1, 2);
grad_buf(:,1) = grad;
% update once every L symbols
if mod(s, L_grad) == 0
avg_grad = mean(grad_buf, 2, 'omitnan');
if mu_lms ~= 0
obj.e = obj.e - mu_lms * avg_grad;
else
obj.e = obj.e - avg_grad;
end
end
end
end
%-- 5) DC adaptation
if obj.mu_dc ~= 0
if obj.adaptive_mu_mode
% VSS for mu_dc
delta_mu = gamma_dc * e_val * err_prev * (U.'*U);
obj.mu_dc = min(max(obj.mu_dc + delta_mu, mu_min), mu_max);
err_prev = e_val;
% DC buffer update & periodic estimate
P_err = alpha*P_err + (1-alpha)*e_val^2;
mu_dc_norm = obj.mu_dc / (P_err + eps);
else
% DC buffer update & periodic estimate
% P_err = alpha*P_err + (1-alpha)*e_val^2;
% mu_dc_norm = obj.mu_dc / (P_err + eps);
mu_dc_norm = obj.mu_dc;
end
e_dc_buf = circshift(e_dc_buf, 1);
e_dc_buf(1) = e_dc_est - mu_dc_norm * e_val;
if mod(s, L) == 0
e_dc_est = median(e_dc_buf, 'omitnan');
end
P_err_save(s) = P_err;
% Pcorr_save(s) = e_val * err_prev;
Ucorr_save(s) = (U.'*U);
mu_dc_save(s) = mu_dc_norm;
e_dc_save(s) = e_dc_est;
end
% store instantaneous squared error
obj.error(epoch, s) = e_val^2;
end
end
% Optional plotting in DD mode (uncomment if needed)
if 0%~training
constellation = unique(d);
lvlcol = cbrewer2('Paired', numel(constellation)*2);
lvlcol = lvlcol(2:2:end, :);
true_err(true_err==0) = NaN;
true_errmoverr = movsum(true_err, 4096, 'omitnan');
true_errmoverr = true_errmoverr./rms(true_errmoverr);
moverr = movsum(err, [100,100], 'omitnan');
moverr = moverr./rms(moverr);
figure(500); clf
hold on
% 1st subplot: true_errmoverr
% subplot(2,2,1); hold on
% for k = 1:4
% scatter(1:numSymbols, true_errmoverr(:,k), 1, lvlcol(k,:), '.');
% end
% scatter(1:numSymbols, Ucorr_save./rms(Ucorr_save), 1, lvlcol(1,:), '.','DisplayName','Ucorr_save');
% scatter(1:numSymbols, Pcorr_save./rms(Pcorr_save), 1, lvlcol(1,:), '.','DisplayName','P_corr');
% scatter(1:numSymbols, P_err_save, 1, lvlcol(1,:), '.','DisplayName','P_err');
% scatter(1:numSymbols, mu_dc_save, 1, lvlcol(2,:), '.','DisplayName','adapted value of $\mu_{DC}$');
% scatter(1:numSymbols, sum(moverr,2,'omitnan'), 1, lvlcol(1,:), '.','DisplayName','Mov Error $\hat{d}$ - x over all levels');
scatter(1:numSymbols, sum(e_dc_save,2,'omitnan'), 1, lvlcol(2,:), '.','DisplayName','Est. Error that is subtracted');
title('Moving Sum Error');
hold off
legend
% 2nd subplot: moverr
subplot(2,2,2); hold on
for k = 1:4
scatter(1:numSymbols, moverr(:,k), 1, lvlcol(k,:), '.');
end
title('Moving Sum Error');
hold off
legend
% 3rd subplot: err
subplot(2,2,3); hold on
for k = 1:4
scatter(1:numSymbols, err(:,k), 1, lvlcol(k,:), '.');
end
title('Error');
hold off
legend
% 4th subplot: err + obj.constellation'
subplot(2,2,4); hold on
for k = 1:4
scatter(1:numSymbols, err(:,k) + obj.constellation(k), 1, lvlcol(k,:), '.');
end
yline(obj.constellation, '--k');
title('Error + Constellation');
hold off
legend
sgtitle('Error Analysis Subplots');
end
end
end
end

View File

@@ -0,0 +1,245 @@
classdef FFE_Kalman < handle
% Implementation of plain and simple FFE.
% 1) Training mode (stable performance when you use NLMS)
% 2) Decision directed mode
% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0);
properties
sps % usually 2
order
e
error
len_tr
mu_tr
epochs_tr
mu_dd
epochs_dd
constellation
decide
end
methods
function obj = FFE_Kalman(options)
arguments(Input)
options.sps = 2;
options.order = 15;
options.len_tr = 4096;
options.mu_tr = 0;
options.epochs_tr = 5;
options.mu_dd = 1e-5;
options.epochs_dd = 5;
options.decide = false;
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
obj.e = zeros(obj.order,1);
obj.error = 0;
end
function [X,Noi] = process(obj, X, D)
% actual processing of the signal (steps 1. - 3.)
% 1 normalize RMS
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
% Training Mode
training = 1;
showviz = 0;
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
% Decision Directed Mode
n = X.length;
training = 0;
showviz = 0;
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training,showviz);
% Output Signal
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
lbdesc = [num2str(obj.order),' tap FFE'];
X = X.logbookentry(lbdesc); % append to logbook
Noi = X;
Noi = X - D;
end
function [y,d_hat] = equalize(obj,x,d,mio,epochs,N,training,showviz)
arguments
obj
x
d
mio
epochs
N
training
showviz
end
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
for epoch = 1 : epochs
symbol = 0;
% Initialization of Kalman filter variables
A = 1; % State transition matrix
H = 1; % Observation matrix
Q = 1e-4; % Process noise covariance
R = 1e-1; % Measurement noise covariance
P = 1; % Initial error covariance
mpi_est = 0; % Initial estimate for MPI noise
K = 0; % Kalman gain
subtract_mpi_est = 1;
for sample = 1 : obj.sps : N
symbol = symbol + 1;
% Get the current input sample and the equalizer output
U = x(obj.order + sample - 1 : -1 : sample);
if subtract_mpi_est || ~training
y(symbol,1) = (obj.e.' * U) - mpi_est .* 1 ; % Subtract MPI estimate
else
y(symbol,1) = obj.e.' * U;
end
% Decision and error calculation
if training
d_hat(symbol,1) = d(symbol);
else
[~, symbol_idx] = min(abs(y(symbol) - obj.constellation)); % Closest constellation point
d_hat(symbol,1) = obj.constellation(symbol_idx);
end
err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous residual error
true_err(symbol) = y(symbol) - d(symbol);
% Kalman filter update to track the MPI noise
% Prediction step
P = A * P * A' + Q; % Update error covariance
K = P * H' / (H * P * H' + R); % Kalman gain
% Update step
mpi_est_new = mpi_est + K * (err(symbol) - H * mpi_est); % MPI noise estimation
alpha=0;
mpi_est = alpha * mpi_est + (1 - alpha) * mpi_est_new;
P = (1 - K * H) * P; % Update error covariance
% Subtract MPI noise from the signal
if subtract_mpi_est || ~training
y(symbol) = y(symbol);% - mpi_est;
else
end
% Equalizer weight update (LMS or NLMS)
if mio ~= 0
obj.e = obj.e - (mio * err(symbol) * U); % LMS weight update
else
normalizationfactor = (U.' * U);
obj.e = obj.e - err(symbol) * U / normalizationfactor; % NLMS weight update
end
% Store MPI estimate for visualization
mpi_estimates(symbol) = mpi_est;
Kgain(symbol) = K;
P_(symbol) = P;
H_(symbol) = H;
end %symbols
end %epoch
if ~training
if 1
% figure;
% subplot(2,2,1)
% hold on
% scatter(1:numel(y),y,1,'.');
% plot(1:numel(mpi_estimates), mpi_estimates);
% subplot(2,2,3)
% plot(1:numel(true_err), true_err);
% subplot(2,2,2)
% scatter(1:numel(y),y'-mpi_estimates,1,'.');
% xlabel('Sample Index');
% ylabel('MPI Noise Estimate');
% title('MPI Noise Estimation Over Time (Kalman Filter)');
% grid on;
figure(111);
subplot(3,1,1)
hold on
cla
scatter(1:numel(y),y,1,'.');
plot(1:numel(mpi_estimates), mpi_estimates,'DisplayName','mpi est');
ylim([-2,2]);
subplot(3,1,2)
cla
plot(1:numel(true_err), true_err,'DisplayName','true err');
ylim([-1,1]);
subplot(3,1,3)
plot(1:numel(true_err), true_err-mpi_estimates,'DisplayName',['diff rms: ',num2str(rms(true_err-mpi_estimates))]);
ylim([-1,1]);
legend
figure(333)
hold on
von = 5000;
bis = 15000;
plot(von:bis,true_err(von:bis),'DisplayName','Error')
plot(von:bis,movmean(true_err(von:bis), [30,30]),'DisplayName','Error')
plot(von:bis,mpi_estimates(von:bis),'DisplayName','Est','LineWidth',2);
legend
figure(222)
hold on
crr = xcorr(mpi_estimates,true_err,'normalized');
plot(crr);
end
% y = y-mpi_estimates';
end
end
end
end

View File

@@ -0,0 +1,197 @@
classdef FFE_Kalman_Feedback < handle
% Implementation of plain and simple FFE.
% 1) Training mode (stable performance when you use NLMS)
% 2) Decision directed mode
% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0);
properties
sps % usually 2
order
e
error
len_tr
mu_tr
epochs_tr
mu_dd
epochs_dd
constellation
decide
end
methods
function obj = FFE_Kalman_Feedback(options)
arguments(Input)
options.sps = 2;
options.order = 15;
options.len_tr = 4096;
options.mu_tr = 0;
options.epochs_tr = 5;
options.mu_dd = 1e-5;
options.epochs_dd = 5;
options.decide = false;
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
obj.e = zeros(obj.order,1);
obj.error = 0;
end
function [X,Noi] = process(obj, X, D)
% actual processing of the signal (steps 1. - 3.)
% 1 normalize RMS
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
% Training Mode
training = 1;
showviz = 0;
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training);
% Decision Directed Mode
n = X.length;
training = 0;
showviz = 0;
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training);
% Output Signal
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
lbdesc = [num2str(obj.order),' tap FFE'];
X = X.logbookentry(lbdesc); % append to logbook
Noi = X;
Noi = X - D;
end
function [y, d_hat] = equalize(obj, x, d, mio, epochs, N, training)
arguments
obj
x
d
mio
epochs
N
training
end
% Initialize Kalman filter variables
A = 0.7; % State transition matrix
H = 1; % Observation matrix
Q = 1e-2; % Process noise covariance
R = 1e-3; % Measurement noise covariance
P = 1; % Initial error covariance
mpi_est = 0; % Initial estimate for MPI noise
K = 0; % Kalman gain
% Error buffers for parallelized structure
parallel_depth = 100; % Depending on your parallelization depth
err_buffer = zeros(parallel_depth, 1); % Store collected errors
mpi_est_buffer = zeros(parallel_depth, 1); % Store MPI estimates
x = [zeros(floor(obj.order/2), 1); x; zeros(obj.order, 1)];
for epoch = 1 : epochs
symbol = 0;
for sample = 1 : obj.sps : N
symbol = symbol + 1;
U = x(obj.order + sample - 1 : -1 : sample); % Input window for FFE
% Subtract the estimated MPI noise from the input signal before FFE
x_mpi_reduced = U;
y(symbol, 1) = obj.e.' * x_mpi_reduced; % Output of FFE
y(symbol, 1) = y(symbol, 1) - mpi_est;
% Decision and error calculation
if training
[~, symbol_idx] = min(abs(d(symbol) - obj.constellation)); % Closest constellation point
d_hat(symbol, 1) = d(symbol);
else
[~, symbol_idx] = min(abs(y(symbol) - obj.constellation)); % Closest constellation point
d_hat(symbol, 1) = obj.constellation(symbol_idx);
end
% Calculate the residual error
err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous residual error
% Collect error for feedback after 'parallel_depth' samples
err_buffer(mod(symbol, parallel_depth) + 1) = err(symbol);
% Update Kalman filter based on the accumulated errors every parallel_depth samples
if mod(symbol, parallel_depth) == 0
% Compute the mean error over the last 'parallel_depth' samples
avg_error = mean(err_buffer);
% Prediction step (Kalman filter)
P = A * P * A' + Q; % Update the error covariance
K = P * H' / (H * P * H' + R); % Compute Kalman gain
% Update MPI noise estimate
mpi_est = mpi_est + K * (avg_error - H * mpi_est); % Update based on averaged error
P = (1 - K * H) * P; % Update error covariance
end
% Store MPI estimate for visualization or debugging
k_buffer(symbol) = K;
mpi_est_buffer(symbol) = mpi_est;
% FFE weight update using NLMS
if mio ~= 0
obj.e = obj.e - (mio * err(symbol) * U); % LMS weight update
else
normalizationfactor = (U.' * U);
obj.e = obj.e - err(symbol) * U / normalizationfactor; % NLMS weight update
end
end
end
if ~training
figure(111);
subplot(3,1,1)
hold on
cla
scatter(1:numel(y),y,1,'.');
plot(1:numel(mpi_est_buffer), mpi_est_buffer,'DisplayName','mpi est');
end
end
end
end

View File

@@ -1,492 +0,0 @@
classdef FFE_plain < handle
% Plain feed-forward equalizer with training, DD mode, and BER mu tuning.
%
% This class intentionally contains only the core FFE tap adaptation logic.
% MPI-specific DC tracking, A1/A2 suppression, and delayed update buffers
% stay in FFE and the MPI_reduction classes.
properties
sps
order
e
e_tr
error
len_tr
mu_tr
epochs_tr
adaption_technique
dd_mode
mu_dd
epochs_dd
dd_len_fraction
P
constellation
decide
save_debug = 0
debug_struct
optmize_mus = 0
mu_optimization
mu_optimization_iter = 0
mu_optimization_len
plot_mu_optimization = 0
mu_optimization_fignum = 3010
end
methods
function obj = FFE_plain(options)
arguments(Input)
options.sps = 2
options.order = 15
options.len_tr = 4096
options.mu_tr = 0
options.epochs_tr = 5
options.adaption_technique adaption_method = adaption_method.lms
options.dd_mode = 1
options.mu_dd = 1e-5
options.epochs_dd = 5
options.dd_len_fraction = 1
options.decide = false
options.save_debug = 0
options.optmize_mus = 0
options.mu_optimization_len = 2^15
options.plot_mu_optimization = 0
options.mu_optimization_fignum = 3010
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
obj.sps = floor(obj.sps);
obj.order = floor(obj.order);
obj.dd_len_fraction = min(max(obj.dd_len_fraction,0),1);
obj.resetState();
end
function [X,Noi] = process(obj, X, D)
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
obj.resetState();
if obj.optmize_mus
obj.optimizeMus(X.signal,D.signal);
obj.resetState();
end
training = true;
showviz = false;
obj.equalize(X.signal,D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
obj.e_tr = obj.e;
training = false;
n = X.length;
if obj.dd_mode
[signal,decision] = obj.equalize(X.signal,D.signal,obj.mu_dd,obj.epochs_dd,n,training,showviz);
else
[signal,decision] = obj.equalize(X.signal,D.signal,0,1,n,training,showviz);
end
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs;
X = X.logbookentry([num2str(obj.order),' tap FFE']);
Noi = X - D;
end
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
arguments
obj
x
d
mu
epochs
N
training
showviz
end
unused_showviz = showviz; %#ok<NASGU>
x = x(:);
d = d(:);
N = obj.validSampleLength(N,x,d);
n_symbols = N / obj.sps;
y = zeros(n_symbols,1);
d_hat = zeros(n_symbols,1);
if n_symbols == 0
return
end
if isempty(obj.constellation)
obj.constellation = unique(d);
end
decision_constellation = obj.constellation(:);
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
lambda = mu;
mask = ones(obj.order,1);
maincursor_pos = ceil(length(obj.e)/2);
adaption_code = obj.adaptionCode();
if mu == 0 || (~obj.dd_mode && ~training)
epochs = 1;
end
debug_enabled = obj.save_debug;
if debug_enabled
obj.initializeDebug(n_symbols,training);
end
for epoch = 1:epochs
symbol = 0;
for sample = 1:obj.sps:N
symbol = symbol + 1;
grad = zeros(obj.order,1);
update = zeros(obj.order,1);
weight = 0;
U = x(obj.order+sample-1:-1:sample);
y(symbol,1) = (obj.e.*mask).' * U;
if training
d_hat(symbol,1) = d(symbol);
else
[~,symbol_idx] = min(abs(y(symbol) - decision_constellation));
d_hat(symbol,1) = decision_constellation(symbol_idx);
end
err = d_hat(symbol) - y(symbol);
if mu ~= 0 && (training || obj.dd_mode)
switch adaption_code
case 1
weight = mu / ((U.'*U) + eps);
grad = err * U;
update = grad * weight;
obj.e = obj.e + update;
case 2
weight = mu;
grad = err * U;
update = grad * weight;
obj.e = obj.e + update;
case 3
denom = lambda + U.' * obj.P * U;
k = (obj.P * U) / denom;
update = k * err;
obj.e = obj.e + update;
obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P));
end
end
if debug_enabled && epoch == 1
obj.debug_struct.error_first_epoch(1,symbol) = err * err';
end
if debug_enabled && epoch == epochs
error_power = err * err';
update_power = update.'*update ./ (sqrt((obj.e.'*obj.e) / obj.order) + eps);
obj.debug_struct.error(1,symbol) = error_power;
obj.debug_struct.main_cursor(1,symbol) = abs(obj.e(maincursor_pos));
obj.debug_struct.mu_nlms(1,symbol) = weight;
obj.debug_struct.update_gradient(1,symbol) = grad.'*grad;
if training
obj.debug_struct.error_tr(1,symbol) = error_power;
obj.debug_struct.update_tr(1,symbol) = update_power;
else
obj.debug_struct.error_dd(1,symbol) = error_power;
obj.debug_struct.update(1,symbol) = update_power;
end
end
end
end
end
function optimizeMus(obj,x,d)
[x_opt,d_opt,N_opt] = obj.optimizationSignals(x,d);
switch obj.adaption_technique
case adaption_method.lms
mu_range = [1e-5, 1e-2];
case adaption_method.nlms
mu_range = [1e-3, 5e-1];
case adaption_method.rls
mu_range = [0.98, 0.99999];
otherwise
builtin("error","FFE_plain:InvalidAdaptionTechnique", ...
"Unsupported FFE adaption technique.");
end
vars = optimizableVariable("mu_tr",mu_range,"Transform","log");
if obj.dd_mode
vars = [vars, optimizableVariable("mu_dd",mu_range,"Transform","log")];
end
obj.mu_optimization_iter = 0;
fprintf("FFE_plain mu opt uses %d samples / %d symbols\n",N_opt,numel(d_opt));
obj.mu_optimization = bayesopt(@(p)obj.muObjective(p,x_opt,d_opt),vars, ...
"MaxObjectiveEvaluations",20, ...
"AcquisitionFunctionName","expected-improvement-plus", ...
"IsObjectiveDeterministic",false, ...
"Verbose",0, ...
"PlotFcn",[]);
obj.mu_tr = obj.mu_optimization.XAtMinObjective.mu_tr;
if obj.dd_mode
obj.mu_dd = obj.mu_optimization.XAtMinObjective.mu_dd;
fprintf("\nFFE_plain mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, BER=%9.3e\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_optimization.MinObjective);
else
fprintf("\nFFE_plain mu opt done: mu_tr=%9.3e, BER=%9.3e\n", ...
obj.mu_tr,obj.mu_optimization.MinObjective);
end
if obj.plot_mu_optimization
obj.plotMuOptimization();
end
end
function objective = muObjective(obj,params,x,d)
old_state = obj.captureObjectiveState();
cleanup = onCleanup(@()obj.restoreObjectiveState(old_state));
obj.save_debug = 0;
obj.resetState();
N_tr = min(obj.len_tr,numel(x));
obj.equalize(x,d,params.mu_tr,obj.epochs_tr,N_tr,true,false);
if obj.dd_mode
[signal,~] = obj.equalize(x,d,params.mu_dd,obj.epochs_dd,numel(x),false,false);
else
[signal,~] = obj.equalize(x,d,0,1,numel(x),false,false);
end
[ber,errors] = obj.berObjective(signal,d);
objective = ber;
if ~isfinite(objective)
objective = inf;
end
obj.mu_optimization_iter = obj.mu_optimization_iter + 1;
if obj.dd_mode
fprintf("\rFFE_plain mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, BER=%9.3e, errors=%d", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,ber,errors);
else
fprintf("\rFFE_plain mu opt %02d: mu_tr=%9.3e, BER=%9.3e, errors=%d", ...
obj.mu_optimization_iter,params.mu_tr,ber,errors);
end
clear cleanup
end
function [x_opt,d_opt,N_opt] = optimizationSignals(obj,x,d,opt_len)
if nargin < 4
opt_len = obj.mu_optimization_len;
end
N_available = min(numel(x),numel(d) * obj.sps);
if isempty(opt_len) || opt_len <= 0 || isinf(opt_len)
N_opt = N_available;
else
N_opt = min(N_available,max(obj.len_tr,opt_len));
end
N_opt = obj.sps * floor(N_opt / obj.sps);
N_opt = max(obj.sps,N_opt);
n_symbols = N_opt / obj.sps;
x_opt = x(1:N_opt);
d_opt = d(1:n_symbols);
end
function [ber,errors] = berObjective(~,signal,d)
M = numel(unique(d));
mapper = PAMmapper(M,0);
eq_signal_sd = Signal(signal);
eq_signal_hd = mapper.quantize(eq_signal_sd);
tx_symbols = Signal(d);
rx_bits = mapper.demap(eq_signal_hd);
tx_bits = mapper.demap(tx_symbols);
[~,errors,ber,~] = calc_ber(rx_bits.signal,tx_bits.signal, ...
"skip_front",1000, ...
"skip_end",0, ...
"returnErrorLocation",1);
end
function plotMuOptimization(obj)
if isempty(obj.mu_optimization)
return
end
X = obj.mu_optimization.XTrace;
objective = obj.mu_optimization.ObjectiveTrace;
objective = objective(:);
valid = isfinite(objective);
if isempty(X) || ~any(valid)
return
end
var_names = X.Properties.VariableNames;
n_vars = numel(var_names);
eval_idx = (1:numel(objective)).';
objective_plot = obj.positiveObjectiveForLogPlot(objective);
best_plot = obj.positiveObjectiveForLogPlot(cummin(objective));
figure(obj.mu_optimization_fignum);
clf;
t = tiledlayout(2,2,"TileSpacing","compact","Padding","compact");
title(t,"FFE_plain Bayesian mu optimization");
nexttile;
h_candidate = semilogy(eval_idx,objective_plot,"o-","DisplayName","candidate");
obj.addOptimizationDataTips(h_candidate,X,objective,objective_plot,eval_idx,var_names);
hold on;
h_best_trace = semilogy(eval_idx,best_plot,"k-","LineWidth",1.2,"DisplayName","best so far");
h_best_trace.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("Evaluation",eval_idx);
h_best_trace.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("Best BER",best_plot);
grid on;
xlabel("Evaluation");
ylabel("BER");
legend("Location","best");
if n_vars < 2
return
end
pairs = nchoosek(1:n_vars,2);
n_pair_plots = min(size(pairs,1),3);
[~,best_idx] = min(objective);
for pair_idx = 1:n_pair_plots
nexttile;
x_name = var_names{pairs(pair_idx,1)};
y_name = var_names{pairs(pair_idx,2)};
x_data = X.(x_name);
y_data = X.(y_name);
c_data = log10(objective_plot);
h_scatter = scatter(log10(x_data),log10(y_data),35,c_data,"filled");
obj.addOptimizationDataTips(h_scatter,X,objective,objective_plot,eval_idx,var_names);
hold on;
h_best = plot(log10(x_data(best_idx)),log10(y_data(best_idx)),"kp", ...
"MarkerSize",12, ...
"MarkerFaceColor","y", ...
"DisplayName","best");
obj.addOptimizationDataTips(h_best,X(best_idx,:),objective(best_idx),objective_plot(best_idx),eval_idx(best_idx),var_names);
grid on;
xlabel("log10(" + string(x_name) + ")");
ylabel("log10(" + string(y_name) + ")");
cb = colorbar;
cb.Label.String = "log10(BER)";
title(string(x_name) + " vs " + string(y_name));
end
end
function addOptimizationDataTips(~,plot_handle,X,objective,objective_plot,eval_idx,var_names)
plot_handle.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("Evaluation",eval_idx);
plot_handle.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("BER",objective);
plot_handle.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow("BER shown",objective_plot);
for var_idx = 1:numel(var_names)
var_name = var_names{var_idx};
plot_handle.DataTipTemplate.DataTipRows(end+1) = dataTipTextRow(var_name,X.(var_name));
end
end
function objective_plot = positiveObjectiveForLogPlot(~,objective)
objective_plot = objective;
positive_values = objective(isfinite(objective) & objective > 0);
if isempty(positive_values)
floor_value = 1e-12;
else
floor_value = min(positive_values) / 10;
end
objective_plot(~isfinite(objective_plot) | objective_plot <= 0) = floor_value;
end
function state = captureObjectiveState(obj)
state.e = obj.e;
state.e_tr = obj.e_tr;
state.error = obj.error;
state.P = obj.P;
state.save_debug = obj.save_debug;
state.debug_struct = obj.debug_struct;
end
function restoreObjectiveState(obj,state)
obj.e = state.e;
obj.e_tr = state.e_tr;
obj.error = state.error;
obj.P = state.P;
obj.save_debug = state.save_debug;
obj.debug_struct = state.debug_struct;
end
end
methods (Access = private)
function resetState(obj)
obj.e = zeros(obj.order,1);
obj.e_tr = zeros(obj.order,1);
obj.error = 0;
obj.P = (1/0.05) * eye(obj.order);
obj.debug_struct = struct();
end
function N = validSampleLength(obj,N,x,d)
N_available = min(numel(x),numel(d) * obj.sps);
N = min(N,N_available);
N = obj.sps * floor(N / obj.sps);
N = max(0,N);
end
function adaption_code = adaptionCode(obj)
if obj.adaption_technique == adaption_method.nlms
adaption_code = 1;
elseif obj.adaption_technique == adaption_method.lms
adaption_code = 2;
elseif obj.adaption_technique == adaption_method.rls
adaption_code = 3;
else
builtin("error","FFE_plain:InvalidAdaptionTechnique", ...
"Unsupported FFE adaption technique.");
end
end
function initializeDebug(obj,n_symbols,training)
obj.debug_struct.error = NaN(1,n_symbols);
obj.debug_struct.error_first_epoch = NaN(1,n_symbols);
obj.debug_struct.main_cursor = NaN(1,n_symbols);
obj.debug_struct.mu_nlms = NaN(1,n_symbols);
obj.debug_struct.update_gradient = NaN(1,n_symbols);
if training
obj.debug_struct.error_tr = NaN(1,n_symbols);
obj.debug_struct.update_tr = NaN(1,n_symbols);
else
obj.debug_struct.error_dd = NaN(1,n_symbols);
obj.debug_struct.update = NaN(1,n_symbols);
end
end
end
end

View File

@@ -1,3 +1,499 @@
% classdef ML_MLSE < handle
% % ALGORITHM DESCRIBED IN:
% % W. Lanneer and Y. Lefevre, Machine Learning-Based Pre-Equalizers for
% % Maximum Likelihood Sequence Estimation in High-Speed PONs,
% % in 2023 31st European Signal Processing Conference
%
% % Further ML Refs:
% % https://machinelearningmastery.com/cross-entropy-for-machine-learning/
% % https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html
%
% % The central idea is to overcome the (white-) noise assumption within the previously described
% % Viterbi algorithm, more precisely a closed-loop optimization is proposed that finds a suitable
% % filter-set to directly compute the branch metrics c_k (s,s^' ). These can directly be used to
% % carry out the conventional Viterbi algorithm. The system consists of S^L S=F linear FIR filters,
% % combined with one bias coefficient respectively. These filters take the received input samples to
% % compute the branch metrics estimates (c_k ) ̂(s,s^' ) according toThe central idea is to overcome
% % the (white-) noise assumption within the previously described Viterbi algorithm, more precisely
% % a closed-loop optimization is proposed that finds a suitable filter-set to directly compute the
% % branch metrics c_k (s,s^' ). These can directly be used to carry out the conventional Viterbi
% % algorithm. The system consists of S^L S=F linear FIR filters, combined with one bias coefficient
% % respectively. These filters take the received input samples to compute the branch metrics
% % estimates. Finally, the usual Viterbi is carried out...
%
% % Recommended Settings and some findings:
%
% % Requires many training epochs. According to ML people, 100,200 or
% % even up to 1000 epochs are normal for ML-convergence
%
% % The mu parameter _can_ be adaptive - using the cross entropy and when
% % analyzing the isolated training it looks very promisig. However, is
% % later use I found this is not as stable as a fixed learning rate.
% % mu = 0.1 worked good for me
%
% % Longer orders/ filter length are not always better. For me order=11
% % was good.
%
% % Delay factor (delta) is good when the order is also increased. With
% % order = 11, a delta of =4 shows good results
%
% properties
% sps % usually 2
% order
% e
% e_tr
% error
%
% len_tr
% mu_tr
% epochs_tr
%
% dd_mode % 1 or 0 to set DD-mode on or off
% mu_dd %weight update in dd mode
% epochs_dd
%
% adaptive_mu
%
% constellation
%
% L %viterbi memory length
%
% alpha
% DIR
% DIR_flip
% trellis_states
%
% traceback_depth
%
% % --- Added internal class variables used later ---
% S
% Nf
% delta
% nStates
% nFeasible
% combs
% first_sym
% last_sym
% valid
% valid_to_idx
% valid_from_idx
% w
%
% % --- New: fast state lookup ---
% true_to_state_idx
% state_dict % containers.Map: key(sequence)->state index
% key_fmt = '%.8g_'; % key format for sequence strings
% nSym % |constellation|
%
% ber = []
% ce = ones(1,1);
% end
%
% methods
% function obj = ML_MLSE(options)
% arguments(Input)
%
% options.sps = 2;
% options.order = 15;
%
% options.len_tr = 4096;
% options.mu_tr = 0;
% options.epochs_tr = 5;
%
% options.dd_mode = 1;
% options.mu_dd = 1e-5;
% options.epochs_dd = 5;
%
% options.adaptive_mu = 1;
%
% options.delta = 0;
% options.traceback_depth = 1024;
%
% options.L = 1
%
% end
%
% fn = fieldnames(options);
% for n = 1:numel(fn)
% obj.(fn{n}) = options.(fn{n});
% end
%
% obj.e = zeros(obj.order,1);
% obj.error = 0;
% end
%
% function [X,X_viterbi] = process(obj, X, D)
%
% % actual processing of the signal (steps 1. - 3.)
% % 1 normalize RMS
% X = X.normalize("mode","rms");
%
% % Use sorted constellation for deterministic mapping
% obj.constellation = sort(unique(D.signal),'ascend');
% obj.nSym = numel(obj.constellation);
%
% if length(X)/length(D) ~= obj.sps
% warning('Signal length does not fit to reference!');
% end
%
% % ==============================================================
% % INITIALIZATION (only before final epoch and detection mode)
% % ==============================================================
%
% % --- Parameters
% obj.S = numel(obj.constellation); % alphabet size
% obj.Nf = obj.order*obj.sps; % filter length
% % obj.delta = 3;%ceil(obj.Nf/2); % delay parameter
% obj.nStates = obj.S^obj.L;
% obj.nFeasible = obj.nStates*obj.S;
%
% % --- Trellis mapping
% obj.trellis_states = reshape(obj.constellation,1,[]);
% pre_comb_mat = repmat(obj.trellis_states, obj.L, 1);
% pre_comb_cell = mat2cell(pre_comb_mat, ones(1,obj.L), size(pre_comb_mat,2));
% obj.combs = fliplr(combvec(pre_comb_cell{:}).'); % rows: states, columns: [x_k, x_{k-1}, ...]
% obj.first_sym = obj.combs(:,1);
% obj.last_sym = obj.combs(:,end);
% obj.nStates = size(obj.combs,1);
%
% % --- Valid transitions
% obj.valid = false(obj.nStates);
% for from = 1:obj.nStates
% for to = 1:obj.nStates
% if all(obj.combs(to,2:end) == obj.combs(from,1:end-1))
% obj.valid(to,from) = true;
% end
% end
% end
% [obj.valid_to_idx, obj.valid_from_idx] = find(obj.valid);
%
% % --- Allocate vectors and weights
% % !! IF SHAPE FIT, then we already have smth there an we want
% % to start with the existing fitler-set
% if isempty(obj.w) || any(size(obj.w) ~= [obj.Nf+1,obj.nFeasible])
% obj.w = zeros(obj.Nf+1,obj.nFeasible); % filter weights per transition + bias tap
% obj.w = randn(obj.Nf+1,obj.nFeasible);
% end
%
% % --- Precompute dictionary for fast state lookup (sequence -> state)
% keys = cell(obj.nStates,1);
% for i = 1:obj.nStates
% keys{i} = obj.seq_key(obj.combs(i,:)); % combs row is already [x_k, x_{k-1}, ...]
% end
% obj.state_dict = containers.Map(keys, 1:obj.nStates);
%
% % ==============================================================
% % TRAINING
% % ==============================================================
%
% % Training Mode
% n = obj.len_tr;
% training = 1;
% obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,n,training);
% obj.e_tr = obj.e;
%
% % ==============================================================
% % DD-Mode / Fixed Mode
% % ==============================================================
%
% % Decision Directed Mode
% n = X.length;
% training = 0;
% [y,y_vit]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training);
%
% X_viterbi = X;
%
% X.signal = y;
% X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
% lbdesc = [num2str(obj.order),' tap FFE'];
% X = X.logbookentry(lbdesc); % append to logbook
%
% X_viterbi.signal = y_vit;
% X_viterbi.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
% lbdesc = [num2str(obj.order),'order FFE + PF + Viterbi'];
% X_viterbi = X_viterbi.logbookentry(lbdesc); % append to logbook
% end
%
% function [y,y_ref] = equalize(obj,x,d,mu,epochs,N,training)
% % ==============================================================
% % FFE + Whitening + ML-Based Branch Metric Estimation + Viterbi
% % ==============================================================
% debug = 1;
% showPlots = 1;
%
% % --- Input padding and preallocation
% y = zeros(N,1);
%
% % number of symbol steps in this block
% nSymbols = ceil(N/obj.sps);
%
% for epoch = 1:epochs
%
% % state metrics (log-domain costs): keep as column [nStates×1]
% pm = zeros(obj.nStates,1); % v_{k-1}(s)
% c_hat = zeros(1,obj.nFeasible);
% v_tilde = zeros(1,obj.nFeasible);
% pred = zeros(nSymbols, obj.nStates, 'uint32');
% pm_sto = nan(obj.nStates, nSymbols,'like',pm);
% CE_accum = 0;
%
%
% %%% START IDX
% if training
% max_start = length(x) - ( (ceil(N/obj.sps)-1)*obj.sps + 1 );
% max_start = max(1, max_start); % safety
% start_sample = randi([1, max_start], 1); %rnd training; not really good
% start_sample = 1;
% end_sample = start_sample + (ceil(N/obj.sps)-1)*obj.sps;
% else
% start_sample = 1;%obj.len_tr;
% end_sample = N;
% end
%
% start_symbol = 1 + floor((start_sample - 1)/obj.sps); % ABSOLUTE symbol index
%
% if numel(d) >= obj.L && start_symbol >= obj.L
% init_seq = d(start_symbol-obj.L+1 : start_symbol); % [d_k-L+1 ... d_k]
% true_to_state_idx = obj.state_dict(obj.seq_key(flip(init_seq))); % [d_k ... d_k-L+1]
% else
% % Not enough history fall back to state 1
% true_to_state_idx = uint32(1);
% end
%
% symbol = 0;
% for sample = start_sample:obj.sps:end_sample
% symbol = symbol + 1;
% k = symbol;
% sym_idx = start_symbol + (symbol - 1);
%
% % --- Build Δ-delayed observation window y_k
% i1 = sample - obj.Nf + 1 + obj.delta;
% i2 = sample + obj.delta;
% buf = x(max(1,i1):min(length(x),i2));
% padL = max(0,1 - i1);
% padR = max(0,i2 - length(x));
% yk = [zeros(padL,1); buf(:); zeros(padR,1)]; % Nf×1
% yk = [yk;1];
%
% % --- Predict branch metrics for all feasible transitions: c_hat
% c_hat = (yk.' * obj.w); % [1×nFeasible]
% c_hat = c_hat.'; % [nFeasible×1]
%
% % --- Extended path metrics: v_tilde = pm(from) + c_hat
% % normalize pm to avoid growth (invariant to additive const)
% pm = pm - min(pm);
% v_tilde = pm(obj.valid_from_idx) + c_hat; % [nFeasible×1]
%
% % ===== Gradient update (Algorithm 1) =====
%
% if 1 %training
% % --- allocate storage once
% if epoch == 1 && symbol == 1
% obj.true_to_state_idx = ones(ceil(N/obj.sps),1,'uint32');
% end
%
% % --- previous "to" becomes current "from"
% if symbol > 1
% true_from_state_idx = obj.true_to_state_idx(symbol-1);
% else
% true_from_state_idx = 1;
% end
%
% % --- compute or reuse "to" state
% if epoch == 1
% % only compute in first epoch
% if sym_idx >= obj.L
% key_to = obj.seq_key(flip(d(sym_idx-obj.L+1 : sym_idx)));
% if isKey(obj.state_dict, key_to)
% obj.true_to_state_idx(symbol) = obj.state_dict(key_to);
% else
% obj.true_to_state_idx(symbol) = true_from_state_idx;
% end
% else
% obj.true_to_state_idx(symbol) = true_from_state_idx;
% end
% end
%
% % --- reuse cached state from second epoch onward
% true_to_state_idx = obj.true_to_state_idx(symbol);
%
% % --- ensure valid (from,to)
% dirac = zeros(obj.nFeasible,1);
% mask = obj.valid_from_idx==true_from_state_idx & ...
% obj.valid_to_idx ==true_to_state_idx;
% if any(mask)
% dirac(mask) = 1;
% else
% idx = find(obj.valid_from_idx==true_from_state_idx,1,'first');
% dirac(idx) = 1;
% obj.true_to_state_idx(symbol) = obj.valid_to_idx(idx);
% end
%
%
%
%
% % softmax over -v_tilde (numerically safe shift)
% v_shift = -(v_tilde - min(v_tilde)); % shift to small positive numbers
% v_shift = min(v_shift, 100); % clamp exponent argument ( exp(50)=3e21)
% expv = exp(v_shift);
% p = expv ./ (sum(expv) + eps);
%
% % for logging only:
% CE_symbol(symbol) = -log(p(dirac==1) + eps);
%
% if sym_idx > obj.L
% CE_smooth(symbol) = 0.01*CE_symbol(symbol) + 0.99*CE_smooth(symbol-1);
% else
% if epoch > 1
% CE_smooth(symbol) = obj.ce(end); %use ce from last epoch or =1 for very first round?!
% else
% CE_smooth(symbol) = CE_symbol(symbol);
% end
% end
%
% CE_accum = CE_symbol(symbol) + CE_accum;
%
%
% % gradient term (t - p)
% dmp = (dirac - p)'; % 1×nFeasible
%
% % Per-feature gradient; implicit expansion gives (Nf+1)×nFeasible
% dL_Dw = (yk) .* dmp;
%
% % Start updates only when the ABSOLUTE symbol index has L history
% if sym_idx >= obj.L
% if obj.adaptive_mu
% mu_eff = CE_smooth(sym_idx);
% mu_eff = max(min(mu_eff, 0.2), 1e-4);
% else
% mu_eff = mu;
% end
%
% obj.w = obj.w - mu_eff .* dL_Dw; % (Nf+1)×nFeasible
% end
%
% % if debug && epoch > 2
% % figure(100);
% % subplot(4,1,1);
% % heatmap(p');
% % title('Probs')
% % subplot(4,1,2);
% % heatmap(dmp);
% % title('Update')
% % subplot(4,1,3);
% % heatmap(dL_Dw);
% % title('Update')
% % subplot(4,1,4);
% % heatmap(bj.w);
% % title('Update')
% %
% % end
%
% end
%
%
%
% % --- Compare-Select (matrix form, min of costs)
% v_tilde_mat = inf(obj.nStates, obj.nStates);
% v_tilde_mat(obj.valid) = v_tilde;
% [pm_next, pred(k,:)] = min(v_tilde_mat, [], 2);
%
% % re-center to keep metrics bounded (decision-invariant)
% pm_next = pm_next - min(pm_next);
%
% pm = pm_next;
% pm_sto(:,symbol) = pm;
% end
%
% % --- Traceback (full; you can window with traceback_depth if desired)
% [~, s_end] = min(pm);
% viterbi_path = zeros(symbol,1,'uint32');
% viterbi_path(symbol) = s_end;
% for n = symbol:-1:2
% viterbi_path(n-1) = pred(n, viterbi_path(n));
% end
%
% y_ref = d(start_symbol:end);
% y = obj.first_sym(viterbi_path);
%
% if debug && training
% sym_start = start_symbol;
% sym_end = start_symbol + symbol - 1;
% ref_slice = d(sym_start : sym_end);
% err = sum(y ~= ref_slice(1:numel(y)));
%
% try
% ref_bits = PAMmapper(obj.S,0).demap(ref_slice);
% eq_bits = PAMmapper(obj.S,0).demap(y);
% [~, ~, ber, ~] = calc_ber(ref_bits, eq_bits, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
% fprintf('Epoch: %d - BER: %.1e \n',epoch, ber);
% obj.ber(epoch) = ber;
% catch
% ser = err./length(y);
% fprintf('Epoch: %d - SER: %.1e \n',epoch, ser);
% end
%
% obj.ce(epoch) = CE_accum./symbol;
%
% if showPlots
% figure(10);clf
% subplot(3,2,1:2);
% heatmap(obj.w);
% title('Filter')
%
% subplot(3,2,3);
% v_tildemat = NaN(obj.nStates, obj.nStates);
% v_tildemat(obj.valid) = v_tilde; % log-domain scores
% heatmap(v_tildemat);
% title('Path Metrics (v_tilde)')
%
% subplot(3,2,4);
% scatter(1:symbol,pm_sto,1,'.')
% title('Path Metric Winners')
%
% subplot(3,2,5);hold on
% scatter(1:symbol,CE_symbol,1,'.');
% scatter(1:symbol,CE_smooth,1,'.')
% title('Cross Entropy')
%
% subplot(3,2,6); hold on
%
% % Left y-axis: Cross Entropy (linear)
% yyaxis left
% scatter(1:length(obj.ce), obj.ce, 10, 's', 'filled')
% ylabel('Cross Entropy')
%
% % Right y-axis: BER (logarithmic)
% yyaxis right
% scatter(1:length(obj.ber), obj.ber, 10, 'd', 'filled')
% set(gca, 'YScale', 'log')
% ylabel('BER (log scale)')
%
% xlim([1, epochs])
% xlabel('Epoch')
% title('Cross Entropy // BER')
% grid on
%
% drawnow
% end
% end
% end
% end
% end
%
% methods (Access=private)
% function k = seq_key(obj, seq)
% % Build a stable key string for a sequence row vector in the *same order as combs rows* ([x_k, x_{k-1}, ...])
% % Use rounding via sprintf to avoid floating-point issues.
% % seq must be a row vector.
% k = sprintf(obj.key_fmt, seq);
% end
% end
% end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
classdef ML_MLSE < handle
% ---------------------------------------------------------------------
% W. Lanneer and Y. Lefevre,
@@ -165,8 +661,8 @@ classdef ML_MLSE < handle
% EQUALIZE
% ==============================================================
function [y,y_ref] = equalize(obj,x,d,mu,epochs,N,training)
debug = 0;
showPlots = 0;
debug = 1;
showPlots = 1;
y = zeros(N,1);
nSymbols = ceil(N/obj.sps);

View File

@@ -1,311 +0,0 @@
classdef FFE_A1 < handle
%FFE_A1 Feed-forward equalizer with A1 moving-average input suppression.
%
% This class is intentionally narrow: it contains the normal adaptive FFE
% flow plus the A1 block-rate moving-average input correction. A2,
% DC-tracking, and optimizer logic are kept out of this file.
properties
sps
order
e
e_tr
error
len_tr
mu_tr
epochs_tr
adaption_technique
dd_mode
mu_dd
epochs_dd
dd_len_fraction
% A1 moving-average input suppression
dc_avg_bufferlength_a1
dc_avg_update_blocklength_a1
dc_smoothing_a1
P
constellation
decide
save_debug = 0
debug_struct
end
methods
function obj = FFE_A1(options)
arguments(Input)
options.sps = 2
options.order = 15
options.len_tr = 4096
options.mu_tr = 0
options.epochs_tr = 5
options.adaption_technique adaption_method = adaption_method.lms
options.dd_mode = 1
options.mu_dd = 1e-5
options.epochs_dd = 5
options.dd_len_fraction = 1
options.dc_avg_bufferlength_a1 = 0
options.dc_avg_update_blocklength_a1 = 0
options.dc_smoothing_a1 = 0
options.decide = false
options.save_debug = 0
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
assert(obj.dc_avg_bufferlength_a1 >= 0);
assert(obj.dc_avg_update_blocklength_a1 >= 0);
obj.e = zeros(obj.order,1);
obj.error = 0;
obj.dc_avg_bufferlength_a1 = floor(obj.dc_avg_bufferlength_a1);
obj.dc_avg_update_blocklength_a1 = floor(obj.dc_avg_update_blocklength_a1);
obj.dc_smoothing_a1 = min(max(obj.dc_smoothing_a1,0),1);
end
function [X,Noi] = process(obj,X,D)
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
delta = 0.05;
obj.P = (1/delta) * eye(obj.order);
training = true;
showviz = false;
obj.equalize(X.signal,D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
obj.e_tr = obj.e;
n = X.length;
training = false;
if obj.dd_mode
[signal,decision] = obj.equalize(X.signal,D.signal,obj.mu_dd,obj.epochs_dd,n,training,showviz);
else
[signal,decision] = obj.equalize(X.signal,D.signal,0,1,n,training,showviz);
end
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs;
X = X.logbookentry([num2str(obj.order),' tap FFE A1']);
Noi = X - D;
end
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
arguments
obj
x
d
mu
epochs
N
training
showviz %#ok<INUSA>
end
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
lambda = mu;
n_symbols = ceil(N / obj.sps);
y = zeros(n_symbols,1);
d_hat = zeros(n_symbols,1);
err = zeros(n_symbols,1);
constellation = obj.constellation;
adaption_code = obj.adaptionCode();
adaption_is_rls = adaption_code == 3;
mask = ones(obj.order,1);
maincursor_pos = ceil(length(obj.e)/2);
grad = 0;
weight = 0;
update = 0;
if mu == 0 || (~obj.dd_mode && ~training)
epochs = 1;
end
dc_avg_enabled = obj.dc_avg_bufferlength_a1 > 1;
if dc_avg_enabled
dc_avg_buffer = NaN(obj.dc_avg_bufferlength_a1,1);
dc_avg_buffer_pos = 0;
dc_avg_sum = 0;
dc_avg_valid_count = 0;
dc_avg_est = 0;
dc_avg_update_blocklength = obj.dc_avg_update_blocklength_a1;
if dc_avg_update_blocklength <= 0
dc_avg_update_blocklength = obj.dc_avg_bufferlength_a1;
end
dc_avg_update_blocklength = max(1,floor(dc_avg_update_blocklength));
dc_avg_input_offset = floor(obj.order/2);
% Hardware-like A1 is causal; dc_smoothing_a1 is reserved for offline variants.
end
debug_enabled = obj.save_debug;
if debug_enabled
obj.debug_struct.error = NaN(1,n_symbols);
obj.debug_struct.error_first_epoch = NaN(1,n_symbols);
obj.debug_struct.main_cursor = NaN(1,n_symbols);
obj.debug_struct.mu_nlms = NaN(1,n_symbols);
obj.debug_struct.update_gradient = NaN(1,n_symbols);
obj.debug_struct.dc_avg_offset = NaN(1,n_symbols);
if training
obj.debug_struct.error_tr = NaN(1,n_symbols);
obj.debug_struct.update_tr = NaN(1,n_symbols);
else
obj.debug_struct.error_dd = NaN(1,n_symbols);
obj.debug_struct.update = NaN(1,n_symbols);
end
end
for epoch = 1:epochs
symbol = 0;
for sample = 1:obj.sps:N
symbol = symbol + 1;
dc_avg_offset = 0;
U = x(obj.order+sample-1:-1:sample);
if dc_avg_enabled
dc_avg_offset = dc_avg_est;
U = U - dc_avg_offset;
end
y(symbol,1) = (obj.e.*mask).' * U;
if dc_avg_enabled
raw_sample_idx = dc_avg_input_offset + sample;
[dc_avg_buffer,dc_avg_buffer_pos,dc_avg_sum,dc_avg_valid_count,dc_avg_est] = ...
obj.updateA1Buffer(dc_avg_buffer,dc_avg_buffer_pos,dc_avg_sum, ...
dc_avg_valid_count,dc_avg_est,x(raw_sample_idx),symbol, ...
dc_avg_update_blocklength);
end
if training
d_hat(symbol,1) = d(symbol);
else
[~,symbol_idx] = min(abs(y(symbol) - constellation));
d_hat(symbol,1) = constellation(symbol_idx);
end
err(symbol) = d_hat(symbol) - y(symbol);
if training || obj.dd_mode
switch adaption_code
case 1
normU = (U.'*U) + eps;
weight = mu / normU;
grad = err(symbol) * U;
update = grad * weight;
obj.e = obj.e + update;
case 2
weight = mu;
grad = err(symbol) * U;
update = grad * weight;
obj.e = obj.e + update;
case 3
denom = lambda + U.' * obj.P * U;
k = (obj.P * U) / denom;
update = k * err(symbol);
obj.e = obj.e + update;
obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P));
end
end
if debug_enabled && epoch == 1
obj.debug_struct.error_first_epoch(1,symbol) = err(symbol) * err(symbol)';
end
if debug_enabled && epoch == epochs
error_power = err(symbol) * err(symbol)';
update_power = update.'*update ./ (sqrt((obj.e.'*obj.e) / obj.order) + eps);
obj.debug_struct.error(1,symbol) = error_power;
obj.debug_struct.main_cursor(1,symbol) = abs(obj.e(maincursor_pos));
obj.debug_struct.mu_nlms(1,symbol) = weight;
obj.debug_struct.update_gradient(1,symbol) = grad.'*grad;
obj.debug_struct.dc_avg_offset(1,symbol) = dc_avg_offset;
if training
obj.debug_struct.error_tr(1,symbol) = error_power;
obj.debug_struct.update_tr(1,symbol) = update_power;
else
obj.debug_struct.error_dd(1,symbol) = error_power;
obj.debug_struct.update(1,symbol) = update_power;
end
end
end
end
if ~adaption_is_rls
obj.P = [];
end
end
end
methods (Access = private)
function adaption_code = adaptionCode(obj)
if obj.adaption_technique == adaption_method.nlms
adaption_code = 1;
elseif obj.adaption_technique == adaption_method.lms
adaption_code = 2;
elseif obj.adaption_technique == adaption_method.rls
adaption_code = 3;
else
builtin("error","FFE_A1:InvalidAdaptionTechnique", ...
"Unsupported FFE adaption technique.");
end
end
end
methods (Static, Access = private)
function [buffer,buffer_pos,buffer_sum,valid_count,estimate] = ...
updateA1Buffer(buffer,buffer_pos,buffer_sum,valid_count,estimate, ...
new_value,symbol,update_blocklength)
buffer_pos = buffer_pos + 1;
if buffer_pos > numel(buffer)
buffer_pos = 1;
end
old_value = buffer(buffer_pos);
if isfinite(old_value)
buffer_sum = buffer_sum - old_value;
valid_count = valid_count - 1;
end
if isfinite(new_value)
buffer(buffer_pos) = new_value;
buffer_sum = buffer_sum + new_value;
valid_count = valid_count + 1;
else
buffer(buffer_pos) = NaN;
end
if mod(symbol,update_blocklength) == 0
if valid_count == 0
estimate = 0;
else
estimate = buffer_sum / valid_count;
end
end
end
end
end

View File

@@ -1,347 +0,0 @@
classdef FFE_A2Residual < FFE_plain
%FFE_A2Residual Plain FFE with A2 residual MPI suppression.
%
% This class reuses FFE_plain for process flow and BER-based mu
% optimization. Only equalize is specialized to add the paper-style A2
% residual correction: average y_raw - d_hat per PAM level, subtract the
% level-weighted residual estimate, then redo the DD decision.
properties
dc_level_avg_bufferlength_a2
dc_level_update_blocklength_a2
dc_smoothing_a2
dc_level_weights_a2
end
methods
function obj = FFE_A2Residual(options)
arguments(Input)
options.sps = 2
options.order = 15
options.len_tr = 4096
options.mu_tr = 0
options.epochs_tr = 5
options.adaption_technique adaption_method = adaption_method.lms
options.dd_mode = 1
options.mu_dd = 1e-5
options.epochs_dd = 5
options.dd_len_fraction = 1
options.dc_level_avg_bufferlength_a2 = 0
options.dc_level_update_blocklength_a2 = 0
options.dc_smoothing_a2 = 0
options.dc_level_weights_a2 = 0
options.decide = false
options.save_debug = 0
options.optmize_mus = 0
options.mu_optimization_len = 2^15
options.plot_mu_optimization = 0
options.mu_optimization_fignum = 3010
end
obj@FFE_plain( ...
"sps",options.sps, ...
"order",options.order, ...
"len_tr",options.len_tr, ...
"mu_tr",options.mu_tr, ...
"epochs_tr",options.epochs_tr, ...
"adaption_technique",options.adaption_technique, ...
"dd_mode",options.dd_mode, ...
"mu_dd",options.mu_dd, ...
"epochs_dd",options.epochs_dd, ...
"dd_len_fraction",options.dd_len_fraction, ...
"decide",options.decide, ...
"save_debug",options.save_debug, ...
"optmize_mus",options.optmize_mus, ...
"mu_optimization_len",options.mu_optimization_len, ...
"plot_mu_optimization",options.plot_mu_optimization, ...
"mu_optimization_fignum",options.mu_optimization_fignum);
obj.dc_level_avg_bufferlength_a2 = floor(options.dc_level_avg_bufferlength_a2);
obj.dc_level_update_blocklength_a2 = floor(options.dc_level_update_blocklength_a2);
obj.dc_smoothing_a2 = min(max(options.dc_smoothing_a2,0),1);
obj.dc_level_weights_a2 = options.dc_level_weights_a2;
assert(obj.dc_level_avg_bufferlength_a2 >= 0);
assert(obj.dc_level_update_blocklength_a2 >= 0);
end
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
arguments
obj
x
d
mu
epochs
N
training
showviz
end
unused_showviz = showviz; %#ok<NASGU>
x = x(:);
d = d(:);
N = obj.validSampleLength(N,x,d);
n_symbols = N / obj.sps;
y = zeros(n_symbols,1);
d_hat = zeros(n_symbols,1);
if n_symbols == 0
return
end
if isempty(obj.constellation)
obj.constellation = unique(d);
end
decision_constellation = obj.constellation(:);
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
lambda = mu;
mask = ones(obj.order,1);
maincursor_pos = ceil(length(obj.e)/2);
adaption_code = obj.adaptionCode();
if mu == 0 || (~obj.dd_mode && ~training)
epochs = 1;
end
dc_level_enabled = obj.dc_level_avg_bufferlength_a2 > 1 && any(obj.dc_level_weights_a2(:) ~= 0);
if dc_level_enabled
[dc_level_weight_by_level,n_levels] = obj.expandedLevelWeights(decision_constellation);
dc_level_buffer_len = obj.dc_level_avg_bufferlength_a2;
dc_level_err_buffer = NaN(n_levels,dc_level_buffer_len);
dc_level_err_buffer_pos_by_level = zeros(n_levels,1);
dc_level_err_sum_by_level = zeros(n_levels,1);
dc_level_buffer_valid_count_by_level = zeros(n_levels,1);
dc_level_mpi_est_by_level = zeros(n_levels,1);
dc_level_valid_count_by_level = zeros(n_levels,1);
dc_level_update_blocklength = obj.dc_level_update_blocklength_a2;
if dc_level_update_blocklength <= 0
dc_level_update_blocklength = dc_level_buffer_len;
end
dc_level_update_blocklength = max(1,floor(dc_level_update_blocklength));
dc_level_window_future_fraction = obj.dc_smoothing_a2; %#ok<NASGU>
end
debug_enabled = obj.save_debug;
if debug_enabled
obj.initializeDebug(n_symbols,training);
end
for epoch = 1:epochs
symbol = 0;
for sample = 1:obj.sps:N
symbol = symbol + 1;
grad = zeros(obj.order,1);
update = zeros(obj.order,1);
weight = 0;
dc_level_mpi_est = 0;
dc_level_weight = 0;
dc_level_valid_count = 0;
U = x(obj.order+sample-1:-1:sample);
y(symbol,1) = (obj.e.*mask).' * U;
y_raw = y(symbol);
if training
d_hat(symbol,1) = d(symbol);
[~,symbol_idx] = min(abs(d_hat(symbol) - decision_constellation));
dc_level_decision_level = decision_constellation(symbol_idx);
else
[~,symbol_idx] = min(abs(y_raw - decision_constellation));
d_hat(symbol,1) = decision_constellation(symbol_idx);
dc_level_decision_level = decision_constellation(symbol_idx);
end
dc_level_symbol_idx = symbol_idx;
if dc_level_enabled
dc_level_mpi_est = dc_level_mpi_est_by_level(dc_level_symbol_idx);
dc_level_valid_count = dc_level_valid_count_by_level(dc_level_symbol_idx);
dc_level_weight = dc_level_weight_by_level(dc_level_symbol_idx) * ...
min(dc_level_valid_count / dc_level_buffer_len,1);
mpi_err = y_raw - d_hat(symbol);
y(symbol,1) = y_raw - dc_level_weight * dc_level_mpi_est;
if training
d_hat(symbol,1) = d(symbol);
else
[~,symbol_idx] = min(abs(y(symbol) - decision_constellation));
d_hat(symbol,1) = decision_constellation(symbol_idx);
dc_level_decision_level = decision_constellation(symbol_idx);
end
[dc_level_err_buffer,dc_level_err_buffer_pos_by_level, ...
dc_level_err_sum_by_level,dc_level_buffer_valid_count_by_level, ...
dc_level_mpi_est_by_level,dc_level_valid_count_by_level] = ...
obj.updateResidualBuffer( ...
dc_level_err_buffer,dc_level_err_buffer_pos_by_level, ...
dc_level_err_sum_by_level,dc_level_buffer_valid_count_by_level, ...
dc_level_mpi_est_by_level,dc_level_valid_count_by_level, ...
mpi_err,dc_level_symbol_idx,symbol,dc_level_update_blocklength);
end
err = d_hat(symbol) - y(symbol);
if mu ~= 0 && (training || obj.dd_mode)
switch adaption_code
case 1
weight = mu / ((U.'*U) + eps);
grad = err * U;
update = grad * weight;
obj.e = obj.e + update;
case 2
weight = mu;
grad = err * U;
update = grad * weight;
obj.e = obj.e + update;
case 3
denom = lambda + U.' * obj.P * U;
k = (obj.P * U) / denom;
update = k * err;
obj.e = obj.e + update;
obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P));
end
end
if debug_enabled && epoch == 1
obj.debug_struct.error_first_epoch(1,symbol) = err * err';
end
if debug_enabled && epoch == epochs
error_power = err * err';
update_power = update.'*update ./ (sqrt((obj.e.'*obj.e) / obj.order) + eps);
obj.debug_struct.error(1,symbol) = error_power;
obj.debug_struct.main_cursor(1,symbol) = abs(obj.e(maincursor_pos));
obj.debug_struct.mu_nlms(1,symbol) = weight;
obj.debug_struct.update_gradient(1,symbol) = grad.'*grad;
obj.debug_struct.dc_level_mpi_est(1,symbol) = dc_level_mpi_est;
obj.debug_struct.dc_level_weight(1,symbol) = dc_level_weight;
obj.debug_struct.dc_level_valid_count(1,symbol) = dc_level_valid_count;
obj.debug_struct.dc_level_symbol_idx(1,symbol) = dc_level_symbol_idx;
obj.debug_struct.dc_level_decision_level(1,symbol) = dc_level_decision_level;
obj.debug_struct.dc_level_y_raw(1,symbol) = y_raw;
if training
obj.debug_struct.error_tr(1,symbol) = error_power;
obj.debug_struct.update_tr(1,symbol) = update_power;
else
obj.debug_struct.error_dd(1,symbol) = error_power;
obj.debug_struct.update(1,symbol) = update_power;
end
end
end
end
end
end
methods (Access = private)
function N = validSampleLength(obj,N,x,d)
N_available = min(numel(x),numel(d) * obj.sps);
N = min(N,N_available);
N = obj.sps * floor(N / obj.sps);
N = max(0,N);
end
function adaption_code = adaptionCode(obj)
if obj.adaption_technique == adaption_method.nlms
adaption_code = 1;
elseif obj.adaption_technique == adaption_method.lms
adaption_code = 2;
elseif obj.adaption_technique == adaption_method.rls
adaption_code = 3;
else
builtin("error","FFE_A2Residual:InvalidAdaptionTechnique", ...
"Unsupported FFE adaption technique.");
end
end
function [weights,n_levels] = expandedLevelWeights(obj,decision_constellation)
n_levels = numel(decision_constellation);
if isscalar(obj.dc_level_weights_a2)
weights = repmat(obj.dc_level_weights_a2,n_levels,1);
elseif numel(obj.dc_level_weights_a2) == n_levels
weights = obj.dc_level_weights_a2(:);
else
builtin("error","FFE_A2Residual:InvalidDCLevelWeights", ...
"dc_level_weights_a2 must be scalar or have one entry per constellation level.");
end
end
function initializeDebug(obj,n_symbols,training)
obj.debug_struct.error = NaN(1,n_symbols);
obj.debug_struct.error_first_epoch = NaN(1,n_symbols);
obj.debug_struct.main_cursor = NaN(1,n_symbols);
obj.debug_struct.mu_nlms = NaN(1,n_symbols);
obj.debug_struct.update_gradient = NaN(1,n_symbols);
obj.debug_struct.dc_level_mpi_est = NaN(1,n_symbols);
obj.debug_struct.dc_level_weight = NaN(1,n_symbols);
obj.debug_struct.dc_level_valid_count = NaN(1,n_symbols);
obj.debug_struct.dc_level_symbol_idx = NaN(1,n_symbols);
obj.debug_struct.dc_level_decision_level = NaN(1,n_symbols);
obj.debug_struct.dc_level_y_raw = NaN(1,n_symbols);
if training
obj.debug_struct.error_tr = NaN(1,n_symbols);
obj.debug_struct.update_tr = NaN(1,n_symbols);
else
obj.debug_struct.error_dd = NaN(1,n_symbols);
obj.debug_struct.update = NaN(1,n_symbols);
end
end
end
methods (Static, Access = private)
function [buffer,buffer_pos_by_level,err_sum_by_level,buffer_valid_count_by_level, ...
mpi_est_by_level,valid_count_by_level] = updateResidualBuffer( ...
buffer,buffer_pos_by_level,err_sum_by_level,buffer_valid_count_by_level, ...
mpi_est_by_level,valid_count_by_level,new_value,symbol_idx,symbol, ...
update_blocklength)
buffer_len = size(buffer,2);
buffer_pos = buffer_pos_by_level(symbol_idx) + 1;
if buffer_pos > buffer_len
buffer_pos = 1;
end
buffer_pos_by_level(symbol_idx) = buffer_pos;
old_value = buffer(symbol_idx,buffer_pos);
if isfinite(old_value)
err_sum_by_level(symbol_idx) = err_sum_by_level(symbol_idx) - old_value;
buffer_valid_count_by_level(symbol_idx) = buffer_valid_count_by_level(symbol_idx) - 1;
end
if isfinite(new_value)
buffer(symbol_idx,buffer_pos) = new_value;
err_sum_by_level(symbol_idx) = err_sum_by_level(symbol_idx) + new_value;
buffer_valid_count_by_level(symbol_idx) = buffer_valid_count_by_level(symbol_idx) + 1;
else
buffer(symbol_idx,buffer_pos) = NaN;
end
if mod(symbol,update_blocklength) == 0
if update_blocklength == 1
valid_count_by_level(symbol_idx) = buffer_valid_count_by_level(symbol_idx);
if valid_count_by_level(symbol_idx) == 0
mpi_est_by_level(symbol_idx) = 0;
else
mpi_est_by_level(symbol_idx) = ...
err_sum_by_level(symbol_idx) / valid_count_by_level(symbol_idx);
end
else
valid_count_by_level = buffer_valid_count_by_level;
has_valid = valid_count_by_level > 0;
mpi_est_by_level(:) = 0;
mpi_est_by_level(has_valid) = ...
err_sum_by_level(has_valid) ./ valid_count_by_level(has_valid);
end
end
end
end
end

View File

@@ -1,361 +0,0 @@
classdef FFE_A2TrackedLevels < handle
%FFE_A2TrackedLevels FFE with A2 tracked-level decision thresholds.
%
% This class is intentionally narrow: it contains the normal adaptive FFE
% flow plus the A2 tracked-level decision logic. The output sample y stays
% unchanged; only the decision constellation is moved by the tracked
% level-wise offsets.
properties
sps
order
e
e_tr
error
len_tr
mu_tr
epochs_tr
adaption_technique
dd_mode
mu_dd
epochs_dd
dd_len_fraction
% A2 tracked-level decision
dc_level_avg_bufferlength_a2
dc_level_update_blocklength_a2
dc_smoothing_a2
dc_level_weights_a2
P
constellation
decide
save_debug = 0
debug_struct
end
methods
function obj = FFE_A2TrackedLevels(options)
arguments(Input)
options.sps = 2
options.order = 15
options.len_tr = 4096
options.mu_tr = 0
options.epochs_tr = 5
options.adaption_technique adaption_method = adaption_method.lms
options.dd_mode = 1
options.mu_dd = 1e-5
options.epochs_dd = 5
options.dd_len_fraction = 1
options.dc_level_avg_bufferlength_a2 = 0
options.dc_level_update_blocklength_a2 = 0
options.dc_smoothing_a2 = 0
options.dc_level_weights_a2 = 0
options.decide = false
options.save_debug = 0
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
assert(obj.dc_level_avg_bufferlength_a2 >= 0);
assert(obj.dc_level_update_blocklength_a2 >= 0);
obj.e = zeros(obj.order,1);
obj.error = 0;
obj.dc_level_avg_bufferlength_a2 = floor(obj.dc_level_avg_bufferlength_a2);
obj.dc_level_update_blocklength_a2 = floor(obj.dc_level_update_blocklength_a2);
obj.dc_smoothing_a2 = min(max(obj.dc_smoothing_a2,0),1);
end
function [X,Noi] = process(obj,X,D)
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
delta = 0.05;
obj.P = (1/delta) * eye(obj.order);
training = true;
showviz = false;
obj.equalize(X.signal,D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
obj.e_tr = obj.e;
n = X.length;
training = false;
if obj.dd_mode
[signal,decision] = obj.equalize(X.signal,D.signal,obj.mu_dd,obj.epochs_dd,n,training,showviz);
else
[signal,decision] = obj.equalize(X.signal,D.signal,0,1,n,training,showviz);
end
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs;
X = X.logbookentry([num2str(obj.order),' tap FFE A2 tracked levels']);
Noi = X - D;
end
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
arguments
obj
x
d
mu
epochs
N
training
showviz %#ok<INUSA>
end
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
lambda = mu;
n_symbols = ceil(N / obj.sps);
y = zeros(n_symbols,1);
d_hat = zeros(n_symbols,1);
err = zeros(n_symbols,1);
constellation = obj.constellation;
adaption_code = obj.adaptionCode();
adaption_is_rls = adaption_code == 3;
mask = ones(obj.order,1);
maincursor_pos = ceil(length(obj.e)/2);
grad = 0;
weight = 0;
update = 0;
if mu == 0 || (~obj.dd_mode && ~training)
epochs = 1;
end
dc_level_enabled = obj.dc_level_avg_bufferlength_a2 > 1 && any(obj.dc_level_weights_a2(:) ~= 0);
if dc_level_enabled
if isempty(constellation)
builtin("error","FFE_A2TrackedLevels:MissingConstellation", ...
"A2 tracked-level decision requires obj.constellation to be set.");
end
n_levels = numel(constellation);
if isscalar(obj.dc_level_weights_a2)
dc_level_weight_by_level = repmat(obj.dc_level_weights_a2,n_levels,1);
elseif numel(obj.dc_level_weights_a2) == n_levels
dc_level_weight_by_level = obj.dc_level_weights_a2(:);
else
builtin("error","FFE_A2TrackedLevels:InvalidDCLevelWeights", ...
"dc_level_weights_a2 must be scalar or have one entry per constellation level.");
end
dc_level_buffer_len = obj.dc_level_avg_bufferlength_a2;
dc_level_buffer = NaN(n_levels,dc_level_buffer_len);
dc_level_buffer_pos_by_level = zeros(n_levels,1);
dc_level_sum_by_level = zeros(n_levels,1);
dc_level_buffer_valid_count_by_level = zeros(n_levels,1);
dc_level_offset_by_level = zeros(n_levels,1);
dc_level_valid_count_by_level = zeros(n_levels,1);
dc_level_update_blocklength = obj.dc_level_update_blocklength_a2;
if dc_level_update_blocklength <= 0
dc_level_update_blocklength = dc_level_buffer_len;
end
dc_level_update_blocklength = max(1,floor(dc_level_update_blocklength));
dc_level_window_future_fraction = obj.dc_smoothing_a2; %#ok<NASGU>
end
debug_enabled = obj.save_debug;
if debug_enabled
obj.debug_struct.error = NaN(1,n_symbols);
obj.debug_struct.error_first_epoch = NaN(1,n_symbols);
obj.debug_struct.main_cursor = NaN(1,n_symbols);
obj.debug_struct.mu_nlms = NaN(1,n_symbols);
obj.debug_struct.update_gradient = NaN(1,n_symbols);
obj.debug_struct.dc_level_mpi_est = NaN(1,n_symbols);
obj.debug_struct.dc_level_weight = NaN(1,n_symbols);
obj.debug_struct.dc_level_valid_count = NaN(1,n_symbols);
obj.debug_struct.dc_level_symbol_idx = NaN(1,n_symbols);
obj.debug_struct.dc_level_decision_level = NaN(1,n_symbols);
obj.debug_struct.dc_level_y_raw = NaN(1,n_symbols);
if training
obj.debug_struct.error_tr = NaN(1,n_symbols);
obj.debug_struct.update_tr = NaN(1,n_symbols);
else
obj.debug_struct.error_dd = NaN(1,n_symbols);
obj.debug_struct.update = NaN(1,n_symbols);
end
end
for epoch = 1:epochs
symbol = 0;
for sample = 1:obj.sps:N
symbol = symbol + 1;
dc_level_offset = 0;
dc_level_weight = 0;
dc_level_valid_count = 0;
U = x(obj.order+sample-1:-1:sample);
y(symbol,1) = (obj.e.*mask).' * U;
y_raw = y(symbol);
if training
d_hat(symbol,1) = d(symbol);
[~,symbol_idx] = min(abs(d_hat(symbol) - constellation));
dc_level_decision_level = constellation(symbol_idx);
else
if dc_level_enabled
dc_level_ramp_by_level = min(dc_level_valid_count_by_level / dc_level_buffer_len,1);
dc_level_decision_constellation = constellation + ...
dc_level_weight_by_level .* dc_level_ramp_by_level .* dc_level_offset_by_level;
[~,symbol_idx] = min(abs(y_raw - dc_level_decision_constellation));
dc_level_decision_level = dc_level_decision_constellation(symbol_idx);
else
[~,symbol_idx] = min(abs(y_raw - constellation));
dc_level_decision_level = constellation(symbol_idx);
end
d_hat(symbol,1) = constellation(symbol_idx);
end
dc_level_symbol_idx = symbol_idx;
if dc_level_enabled
dc_level_offset = dc_level_offset_by_level(symbol_idx);
dc_level_valid_count = dc_level_valid_count_by_level(symbol_idx);
dc_level_weight = dc_level_weight_by_level(symbol_idx) * ...
min(dc_level_valid_count / dc_level_buffer_len,1);
[dc_level_buffer,dc_level_buffer_pos_by_level,dc_level_sum_by_level, ...
dc_level_buffer_valid_count_by_level,dc_level_offset_by_level, ...
dc_level_valid_count_by_level] = obj.updateTrackedLevelBuffer( ...
dc_level_buffer,dc_level_buffer_pos_by_level,dc_level_sum_by_level, ...
dc_level_buffer_valid_count_by_level,dc_level_offset_by_level, ...
dc_level_valid_count_by_level,y_raw,symbol_idx,symbol, ...
dc_level_update_blocklength,constellation);
end
err(symbol) = d_hat(symbol) - y(symbol);
if training || obj.dd_mode
switch adaption_code
case 1
normU = (U.'*U) + eps;
weight = mu / normU;
grad = err(symbol) * U;
update = grad * weight;
obj.e = obj.e + update;
case 2
weight = mu;
grad = err(symbol) * U;
update = grad * weight;
obj.e = obj.e + update;
case 3
denom = lambda + U.' * obj.P * U;
k = (obj.P * U) / denom;
update = k * err(symbol);
obj.e = obj.e + update;
obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P));
end
end
if debug_enabled && epoch == 1
obj.debug_struct.error_first_epoch(1,symbol) = err(symbol) * err(symbol)';
end
if debug_enabled && epoch == epochs
error_power = err(symbol) * err(symbol)';
update_power = update.'*update ./ (sqrt((obj.e.'*obj.e) / obj.order) + eps);
obj.debug_struct.error(1,symbol) = error_power;
obj.debug_struct.main_cursor(1,symbol) = abs(obj.e(maincursor_pos));
obj.debug_struct.mu_nlms(1,symbol) = weight;
obj.debug_struct.update_gradient(1,symbol) = grad.'*grad;
obj.debug_struct.dc_level_mpi_est(1,symbol) = dc_level_offset;
obj.debug_struct.dc_level_weight(1,symbol) = dc_level_weight;
obj.debug_struct.dc_level_valid_count(1,symbol) = dc_level_valid_count;
obj.debug_struct.dc_level_symbol_idx(1,symbol) = dc_level_symbol_idx;
obj.debug_struct.dc_level_decision_level(1,symbol) = dc_level_decision_level;
obj.debug_struct.dc_level_y_raw(1,symbol) = y_raw;
if training
obj.debug_struct.error_tr(1,symbol) = error_power;
obj.debug_struct.update_tr(1,symbol) = update_power;
else
obj.debug_struct.error_dd(1,symbol) = error_power;
obj.debug_struct.update(1,symbol) = update_power;
end
end
end
end
if ~adaption_is_rls
obj.P = [];
end
end
end
methods (Access = private)
function adaption_code = adaptionCode(obj)
if obj.adaption_technique == adaption_method.nlms
adaption_code = 1;
elseif obj.adaption_technique == adaption_method.lms
adaption_code = 2;
elseif obj.adaption_technique == adaption_method.rls
adaption_code = 3;
else
builtin("error","FFE_A2TrackedLevels:InvalidAdaptionTechnique", ...
"Unsupported FFE adaption technique.");
end
end
end
methods (Static, Access = private)
function [buffer,buffer_pos_by_level,level_sum_by_level,buffer_valid_count_by_level, ...
offset_by_level,valid_count_by_level] = updateTrackedLevelBuffer( ...
buffer,buffer_pos_by_level,level_sum_by_level,buffer_valid_count_by_level, ...
offset_by_level,valid_count_by_level,new_value,symbol_idx,symbol, ...
update_blocklength,constellation)
buffer_pos = buffer_pos_by_level(symbol_idx) + 1;
if buffer_pos > size(buffer,2)
buffer_pos = 1;
end
buffer_pos_by_level(symbol_idx) = buffer_pos;
old_value = buffer(symbol_idx,buffer_pos);
if isfinite(old_value)
level_sum_by_level(symbol_idx) = level_sum_by_level(symbol_idx) - old_value;
buffer_valid_count_by_level(symbol_idx) = buffer_valid_count_by_level(symbol_idx) - 1;
end
if isfinite(new_value)
buffer(symbol_idx,buffer_pos) = new_value;
level_sum_by_level(symbol_idx) = level_sum_by_level(symbol_idx) + new_value;
buffer_valid_count_by_level(symbol_idx) = buffer_valid_count_by_level(symbol_idx) + 1;
else
buffer(symbol_idx,buffer_pos) = NaN;
end
if mod(symbol,update_blocklength) == 0
valid_count_by_level = buffer_valid_count_by_level;
has_valid = valid_count_by_level > 0;
offset_by_level(:) = 0;
offset_by_level(has_valid) = ...
level_sum_by_level(has_valid) ./ valid_count_by_level(has_valid) - ...
constellation(has_valid);
end
end
end
end

View File

@@ -1,752 +0,0 @@
classdef FFE_DCTracking < FFE_plain
%FFE_DCTracking Plain FFE with the DC-tracking offset loop.
%
% This class reuses the plain FFE process idea and BER mu optimization,
% but keeps only the DC-tracking path from the larger FFE class. A1, A2,
% and delayed FFE tap-update buffers are intentionally excluded.
properties
dc_tracking_mu
dc_tracking_adaptive_enabled
dc_tracking_persistence_gain
dc_tracking_power_exponent
dc_tracking_buffer_len
dc_tracking_mu_eff_max
e_dc
optimize_dc_tracking_params = 0
dc_tracking_optimization
dc_tracking_optimization_iter = 0
dc_tracking_optimization_len
dc_tracking_optimization_max_evals
dc_tracking_optimization_delay_weight
dc_tracking_optimization_smoothing_len
end
properties (Access = private)
dc_tracking_mu_min = 1e-6
dc_tracking_mu_max = 3e-1
dc_tracking_mu_eff_min = 0
end
methods
function obj = FFE_DCTracking(options)
arguments(Input)
options.sps = 2
options.order = 15
options.len_tr = 4096
options.mu_tr = 0
options.epochs_tr = 5
options.adaption_technique adaption_method = adaption_method.lms
options.dd_mode = 1
options.mu_dd = 1e-5
options.epochs_dd = 5
options.dd_len_fraction = 1
options.dc_tracking_mu = 0
options.dc_tracking_adaptive_enabled = false
options.dc_tracking_persistence_gain = 0
options.dc_tracking_power_exponent = 2
options.dc_tracking_buffer_len = 1
options.dc_tracking_mu_eff_max = inf
options.decide = false
options.save_debug = 0
options.optmize_mus = 0
options.mu_optimization_len = 2^15
options.plot_mu_optimization = 0
options.mu_optimization_fignum = 3010
options.optimize_dc_tracking_params = 0
options.dc_tracking_optimization_len = 2^15
options.dc_tracking_optimization_max_evals = 20
options.dc_tracking_optimization_delay_weight = 1e-3
options.dc_tracking_optimization_smoothing_len = 501
end
obj@FFE_plain( ...
"sps",options.sps, ...
"order",options.order, ...
"len_tr",options.len_tr, ...
"mu_tr",options.mu_tr, ...
"epochs_tr",options.epochs_tr, ...
"adaption_technique",options.adaption_technique, ...
"dd_mode",options.dd_mode, ...
"mu_dd",options.mu_dd, ...
"epochs_dd",options.epochs_dd, ...
"dd_len_fraction",options.dd_len_fraction, ...
"decide",options.decide, ...
"save_debug",options.save_debug, ...
"optmize_mus",options.optmize_mus, ...
"mu_optimization_len",options.mu_optimization_len, ...
"plot_mu_optimization",options.plot_mu_optimization, ...
"mu_optimization_fignum",options.mu_optimization_fignum);
obj.dc_tracking_mu = options.dc_tracking_mu;
obj.dc_tracking_adaptive_enabled = options.dc_tracking_adaptive_enabled;
obj.dc_tracking_persistence_gain = options.dc_tracking_persistence_gain;
obj.dc_tracking_power_exponent = options.dc_tracking_power_exponent;
obj.dc_tracking_buffer_len = floor(options.dc_tracking_buffer_len);
obj.dc_tracking_mu_eff_max = options.dc_tracking_mu_eff_max;
obj.e_dc = 0;
obj.optimize_dc_tracking_params = options.optimize_dc_tracking_params;
obj.dc_tracking_optimization_len = options.dc_tracking_optimization_len;
obj.dc_tracking_optimization_max_evals = max(1, ...
floor(options.dc_tracking_optimization_max_evals));
obj.dc_tracking_optimization_delay_weight = ...
max(0,options.dc_tracking_optimization_delay_weight);
obj.dc_tracking_optimization_smoothing_len = max(1, ...
floor(options.dc_tracking_optimization_smoothing_len));
assert(obj.dc_tracking_buffer_len >= 0);
end
function [X,Noi] = process(obj,X,D)
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
obj.resetTrackingState();
if obj.optmize_mus
obj.optimizeMus(X.signal,D.signal);
obj.resetTrackingState();
end
if obj.optimize_dc_tracking_params
obj.optimizeDcTrackingParams(X.signal,D.signal);
obj.resetTrackingState();
end
training = true;
showviz = false;
obj.equalize(X.signal,D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
obj.e_tr = obj.e;
n = X.length;
training = false;
if obj.dd_mode
[signal,decision] = obj.equalize(X.signal,D.signal,obj.mu_dd,obj.epochs_dd,n,training,showviz);
else
[signal,decision] = obj.equalize(X.signal,D.signal,0,1,n,training,showviz);
end
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs;
X = X.logbookentry([num2str(obj.order),' tap FFE DC tracking']);
Noi = X - D;
end
function optimizeMus(obj,x,d)
[x_opt,d_opt,N_opt] = obj.optimizationSignals(x,d);
switch obj.adaption_technique
case adaption_method.lms
mu_range = [1e-5, 1e-2];
case adaption_method.nlms
mu_range = [1e-3, 5e-1];
case adaption_method.rls
mu_range = [0.98, 0.99999];
otherwise
builtin("error","FFE_DCTracking:InvalidAdaptionTechnique", ...
"Unsupported FFE adaption technique.");
end
vars = optimizableVariable("mu_tr",mu_range,"Transform","log");
if obj.dd_mode
vars = [vars, optimizableVariable("mu_dd",mu_range,"Transform","log")];
end
optimize_dc_tracking_mu = obj.dc_tracking_mu ~= 0 && ...
~obj.optimize_dc_tracking_params;
if optimize_dc_tracking_mu
vars = [vars, optimizableVariable("dc_tracking_mu",[1e-5,1e-1],"Transform","log")];
end
obj.mu_optimization_iter = 0;
fprintf("FFE_DCTracking mu opt uses %d samples / %d symbols\n",N_opt,numel(d_opt));
obj.mu_optimization = bayesopt(@(p)obj.muObjective(p,x_opt,d_opt),vars, ...
"MaxObjectiveEvaluations",20, ...
"AcquisitionFunctionName","expected-improvement-plus", ...
"IsObjectiveDeterministic",false, ...
"Verbose",0, ...
"PlotFcn",[]);
obj.mu_tr = obj.mu_optimization.XAtMinObjective.mu_tr;
if obj.dd_mode
obj.mu_dd = obj.mu_optimization.XAtMinObjective.mu_dd;
end
if optimize_dc_tracking_mu
obj.dc_tracking_mu = obj.mu_optimization.XAtMinObjective.dc_tracking_mu;
end
if obj.dd_mode && optimize_dc_tracking_mu
fprintf("\nFFE_DCTracking mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, dc_tracking_mu=%9.3e, BER=%9.3e\n", ...
obj.mu_tr,obj.mu_dd,obj.dc_tracking_mu,obj.mu_optimization.MinObjective);
elseif obj.dd_mode
fprintf("\nFFE_DCTracking mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, BER=%9.3e\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_optimization.MinObjective);
elseif optimize_dc_tracking_mu
fprintf("\nFFE_DCTracking mu opt done: mu_tr=%9.3e, dc_tracking_mu=%9.3e, BER=%9.3e\n", ...
obj.mu_tr,obj.dc_tracking_mu,obj.mu_optimization.MinObjective);
else
fprintf("\nFFE_DCTracking mu opt done: mu_tr=%9.3e, BER=%9.3e\n", ...
obj.mu_tr,obj.mu_optimization.MinObjective);
end
if obj.plot_mu_optimization
obj.plotMuOptimization();
end
end
function optimizeDcTrackingParams(obj,x,d)
[x_opt,d_opt,N_opt] = obj.optimizationSignals( ...
x,d,obj.dc_tracking_optimization_len);
vars = optimizableVariable( ...
"dc_tracking_mu",[1e-5,1e-1],"Transform","log");
initial_values = min(max(obj.dc_tracking_mu,1e-5),1e-1);
initial_names = "dc_tracking_mu";
if obj.dc_tracking_adaptive_enabled
vars = [vars, ...
optimizableVariable("dc_tracking_persistence_gain",[0,2]), ...
optimizableVariable("dc_tracking_mu_eff_max",[1e-3,3e-1], ...
"Transform","log")];
initial_values = [initial_values, ...
min(max(obj.dc_tracking_persistence_gain,0),2), ...
min(max(obj.dc_tracking_mu_eff_max,1e-3),3e-1)];
initial_names = [initial_names, ...
"dc_tracking_persistence_gain", "dc_tracking_mu_eff_max"];
end
initial_x = array2table(initial_values, ...
"VariableNames",cellstr(initial_names));
obj.dc_tracking_optimization_iter = 0;
fprintf("FFE_DCTracking DC opt uses fixed mu_tr=%9.3e, mu_dd=%9.3e on %d samples / %d symbols\n", ...
obj.mu_tr,obj.mu_dd,N_opt,numel(d_opt));
old_rng = rng;
cleanup_rng = onCleanup(@()rng(old_rng));
rng(42,"twister");
obj.dc_tracking_optimization = bayesopt( ...
@(p)obj.dcTrackingObjective(p,x_opt,d_opt),vars, ...
"MaxObjectiveEvaluations",obj.dc_tracking_optimization_max_evals, ...
"InitialX",initial_x, ...
"AcquisitionFunctionName","expected-improvement-plus", ...
"IsObjectiveDeterministic",true, ...
"Verbose",0, ...
"PlotFcn",[]);
clear cleanup_rng
best = obj.dc_tracking_optimization.XAtMinObjective;
obj.dc_tracking_mu = best.dc_tracking_mu;
if obj.dc_tracking_adaptive_enabled
obj.dc_tracking_persistence_gain = ...
best.dc_tracking_persistence_gain;
obj.dc_tracking_mu_eff_max = best.dc_tracking_mu_eff_max;
fprintf("\nFFE_DCTracking DC opt done: dc_tracking_mu=%9.3e, persistence_gain=%6.3f, mu_eff_max=%9.3e, objective=%9.3e\n", ...
obj.dc_tracking_mu,obj.dc_tracking_persistence_gain, ...
obj.dc_tracking_mu_eff_max, ...
obj.dc_tracking_optimization.MinObjective);
else
fprintf("\nFFE_DCTracking DC opt done: dc_tracking_mu=%9.3e, objective=%9.3e\n", ...
obj.dc_tracking_mu,obj.dc_tracking_optimization.MinObjective);
end
end
function objective = muObjective(obj,params,x,d)
old_state = obj.captureTrackingObjectiveState();
cleanup = onCleanup(@()obj.restoreTrackingObjectiveState(old_state));
obj.save_debug = 0;
if any(strcmp(params.Properties.VariableNames,"dc_tracking_mu"))
obj.dc_tracking_mu = params.dc_tracking_mu;
end
obj.resetTrackingState();
N_tr = min(obj.len_tr,numel(x));
obj.equalize(x,d,params.mu_tr,obj.epochs_tr,N_tr,true,false);
if obj.dd_mode
[signal,~] = obj.equalize(x,d,params.mu_dd,obj.epochs_dd,numel(x),false,false);
else
[signal,~] = obj.equalize(x,d,0,1,numel(x),false,false);
end
[ber,errors] = obj.berObjective(signal,d);
objective = ber;
if ~isfinite(objective)
objective = inf;
end
obj.mu_optimization_iter = obj.mu_optimization_iter + 1;
has_dc_tracking_mu = any(strcmp(params.Properties.VariableNames,"dc_tracking_mu"));
if obj.dd_mode && has_dc_tracking_mu
fprintf("\rFFE_DCTracking mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, dc_tracking_mu=%9.3e, BER=%9.3e, errors=%d", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,params.dc_tracking_mu,ber,errors);
elseif obj.dd_mode
fprintf("\rFFE_DCTracking mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, BER=%9.3e, errors=%d", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,ber,errors);
elseif has_dc_tracking_mu
fprintf("\rFFE_DCTracking mu opt %02d: mu_tr=%9.3e, dc_tracking_mu=%9.3e, BER=%9.3e, errors=%d", ...
obj.mu_optimization_iter,params.mu_tr,params.dc_tracking_mu,ber,errors);
else
fprintf("\rFFE_DCTracking mu opt %02d: mu_tr=%9.3e, BER=%9.3e, errors=%d", ...
obj.mu_optimization_iter,params.mu_tr,ber,errors);
end
clear cleanup
end
function objective = dcTrackingObjective(obj,params,x,d)
old_state = obj.captureTrackingObjectiveState();
cleanup = onCleanup(@()obj.restoreTrackingObjectiveState(old_state));
obj.applyDcTrackingObjectiveParams(params);
obj.save_debug = 1;
obj.resetTrackingState();
N_tr = min(obj.len_tr,numel(x));
obj.equalize(x,d,obj.mu_tr,obj.epochs_tr,N_tr,true,false);
if obj.dd_mode
[signal,~] = obj.equalize( ...
x,d,obj.mu_dd,obj.epochs_dd,numel(x),false,false);
else
[signal,~] = obj.equalize(x,d,0,1,numel(x),false,false);
end
[ber,errors] = obj.berObjective(signal,d);
[delay_symbols,delay_corr] = obj.dcTrackingDelayObjective(signal,d);
delay_penalty = obj.dc_tracking_optimization_delay_weight * ...
abs(delay_symbols) / max(numel(d),1);
objective = ber + delay_penalty;
if ~isfinite(objective)
objective = inf;
end
obj.dc_tracking_optimization_iter = ...
obj.dc_tracking_optimization_iter + 1;
if obj.dc_tracking_adaptive_enabled
fprintf("\rFFE_DCTracking DC opt %02d: dc_tracking_mu=%9.3e, persistence_gain=%6.3f, mu_eff_max=%9.3e, BER=%9.3e, delay=%7.0f, corr=%6.3f, obj=%9.3e, errors=%d", ...
obj.dc_tracking_optimization_iter,params.dc_tracking_mu, ...
params.dc_tracking_persistence_gain, ...
params.dc_tracking_mu_eff_max,ber,delay_symbols,delay_corr, ...
objective,errors);
else
fprintf("\rFFE_DCTracking DC opt %02d: dc_tracking_mu=%9.3e, BER=%9.3e, delay=%7.0f, corr=%6.3f, obj=%9.3e, errors=%d", ...
obj.dc_tracking_optimization_iter,params.dc_tracking_mu, ...
ber,delay_symbols,delay_corr,objective,errors);
end
clear cleanup
end
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
arguments
obj
x
d
mu
epochs
N
training
showviz
end
unused_showviz = showviz; %#ok<NASGU>
x = x(:);
d = d(:);
N = obj.validSampleLength(N,x,d);
n_symbols = N / obj.sps;
y = zeros(n_symbols,1);
d_hat = zeros(n_symbols,1);
if n_symbols == 0
return
end
if isempty(obj.constellation)
obj.constellation = unique(d);
end
decision_constellation = obj.constellation(:);
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
lambda = mu;
mask = ones(obj.order,1);
maincursor_pos = ceil(length(obj.e)/2);
adaption_code = obj.adaptionCode();
adaption_is_rls = adaption_code == 3;
if mu == 0 || (~obj.dd_mode && ~training)
epochs = 1;
end
dc_tracking_mu_eff_min = obj.dc_tracking_mu_eff_min;
dc_tracking_mu_eff_max = obj.dc_tracking_mu_eff_max;
dc_tracking_persistence_gain = 0;
if obj.dc_tracking_adaptive_enabled
dc_tracking_persistence_gain = obj.dc_tracking_persistence_gain;
end
dc_tracking_enabled = obj.dc_tracking_mu ~= 0;
if dc_tracking_enabled
obj.dc_tracking_mu = min(max(obj.dc_tracking_mu, ...
obj.dc_tracking_mu_min),obj.dc_tracking_mu_max);
end
dc_tracking_mu = obj.dc_tracking_mu;
dc_tracking_use_persistence = dc_tracking_persistence_gain > 0;
dc_tracking_base_mu_eff = min(max(dc_tracking_mu, ...
dc_tracking_mu_eff_min),dc_tracking_mu_eff_max);
dc_buffer_enabled = dc_tracking_enabled && obj.dc_tracking_buffer_len > 1;
if dc_buffer_enabled
dc_tracking_err_buffer = NaN(obj.dc_tracking_buffer_len,1);
dc_tracking_err_buffer_pos = 0;
dc_tracking_err_sum = 0;
dc_tracking_abs_err_sum = 0;
dc_tracking_valid_count = 0;
else
dc_tracking_err_buffer = [];
dc_tracking_err_buffer_pos = 0;
dc_tracking_err_sum = 0;
dc_tracking_abs_err_sum = 0;
dc_tracking_valid_count = 0;
end
debug_enabled = obj.save_debug;
if debug_enabled
obj.initializeTrackingDebug(n_symbols,training);
end
for epoch = 1:epochs
symbol = 0;
for sample = 1:obj.sps:N
symbol = symbol + 1;
grad = zeros(obj.order,1);
update = zeros(obj.order,1);
weight = 0;
dc_tracking_mu_eff = 0;
U = x(obj.order+sample-1:-1:sample);
y(symbol,1) = obj.e_dc + (obj.e.*mask).' * U;
if training
d_hat(symbol,1) = d(symbol);
else
[~,symbol_idx] = min(abs(y(symbol) - decision_constellation));
d_hat(symbol,1) = decision_constellation(symbol_idx);
end
err = d_hat(symbol) - y(symbol);
switch adaption_code
case 1
weight = mu / ((U.'*U) + eps);
grad = err * U;
update = grad * weight;
case 2
weight = mu;
grad = err * U;
update = grad * weight;
case 3
denom = lambda + U.' * obj.P * U;
k = (obj.P * U) / denom;
update = k * err;
end
obj.e = obj.e + update;
if adaption_is_rls
obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P));
end
if dc_tracking_enabled
[obj.e_dc,dc_tracking_mu_eff,dc_tracking_err_buffer, ...
dc_tracking_err_buffer_pos,dc_tracking_err_sum, ...
dc_tracking_abs_err_sum,dc_tracking_valid_count] = ...
obj.updateDcTracking( ...
err,dc_tracking_mu,dc_tracking_base_mu_eff, ...
dc_tracking_persistence_gain,dc_tracking_use_persistence, ...
dc_buffer_enabled,dc_tracking_mu_eff_min,dc_tracking_mu_eff_max, ...
symbol,dc_tracking_err_buffer,dc_tracking_err_buffer_pos, ...
dc_tracking_err_sum,dc_tracking_abs_err_sum, ...
dc_tracking_valid_count);
end
if debug_enabled && epoch == epochs
error_power = err * err';
update_power = update.'*update ./ (sqrt((obj.e.'*obj.e) / obj.order) + eps);
obj.debug_struct.error(1,symbol) = error_power;
obj.debug_struct.main_cursor(1,symbol) = abs(obj.e(maincursor_pos));
obj.debug_struct.mu_nlms(1,symbol) = weight;
obj.debug_struct.update_gradient(1,symbol) = grad.'*grad;
obj.debug_struct.dc_tracking_mu_eff(1,symbol) = dc_tracking_mu_eff;
obj.debug_struct.dc_tracking_est(1,symbol) = obj.e_dc;
if training
obj.debug_struct.error_tr(1,symbol) = error_power;
obj.debug_struct.update_tr(1,symbol) = update_power;
else
obj.debug_struct.error_dd(1,symbol) = error_power;
obj.debug_struct.update(1,symbol) = update_power;
end
end
end
end
end
end
methods (Access = private)
function resetTrackingState(obj)
obj.e = zeros(obj.order,1);
obj.e_tr = zeros(obj.order,1);
obj.error = 0;
obj.e_dc = 0;
obj.P = (1/0.05) * eye(obj.order);
obj.debug_struct = struct();
end
function state = captureTrackingObjectiveState(obj)
state.e = obj.e;
state.e_tr = obj.e_tr;
state.error = obj.error;
state.e_dc = obj.e_dc;
state.P = obj.P;
state.save_debug = obj.save_debug;
state.debug_struct = obj.debug_struct;
state.dc_tracking_mu = obj.dc_tracking_mu;
state.dc_tracking_persistence_gain = ...
obj.dc_tracking_persistence_gain;
state.dc_tracking_mu_eff_max = obj.dc_tracking_mu_eff_max;
end
function restoreTrackingObjectiveState(obj,state)
obj.e = state.e;
obj.e_tr = state.e_tr;
obj.error = state.error;
obj.e_dc = state.e_dc;
obj.P = state.P;
obj.save_debug = state.save_debug;
obj.debug_struct = state.debug_struct;
obj.dc_tracking_mu = state.dc_tracking_mu;
obj.dc_tracking_persistence_gain = ...
state.dc_tracking_persistence_gain;
obj.dc_tracking_mu_eff_max = state.dc_tracking_mu_eff_max;
end
function applyDcTrackingObjectiveParams(obj,params)
var_names = string(params.Properties.VariableNames);
if any(var_names == "dc_tracking_mu")
obj.dc_tracking_mu = params.dc_tracking_mu;
end
if any(var_names == "dc_tracking_persistence_gain")
obj.dc_tracking_persistence_gain = ...
params.dc_tracking_persistence_gain;
end
if any(var_names == "dc_tracking_mu_eff_max")
obj.dc_tracking_mu_eff_max = params.dc_tracking_mu_eff_max;
end
end
function [delay_symbols,delay_corr] = ...
dcTrackingDelayObjective(obj,signal,d)
delay_symbols = 0;
delay_corr = 0;
if ~isfield(obj.debug_struct,"dc_tracking_est") || ...
isempty(obj.debug_struct.dc_tracking_est)
return
end
smooth_len = obj.dc_tracking_optimization_smoothing_len;
dc_tracking_est_s = movmean( ...
obj.debug_struct.dc_tracking_est(:),smooth_len,"omitnan");
avg_lvl_dc = obj.averageLevelTrace(signal,d,smooth_len);
xcorr_len = min(numel(dc_tracking_est_s),numel(avg_lvl_dc));
if xcorr_len < 2
return
end
inv_dc_xcorr = -dc_tracking_est_s(1:xcorr_len);
avg_lvl_xcorr = avg_lvl_dc(1:xcorr_len);
inv_dc_xcorr = fillmissing( ...
inv_dc_xcorr,"linear","EndValues","nearest");
avg_lvl_xcorr = fillmissing( ...
avg_lvl_xcorr,"linear","EndValues","nearest");
inv_dc_xcorr = inv_dc_xcorr - mean(inv_dc_xcorr,"omitnan");
avg_lvl_xcorr = avg_lvl_xcorr - mean(avg_lvl_xcorr,"omitnan");
if rms(inv_dc_xcorr) <= eps || rms(avg_lvl_xcorr) <= eps
return
end
[dc_level_xcorr,dc_level_lags] = xcorr( ...
inv_dc_xcorr,avg_lvl_xcorr,"coeff");
[delay_corr,delay_idx] = max(dc_level_xcorr);
delay_symbols = dc_level_lags(delay_idx);
if ~isfinite(delay_corr)
delay_corr = 0;
delay_symbols = 0;
end
end
function avg_lvl_dc = averageLevelTrace(obj,signal,d,smooth_len)
signal = signal(:);
d = d(:);
n_symbols = min(numel(signal),numel(d));
signal = signal(1:n_symbols);
d = d(1:n_symbols);
levels = unique(d);
avg_for_lvl = NaN(numel(levels),n_symbols);
for level_idx = 1:numel(levels)
level_mask = d == levels(level_idx);
level_samples = signal(level_mask);
if isempty(level_samples)
continue
end
smooth_window = min(smooth_len,numel(level_samples));
avg_for_lvl(level_idx,level_mask) = movmean( ...
level_samples,smooth_window,"omitnan","Endpoints","shrink");
avg_for_lvl(level_idx,:) = obj.interpolateMissingAverage( ...
avg_for_lvl(level_idx,:));
end
avg_lvl_dc = mean(avg_for_lvl,1,"omitnan").';
end
function level_average = interpolateMissingAverage(~,level_average)
valid_samples = isfinite(level_average);
if nnz(valid_samples) == 0
return
elseif nnz(valid_samples) == 1
level_average(:) = level_average(valid_samples);
return
end
t = 1:numel(level_average);
level_average(~valid_samples) = interp1( ...
t(valid_samples),level_average(valid_samples), ...
t(~valid_samples),"linear","extrap");
end
function N = validSampleLength(obj,N,x,d)
N_available = min(numel(x),numel(d) * obj.sps);
N = min(N,N_available);
N = obj.sps * floor(N / obj.sps);
N = max(0,N);
end
function adaption_code = adaptionCode(obj)
if obj.adaption_technique == adaption_method.nlms
adaption_code = 1;
elseif obj.adaption_technique == adaption_method.lms
adaption_code = 2;
elseif obj.adaption_technique == adaption_method.rls
adaption_code = 3;
else
builtin("error","FFE_DCTracking:InvalidAdaptionTechnique", ...
"Unsupported FFE adaption technique.");
end
end
function initializeTrackingDebug(obj,n_symbols,training)
obj.debug_struct.error = NaN(1,n_symbols);
obj.debug_struct.error_first_epoch = NaN(1,n_symbols);
obj.debug_struct.main_cursor = NaN(1,n_symbols);
obj.debug_struct.mu_nlms = NaN(1,n_symbols);
obj.debug_struct.update_gradient = NaN(1,n_symbols);
obj.debug_struct.dc_tracking_mu_eff = NaN(1,n_symbols);
obj.debug_struct.dc_tracking_est = NaN(1,n_symbols);
if training
obj.debug_struct.error_tr = NaN(1,n_symbols);
obj.debug_struct.update_tr = NaN(1,n_symbols);
else
obj.debug_struct.error_dd = NaN(1,n_symbols);
obj.debug_struct.update = NaN(1,n_symbols);
end
end
function [e_dc,mu_eff,err_buffer,err_buffer_pos,err_sum,abs_err_sum,valid_count] = ...
updateDcTracking(obj,err_current,mu_dc,base_mu_eff,persistence_gain, ...
use_persistence,buffer_enabled,mu_eff_min,mu_eff_max,symbol, ...
err_buffer,err_buffer_pos,err_sum,abs_err_sum,valid_count)
mu_eff = 0;
e_dc = obj.e_dc;
if buffer_enabled
err_buffer_pos = err_buffer_pos + 1;
if err_buffer_pos > obj.dc_tracking_buffer_len
err_buffer_pos = 1;
end
old_err = err_buffer(err_buffer_pos);
if isfinite(old_err)
err_sum = err_sum - old_err;
valid_count = valid_count - 1;
if use_persistence
abs_err_sum = abs_err_sum - abs(old_err);
end
end
if isfinite(err_current)
err_buffer(err_buffer_pos) = err_current;
err_sum = err_sum + err_current;
valid_count = valid_count + 1;
if use_persistence
abs_err_sum = abs_err_sum + abs(err_current);
end
else
err_buffer(err_buffer_pos) = NaN;
end
if mod(symbol,obj.dc_tracking_buffer_len) == 0
if valid_count == 0
err_mean = 0;
err_abs_mean = 0;
else
err_mean = err_sum / valid_count;
err_abs_mean = abs_err_sum / valid_count;
end
mu_eff = obj.effectiveDcMu(mu_dc,base_mu_eff,persistence_gain, ...
use_persistence,err_mean,err_abs_mean,mu_eff_min,mu_eff_max);
e_dc = e_dc + mu_eff * err_mean;
end
else
if isfinite(err_current)
err_mean = err_current;
err_abs_mean = abs(err_current);
else
err_mean = 0;
err_abs_mean = 0;
end
mu_eff = obj.effectiveDcMu(mu_dc,base_mu_eff,persistence_gain, ...
use_persistence,err_mean,err_abs_mean,mu_eff_min,mu_eff_max);
e_dc = e_dc + mu_eff * err_mean;
end
end
function mu_eff = effectiveDcMu(~,mu_dc,base_mu_eff,persistence_gain, ...
use_persistence,err_mean,err_abs_mean,mu_eff_min,mu_eff_max)
if use_persistence
persistence_scale = abs(err_mean) / (err_abs_mean + eps);
persistence_scale = min(max(persistence_scale,0),1);
mu_eff = mu_dc * (1 + persistence_gain * persistence_scale);
mu_eff = min(max(mu_eff,mu_eff_min),mu_eff_max);
else
mu_eff = base_mu_eff;
end
end
end
end

View File

@@ -1,411 +0,0 @@
classdef FFE_DCremoval_adaptive_mu < handle
% FFE variant for MPI/DC-removal experiments.
% With dc_buffer_len <= 1, ffe_buffer_len <= 1 and no smoothing, this
% follows FFE.m semantics so MPI-reduction changes can be isolated.
properties
sps
order
e
e_tr
error
len_tr
mu_tr
epochs_tr
adaption_technique
dd_mode
mu_dd
epochs_dd
dd_len_fraction
mu_dc
e_dc
P
dc_buffer_len
adaptive_mu_mode
ffe_buffer_len
smoothing_buffer_length
smoothing_buffer_update
constellation
decide
save_debug = 0;
debug_struct
optmize_mus = 0;
mu_optimization
mu_optimization_iter = 0;
end
methods
function obj = FFE_DCremoval_adaptive_mu(options)
arguments(Input)
options.sps = 2;
options.order = 15;
options.len_tr = 4096;
options.mu_tr = 0;
options.epochs_tr = 5;
options.adaption_technique adaption_method = adaption_method.lms;
options.dd_mode = 1;
options.mu_dd = 1e-5;
options.epochs_dd = 5;
options.dd_len_fraction = 0.25;
options.mu_dc = 0.05;
options.dc_buffer_len = 1;
options.adaptive_mu_mode = 1;
options.ffe_buffer_len = 1;
options.smoothing_buffer_length = 0;
options.smoothing_buffer_update = 0;
options.decide = false;
options.save_debug = 0;
options.optmize_mus = 0;
end
assert(options.dc_buffer_len >= 0);
assert(options.ffe_buffer_len >= 0);
assert(options.smoothing_buffer_length >= 0);
if options.smoothing_buffer_length > 0
assert(options.smoothing_buffer_update > 0);
end
fn = fieldnames(options);
for n = 1:numel(fn)
obj.(fn{n}) = options.(fn{n});
end
obj.e = zeros(obj.order,1);
obj.e_dc = 0;
obj.error = 0;
obj.dc_buffer_len = floor(obj.dc_buffer_len);
obj.ffe_buffer_len = floor(obj.ffe_buffer_len);
obj.smoothing_buffer_length = floor(obj.smoothing_buffer_length);
obj.smoothing_buffer_update = floor(obj.smoothing_buffer_update);
end
function [X,Noi] = process(obj, X, D)
X = X.normalize("mode","rms");
obj.constellation = unique(D.signal);
obj.e_dc = 0;
delta = 0.05;
obj.P = (1/delta) * eye(obj.order);
if obj.optmize_mus
obj.optimizeMus(X.signal,D.signal);
obj.e = zeros(obj.order,1);
obj.e_dc = 0;
obj.P = (1/delta) * eye(obj.order);
end
training = 1;
showviz = 0;
obj.equalize(X.signal,D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
obj.e_tr = obj.e;
n = X.length;
training = 0;
if obj.dd_mode
n_dd = obj.ddLength(n);
obj.equalize(X.signal,D.signal,obj.mu_dd,obj.epochs_dd,n_dd,training,showviz);
end
[signal,decision] = obj.applyCurrentTaps(X.signal,n);
if obj.decide
X.signal = decision;
else
X.signal = signal;
end
X.fs = D.fs;
lbdesc = [num2str(obj.order),' tap FFE'];
X = X.logbookentry(lbdesc);
Noi = X;
Noi = X - D;
end
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
arguments
obj
x
d
mu
epochs
N
training
showviz = 0 %#ok<INUSD>
end
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
lambda = mu;
if training
mask = ones(obj.order,1);
else
mask = zeros(obj.order,1);
mask(900:end) = 1;
mask(ceil(length(obj.e)/2)) = 1;
end
mask = ones(obj.order,1);
always_ideal_decision = 0;
grad = 0;
weight = 0;
update = 0;
if mu == 0 || (~obj.dd_mode && ~training)
epochs = 1;
end
dc_buffer_enabled = obj.mu_dc ~= 0 && obj.dc_buffer_len > 1;
adaptive_dc_enabled = dc_buffer_enabled && obj.adaptive_mu_mode;
if dc_buffer_enabled
e_dc_buffer = NaN(obj.dc_buffer_len,1);
end
ffe_buffer_enabled = ~training && obj.ffe_buffer_len > 1 && ...
obj.adaption_technique ~= adaption_method.rls;
if ffe_buffer_enabled
grad_buffer = NaN(obj.order,obj.ffe_buffer_len);
end
if obj.smoothing_buffer_length > 0
smoothing_buffer = ones(1,obj.smoothing_buffer_length) .* mean(obj.constellation);
smoothing_mean = mean(obj.constellation);
end
P_err = 0;
alpha = 0.98;
err_prev = 0;
gamma_dc = 1e-6;
mu_min = 1e-6;
mu_max = 3e-1;
for epoch = 1 : epochs
symbol = 0;
for sample = 1 : obj.sps : N
symbol = symbol + 1;
if obj.smoothing_buffer_length > 0
smoothing_buffer = circshift(smoothing_buffer,1,2);
smoothing_buffer(1) = x(sample);
if mod(symbol,obj.smoothing_buffer_update) == 0
smoothing_mean = mean(smoothing_buffer);
end
x(sample:sample+obj.sps-1) = x(sample:sample+obj.sps-1) - smoothing_mean;
end
U = x(obj.order+sample-1:-1:sample);
y(symbol,1) = obj.e_dc + (obj.e.*mask).' * U;
if training
d_hat(symbol,1) = d(symbol);
else
if ~always_ideal_decision
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation));
d_hat(symbol,1) = obj.constellation(symbol_idx);
else
d_hat(symbol,1) = d(symbol);
end
end
err(symbol) = d_hat(symbol) - y(symbol); %#ok<AGROW>
true_err(symbol) = y(symbol) - d(symbol); %#ok<AGROW,NASGU>
if training || obj.dd_mode
switch obj.adaption_technique
case adaption_method.lms
weight = mu;
grad = err(symbol) * U;
update = grad * weight;
case adaption_method.nlms
normU = (U.'*U) + eps;
weight = mu / normU;
grad = err(symbol) * U;
update = grad * weight;
case adaption_method.rls
denom = lambda + U.' * obj.P * U;
k = (obj.P * U) / denom;
update = k * err(symbol);
end
if ffe_buffer_enabled
grad_buffer = circshift(grad_buffer,1,2);
grad_buffer(:,1) = update;
if mod(symbol,obj.ffe_buffer_len) == 0
obj.e = obj.e + mean(grad_buffer,2,"omitnan");
end
else
obj.e = obj.e + update;
end
if obj.adaption_technique == adaption_method.rls
obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P));
end
if obj.mu_dc ~= 0
if adaptive_dc_enabled
delta_mu = gamma_dc * err(symbol) * err_prev * (U.'*U);
obj.mu_dc = min(max(obj.mu_dc + delta_mu,mu_min),mu_max);
err_prev = err(symbol);
P_err = alpha*P_err + (1-alpha)*err(symbol)^2;
mu_dc_eff = obj.mu_dc / (P_err + eps);
else
mu_dc_eff = obj.mu_dc;
end
if dc_buffer_enabled
e_dc_buffer = circshift(e_dc_buffer,1);
e_dc_buffer(1) = obj.e_dc + mu_dc_eff * err(symbol);
if mod(symbol,obj.dc_buffer_len) == 0
obj.e_dc = median(e_dc_buffer,"omitnan");
end
else
obj.e_dc = obj.e_dc + mu_dc_eff * err(symbol);
end
end
end
if obj.save_debug
obj.debug_struct.error(epoch,symbol) = err(symbol) * err(symbol)';
if training
obj.debug_struct.error_tr(epoch,symbol) = err(symbol) * err(symbol)';
obj.debug_struct.update_tr(epoch,symbol) = update.'*update ./ rms(obj.e);
end
end
% obj.error(epoch,symbol) = err(symbol) * err(symbol)';
end
end
end
function [y,d_hat] = applyCurrentTaps(obj,x,N)
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
for sample = 1 : obj.sps : N
symbol = (sample - 1) / obj.sps + 1;
U = x(obj.order+sample-1:-1:sample);
y(symbol,1) = obj.e_dc + obj.e.' * U;
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation));
d_hat(symbol,1) = obj.constellation(symbol_idx);
end
end
function N_dd = ddLength(obj,N)
if isempty(obj.dd_len_fraction) || obj.dd_len_fraction <= 0 || obj.dd_len_fraction >= 1
N_dd = N;
return
end
N_dd = floor(N * obj.dd_len_fraction);
N_dd = max(obj.sps,N_dd);
N_dd = min(N,N_dd);
end
function optimizeMus(obj,x,d)
switch obj.adaption_technique
case adaption_method.lms
mu_range = [1e-5, 1e-2];
case adaption_method.nlms
mu_range = [1e-3, 5e-1];
case adaption_method.rls
mu_range = [0.98, 0.99999];
end
mu_dc_range = [1e-5, 1e-1];
mu_tr_var = optimizableVariable("mu_tr",mu_range,"Transform","log");
vars = mu_tr_var;
if obj.dd_mode
vars = [vars, optimizableVariable("mu_dd",mu_range,"Transform","log")];
end
optimize_mu_dc = obj.mu_dc ~= 0;
if optimize_mu_dc
vars = [vars, optimizableVariable("mu_dc",mu_dc_range,"Transform","log")];
end
obj.mu_optimization_iter = 0;
obj.mu_optimization = bayesopt(@(p)obj.muObjective(p,x,d),vars, ...
"MaxObjectiveEvaluations",10, ...
"AcquisitionFunctionName","expected-improvement-plus", ...
"IsObjectiveDeterministic",false, ...
"Verbose",0, ...
"PlotFcn",[]);
obj.mu_tr = obj.mu_optimization.XAtMinObjective.mu_tr;
if obj.dd_mode
obj.mu_dd = obj.mu_optimization.XAtMinObjective.mu_dd;
end
if optimize_mu_dc
obj.mu_dc = obj.mu_optimization.XAtMinObjective.mu_dc;
end
objective_db = 10*log10(obj.mu_optimization.MinObjective);
if obj.dd_mode && optimize_mu_dc
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_dc,obj.mu_optimization.MinObjective,objective_db);
elseif obj.dd_mode
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_optimization.MinObjective,objective_db);
elseif optimize_mu_dc
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_dc,obj.mu_optimization.MinObjective,objective_db);
else
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_optimization.MinObjective,objective_db);
end
end
function objective = muObjective(obj,params,x,d)
old_debug = obj.save_debug;
old_mu_dc = obj.mu_dc;
obj.save_debug = 1;
if isprop(params,"mu_dc")
obj.mu_dc = params.mu_dc;
end
obj.e = zeros(obj.order,1);
obj.e_dc = 0;
obj.P = (1/0.05) * eye(obj.order);
obj.debug_struct = struct();
obj.equalize(x,d,params.mu_tr,obj.epochs_tr,obj.len_tr,1,0);
if obj.dd_mode
obj.equalize(x,d,params.mu_dd,obj.epochs_dd,obj.ddLength(numel(x)),0,0);
objective = mean(obj.debug_struct.error(end,:),"omitnan");
else
objective = mean(obj.debug_struct.error_tr(end,:),"omitnan");
end
if ~isfinite(objective)
objective = inf;
end
objective_db = 10*log10(objective);
obj.mu_optimization_iter = obj.mu_optimization_iter + 1;
optimize_mu_dc = isprop(params,"mu_dc");
if obj.dd_mode && optimize_mu_dc
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,params.mu_dc,objective,objective_db);
elseif obj.dd_mode
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,objective,objective_db);
elseif optimize_mu_dc
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dc,objective,objective_db);
else
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,objective,objective_db);
end
obj.save_debug = old_debug;
obj.mu_dc = old_mu_dc;
end
end
end

View File

@@ -10,7 +10,6 @@ classdef VNLE < handle
sps % usually 2
order
e
e_dc
error
len_tr
@@ -19,17 +18,10 @@ classdef VNLE < handle
mu_dd
epochs_dd
mu_dc
constellation
decide
save_debug = 0;
debug_struct
optmize_mus = 0;
mu_optimization
mu_optimization_iter = 0;
x_norm
ce
@@ -50,11 +42,8 @@ classdef VNLE < handle
options.mu_dd = 1e-5;
options.epochs_dd = 5;
options.mu_dc = 0;
options.decide = false;
options.save_debug = 0;
options.optmize_mus = 0;
end
@@ -65,7 +54,6 @@ classdef VNLE < handle
obj.error = 0;
obj.e_dc = 0;
end
@@ -81,13 +69,6 @@ classdef VNLE < handle
[obj.ie2,obj.ie3] = obj.calcIndiceVectors(obj.order);
obj.e = zeros( sum(obj.ce) ,1);
obj.e_dc = 0;
if obj.optmize_mus
obj.optimizeMus(X.signal,D.signal);
obj.e = zeros(sum(obj.ce),1);
obj.e_dc = 0;
end
% Training Mode
training = 1;
@@ -167,7 +148,7 @@ classdef VNLE < handle
x_in = x(obj.order(1)+sample-1:-1:sample);
x_in = obj.calcVNLENonlinVecs(x_in,obj.ie2,obj.ie3,obj.order,obj.x_norm);
y(symbol,1) = obj.e_dc + obj.e.' * x_in; % Calculating output of LMS __ * |
y(symbol,1) = obj.e.' * x_in; % Calculating output of LMS __ * |
if training
err = y(symbol) - d(symbol); % Instantaneous error
@@ -183,9 +164,6 @@ classdef VNLE < handle
normalizationfactor = (x_in.' * x_in);
obj.e = obj.e - err * x_in / normalizationfactor; % Weight update rule of NLMS
end
if obj.mu_dc ~= 0
obj.e_dc = obj.e_dc - obj.mu_dc * err;
end
if mod(sample,100) == 1 && showviz
a2.XData = 1:2*numel(y);
@@ -199,85 +177,12 @@ classdef VNLE < handle
end
obj.error(epoch,symbol) = err * err'; % Instantaneous square error
if obj.save_debug
obj.debug_struct.error(epoch,symbol) = err * err';
if training
obj.debug_struct.error_tr(epoch,symbol) = err * err';
end
end
end
end
end
function optimizeMus(obj,x,d)
mu_range = [1e-5, 1e-2];
mu_dc_range = [1e-5, 1e-1];
vars = [optimizableVariable("mu_tr",mu_range,"Transform","log"), ...
optimizableVariable("mu_dd",mu_range,"Transform","log")];
optimize_mu_dc = obj.mu_dc ~= 0;
if optimize_mu_dc
vars = [vars, optimizableVariable("mu_dc",mu_dc_range,"Transform","log")];
end
obj.mu_optimization_iter = 0;
obj.mu_optimization = bayesopt(@(p)obj.muObjective(p,x,d),vars, ...
"MaxObjectiveEvaluations",10, ...
"AcquisitionFunctionName","expected-improvement-plus", ...
"IsObjectiveDeterministic",false, ...
"Verbose",0, ...
"PlotFcn",[]);
obj.mu_tr = obj.mu_optimization.XAtMinObjective.mu_tr;
obj.mu_dd = obj.mu_optimization.XAtMinObjective.mu_dd;
if optimize_mu_dc
obj.mu_dc = obj.mu_optimization.XAtMinObjective.mu_dc;
end
objective_db = 10*log10(obj.mu_optimization.MinObjective);
if optimize_mu_dc
fprintf("\nVNLE mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_dc,obj.mu_optimization.MinObjective,objective_db);
else
fprintf("\nVNLE mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
obj.mu_tr,obj.mu_dd,obj.mu_optimization.MinObjective,objective_db);
end
end
function objective = muObjective(obj,params,x,d)
old_debug = obj.save_debug;
old_mu_dc = obj.mu_dc;
obj.save_debug = 1;
optimize_mu_dc = ismember("mu_dc",string(params.Properties.VariableNames));
if optimize_mu_dc
obj.mu_dc = params.mu_dc;
end
obj.e = zeros(sum(obj.ce),1);
obj.e_dc = 0;
obj.debug_struct = struct();
obj.equalize(x,d,params.mu_tr,obj.epochs_tr,obj.len_tr,1,0);
obj.equalize(x,d,params.mu_dd,obj.epochs_dd,numel(x),0,0);
objective = mean(obj.debug_struct.error(end,:),"omitnan");
if ~isfinite(objective)
objective = inf;
end
objective_db = 10*log10(objective);
obj.mu_optimization_iter = obj.mu_optimization_iter + 1;
if optimize_mu_dc
fprintf("\rVNLE mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,params.mu_dc,objective,objective_db);
else
fprintf("\rVNLE mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,objective,objective_db);
end
obj.save_debug = old_debug;
obj.mu_dc = old_mu_dc;
end
%% Functions needed During Adaption
function x_in_vnle_format = calcVNLENonlinVecs(~,x_in_block,I_2,I_3,N_,norm_)
% These are the second and third order input signal products of the VNLE EQ

View File

@@ -103,7 +103,6 @@ classdef MaxVar_Timing_Recovery < handle
% y = [y 0];
% end
data_out.signal = y.';
data_out.fs = obj.fsym;
end

View File

@@ -23,7 +23,7 @@ classdef DBHandler < handle
arguments
options.dataBase = "labor_highspeed"; % Default value for pathToDB if not provided
options.type = "mysql";
options.server = "192.168.178.192"; % university coffee PC: "134.245.243.254";
options.server = "134.245.243.254";
options.port = 3306;
options.user = "silas";
options.password = "silas";
@@ -47,20 +47,15 @@ classdef DBHandler < handle
elseif options.type == "mysql"
% datasource = "jdbc:mysql://134.245.243.254:3306/labor";
if ismac
driverlocation = "/Users/silas/Documents/mysql-connector-j-9.6.0/mysql-connector-j-9.6.0.jar";
else
driverlocation = "C:\Users\Silas\Documents\mysql-connector-j-9.3.0\mysql-connector-j-9.3.0.jar";
end
obj.conn = database( ...
obj.conn = database( ...
string(obj.dataBase), ... % Database name
options.user, ... % Username
options.password, ... % Password (or getSecret)
"Vendor", "MySQL", ...
"Server", options.server, ...
"PortNumber", options.port, ...
"JDBCDriverLocation", driverlocation);
"PortNumber", 3306, ...
"JDBCDriverLocation", "C:\Users\Silas\Documents\mysql-connector-j-9.3.0\mysql-connector-j-9.3.0.jar");
end

View File

@@ -7,11 +7,11 @@ classdef Metricstruct
eqParam_id (1,1) double {mustBeNumeric} = NaN
date_of_processing (1,1) datetime = datetime('now')
numBits (1,1) double {mustBeInteger, mustBeNonnegative} = 0
BER (1,1) double {mustBeNumeric, mustBeNonnegative} = 0
numBitErr (1,1) double {mustBeInteger, mustBeNonnegative} = 0
BER_precoded (1,1) double {mustBeNumeric, mustBeNonnegative} = 0
numBitErr_precoded (1,1) double {mustBeInteger, mustBeNonnegative} = 0
numBits (1,1) double = 0
BER (1,1) double = 0
numBitErr (1,1) double = 0
BER_precoded (1,1) double = 0
numBitErr_precoded (1,1) double = 0
SNR (1,1) double {mustBeNumeric} = NaN
SNR_level (:,1) double {mustBeNumeric} = []

View File

@@ -1,400 +0,0 @@
SELECT COUNT(*)
FROM `Results`
SELECT COUNT(DISTINCT run_id) AS unique_run_count
FROM `Results`;
SELECT COUNT(*) AS entries_for_run
FROM `Results`
WHERE run_id = -- your desired run_id here
2937;
ALTER TABLE `Runs`
ADD COLUMN grossrate DOUBLE GENERATED ALWAYS AS (
(
CASE
WHEN pam_level = 4 THEN 2.0
WHEN pam_level = 6 THEN 2.5
WHEN pam_level = 8 THEN 3.0
ELSE FLOOR(LOG2(pam_level) * 10) / 10
END
) * symbolrate
) STORED;
SELECT
JSON_EXTRACT(voa_class, '$.atten_state[1]') AS atten_state_second
FROM Runs
WHERE run_id = 1000;
SELECT DISTINCT
run_id
FROM Results r
WHERE date_of_processing > '2025-11-14 12:00:00';
SELECT DISTINCT
symbolrate
FROM Runs
WHERE pam_level = 4
AND symbolrate IS NOT NULL
ORDER BY symbolrate;
SELECT DISTINCT
wavelength
FROM Runs
SELECT
run_id,
COUNT(DISTINCT eq_id) AS unique_eq_count,
COUNT(*) AS entries_for_run
FROM `Results`
WHERE run_id IS NOT NULL
GROUP BY run_id
ORDER BY run_id;
SELECT
COUNT(DISTINCT eq_id) AS unique_eq_count,
COUNT(*) AS entries_for_run
FROM `Results`
WHERE run_id = -- your run_id here
2936;
SELECT
run_id,
COUNT(DISTINCT eq_id) AS unique_eq_count
FROM `Results`
WHERE run_id IS NOT NULL
GROUP BY run_id
ORDER BY run_id;
SELECT DISTINCT
r.run_id,
r.eq_id,
e.equalizer_structure,
e.eq,
e.mlse
FROM Results AS r
JOIN Equalizer AS e USING (eq_id)
WHERE r.run_id = -- your run_id here
2731
ORDER BY r.run_id, r.eq_id
SELECT DISTINCT
r.run_id
FROM Results AS r
JOIN Runs AS u
ON r.run_id = u.run_id
WHERE u.pam_level = 6
ORDER BY r.run_id;
-- older than aug 2025
CREATE OR REPLACE VIEW dashboard_old AS
SELECT
r.run_id,
r.eq_id,
e.equalizer_structure,
-- extract the DIR field from the JSON in e.MLSE
MAX(JSON_UNQUOTE(JSON_EXTRACT(e.MLSE, '$.DIR'))) AS DIR,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode,
-- accumulated dispersion in ps
0.09 * u.fiber_length * (u.wavelength - 1310) AS accumulated_dispersion,
-- BER & new averages
AVG(r.BER) AS avg_BER,
MAX(r.SNR) AS max_SNR,
MAX(r.GMI) AS max_GMI,
AVG(r.Alpha) AS avg_Alpha,
MIN(r.BER) AS min_BER,
AVG(r.BER_precoded) AS avg_BER_precoded,
MIN(r.BER_precoded) AS min_BER_precoded,
COUNT(*) AS num_occurrences
FROM Results AS r
JOIN Runs AS u ON r.run_id = u.run_id
JOIN Equalizer AS e ON r.eq_id = e.eq_id
WHERE r.eq_id IS NOT NULL
AND r.date_of_processing < '2025-08-01 00:00:00'
GROUP BY
r.run_id,
r.eq_id,
e.equalizer_structure,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode
ORDER BY
r.run_id,
r.eq_id;
CREATE OR REPLACE VIEW dashboard_new AS
SELECT
r.run_id,
r.eq_id,
e.equalizer_structure,
-- extract the DIR field from the JSON in e.MLSE
MAX(JSON_UNQUOTE(JSON_EXTRACT(e.MLSE, '$.DIR'))) AS DIR,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode,
-- accumulated dispersion in ps
0.09 * u.fiber_length * (u.wavelength - 1310) AS accumulated_dispersion,
-- BER & new averages
AVG(r.BER) AS avg_BER,
MAX(r.SNR) AS max_SNR,
MAX(r.GMI) AS max_GMI,
AVG(r.Alpha) AS avg_Alpha,
MIN(r.BER) AS min_BER,
AVG(r.BER_precoded) AS avg_BER_precoded,
MIN(r.BER_precoded) AS min_BER_precoded,
COUNT(*) AS num_occurrences
FROM Results AS r
JOIN Runs AS u ON r.run_id = u.run_id
JOIN Equalizer AS e ON r.eq_id = e.eq_id
WHERE r.eq_id IS NOT NULL
AND r.date_of_processing >= '2025-08-01 00:00:00'
GROUP BY
r.run_id,
r.eq_id,
e.equalizer_structure,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode
ORDER BY
r.run_id,
r.eq_id;
CREATE OR REPLACE VIEW dashboard_ungrouped_alltime AS
SELECT
r.result_id,
r.run_id,
r.eq_id,
e.equalizer_structure,
u.bitrate,
u.grossrate,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode,
u.rop_attenuation,
-- accumulated dispersion in ps
0.07 * u.fiber_length * (u.wavelength - 1310) AS accumulated_dispersion,
r.date_of_processing,
r.numBits,
r.numBitErr,
r.BER,
r.numBitErr_precoded,
r.BER_precoded,
r.STD,
r.STDrx,
r.GMI,
r.AIR,
r.EVM,
r.Alpha
FROM Results AS r
JOIN Runs AS u ON r.run_id = u.run_id
JOIN Equalizer AS e ON r.eq_id = e.eq_id
WHERE r.eq_id IS NOT NULL;
CREATE OR REPLACE VIEW dashboard_ungrouped_aug_nov_2025 AS
SELECT
r.result_id,
r.run_id,
r.eq_id,
e.equalizer_structure,
u.bitrate,
u.grossrate,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode,
u.rop_attenuation,
-- accumulated dispersion in ps
0.07 * u.fiber_length * (u.wavelength - 1310) AS accumulated_dispersion,
r.date_of_processing,
r.numBits,
r.numBitErr,
r.BER,
r.numBitErr_precoded,
r.BER_precoded,
r.STD,
r.STDrx,
r.GMI,
r.AIR,
r.EVM,
r.Alpha
FROM Results AS r
JOIN Runs AS u ON r.run_id = u.run_id
JOIN Equalizer AS e ON r.eq_id = e.eq_id
WHERE r.eq_id IS NOT NULL
AND r.date_of_processing >= '2025-08-01 00:00:00'
AND r.date_of_processing < '2025-11-14 00:00:00';
-- Das waren meine ehemals besten BERs, im Nov habe ich ML-based hinzugefügt und
-- das Rx Filter nochmal sehr eng gestellt, das war inbesondere bei geringeren Raten sehr hilfreich um
-- out.of-band noise zu filtern -> BER ging ja teilweise runter und wieder hoch...
CREATE OR REPLACE VIEW dashboard_ungrouped_aug_nov_2025 AS
SELECT
r.result_id,
r.run_id,
r.eq_id,
e.equalizer_structure,
u.bitrate,
u.grossrate,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode,
u.rop_attenuation,
-- accumulated dispersion in ps
0.07 * u.fiber_length * (u.wavelength - 1310) AS accumulated_dispersion,
r.date_of_processing,
r.numBits,
r.numBitErr,
r.BER,
r.numBitErr_precoded,
r.BER_precoded,
r.STD,
r.STDrx,
r.GMI,
r.AIR,
r.EVM
FROM Results AS r
JOIN Runs AS u ON r.run_id = u.run_id
JOIN Equalizer AS e ON r.eq_id = e.eq_id
WHERE r.eq_id IS NOT NULL
AND r.date_of_processing >= '2025-08-01 00:00:00'
AND r.date_of_processing < '2025-11-14 00:00:00';
CREATE OR REPLACE VIEW dashboard_ungrouped_old AS
SELECT
r.result_id,
r.run_id,
r.eq_id,
e.equalizer_structure,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode,
-- accumulated dispersion in ps
0.07 * u.fiber_length * (u.wavelength - 1310) AS accumulated_dispersion,
r.date_of_processing,
r.numBits,
r.numBitErr,
r.BER,
r.numBitErr_precoded,
r.BER_precoded,
r.STD,
r.STDrx,
r.GMI,
r.AIR,
r.EVM
FROM Results AS r
JOIN Runs AS u ON r.run_id = u.run_id
JOIN Equalizer AS e ON r.eq_id = e.eq_id
WHERE r.eq_id IS NOT NULL
AND r.date_of_processing < '2025-08-01 00:00:00';
CREATE OR REPLACE VIEW dashboard_ungrouped_during_ecoc AS
SELECT
r.result_id,
r.run_id,
r.eq_id,
e.equalizer_structure,
u.symbolrate,
u.pam_level,
u.wavelength,
u.fiber_length,
u.db_mode,
-- accumulated dispersion in ps
0.07 * u.fiber_length * (u.wavelength - 1310) AS accumulated_dispersion,
r.date_of_processing,
r.numBits,
r.numBitErr,
r.BER,
r.numBitErr_precoded,
r.BER_precoded,
r.STD,
r.STDrx,
r.GMI,
r.AIR,
r.EVM
FROM Results AS r
JOIN Runs AS u ON r.run_id = u.run_id
JOIN Equalizer AS e ON r.eq_id = e.eq_id
WHERE r.eq_id IS NOT NULL
AND r.date_of_processing > '2025-09-20 00:00:00';
SELECT COUNT(*)
FROM `dashboard_ungrouped`
CREATE OR REPLACE VIEW power_state_info AS
SELECT
run_id,
power_laser AS power_laser,
power_rop AS power_mzm,
-- new power_rop first
power_pd_in
+ CAST(voa_class->>'$.atten_state[1]' AS DECIMAL(12,6))
AS power_rop,
-- then voa_atten
CAST(voa_class->>'$.atten_state[1]' AS DECIMAL(12,6))
AS voa_atten,
-- then the photodiode input power
power_pd_in AS power_pd_in,
-- appended columns
wavelength,
pam_level,
db_mode,
is_mpi,
fiber_length
FROM Runs;

View File

@@ -95,14 +95,14 @@ classdef DataStorage < handle
end
function addStorage(obj,varName)
% add a storage
storage = cell(obj.getStorageSize());
obj.sto.(string(varName)) = storage;
end
function addStorage(obj,varName)
% add a storage
storage = cell(obj.dim);
obj.sto.(string(varName)) = storage;
end
function addValueToStorage(obj, valueToStore ,storageVarName, varargin)
@@ -273,18 +273,14 @@ classdef DataStorage < handle
end
%append to index list :-)
if isscalar(indices)
lin_idx(c,:) = indices{1};
else
fn_=fieldnames(obj.sto);
n_ = fn_{1};
lin_idx(c,:) = sub2ind(size(obj.sto.(n_)),indices{:});
% lin_idx(c,:) = eval(['sub2ind(size(obj.sto.',n_,')',str,');']);
end
end
fn_=fieldnames(obj.sto);
n_ = fn_{1};
lin_idx(c,:) = sub2ind(size(obj.sto.(n_)),indices{:});
% lin_idx(c,:) = eval(['sub2ind(size(obj.sto.',n_,')',str,');']);
end
end
% Mapping for single Index
@@ -295,33 +291,24 @@ classdef DataStorage < handle
function [phys_indices,param_name] = getPhysIndicesByLinIndex(obj, lin_idx)
% Converts a linear index into the corresponding physical parameter values
% Inputs:
% - lin_idx: The linear index within the storage array
function [phys_indices,param_name] = getPhysIndicesByLinIndex(obj, lin_idx)
% Converts a linear index into the corresponding physical parameter values
% Inputs:
% - lin_idx: The linear index within the storage array
% Output:
% - phys_indices: A cell array containing the physical parameter values for each dimension
% Initialize output cell array
phys_indices = cell(1, numel(obj.fn));
param_name = cell(1, numel(obj.fn));
if isempty(obj.fn)
return
end
% Convert linear index to subscript indices
if isscalar(obj.dim)
subscripts = {lin_idx};
else
[subscripts{1:numel(obj.dim)}] = ind2sub(obj.dim, lin_idx);
end
% Map subscripts to physical values for each parameter
for i = 1:numel(obj.fn)
param_name{i} = obj.fn(i);
phys_indices{i} = obj.parameter.(param_name{i}).getPhysForIndex(subscripts{i});
end
% Initialize output cell array
phys_indices = cell(1, numel(obj.fn));
% Convert linear index to subscript indices
[subscripts{1:numel(obj.dim)}] = ind2sub(obj.dim, lin_idx);
% Map subscripts to physical values for each parameter
for i = 1:numel(obj.fn)
param_name{i} = obj.fn(i);
phys_indices{i} = obj.parameter.(param_name{i}).getPhysForIndex(subscripts{i});
end
end
function [physStruct, stored_value] = getPhysAndValueByLinIndex(obj, storageVarName, lin_idx)
@@ -340,14 +327,8 @@ classdef DataStorage < handle
% Initialize an empty structure
physStruct = struct();
% Convert linear index to subscript indices
if isempty(obj.fn)
subscripts = {};
elseif isscalar(obj.dim)
subscripts = {lin_idx};
else
[subscripts{1:numel(obj.dim)}] = ind2sub(obj.dim, lin_idx);
end
% Convert linear index to subscript indices
[subscripts{1:numel(obj.dim)}] = ind2sub(obj.dim, lin_idx);
% Map subscripts to physical values and parameter names for each dimension
for i = 1:numel(obj.fn)
@@ -362,31 +343,17 @@ classdef DataStorage < handle
stored_value = obj.sto.(storageVarName){lin_idx};
end
function num_elements = getLastLinIndice(obj)
% Returns all possible linear indices for the data structure
% Output:
% - lin_indices: A column vector containing all linear indices for the storage array
% Calculate the total number of elements in the storage array
if isempty(obj.dim)
num_elements = 1;
else
num_elements = prod(obj.dim);
end
end
function storageSize = getStorageSize(obj)
if isempty(obj.dim)
storageSize = [1, 1];
elseif isscalar(obj.dim)
storageSize = [obj.dim, 1];
else
storageSize = obj.dim;
end
end
end
function num_elements = getLastLinIndice(obj)
% Returns all possible linear indices for the data structure
% Output:
% - lin_indices: A column vector containing all linear indices for the storage array
% Calculate the total number of elements in the storage array
num_elements = prod(obj.dim);
end
end
end

View File

@@ -1,9 +0,0 @@
classdef channel_model < int32
enumeration
awgn (1)
awgn_alphad (2)
physical (3)
end
end

View File

@@ -4,5 +4,5 @@ classdef db_decoder < int32
sequencedetection (0) % use MLSE for decoding
memoryless (1) % use modulo
end
end

View File

@@ -5,8 +5,6 @@ classdef signalform < int32
sawtooth (2)
square (3)
noise (4)
random (5)
prms (6)
end
end
end

View File

@@ -1,32 +0,0 @@
function signal_out = awgn_alpha_d_channel(signal_in, options)
%AWGN_ALPHA_D_CHANNEL Apply a 1 + alpha*D FIR channel followed by AWGN.
arguments
signal_in
options.alpha (1,1) double = 0
options.delay_samples (1,1) double {mustBeInteger, mustBePositive} = 1
options.snr_dB (1,1) double = 20
options.randkey = []
end
signal_out = signal_in;
taps = zeros(1, options.delay_samples + 1);
taps(1) = 1;
taps(end) = options.alpha;
signal_out.signal = filter(taps, 1, signal_in.signal, [], 1);
if isa(signal_out, 'Signal')
desc = sprintf('1 + alpha*D channel with alpha %.4f and delay %d samples', ...
options.alpha, options.delay_samples);
meta = struct( ...
'alpha', options.alpha, ...
'delay_samples', options.delay_samples);
signal_out = signal_out.logbookentry(desc, meta);
end
signal_out = awgn_channel(signal_out, ...
"snr_dB", options.snr_dB, ...
"randkey", options.randkey);
end

View File

@@ -1,43 +0,0 @@
function signal_out = awgn_channel(signal_in, options)
%AWGN_CHANNEL Apply additive white Gaussian noise to a signal object.
arguments
signal_in
options.snr_dB (1,1) double = 20
options.randkey = []
end
signal_out = signal_in;
x = signal_in.signal;
signalPower = mean(abs(x).^2, 'all'); %or use signal_in.power
if signalPower == 0
return
end
snrLinear = 10^(options.snr_dB/10);
noisePower = signalPower / snrLinear;
if isempty(options.randkey)
if isreal(x)
noise = sqrt(noisePower) * randn(size(x));
else
noise = sqrt(noisePower / 2) * (randn(size(x)) + 1i * randn(size(x)));
end
else
rs = RandStream('mt19937ar', 'Seed', options.randkey);
if isreal(x)
noise = sqrt(noisePower) * randn(rs, size(x));
else
noise = sqrt(noisePower / 2) * (randn(rs, size(x)) + 1i * randn(rs, size(x)));
end
end
signal_out.signal = x + noise;
if isa(signal_out, 'Signal')
desc = sprintf('AWGN channel with SNR %.2f dB', options.snr_dB);
meta = struct('snr_dB', options.snr_dB, 'randkey', options.randkey);
signal_out = signal_out.logbookentry(desc, meta);
end
end

View File

@@ -1,211 +0,0 @@
function [ffe_results] = ffe_db(eq_, M, rx_signal, tx_symbols, tx_bits, options)
% FFE Processes signals through FFE equalizer
%
% Inputs:
% eq_ - Equalizer object
% M - Modulation order
% rx_signal - Received signal
% tx_symbols - Transmitted symbols
% tx_bits - Transmitted bits
% options - Optional parameters
%
% Outputs:
% ffe_results - Results from FFE processing
arguments
eq_
M
rx_signal
tx_symbols
tx_bits
options.db_target = 0;
options.precode_mode db_mode
options.showAnalysis = 0;
options.eth_style_symbol_mapping = 0;
options.postFFE = [];
options.database = [];
end
%% Process signals through equalizer
% FFE or VNLE
if options.db_target
tx_symbols_ref = Duobinary().encode(tx_symbols);
db_ref_constellation = unique(tx_symbols_ref.signal);
[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols_ref);
else
[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols);
end
% Apply post-FFE if provided
if ~isempty(options.postFFE)
tic
[eq_signal_sd, eq_noise] = options.postFFE.process(eq_signal_sd, tx_symbols);
toc
end
try
ch_coefficients = arburg(eq_noise.signal,1);
channel_alpha = ch_coefficients(2);
end
% Hard decision on FFE output
if options.db_target
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd,'custom_const',db_ref_constellation.');
else
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
end
if options.db_target
eq_signal_hd = Duobinary().decode(eq_signal_hd);
tx_symbols = Duobinary().encode(tx_symbols);
tx_symbols = Duobinary().decode(tx_symbols);
end
%% Calculate BER based on precoding mode
[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
%% Calculate performance metrics
[snr, snr_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal);
% [gmi] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000);
[gmi] = calc_ngmi(eq_signal_sd,tx_symbols);
gmi = max(gmi,0);
air = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M));
[evm_total, evm_lvl] = calc_evm(eq_signal_sd, tx_symbols);
[std_total, std_lvl] = calc_std(eq_signal_sd, tx_symbols);
[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols);
%% Display analysis if requested
if options.showAnalysis
displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, options.postFFE);
end
%% Prepare output structure
% Determine postFFE order
if ~isempty(options.postFFE)
npostFFE = options.postFFE.order;
else
npostFFE = 0;
end
% Create FFE results structure
ffe_results = struct();
try
eq_.e = [];
eq_.e2 = [];
eq_.e3 = [];
eq_.b = [];
eq_.b2 = [];
eq_.b3 = [];
end
ffe_results.config = Equalizerstruct();
ffe_results.config.eq = jsonencode(eq_);
ffe_results.config.equalizer_structure = int32(equalizer_structure.ffe);
ffe_results.config.comment = 'function: ffe';
ffe_results.metrics = Metricstruct;
ffe_results.metrics.result_id = NaN;
ffe_results.metrics.run_id = NaN;
ffe_results.metrics.eqParam_id = NaN;
ffe_results.metrics.date_of_processing = datetime('now');
ffe_results.metrics.BER = ber;
ffe_results.metrics.numBits = bits;
ffe_results.metrics.numBitErr = errors;
ffe_results.metrics.BER_precoded = ber_precoded;
ffe_results.metrics.numBitErr_precoded = errors_precoded;
ffe_results.metrics.SNR = snr;
ffe_results.metrics.SNR_level = snr_lvl;
ffe_results.metrics.STD = std_total;
ffe_results.metrics.STD_level = std_lvl;
ffe_results.metrics.STDrx = std_rxraw_total;
ffe_results.metrics.STDrx_level = std_rxraw_lvl;
ffe_results.metrics.GMI = gmi;
ffe_results.metrics.AIR = air;
ffe_results.metrics.EVM = evm_total;
ffe_results.metrics.EVM_level = evm_lvl;
ffe_results.metrics.Alpha = channel_alpha;
end
%% Helper Functions
function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
% Calculate BER based on precoding mode
mapper = PAMmapper(M, 0, "eth_style", eth_style);
switch precode_mode
case db_mode.no_db
% TX Data is not precoded
% A) Emulate diff precoding
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
tx_symbols_precoded = Duobinary().encode(tx_symbols);
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
rx_bits = mapper.demap(eq_signal_hd_precoded);
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
% B) Just determine BER
rx_bits = mapper.demap(eq_signal_hd);
tx_bits = mapper.demap(tx_symbols);
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
case db_mode.db_precoded
% Data is precoded on TX side
% A) Decode at Rx if no DB targeting was applied
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
rx_bits_decoded = mapper.demap(eq_signal_hd_decoded);
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
% B) Omit the Coding by comparing with demapped TX symbol sequence
tx_bits_demapped = mapper.demap(tx_symbols);
rx_bits = mapper.demap(eq_signal_hd);
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
end
end
function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, postFFE)
% Display analysis plots and metrics
% Initialize figure handles
% Corrected line - added tx_symbols as second positional argument
% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 100);
warning off
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 101);
figure(gcf);hold on; plot(((1:length(eq_noise.signal)) / eq_noise.fs) * 1e6,movmean(eq_noise.signal,2000,1), 'LineWidth',3,'Color','black')
warning on
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 102);
showEQNoisePSD(eq_noise, "fignum", 103, "displayname", 'Residual Noise after FFE');
% Figure 2: Post-FFE coefficients (if available)
if ~isempty(postFFE)
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 104);
end
try
figure(339);hold on
showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'training','fignum',339);
% showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'dec. directed','fignum',339);
legend on
end
% try
% figure(240); hold on; plot(pow2db(movmean(eq_.debug_struct.error_tr',100)));ylim([-30,3]);title('error training');
%
% figure(241); hold on; plot(pow2db(movmean(eq_.debug_struct.update_tr',100)));title('update step training');
%
% figure(242); hold on; plot(pow2db(movmean(eq_.debug_struct.update',1000)));title('update step dd');
% end
eq_signal_sd.eye(eq_signal_sd.fs,M,"displayname",'Eye','fignum',105);
end

View File

@@ -1,146 +0,0 @@
function Scpe_sig = preprocessSignal(Scpe_sig, Symbols, fsym, options)
% PREPROCESSSIGNAL Performs standard preprocessing on a signal
%
% Inputs:
% Scpe_sig - Input signal
% Symbols - Reference symbols for synchronization
% fsym - Symbol frequency
%
% Outputs:
% Scpe_sig - Preprocessed signal
arguments
Scpe_sig
Symbols
fsym
options.mode string = "auto"
options.tx_pulseformer = []
options.debug_plots (1,1) logical = false
options.apply_gaussian_filter (1,1) logical = true
options.gaussian_cutoff_factor (1,1) double = 0.52
end
preprocessMode = resolvePreprocessMode(options.mode, options.tx_pulseformer, Symbols);
targetFs = 2 * fsym;
switch preprocessMode
case "matched_filter"
matchedPulseformer = buildMatchedPulseformer(options.tx_pulseformer, Symbols, fsym, targetFs);
Scpe_sig = matchedPulseformer.process(Scpe_sig);
case "resample"
Scpe_sig = Scpe_sig.resample("fs_out", targetFs);
otherwise
error('preprocessSignal:InvalidMode', 'Unsupported preprocessing mode "%s".', preprocessMode);
end
[Scpe_sig, Scpe_cell, inverted] = Scpe_sig.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", options.debug_plots);
% Scpe_sig = Scpe_cell{1};
% Apply Gaussian filter
if options.apply_gaussian_filter
Scpe_sig = Filter('filtdegree', 8, "f_cutoff", Symbols.fs.*options.gaussian_cutoff_factor, ...
"fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
"active", true).process(Scpe_sig);
end
%Remove DC offset
Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);
end
function preprocessMode = resolvePreprocessMode(requestedMode, txPulseformer, Symbols)
requestedMode = string(requestedMode);
if requestedMode ~= "auto"
preprocessMode = requestedMode;
return
end
pulseformer = txPulseformer;
if isempty(pulseformer)
pulseformer = findSignalPulseformer(Symbols);
end
if isRootRaisedCosinePulseformer(pulseformer)
preprocessMode = "matched_filter";
else
preprocessMode = "resample";
end
end
function tf = isRootRaisedCosinePulseformer(pulseformer)
tf = false;
if isempty(pulseformer)
return
end
pulseValue = readPulseformerField(pulseformer, 'pulse', []);
if isempty(pulseValue)
return
end
pulseValue = normalizePulseValue(pulseValue);
tf = pulseValue == pulseform.rrc;
end
function matchedPulseformer = buildMatchedPulseformer(txPulseformer, Symbols, fsym, targetFs)
pulseformerMeta = txPulseformer;
if isempty(pulseformerMeta)
pulseformerMeta = findSignalPulseformer(Symbols);
end
pulseValue = normalizePulseValue(readPulseformerField(pulseformerMeta, 'pulse', pulseform.rrc));
alphaValue = readPulseformerField(pulseformerMeta, 'alpha', 0.05);
pulseLengthValue = readPulseformerField(pulseformerMeta, 'pulselength', 16);
if isempty(alphaValue)
alphaValue = 0.05;
end
matchedPulseformer = Pulseformer( ...
"fsym", fsym, ...
"fdac", targetFs, ...
"pulse", pulseValue, ...
"pulselength", pulseLengthValue, ...
"alpha", alphaValue, ...
"matched", 1);
end
function value = readPulseformerField(pulseformerMeta, fieldName, defaultValue)
value = defaultValue;
if isempty(pulseformerMeta)
return
end
if isstruct(pulseformerMeta) && isfield(pulseformerMeta, fieldName)
candidate = pulseformerMeta.(fieldName);
if ~isempty(candidate)
value = candidate;
end
return
end
if isobject(pulseformerMeta) && isprop(pulseformerMeta, fieldName)
candidate = pulseformerMeta.(fieldName);
if ~isempty(candidate)
value = candidate;
end
end
end
function pulseValue = normalizePulseValue(rawValue)
if isa(rawValue, 'pulseform')
pulseValue = rawValue;
return
end
if iscell(rawValue)
rawValue = rawValue{1};
end
if isstring(rawValue) || ischar(rawValue)
pulseValue = pulseform.(char(string(rawValue)));
return
end
pulseValue = pulseform(rawValue);
end

View File

@@ -1,36 +0,0 @@
function output = dsp_recipe_minimal(Scpe_sig_raw, Symbols, Tx_bits, options)
%DSP_RECIPE_MINIMAL Minimal example recipe for the DSP job framework.
% This recipe intentionally performs only light preprocessing and records
% summary values. It demonstrates the recipe interface without running a
% full equalizer chain.
arguments
Scpe_sig_raw
Symbols
Tx_bits
options.fsym
options.M
options.duob_mode
options.dataTable table
options.userParameters struct = struct()
options.debug_plots (1,1) logical = false
end
Scpe_sig = preprocessSignal(Scpe_sig_raw, Symbols, options.fsym, ...
"mode", "auto", ...
"debug_plots", options.debug_plots);
eq_ffe = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-1,"mu_tr",0.4,"order",25,...
"sps",2,"decide",0,"optmize_mus",0,"dd_mode",options.userParameters.dd_mode,"adaption_technique","nlms","dc_tracking_mu",1.021e-05);
ffe_results = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.config.equalizer_structure = "ffe";
ffe_results.metrics.print("description",'FFE');
output.ffe_package = ffe_results;
end

View File

@@ -1,206 +0,0 @@
function output = dsp_scope_signal(Scpe_sig_raw, Symbols, Tx_bits, options)
%DSP_SCOPE_SIGNAL Run the common DSP chain starting from a scope signal.
arguments
Scpe_sig_raw
Symbols
Tx_bits
options.fsym
options.M
options.duob_mode
options.userParameters struct = struct()
options.preprocess_mode string = "auto"
options.tx_pulseformer = []
options.debug_plots (1,1) logical = false
end
output.ffe_package = [];
output.dfe_package = [];
output.mlse_package = [];
output.vnle_package = [];
output.dbtgt_package = [];
output.dbenc_package = [];
output.mlmlse_package = [];
fsym = options.fsym;
M = options.M;
duob_mode = options.duob_mode;
len_tr = 4096*2;
ffe_order_ffe = [50, 0, 0];
ffe_order_dfe = [50, 5, 5];
ffe_order_vnle = [50, 5, 5];
ffe_order_dbtgt = [50, 5, 5];
dfe_order_vnle = [0, 0, 0];
dfe_order_dbtgt = [0, 0, 0];
dfe_feedback_order = [2, 0, 0];
pf_ncoeffs = 1;
mu_ffe = [0.0001, 0.0008, 0.001];
mu_dfe = 0.0004;
mu_dc = 0.005;
dc_buffer_len = 1; %#ok<NASGU>
mu_tr = 0;
mu_dd = 0.05;
adaption = 1;
use_dd_mode = 1;
use_ffe = 1;
use_dfe = 1;
use_vnle_mlse = 1;
use_dbtgt = 1;
use_dbenc = 0;
use_ml_mlse = 0;
showAnalysis = 0;
decoding_mode = [];
addProcessingResultToDatabase = 0; %#ok<NASGU>
paramStruct = options.userParameters;
if ~isempty(paramStruct)
paramNames = fieldnames(paramStruct);
for i = 1:numel(paramNames)
thisName = paramNames{i};
thisValue = paramStruct.(thisName);
eval([thisName ' = thisValue;']);
end
end
pf_ = Postfilter("ncoeff", pf_ncoeffs, "useBurg", 1); %#ok<NASGU>
mlse_ = MLSE("duobinary_output", 0, 'M', M, 'trellis_states', PAMmapper(M,0).levels); %#ok<NASGU>
eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0,"adaption_technique","lms","dc_tracking_mu",mu_dc); %#ok<NASGU>
eq_post = FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption_technique",adaption_method(adaption),"dd_mode",use_dd_mode,"dc_tracking_mu",mu_dc); %#ok<NASGU>
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels); %#ok<NASGU>
eq_db_enc = EQ("Ne", ffe_order_dbtgt, "Nb", dfe_order_dbtgt, "training_length", len_tr, ...
"training_loops", 5, "dd_loops", 5, "K", 2, "DCmu", mu_dc, ...
"DDmu", [mu_ffe mu_dfe], "DFEmu", 0.005, "FFEmu", 0, "plotfinal", 0, "ideal_dfe", 1);
Scpe_sig = preprocessSignal(Scpe_sig_raw, Symbols, fsym, ...
"mode", options.preprocess_mode, ...
"tx_pulseformer", options.tx_pulseformer, ...
"debug_plots", 0);
% Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',-6);
% ylim([-30,3]);
% xlim([-5,100]);
Scpe_sig.signal = Scpe_sig.signal(1:2*Symbols.length);
Scpe_sig.signal = real(Scpe_sig.signal);
if duob_mode ~= db_mode.db_encoded
if use_ffe
eq_ffe = EQ("Ne",ffe_order_ffe,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
eq_ffe = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-1,"mu_tr",0.4,"order",ffe_order_ffe(1),...
"sps",2,"decide",0,"optmize_mus",0,"dd_mode",1,"adaption_technique","nlms","dc_tracking_mu",1.021e-05);
ffe_results = ffe(eq_ffe, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.config.equalizer_structure = "ffe";
ffe_results.metrics.print("description",'FFE');
output.ffe_package = ffe_results;
end
if use_dfe
eq_dfe = EQ("Ne",ffe_order_dfe,"Nb",dfe_feedback_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
dfe_results = ffe(eq_dfe, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
dfe_results.config.equalizer_structure = "dfe";
dfe_results.metrics.print("description",'DFE');
output.dfe_package = dfe_results;
end
if use_vnle_mlse
eq_ = EQ("Ne",ffe_order_vnle,"Nb",dfe_order_vnle,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
useviterbi = 0;
if useviterbi
mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
else
if duob_mode == db_mode.no_db && M == 6
trellexlusion = 1;
else
trellexlusion = 0;
end
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',2);
end
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, ...
'showAnalysis', showAnalysis, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.config.equalizer_structure = "vnle";
ffe_results.metrics.print("description",'VNLE');
mlse_results.metrics.print("description",'MLSE');
output.mlse_package = mlse_results;
output.vnle_package = ffe_results;
end
if use_ml_mlse
mu_ml = 0.01;
training_epochs = 100;
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
"len_tr",length(Scpe_sig)/4,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
"traceback_depth",128,"L",1,"delta",4,"adaptive_mu",0);
ml_mlse_results = ml_mlse(ml_mlse_equalizer, M, Scpe_sig, Symbols, Tx_bits, "precode_mode", duob_mode);
output.mlmlse_package = ml_mlse_results;
end
if use_dbtgt
useviterbi = 0;
if useviterbi
mlse_db_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
else
if duob_mode == db_mode.no_db && M == 6
trellexlusion = 1;
else
trellexlusion = 0;
end
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',3);
end
eq_ = EQ("Ne",ffe_order_dbtgt,"Nb",dfe_order_dbtgt,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
if isempty(decoding_mode)
dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, ...
'showAnalysis', showAnalysis, ...
"postFFE", []);
else
dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, ...
'showAnalysis', showAnalysis, ...
"postFFE", [], ...
"decoding_mode", decoding_mode);
end
dbt_results.metrics.print("description",'Duob. Target');
output.dbtgt_package = dbt_results;
end
end
if duob_mode == db_mode.db_encoded
mlse_db_enc = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); %#ok<NASGU>
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels);
db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",showAnalysis,"postFFE",[]);
output.dbenc_package = db_results;
end
end

View File

@@ -1,404 +0,0 @@
function output = mpi_recipe_dev(Scpe_sig_raw, Symbols, Tx_bits, options)
%mpi_recipe_dev Minimal example recipe for the DSP job framework.
% This recipe intentionally performs only light preprocessing and records
% summary values. It demonstrates the recipe interface without running a
% full equalizer chain.
arguments
Scpe_sig_raw
Symbols
Tx_bits
options.fsym
options.M
options.duob_mode
options.dataTable table
options.userParameters struct = struct()
options.debug_plots (1,1) logical = false
end
Scpe_sig = preprocessSignal(Scpe_sig_raw, Symbols, options.fsym, ...
"mode", "auto", ...
"debug_plots", options.debug_plots);
output = struct();
%% conventional FFE
eq_core_settings = { ...
"sps", 2, ...
"order", 25, ...
"decide", 0, ...
"adaption_technique", "nlms"};
eq_training_settings = { ...
"len_tr", 4096, ...
"epochs_tr", 5, ...
"mu_tr", 0.04};
eq_dd_settings = { ...
"dd_mode", 1, ...
"epochs_dd", 3, ...
"mu_dd", 0.012};
eq_a1_settings = { ...
"dc_smoothing_a1", 0, ...
"dc_avg_bufferlength_a1", 0, ...
"dc_avg_update_blocklength_a1", 1};
eq_a2_settings = { ...
"dc_smoothing_a2", 0, ...
"dc_level_avg_bufferlength_a2", 0, ...
"dc_level_update_blocklength_a2", 0, ...
"dc_level_weights_a2", [0.6 0.6 0.6 0.6]};
eq_dc_tracking_settings = { ...
"dc_tracking_mu", 0, ...
"dc_tracking_adaptive_enabled", 0, ...
"dc_tracking_persistence_gain", 0, ...
"dc_tracking_buffer_len", 0};
eq_ffe_update_settings = { ...
"ffe_update_buffer_len", 1};
eq_optimizer_settings = { ...
"optmize_mus", 0, ...
"plot_mu_optimization", options.debug_plots, ...
"optimize_dc_tracking_params", 0, ...
"optimize_a2_level_weights", 0, ...
"a2_level_weight_optimization_len", 2^15, ...
"a2_level_weight_optimization_max_evals", 30, ...
"a2_level_weight_max", 1};
eq_debug_settings = { ...
"save_debug", false};
eq_settings = [ ...
eq_core_settings, ...
eq_training_settings, ...
eq_dd_settings, ...
eq_a1_settings, ...
eq_a2_settings, ...
eq_dc_tracking_settings, ...
eq_ffe_update_settings, ...
eq_optimizer_settings, ...
eq_debug_settings];
eq_ffe = FFE(eq_settings{:});
if 1
[ffe_results,equalized_signal] = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print("description",sprintf('Immediate A2; SIR %d dB',options.dataTable.sir));
output.conventional_ffe = ffe_results;
end
if options.debug_plots
showLevelScatter(Scpe_sig_raw, Symbols, ...
"fsym", options.fsym, ...
"fignum", 400, ...
"normalize", true);
[~, avg_for_lvl] = showLevelScatter(equalized_signal, Symbols, ...
"fsym", options.fsym, ...
"fignum", 401, ...
"normalize", true);
end
%% A2
eq_core_settings = { ...
"sps", 2, ...
"order", 25, ...
"decide", 0, ...
"adaption_technique", "nlms"};
eq_training_settings = { ...
"len_tr", 4096, ...
"epochs_tr", 5, ...
"mu_tr", 0.04};
eq_dd_settings = { ...
"dd_mode", 1, ...
"epochs_dd", 3, ...
"mu_dd", 0.012};
eq_a1_settings = { ...
"dc_smoothing_a1", 0, ...
"dc_avg_bufferlength_a1", 0, ...
"dc_avg_update_blocklength_a1", 1};
eq_a2_settings = { ...
"dc_smoothing_a2", 1, ...
"dc_level_avg_bufferlength_a2", 256, ...
"dc_level_update_blocklength_a2", options.userParameters.block_update, ...
"dc_level_weights_a2", [0.6 0.6 0.6 0.6]};
eq_dc_tracking_settings = { ...
"dc_tracking_mu", 0, ...
"dc_tracking_adaptive_enabled", 0, ...
"dc_tracking_persistence_gain", 0, ...
"dc_tracking_buffer_len", 0};
eq_ffe_update_settings = { ...
"ffe_update_buffer_len", 1};
eq_optimizer_settings = { ...
"optmize_mus", 0, ...
"plot_mu_optimization", options.debug_plots, ...
"optimize_dc_tracking_params", 0, ...
"optimize_a2_level_weights", 1, ...
"a2_level_weight_optimization_len", 2^15, ...
"a2_level_weight_optimization_max_evals", 30, ...
"a2_level_weight_max", 1};
eq_debug_settings = { ...
"save_debug", false};
eq_settings = [ ...
eq_core_settings, ...
eq_training_settings, ...
eq_dd_settings, ...
eq_a1_settings, ...
eq_a2_settings, ...
eq_dc_tracking_settings, ...
eq_ffe_update_settings, ...
eq_optimizer_settings, ...
eq_debug_settings];
eq_ffe = FFE(eq_settings{:});
if 1
[ffe_results,equalized_signal] = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print("description",sprintf('Immediate A2; SIR %d dB',options.dataTable.sir));
output.a2_immediate_updates = ffe_results;
end
if options.debug_plots
showLevelScatter(Scpe_sig_raw, Symbols, ...
"fsym", options.fsym, ...
"fignum", 400, ...
"normalize", true);
[~, avg_for_lvl] = showLevelScatter(equalized_signal, Symbols, ...
"fsym", options.fsym, ...
"fignum", 401, ...
"normalize", true);
end
%% A1
eq_a1_settings = { ...
"dc_smoothing_a1", 1 ...
"dc_avg_bufferlength_a1", 2048, ...
"dc_avg_update_blocklength_a1", options.userParameters.block_update};
eq_a2_settings = { ...
"dc_smoothing_a2", 0, ...
"dc_level_avg_bufferlength_a2", 0, ...
"dc_level_update_blocklength_a2", 0, ...
"dc_level_weights_a2", [0.6 0.6 0.6 0.6]};
eq_optimizer_settings = { ...
"optmize_mus", 0, ...
"plot_mu_optimization", options.debug_plots, ...
"optimize_dc_tracking_params", 0, ...
"optimize_a2_level_weights", 0, ...
"a2_level_weight_optimization_len", 2^15, ...
"a2_level_weight_optimization_max_evals", 30, ...
"a2_level_weight_max", 1};
eq_debug_settings = { ...
"save_debug", false};
eq_settings = [ ...
eq_core_settings, ...
eq_training_settings, ...
eq_dd_settings, ...
eq_a1_settings, ...
eq_a2_settings, ...
eq_dc_tracking_settings, ...
eq_ffe_update_settings, ...
eq_optimizer_settings, ...
eq_debug_settings];
eq_ffe = FFE(eq_settings{:});
if 1
[ffe_results,equalized_signal] = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print("description",sprintf('Immediate A1; SIR %d dB',options.dataTable.sir));
output.a1_immediate_updates = ffe_results;
end
%% Tracking
eq_core_settings = { ...
"sps", 2, ...
"order", 25, ...
"decide", 0, ...
"adaption_technique", "nlms"};
eq_training_settings = { ...
"len_tr", 4096, ...
"epochs_tr", 5, ...
"mu_tr", 0.04};
eq_dd_settings = { ...
"dd_mode", 1, ...
"epochs_dd", 3, ...
"mu_dd", 0.012};
eq_a1_settings = { ...
"dc_smoothing_a1", 0, ...
"dc_avg_bufferlength_a1", 0, ...
"dc_avg_update_blocklength_a1", 1};
eq_a2_settings = { ...
"dc_smoothing_a2", 0, ...
"dc_level_avg_bufferlength_a2", 0, ...
"dc_level_update_blocklength_a2", 0, ...
"dc_level_weights_a2", [0.6 0.6 0.6 0.6]};
eq_dc_tracking_settings = { ...
"dc_tracking_mu", 0.002, ...
"dc_tracking_adaptive_enabled", 0, ...
"dc_tracking_persistence_gain", 0, ...
"dc_tracking_buffer_len", options.userParameters.block_update};
eq_ffe_update_settings = { ...
"ffe_update_buffer_len", 1};
eq_optimizer_settings = { ...
"optmize_mus", 0, ...
"plot_mu_optimization", options.debug_plots, ...
"optimize_dc_tracking_params", 1, ...
"optimize_a2_level_weights", 0, ...
"a2_level_weight_optimization_len", 2^15, ...
"a2_level_weight_optimization_max_evals", 30, ...
"a2_level_weight_max", 1};
eq_debug_settings = { ...
"save_debug", false};
eq_settings = [ ...
eq_core_settings, ...
eq_training_settings, ...
eq_dd_settings, ...
eq_a1_settings, ...
eq_a2_settings, ...
eq_dc_tracking_settings, ...
eq_ffe_update_settings, ...
eq_optimizer_settings, ...
eq_debug_settings];
eq_ffe = FFE(eq_settings{:});
if 1
[ffe_results,equalized_signal] = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print("description",sprintf('Immediate A2; SIR %d dB',options.dataTable.sir));
output.tracking_fixmu_immediate_updates = ffe_results;
end
%% Tracking Adaptive
eq_core_settings = { ...
"sps", 2, ...
"order", 25, ...
"decide", 0, ...
"adaption_technique", "nlms"};
eq_training_settings = { ...
"len_tr", 4096, ...
"epochs_tr", 5, ...
"mu_tr", 0.04};
eq_dd_settings = { ...
"dd_mode", 1, ...
"epochs_dd", 3, ...
"mu_dd", 0.012};
eq_a1_settings = { ...
"dc_smoothing_a1", 0, ...
"dc_avg_bufferlength_a1", 0, ...
"dc_avg_update_blocklength_a1", 1};
eq_a2_settings = { ...
"dc_smoothing_a2", 0, ...
"dc_level_avg_bufferlength_a2", 0, ...
"dc_level_update_blocklength_a2", 0, ...
"dc_level_weights_a2", [0.6 0.6 0.6 0.6]};
eq_dc_tracking_settings = { ...
"dc_tracking_mu", 0.002, ...
"dc_tracking_adaptive_enabled", 1, ...
"dc_tracking_persistence_gain", 0, ...
"dc_tracking_buffer_len", options.userParameters.block_update};
eq_ffe_update_settings = { ...
"ffe_update_buffer_len", 1};
eq_optimizer_settings = { ...
"optmize_mus", 0, ...
"plot_mu_optimization", options.debug_plots, ...
"optimize_dc_tracking_params", 1, ...
"optimize_a2_level_weights", 0, ...
"a2_level_weight_optimization_len", 2^15, ...
"a2_level_weight_optimization_max_evals", 30, ...
"a2_level_weight_max", 1};
eq_debug_settings = { ...
"save_debug", false};
eq_settings = [ ...
eq_core_settings, ...
eq_training_settings, ...
eq_dd_settings, ...
eq_a1_settings, ...
eq_a2_settings, ...
eq_dc_tracking_settings, ...
eq_ffe_update_settings, ...
eq_optimizer_settings, ...
eq_debug_settings];
eq_ffe = FFE(eq_settings{:});
if 1
[ffe_results,equalized_signal] = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
'showAnalysis', options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print("description",sprintf('Immediate A2; SIR %d dB',options.dataTable.sir));
output.tracking_adaptive_immediate_updates = ffe_results;
end
end

View File

@@ -1,396 +0,0 @@
function output = mpi_recipe_dev(Scpe_sig_raw, Symbols, Tx_bits, options)
%mpi_recipe_dev Minimal MPI equalizer comparison recipe.
arguments
Scpe_sig_raw
Symbols
Tx_bits
options.fsym
options.M
options.duob_mode
options.dataTable table
options.userParameters struct = struct()
options.debug_plots (1,1) logical = false
end
%% Execution toggles
run_conventional_ffe = 1;
run_ma = 0;
run_hpf = 0;
run_a2_tracked_levels = 0;
run_a2_residual = 0;
run_a1 = 0;
run_tracking_adaptive = 1;
plot_output_signals = 1;
%% Shared fixed EQ settings
eq_sps = 2;
eq_order = 50;
eq_adaption = "nlms";
eq_len_tr = 4096;
eq_epochs_tr = 5;
eq_mu_tr = 0.04;
eq_epochs_dd = 3;
eq_save_debug = false;
block_update = 1;
if isfield(options.userParameters,"block_update")
block_update = options.userParameters.block_update;
end
Scpe_sig = preprocessSignal(Scpe_sig_raw, Symbols, options.fsym, ...
"mode", "auto", ...
"debug_plots", options.debug_plots);
output = struct();
%%
if plot_output_signals
showLevelScatter(Scpe_sig, Symbols, ...
"fsym", options.fsym, ...
"fignum", 400, ...
"normalize", true);
end
%% Conventional FFE
if run_conventional_ffe
eq_ffe = FFE_plain( ...
"sps", eq_sps, ...
"order", eq_order, ...
"decide", false, ...
"adaption_technique", eq_adaption, ...
"len_tr", eq_len_tr, ...
"epochs_tr", eq_epochs_tr, ...
"mu_tr", eq_mu_tr, ...
"dd_mode", true, ...
"epochs_dd", eq_epochs_dd, ...
"mu_dd", 0.0012, ...
"optmize_mus", false, ...
"plot_mu_optimization", options.debug_plots, ...
"save_debug", eq_save_debug);
storageName = "conventional_ffe";
[ffe_results,equalized_signal] = runFfe(eq_ffe, "Conventional FFE", ...
Scpe_sig, Symbols, Tx_bits, options);
ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
"plain_ffe", "baseline", block_update);
output.(char(storageName)) = ffe_results;
eq_noise_ffe = equalized_signal - Symbols;
if plot_output_signals
plotEqSignals(equalized_signal,Symbols,options,400,-1);
end
end
%% Moving Average Filter -> Conventional FFE
if run_ma
eq_ffe = FFE_plain( ...
"sps", eq_sps, ...
"order", eq_order, ...
"decide", false, ...
"adaption_technique", eq_adaption, ...
"len_tr", eq_len_tr, ...
"epochs_tr", eq_epochs_tr, ...
"mu_tr", eq_mu_tr, ...
"dd_mode", true, ...
"epochs_dd", eq_epochs_dd, ...
"mu_dd", 0.0012, ...
"optmize_mus", false, ...
"plot_mu_optimization", options.debug_plots, ...
"save_debug", eq_save_debug);
%%% APPLY MA filter
movavg_window_length = options.userParameters.bufferlen;
movavg_window_symmetry = "causal"; % "causal" or "noncausal"
switch movavg_window_symmetry
case "causal"
dc_estimate = movmean(Scpe_sig.signal, ...
[movavg_window_length-1,0],"Endpoints","shrink");
case "noncausal"
dc_estimate = movmean(Scpe_sig.signal, ...
movavg_window_length,"Endpoints","shrink");
otherwise
error("mpi_recipe_dev:InvalidMovAvgSymmetry", ...
"movavg_window_symmetry must be 'causal' or 'noncausal'.");
end
Scpe_sig_filt = Scpe_sig;
Scpe_sig_filt.signal = Scpe_sig.signal - dc_estimate;
clear dc_estimate;
storageName = "MovAvg_plus_conventional_FFE";
[ffe_results,equalized_signal] = runFfe(eq_ffe, "Moving-average + conventional FFE", ...
Scpe_sig_filt, Symbols, Tx_bits, options);
clear Scpe_sig_filt;
ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
"MovAvg_plus_conventional_FFE", char(movavg_window_symmetry), block_update);
output.(char(storageName)) = ffe_results;
if plot_output_signals
plotEqSignals(equalized_signal,Symbols,options,400,-1);
end
end
%% High Pass Filter -> Conventional FFE
if run_hpf
eq_ffe = FFE_plain( ...
"sps", eq_sps, ...
"order", eq_order, ...
"decide", false, ...
"adaption_technique", eq_adaption, ...
"len_tr", eq_len_tr, ...
"epochs_tr", eq_epochs_tr, ...
"mu_tr", eq_mu_tr, ...
"dd_mode", true, ...
"epochs_dd", eq_epochs_dd, ...
"mu_dd", 0.0012, ...
"optmize_mus", false, ...
"plot_mu_optimization", options.debug_plots, ...
"save_debug", eq_save_debug);
%%% APPLY HPF in MHZ order
f = Filter("f_cutoff",options.userParameters.hpf,"filterType","butterworth","lowpass",0,"filtdegree",4,"fs",options.fsym*2);
Scpe_sig_filt = f.process(Scpe_sig);
storageName = "HPF_plus_conventional_FFE";
[ffe_results,equalized_signal] = runFfe(eq_ffe, "Conventional FFE", ...
Scpe_sig_filt, Symbols, Tx_bits, options);
clear Scpe_sig_filt;
ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
"HPF_plus_conventional_FFE", "hpf", block_update);
output.(char(storageName)) = ffe_results;
if plot_output_signals
plotEqSignals(equalized_signal,Symbols,options,400,-1);
end
end
%% A2 tracked-level decision
if run_a2_tracked_levels
eq_ffe = FFE_A2TrackedLevels( ...
"sps", eq_sps, ...
"order", eq_order, ...
"decide", true, ...
"adaption_technique", eq_adaption, ...
"len_tr", eq_len_tr, ...
"epochs_tr", eq_epochs_tr, ...
"mu_tr", eq_mu_tr, ...
"dd_mode", true, ...
"epochs_dd", eq_epochs_dd, ...
"mu_dd", 0.012, ...
"dc_smoothing_a2", 1, ...
"dc_level_avg_bufferlength_a2", 112, ... %war 112
"dc_level_update_blocklength_a2", block_update, ... % war block_update
"dc_level_weights_a2", [1], ...
"save_debug", eq_save_debug);
storageName = "a2_adaptive_levels";
[ffe_results,equalized_signal] = runFfe(eq_ffe, "A2 tracked levels", ...
Scpe_sig, Symbols, Tx_bits, options);
ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
"a2_tracked_levels", "tracked_levels", block_update);
output.(char(storageName)) = ffe_results;
if plot_output_signals
plotEqSignals(equalized_signal,Symbols,options,410,-1);
end
end
%% A2 residual correction
if run_a2_residual
eq_ffe = FFE_A2Residual( ...
"sps", eq_sps, ...
"order", eq_order, ...
"decide", false, ...
"adaption_technique", eq_adaption, ...
"len_tr", eq_len_tr, ...
"epochs_tr", eq_epochs_tr, ...
"mu_tr", eq_mu_tr, ...
"dd_mode", true, ...
"epochs_dd", eq_epochs_dd, ...
"mu_dd", 0.012, ...
"dc_smoothing_a2", 1, ...
"dc_level_avg_bufferlength_a2", 112, ...
"dc_level_update_blocklength_a2", block_update, ...
"dc_level_weights_a2", [0.6], ...
"save_debug", eq_save_debug, "optmize_mus",0);
storageName = "a2_residual";
[ffe_results,equalized_signal] = runFfe(eq_ffe, "A2 residual", ...
Scpe_sig, Symbols, Tx_bits, options);
ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
"a2_residual", "residual_correction", block_update);
output.(char(storageName)) = ffe_results;
if plot_output_signals
plotEqSignals(equalized_signal,Symbols,options,420,-1);
end
end
%% A1 moving-average input suppression
if run_a1
eq_ffe = FFE_A1( ...
"sps", eq_sps, ...
"order", eq_order, ...
"decide", false, ...
"adaption_technique", eq_adaption, ...
"len_tr", eq_len_tr, ...
"epochs_tr", eq_epochs_tr, ...
"mu_tr", eq_mu_tr, ...
"dd_mode", false, ...
"epochs_dd", eq_epochs_dd, ...
"mu_dd", 0.012, ...
"dc_smoothing_a1", 1, ...
"dc_avg_bufferlength_a1", 2048, ... %was 2048
"dc_avg_update_blocklength_a1", block_update, ...
"save_debug", eq_save_debug);
storageName = "a1_ff_dc_avg";
[ffe_results,equalized_signal] = runFfe(eq_ffe, "A1", ...
Scpe_sig, Symbols, Tx_bits, options);
ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
"a1_moving_average", "ff_dc_avg", block_update);
output.(char(storageName)) = ffe_results;
if plot_output_signals
plotEqSignals(equalized_signal,Symbols,options,430,-1);
end
end
%% DC tracking, adaptive/persistence path
if run_tracking_adaptive
eq_ffe = FFE_DCTracking( ...
"sps", eq_sps, ...
"order", eq_order, ...
"decide", false, ...
"adaption_technique", eq_adaption, ...
"len_tr", eq_len_tr, ...
"epochs_tr", eq_epochs_tr, ...
"mu_tr", eq_mu_tr, ...
"dd_mode", true, ...
"epochs_dd", eq_epochs_dd, ...
"mu_dd", 0.012, ...
"dc_tracking_mu", 0.002, ...
"dc_tracking_adaptive_enabled", true, ...
"dc_tracking_persistence_gain", 0, ...
"dc_tracking_buffer_len", block_update, ...
"optmize_mus", false, ...
"optimize_dc_tracking_params", false, ...
"dc_tracking_optimization_len", 2^15, ...
"dc_tracking_optimization_max_evals", 30, ...
"plot_mu_optimization", options.debug_plots, ...
"save_debug", eq_save_debug);
storageName = "dc_tracking";
[ffe_results,equalized_signal] = runFfe(eq_ffe, "DC tracking adaptive", ...
Scpe_sig, Symbols, Tx_bits, options);
ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
"dc_tracking", "adaptive", block_update);
output.(char(storageName)) = ffe_results;
fignum = 106;
eq_noise = equalized_signal - Symbols;
dn = sprintf("FFE DCT; SIR: %d dB",options.dataTable.sir);
showEQNoisePSD(eq_noise, "fignum", fignum, "displayname", dn,"colormode","diverging");
ylim([-70 -30]);
%
% dn = sprintf("FFE only; SIR: %d dB",options.dataTable.sir);
% showEQNoisePSD(eq_noise_ffe, "fignum", fignum+1, "displayname", dn,"colormode","diverging");
% ylim([-70 -30]);
if plot_output_signals
plotEqSignals(equalized_signal,Symbols,options,450,-1);
end
end
end
function [ffe_results,equalized_signal] = runFfe(eq_ffe,description,Scpe_sig,Symbols,Tx_bits,options)
[ffe_results,equalized_signal] = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print("description",resultDescription(description,options));
end
function ffe_results = attachMpiReductionConfig(ffe_results, eq_ffe, storageName, ...
algorithm, algorithmVariant, block_update)
ffe_results.mpi_reduction_config = struct( ...
"storage_name", char(storageName), ...
"algorithm", char(algorithm), ...
"algorithm_variant", char(algorithmVariant), ...
"eq_class", class(eq_ffe), ...
"params", collectMpiReductionParams(eq_ffe, block_update));
end
function params = collectMpiReductionParams(eq_ffe, block_update)
params = struct();
params.block_update = block_update;
whitelistedProps = [ ...
"sps", ...
"order", ...
"decide", ...
"adaption_technique", ...
"len_tr", ...
"epochs_tr", ...
"mu_tr", ...
"dd_mode", ...
"epochs_dd", ...
"mu_dd", ...
"optmize_mus", ...
"plot_mu_optimization", ...
"save_debug", ...
"dc_smoothing_a1", ...
"dc_avg_bufferlength_a1", ...
"dc_avg_update_blocklength_a1", ...
"dc_smoothing_a2", ...
"dc_level_avg_bufferlength_a2", ...
"dc_level_update_blocklength_a2", ...
"dc_level_weights_a2", ...
"dc_tracking_mu", ...
"dc_tracking_adaptive_enabled", ...
"dc_tracking_persistence_gain", ...
"dc_tracking_buffer_len", ...
"dc_tracking_mu_eff_max", ...
"optimize_dc_tracking_params", ...
"dc_tracking_optimization_len", ...
"dc_tracking_optimization_max_evals", ...
"dc_tracking_optimization_delay_weight", ...
"dc_tracking_optimization_smoothing_len"];
for propIdx = 1:numel(whitelistedProps)
propName = char(whitelistedProps(propIdx));
if isprop(eq_ffe, propName)
params.(propName) = eq_ffe.(propName);
end
end
end
function description = resultDescription(prefix,options)
sir = options.dataTable.sir;
if numel(sir) > 1
sir = sir(1);
end
description = sprintf('%s; SIR %g dB',prefix,sir);
end
function plotEqSignals(equalized_signal,Symbols,options,fignum,output_scale)
showLevelScatter(equalized_signal .* output_scale, Symbols, ...
"fsym", options.fsym, ...
"fignum", fignum + 1, ...
"normalize", true);
end

View File

@@ -0,0 +1,319 @@
function [output] = dsp_runid(run_id, options)
arguments
run_id
options.append_to_db = 0;
options.max_occurences = 4;
options.parameters = struct();
options.database_type
options.dataBase
options.load_file_path = struct();
options.storage_path
options.mode
end
try
% Initialize output structures
output.ffe_package = {};
output.mlse_package = {};
output.vnle_package = {};
output.dbtgt_package = {};
output.dbenc_package = {};
output.mlmlse_package = {};
if options.mode == "load_run_id" || options.append_to_db
% Initialize database connection
database = DBHandler("dataBase", [options.dataBase], "type", options.database_type, 'user',"silas","password","silas","server","134.245.243.254");
if 0
% 2. Check if an equalizer configuration with the same hash exists
queryStr = sprintf('SELECT COUNT(DISTINCT eq_id) AS unique_eq_count, COUNT(*) AS entries_for_run FROM `Results` WHERE run_id = %d', run_id);
existing_results = database.fetch(queryStr);
if existing_results.unique_eq_count >= 6
if (existing_results.entries_for_run / existing_results.unique_eq_count) > 5
return
end
end
end
end
if options.mode == "load_run_id"
dataTable = queryRunid(run_id, database);
fsym = dataTable.symbolrate;
M = double(dataTable.pam_level);
duob_mode = db_mode(strrep(dataTable.db_mode,'"',''));
% if database.checkIfRunExists('Results','run_id',run_id)
% disp(['Already got at least one reulst for run id: ',num2str(run_id),' '])
% return
% end
% Load and Sync signal data from DB
[Tx_bits, Symbols, Scpe_cell, ~] = loadAndSyncSignalDataFromDb(dataTable, options);
elseif options.mode == "load_files"
Tx_bits = load(options.load_file_path.tx_bits_path);
Symbols = load(options.load_file_path.tx_symbols_path);
Scpe_sig_raw = load(options.load_file_path.rx_raw_path);
Tx_bits = Tx_bits.Bits;
Symbols = Symbols.Symbols;
Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw;
fsym = Symbols.fs;
M = Symbols.logbook.ModifierCopy{1}.M;
duob_mode = Symbols.logbook.ModifierCopy{1}.duobinary_mode;
Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in", Scpe_sig_raw.fs, "fs_out", 2*fsym);
[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
else
% Run quick Simulation
tx_simulation;
end
% Handle Settings and argument replacement
len_tr = 4096*2;
ffe_order = [50, 5, 5];
dfe_order = [0, 0, 0];
pf_ncoeffs = 1;
mu_ffe = [0.0001, 0.0008, 0.001];
mu_dfe = 0.0004;
mu_dc = 0.005;
dc_buffer_len = 1;
mu_tr = 0;
mu_dd = 0.05;
adaption= 1;
use_dd_mode = 1;
use_ffe = 0;
use_dfe = 0;
use_vnle_mlse = 0;
use_dbtgt = 0;
use_dbenc = 0;
use_ml_mlse = 1;
addProcessingResultToDatabase = 0;
% Overwrite default parameters if given in options.parameters
paramStruct = options.parameters;
if ~isempty(paramStruct)
paramNames = fieldnames(paramStruct);
for i = 1:numel(paramNames)
thisName = paramNames{i};
thisValue = paramStruct.(thisName);
eval([thisName ' = thisValue;']);
end
end
% Configure equalizers
options.max_occurences = min(options.max_occurences,length(Scpe_cell));
for r = 1:options.max_occurences
%FFE
% eq_dfe = FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
%
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
% mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0,"adaption_technique","lms");
eq_post = FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
% Duobinary signaling (db encoded)
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels);
eq_db_enc = EQ("Ne", ffe_order, "Nb", dfe_order, "training_length", len_tr, ...
"training_loops", 5, "dd_loops", 5, "K", 2, "DCmu", mu_dc, ...
"DDmu", [mu_ffe mu_dfe], "DFEmu", 0.005, "FFEmu", 0, "plotfinal", 0, "ideal_dfe", 1);
% Preprocess signal
Scpe_sig = preprocessSignal(Scpe_cell{r}, Symbols, fsym);
Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',-6);
Scpe_sig.spectrum("fignum",201,"normalizeTo0dB",0,"displayname",'Rx');
ylim([-30,3]);
xlim([-5,100]);
% Scpe_sig.spectrum("fignum",22233,"normalizeTo0dB",0,"displayname",'Rx');
% Scpe_sig.eye(fsym,M,"fignum",1024);
Scpe_sig.signal = Scpe_sig.signal(1:2*Symbols.length);
Scpe_sig.signal = real(Scpe_sig.signal);
if duob_mode ~= db_mode.db_encoded
if use_ffe
ffe_order = [50, 0, 0];
eq_dfe = EQ("Ne",ffe_order,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
ffe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,...
"precode_mode",duob_mode,...
'showAnalysis',0,...
"postFFE",[],...
"eth_style_symbol_mapping",0);
output.ffe_package{r} = ffe_results;
ffe_results.metrics.print;
ffe_results.config.equalizer_structure = "ffe";
if options.append_to_db
database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config);
end
end
if use_dfe
ffe_order = [50, 5, 5];
eq_dfe = EQ("Ne",ffe_order,"Nb",[2,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
dfe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits,...
"precode_mode",duob_mode,...
'showAnalysis',0,...
"postFFE",[],...
"eth_style_symbol_mapping",0);
output.ffe_package{r} = dfe_results;
dfe_results.config.equalizer_structure = "dfe";
dfe_results.metrics.print;
if options.append_to_db
database.addProcessingResult(run_id, dfe_results.metrics, dfe_results.config);
end
end
if use_vnle_mlse
pf_ncoeffs = 1;
ffe_order = [50, 5, 5];
eq_ = EQ("Ne",ffe_order,"Nb",dfe_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
% eq_ = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0.0004,"order",[50,5,5],"sps",2,"decide",0);
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
useviterbi = 0;
if useviterbi
mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
else
if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation
trellexlusion = 1;
else
trellexlusion = 0;
end
%state_mode 3 -> stat lvl; state_mode 2 -> use target lvls
%scale_mode 2 -> mmse adaption
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',2);
end
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode,...
'showAnalysis', 0, ...
"postFFE", [],...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print;
ffe_results.config.equalizer_structure = "vnle";
mlse_results.metrics.print;
output.mlse_package{r} = mlse_results;
output.vnle_package{r} = ffe_results;
if options.append_to_db
database.addProcessingResult(run_id, mlse_results.metrics, mlse_results.config);
database.addProcessingResult(run_id, ffe_results.metrics, ffe_results.config);
end
end
if use_ml_mlse
%ML-based MLSE (L=2)
mu_ml = 0.01; training_epochs = 100;
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
"len_tr",length(Scpe_sig)/4,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
"traceback_depth",128,"L",1,"delta",4,"adaptive_mu",0);
[ml_mlse_results] = ml_mlse(ml_mlse_equalizer, M, Scpe_sig, Symbols, Tx_bits,"precode_mode",duob_mode);
output.mlmlse_package{r} = ml_mlse_results;
if options.append_to_db
database.addProcessingResult(run_id, ml_mlse_results.metrics, ml_mlse_results.config);
end
end
if use_dbtgt
useviterbi = 0;
if useviterbi
mlse_db_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
else
if duob_mode == db_mode.no_db && M == 6 %only for PAM-6 and no duobinary precoding, otherwise leads to false sequence estimation
trellexlusion = 1;
else
trellexlusion = 0;
end
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',3);
end
ffe_order = [50, 5, 5];
eq_ = EQ("Ne",ffe_order,"Nb",dfe_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, ...
'showAnalysis', 0,...
"postFFE", []);
dbt_results.metrics.print;
output.dbtgt_package{r} = dbt_results;
if options.append_to_db
database.addProcessingResult(run_id, dbt_results.metrics, dbt_results.config);
end
end
end
if duob_mode == db_mode.db_encoded
mlse_db_enc = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels);
db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",0,"postFFE",[]);
output.dbenc_package{r} = db_results;
if options.append_to_db
database.addProcessingResult(run_id, db_results.metrics, db_results.config);
end
end
end
catch ME
save('workerError.mat','ME');
rethrow(ME);
end
end

View File

@@ -14,7 +14,6 @@ function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx
options.decoding_mode db_decoder = db_decoder.sequencedetection;
end
%Duobinary Targeting
db_ref_sequence = Duobinary().encode(tx_symbols);
@@ -30,18 +29,16 @@ function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx
case db_decoder.sequencedetection %MLSE
mlse_.DIR = [1,1];
if isa(mlse_,'MLSE_viterbi')
mlse_sig_sd = mlse_.process(eq_signal);
pam_sig_sd = mlse_.process(eq_signal);
else
[mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols);
[pam_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols);
end
pam_sig_hd = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).quantize(mlse_sig_sd);
pam_sig_hd = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).quantize(pam_sig_sd);
case db_decoder.memoryless %DB Target FFE
% Hard decision on FFE output
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal,'custom_const',db_ref_constellation.');
eq_signal_hd = Duobinary().decode(eq_signal_hd);
pam_sig_hd = eq_signal_hd;
% tx_symbols = Duobinary().encode(tx_symbols);
% tx_symbols = Duobinary().decode(tx_symbols.*db_const_scale_factor);
end
% precoding to mitigate error propagation, most prominently used in
@@ -53,73 +50,59 @@ function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx
% TX Data is not precoded:
% A) Emulate diff precoding
switch options.decoding_mode
case db_decoder.sequencedetection %MLSE
mlse_sig_hd_precoded = Duobinary().encode(pam_sig_hd);
mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded);
case db_decoder.memoryless %DB Target FFE
% mlse_sig_hd_precoded = Duobinary().encode(pam_sig_hd);
% mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded);
mlse_sig_hd_precoded = pam_sig_hd;
if options.decoding_mode == db_decoder.sequencedetection
pam_sig_hd_precoded = Duobinary().encode(pam_sig_hd,"M",M);
pam_sig_hd_precoded = Duobinary().decode(pam_sig_hd_precoded,"M",M);
else
pam_sig_hd_precoded = pam_sig_hd;
end
tx_symbols_precoded = Duobinary().encode(tx_symbols);
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
tx_bits_precoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols_precoded);
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_precoded);
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd_precoded);
[bits_db,errors_db_diff_precoded,ber_db_diff_precoded,~] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
[~,errors_db_diff_precoded,ber_db_diff_precoded,~] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
%B) Just determine BER
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols);
[bits_mlse,errors_mlse,ber_db,~] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
if options.decoding_mode == db_decoder.sequencedetection
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols);
[bits_mlse,errors_mlse,ber_db,~] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
else
ber_db = NaN;
errors_db = NaN;
end
case db_mode.db_precoded
% Daten SIND TATSÄCHLICH precoded auf TX Seite:
% A) Decode at Rx if no DB targeting was applied (we are in VNLE or MLSE EQ structure here!
switch options.decoding_mode
case db_decoder.sequencedetection %MLSE
mlse_sig_hd_decoded = Duobinary().encode(pam_sig_hd,"M",M);
mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded,"M",M);
case db_decoder.memoryless %DB Target FFE
mlse_sig_hd_decoded = pam_sig_hd;
if options.decoding_mode == db_decoder.sequencedetection
pam_sig_hd_decoded = Duobinary().encode(pam_sig_hd,"M",M);
pam_sig_hd_decoded = Duobinary().decode(pam_sig_hd_decoded,"M",M);
else
pam_sig_hd_decoded = pam_sig_hd;
end
tx_symbols_precoded = Duobinary().encode(tx_symbols,"M",M);
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded,"M",M);
tx_bits_precoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols_precoded);
rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_decoded);
[~,errors_db_diff_precoded,ber_db_diff_precoded,a] = calc_ber(rx_bits_mlse_decoded.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd_decoded);
[bits_db,errors_db_diff_precoded,ber_db_diff_precoded,a] = calc_ber(rx_bits_mlse_decoded.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db_precoded = count_error_bursts(a, 40);
% B) Omit the Coding by comparing with demapped TX symbol sequence
tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols);
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
[bits_db,errors_db,ber_db,a] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db = count_error_bursts(a, 40);
cols = linspecer(8);
figure();hold on;
stem(1:40,burst_db,'LineWidth',1,'Color',cols(4,:),'Marker','_','DisplayName','w/o diff. precoder');
stem(1:40,burst_db_precoded,'LineWidth',1,'Color',cols(3,:),'Marker','.','LineStyle','-','DisplayName','w diff. precoder');
xlabel('Bit Error Burst Length')
ylabel('Occurence')
set(gca, 'yscale', 'log');
if options.decoding_mode == db_decoder.sequencedetection
tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols);
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
[bits_db,errors_db,ber_db,a] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db = count_error_bursts(a, 40);
else
ber_db = NaN;
errors_db = NaN;
end
end
% M = numel(unique(tx_symbols.signal));
rx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
[bits_db,errors_db,ber_db,errorIndice_db] = calc_ber(rx_bits.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
alpha = arburg(eq_noise.signal,1);%pf_.coefficients(2);
alpha = alpha(2);
switch options.decoding_mode
case db_decoder.sequencedetection %MLSE
@@ -133,8 +116,8 @@ function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx
case db_decoder.memoryless %DB Target FFE
% [gmi] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000);
[gmi] = calc_ngmi(eq_signal,tx_symbols);
gmi_mlse = max(gmi,0);
air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M));
gmi_mlse = NaN;
air_mlse = NaN;
end
db_results = struct();
@@ -151,7 +134,7 @@ function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx
db_results.metrics.GMI = gmi_mlse;
db_results.metrics.AIR = air_mlse;
db_results.metrics.MLSE_dir = mlse_.DIR;
db_results.metrics.Alpha = alpha;
db_results.metrics.Alpha = NaN;
% Create DB results structure
db_results.config = Equalizerstruct();
@@ -173,15 +156,14 @@ function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx
rx_signal.spectrum("normalizeTo0dB",1,"fignum",250,"displayname","Rx Spectrum");
Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",250,"displayname","DB encoded reference");
Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",10,"displayname","DB encoded reference");
showEQNoisePSD(eq_noise,"fignum",250,"displayname",'Duobinary Target Noise after Equalization');
fprintf('DB tgt BER: %.2e \n',ber_db);
figure(341); clf;
tx_symbols_uncoded = Duobinary().decode(db_ref_sequence);
showLevelHistogram(eq_signal, db_ref_sequence, "fignum", 341,"ref_symbol_uncoded",tx_symbols_uncoded);
showLevelHistogram(eq_signal, db_ref_sequence, "fignum", 341);
end

View File

@@ -1,4 +1,4 @@
function [ffe_results,eq_signal_sd] = ffe(eq_, M, rx_signal, tx_symbols, tx_bits, options)
function [ffe_results] = ffe(eq_, M, rx_signal, tx_symbols, tx_bits, options)
% FFE Processes signals through FFE equalizer
%
% Inputs:
@@ -74,22 +74,13 @@ end
% Create FFE results structure
ffe_results = struct();
config_clear_props = { ...
"e", ...
"e2", ...
"e3", ...
"b", ...
"b2", ...
"b3", ...
"mu_optimization", ...
"dc_optimization", ...
"dc_tracking_optimization", ...
"a2_level_weight_optimization"};
for config_clear_idx = 1:numel(config_clear_props)
prop_name = config_clear_props{config_clear_idx};
if isprop(eq_,prop_name)
eq_.(prop_name) = [];
end
try
eq_.e = [];
eq_.e2 = [];
eq_.e3 = [];
eq_.b = [];
eq_.b2 = [];
eq_.b3 = [];
end
ffe_results.config = Equalizerstruct();
@@ -177,8 +168,6 @@ function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M,
showEQNoisePSD(eq_noise, "fignum", 103, "displayname", 'Residual Noise after FFE');
% showEQcoefficients('n1', eq_.e, "displayname", 'Coefficients', 'fignum', 104);
% Figure 2: Post-FFE coefficients (if available)
if ~isempty(postFFE)
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 104);
@@ -187,11 +176,10 @@ function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M,
try
figure(339);hold on
showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'training','fignum',339);
% showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'dec. directed','fignum',339);
legend on
end
show2Dconstellation(eq_signal_sd, tx_symbols,"displayname",'Visualization of symbol correlation','fignum',340);
% try
% figure(240); hold on; plot(pow2db(movmean(eq_.debug_struct.error_tr',100)));ylim([-30,3]);title('error training');
%
@@ -200,6 +188,6 @@ function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M,
% figure(242); hold on; plot(pow2db(movmean(eq_.debug_struct.update',1000)));title('update step dd');
% end
% eq_signal_sd.eye(eq_signal_sd.fs,M,"displayname",'Eye','fignum',105);
eq_signal_sd.eye(eq_signal_sd.fs,M,"displayname",'Eye','fignum',105);
end
end

View File

@@ -48,8 +48,6 @@ eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
[mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise);
[tx_symbols_pr,~] = pf_.process(tx_symbols, eq_noise);
if 0 %tx_symbols.fs > 190e9
if pf_.ncoeff == 1
if pf_.coefficients(2) < 0
@@ -274,35 +272,34 @@ eq_noise.spectrum("displayname",'Equalized Signal','fignum',336,'normalizeTo0dB'
showEQNoisePSD(eq_noise, "fignum", 338, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
for t = 1:4
pf_.ncoeff = t;
[~,~] = pf_.process(eq_signal_sd, eq_noise);
showEQNoisePSD(eq_noise, "fignum", 339, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
showEQNoisePSD(eq_noise, "fignum", 3388, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
end
tx_symbols.spectrum("displayname",'Equalized Signal','fignum',340,'normalizeTo0dB',1);
tx_symbols.spectrum("displayname",'Equalized Signal','fignum',1234,'normalizeTo0dB',1);
if ~isempty(postFFE)
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 341);
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 338);
end
showEQcoefficients('n1', eq_.e,'n2', eq_.e2,'n3', eq_.e3, "displayname", 'Coefficients', 'fignum', 339);
% showEQcoefficients('n1', eq_.e1,'n2', eq_.e2,'n3', eq_.e3, "displayname", 'Coefficients', 'fignum', 339);
showEQfilter(eq_.e, eq_signal_sd.fs.*2);
figure(340); clf;
eq_signal_sd.eye(eq_signal_sd.fs, M, "fignum", 342);
eq_signal_sd.eye(eq_signal_sd.fs, M, "fignum", 340);
figure(341); clf;
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 343);
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 341);
warning off
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 344);
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 400);
% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401);
drawnow;
warning on
whitened_noise.spectrum("displayname",'after postfilter','fignum',345);
eq_noise.spectrum("displayname",'before postfilter','fignum',345);
whitened_noise.spectrum("displayname",'after postfilter','fignum',342);
eq_noise.spectrum("displayname",'before postfilter','fignum',342);
end

View File

@@ -1,320 +0,0 @@
function exportLevelScatterTikz(sourceAx, pngFile, tikzFile, pngTikzPath, options)
%EXPORTLEVELSCATTERTIKZ Export showLevelScatter axes as PNG-backed TikZ.
% Rasterizes scatter objects into a PNG layer and writes average lines plus
% text annotations as PGFPlots objects.
arguments
sourceAx
pngFile (1,1) string
tikzFile (1,1) string
pngTikzPath (1,1) string
options.resolutionDpi (1,1) double {mustBePositive, mustBeInteger} = 150
options.xLabel (1,1) string = "Time in $\mu$s"
options.yLabel (1,1) string = "Normalized Amplitude"
options.generatedBy (1,1) string = "exportLevelScatterTikz.m"
end
outputDir = fileparts(pngFile);
if ~exist(outputDir, "dir")
mkdir(outputDir);
end
exportScatterLayerPng(sourceAx, pngFile, options.resolutionDpi);
linePlots = collectTikzLinePlots(sourceAx);
textAnnotations = collectTikzTextAnnotations(sourceAx);
writeTikzPngAxis(tikzFile, pngTikzPath, sourceAx, linePlots, textAnnotations, options);
end
function exportScatterLayerPng(sourceAx, pngFile, resolutionDpi)
sourceFig = ancestor(sourceAx, "figure");
xLimits = sourceAx.XLim;
yLimits = sourceAx.YLim;
scatterHandles = findGraphicsObjectsByType(sourceAx, "scatter");
exportFig = figure( ...
"Visible", "off", ...
"Color", "white", ...
"Units", sourceFig.Units, ...
"Position", sourceFig.Position);
cleanupFigure = onCleanup(@() close(exportFig));
exportAx = copyobj(sourceAx, exportFig);
exportAx.Units = "normalized";
exportAx.Position = [0 0 1 1];
exportAx.XLim = xLimits;
exportAx.YLim = yLimits;
exportAx.XLimMode = "manual";
exportAx.YLimMode = "manual";
stripAxesToScatterOnly(exportAx);
hideAxesInkForRasterExport(exportAx);
fprintf("Exporting scatter PNG with XLim=[%g %g], YLim=[%g %g], scatter objects=%d: %s\n", ...
xLimits(1), xLimits(2), yLimits(1), yLimits(2), numel(scatterHandles), pngFile);
drawnow;
exportFig.PaperPositionMode = "auto";
print(exportFig, char(pngFile), "-dpng", sprintf("-r%d", resolutionDpi));
end
function stripAxesToScatterOnly(ax)
plotObjects = findall(ax);
for objIdx = 1:numel(plotObjects)
obj = plotObjects(objIdx);
if obj == ax || ~isvalid(obj)
continue
end
objType = string(get(obj, "Type"));
if lower(objType) ~= "scatter"
delete(obj);
end
end
end
function hideAxesInkForRasterExport(ax)
ax.Visible = "on";
ax.Color = "white";
ax.Box = "off";
ax.XGrid = "off";
ax.YGrid = "off";
ax.XMinorGrid = "off";
ax.YMinorGrid = "off";
ax.XTick = [];
ax.YTick = [];
ax.XColor = "white";
ax.YColor = "white";
ax.Title.String = "";
ax.XLabel.String = "";
ax.YLabel.String = "";
end
function handles = findGraphicsObjectsByType(parentHandle, objectType)
allHandles = findall(parentHandle);
matches = false(size(allHandles));
for handleIdx = 1:numel(allHandles)
currentHandle = allHandles(handleIdx);
if ~isvalid(currentHandle)
continue
end
matches(handleIdx) = lower(string(get(currentHandle, "Type"))) == lower(string(objectType));
end
handles = allHandles(matches);
end
function linePlots = collectTikzLinePlots(sourceAx)
lineHandles = findGraphicsObjectsByType(sourceAx, "line");
lineHandles = flipud(lineHandles(:));
linePlots = repmat(struct( ...
"xData", [], ...
"yData", [], ...
"color", [], ...
"lineWidth", []), numel(lineHandles), 1);
validLineCount = 0;
for lineIdx = 1:numel(lineHandles)
lineHandle = lineHandles(lineIdx);
xData = lineHandle.XData(:);
yData = lineHandle.YData(:);
validSamples = isfinite(xData) & isfinite(yData);
if ~any(validSamples)
continue
end
validLineCount = validLineCount + 1;
linePlots(validLineCount).xData = xData(validSamples);
linePlots(validLineCount).yData = yData(validSamples);
linePlots(validLineCount).color = lineHandle.Color;
linePlots(validLineCount).lineWidth = lineHandle.LineWidth;
end
linePlots = linePlots(1:validLineCount);
end
function textAnnotations = collectTikzTextAnnotations(sourceAx)
textHandles = findGraphicsObjectsByType(sourceAx, "text");
textHandles = flipud(textHandles(:));
xLimits = sourceAx.XLim;
yLimits = sourceAx.YLim;
textAnnotations = repmat(struct( ...
"x", [], ...
"y", [], ...
"text", "", ...
"anchor", "center"), numel(textHandles), 1);
validTextCount = 0;
for textIdx = 1:numel(textHandles)
textHandle = textHandles(textIdx);
labelText = string(textHandle.String);
if strlength(strtrim(labelText)) == 0
continue
end
textPosition = textHandle.Position;
if textPosition(1) < xLimits(1) || textPosition(1) > xLimits(2) || ...
textPosition(2) < yLimits(1) || textPosition(2) > yLimits(2)
continue
end
validTextCount = validTextCount + 1;
textAnnotations(validTextCount).x = textPosition(1);
textAnnotations(validTextCount).y = textPosition(2);
textAnnotations(validTextCount).text = labelText;
textAnnotations(validTextCount).anchor = matlabTextAlignmentToTikzAnchor( ...
textHandle.HorizontalAlignment, textHandle.VerticalAlignment);
end
textAnnotations = textAnnotations(1:validTextCount);
end
function writeTikzPngAxis(tikzFile, pngTikzPath, sourceAx, linePlots, textAnnotations, options)
outputDir = fileparts(tikzFile);
if ~exist(outputDir, "dir")
mkdir(outputDir);
end
xLimits = sourceAx.XLim;
yLimits = sourceAx.YLim;
fid = fopen(tikzFile, "w");
if fid < 0
error("exportLevelScatterTikz:TikzOpenFailed", ...
"Could not open TikZ export file: %s", tikzFile);
end
cleanupFile = onCleanup(@() fclose(fid));
axisOptions = {
" every axis/.append style={font=\scriptsize},"
"width=\fwidth,"
"height=\fheight,"
"at={(0\fwidth,0\fheight)},"
"scale only axis,"
"axis on top,"
"xmin=" + formatPgfNumber(xLimits(1)) + ","
"xmax=" + formatPgfNumber(xLimits(2)) + ","
"xlabel style={font=\color{white!15!black}\small},"
"xlabel={" + options.xLabel + "},"
"ymin=" + formatPgfNumber(yLimits(1)) + ","
"ymax=" + formatPgfNumber(yLimits(2)) + ","
"ylabel style={font=\color{white!15!black}\small},"
"ylabel={" + options.yLabel + "},"
"axis background/.style={fill=white},"
"xmajorgrids,"
"ymajorgrids,"
"grid style={line width=0.4pt, dotted, color=black!20},"
"scaled ticks=false,"
"tick label style={/pgf/number format/fixed, /pgf/number format/1000 sep={}},"
"legend columns=1"
};
fprintf(fid, "%% This file was generated by %s.\n", options.generatedBy);
fprintf(fid, "%%\n");
for lineIdx = 1:numel(linePlots)
fprintf(fid, "%s\n", formatTikzColorDefinition(lineIdx, linePlots(lineIdx).color));
end
if ~isempty(linePlots)
fprintf(fid, "%%\n");
end
fprintf(fid, "%s\n\n", "\begin{tikzpicture}");
fprintf(fid, "%s\n", "\begin{axis}[%");
for optionIdx = 1:numel(axisOptions)
fprintf(fid, "%s\n", axisOptions{optionIdx});
end
fprintf(fid, "%s\n", "]");
graphicsLine = sprintf( ...
"\\addplot [forget plot] graphics [xmin=%s, xmax=%s, ymin=%s, ymax=%s] {%s};", ...
formatPgfNumber(xLimits(1)), ...
formatPgfNumber(xLimits(2)), ...
formatPgfNumber(yLimits(1)), ...
formatPgfNumber(yLimits(2)), ...
char(strrep(pngTikzPath, "\", "/")));
fprintf(fid, "%s\n\n", graphicsLine);
writeTikzLinePlots(fid, linePlots);
writeTikzTextAnnotations(fid, textAnnotations);
fprintf(fid, "%s\n\n", "\end{axis}");
fprintf(fid, "%s", "\end{tikzpicture}%");
end
function valueText = formatPgfNumber(value)
valueText = string(sprintf("%.15g", value));
end
function colorDefinition = formatTikzColorDefinition(colorIdx, rgbColor)
colorDefinition = sprintf( ...
"\\definecolor{mycolor%d}{rgb}{%.5f,%.5f,%.5f}%%", ...
colorIdx, rgbColor(1), rgbColor(2), rgbColor(3));
end
function writeTikzLinePlots(fid, linePlots)
for lineIdx = 1:numel(linePlots)
fprintf(fid, "\\addplot [color=mycolor%d, line width=%.1fpt, forget plot]\n", ...
lineIdx, linePlots(lineIdx).lineWidth);
fprintf(fid, " table[row sep=crcr]{%%\n");
for sampleIdx = 1:numel(linePlots(lineIdx).xData)
fprintf(fid, "%s\t%s\\\\\n", ...
formatPgfNumber(linePlots(lineIdx).xData(sampleIdx)), ...
formatPgfNumber(linePlots(lineIdx).yData(sampleIdx)));
end
fprintf(fid, "};\n\n");
end
end
function writeTikzTextAnnotations(fid, textAnnotations)
for textIdx = 1:numel(textAnnotations)
fprintf(fid, "\\node[font=\\scriptsize, anchor=%s, fill=white, draw=black!40, rounded corners=2pt, inner sep=2pt]\n", ...
textAnnotations(textIdx).anchor);
fprintf(fid, " at (axis cs:%s,%s) {%s};\n\n", ...
formatPgfNumber(textAnnotations(textIdx).x), ...
formatPgfNumber(textAnnotations(textIdx).y), ...
escapeTikzText(textAnnotations(textIdx).text));
end
end
function anchor = matlabTextAlignmentToTikzAnchor(horizontalAlignment, verticalAlignment)
horizontalAlignment = string(horizontalAlignment);
verticalAlignment = string(verticalAlignment);
if horizontalAlignment == "left"
horizontalAnchor = "west";
elseif horizontalAlignment == "right"
horizontalAnchor = "east";
else
horizontalAnchor = "";
end
if verticalAlignment == "top"
verticalAnchor = "north";
elseif verticalAlignment == "bottom"
verticalAnchor = "south";
else
verticalAnchor = "";
end
if strlength(horizontalAnchor) > 0 && strlength(verticalAnchor) > 0
anchor = verticalAnchor + " " + horizontalAnchor;
elseif strlength(horizontalAnchor) > 0
anchor = horizontalAnchor;
elseif strlength(verticalAnchor) > 0
anchor = verticalAnchor;
else
anchor = "center";
end
end
function textOut = escapeTikzText(textIn)
textOut = char(textIn);
% textOut = strrep(textOut, "\", "\textbackslash{}");
textOut = strrep(textOut, "{", "\{");
textOut = strrep(textOut, "}", "\}");
textOut = strrep(textOut, "%", "\%");
textOut = strrep(textOut, "&", "\&");
textOut = strrep(textOut, "#", "\#");
textOut = strrep(textOut, "_", "\_");
% textOut = strrep(textOut, "$", "\$");
end

View File

@@ -4,12 +4,6 @@ arguments
ref_symbols
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [];
options.clear = true;
options.rasterize = false;
options.raster_resolution (1,1) double = 1000;
options.raster_markersize (1,1) double = 1;
options.raster_clim = [];
end
plot_shit = 1;
@@ -28,9 +22,7 @@ if plot_shit
fig = figure; % Create a new figure and get its handle
else
fig = figure(options.fignum); % Use the specified figure number
if options.clear
clf;
end
clf;
end
end
@@ -38,9 +30,7 @@ end
rx_symbols = eq_signal; %./ rms(eq_signal);
correct_symbols = ref_symbols;
col = cbrewer2('Paired',numel(unique(correct_symbols))*3);
useDefaultColormap = isempty(options.color);
scatterColor = options.color;
col = cbrewer2('Paired',numel(unique(correct_symbols))*2);
ccnt = -1;
levels = unique(correct_symbols);
@@ -76,146 +66,20 @@ D = sqrt(D_even.^2 + D_odd.^2);
[X,Y] = meshgrid(levels, levels);
[X_,Y_] = meshgrid(statistical_mean, statistical_mean);
plot_xlim = [floor(min(X_(:)))-1, ceil(max(X_(:)))+1];
plot_ylim = [floor(min(Y_(:)))-1, ceil(max(Y_(:)))+1];
hold on;
if isempty(options.displayname)
displayName = ['Even/Odd PAM-',num2str(M)];
else
displayName = options.displayname;
end
if useDefaultColormap
cols = flip(cbrewer2('RdYlBu',100));
cols=(cbrewer2("RdYlBu",4096));
% cols(45:55,:) = [];
colormap(gca,cols);
if isempty(options.raster_clim)
cLim = [0 max(D).*0.45];
else
cLim = options.raster_clim;
end
if cLim(2) <= cLim(1)
cLim(2) = cLim(1) + eps;
end
clim(cLim);
% colormap(gca,'hsv');
if options.rasterize
nBins = options.raster_resolution;
xEdges = linspace(plot_xlim(1),plot_xlim(2),nBins+1);
yEdges = linspace(plot_ylim(1),plot_ylim(2),nBins+1);
xBin = discretize(rx_even,xEdges);
yBin = discretize(rx_odd,yEdges);
valid = ~isnan(xBin) & ~isnan(yBin) & isfinite(D);
countImg = accumarray([yBin(valid), xBin(valid)],1,[nBins nBins],@sum,0);
dImg = accumarray([yBin(valid), xBin(valid)],D(valid),[nBins nBins],@mean,NaN);
countImgRaw = countImg;
markerRadius = max(0, round((options.raster_markersize - 1) / 2));
if markerRadius > 0
kernel = ones(2*markerRadius+1);
countImg = conv2(countImg,kernel,'same');
weightedD = dImg;
weightedD(~isfinite(weightedD)) = 0;
weightedD = conv2(weightedD .* countImgRaw,kernel,'same');
dWeight = conv2(isfinite(dImg) .* countImgRaw,kernel,'same');
dImg = weightedD ./ dWeight;
end
colorIdx = round((dImg-cLim(1)) ./ diff(cLim) * (size(cols,1)-1)) + 1;
colorIdx = min(max(colorIdx,1),size(cols,1));
density = log1p(countImg);
if max(density(:)) > 0
density = density ./ max(density(:));
end
rgbImg = ones(nBins,nBins,3);
for rgbIdx = 1:3
colorPlane = reshape(cols(colorIdx,rgbIdx),nBins,nBins);
colorPlane(~isfinite(dImg)) = 1;
rgbImg(:,:,rgbIdx) = (1-density) + density .* colorPlane;
end
hImg = image(linspace(plot_xlim(1),plot_xlim(2),nBins), ...
linspace(plot_ylim(1),plot_ylim(2),nBins), ...
rgbImg);
hImg.HandleVisibility = 'off';
set(gca,'YDir','normal');
uistack(hImg,'bottom');
plot(nan,nan,'.','DisplayName',displayName,'MarkerEdgeColor',col(2,:));
else
sz = ones(1,length(D)).*1;
scatter(rx_even,rx_odd,sz,D,'.','DisplayName',displayName);
end
rxLevelColor = col(6,:);
else
if options.rasterize
nBins = options.raster_resolution;
xEdges = linspace(plot_xlim(1),plot_xlim(2),nBins+1);
yEdges = linspace(plot_ylim(1),plot_ylim(2),nBins+1);
xBin = discretize(rx_even,xEdges);
yBin = discretize(rx_odd,yEdges);
valid = ~isnan(xBin) & ~isnan(yBin);
countImg = accumarray([yBin(valid), xBin(valid)],1,[nBins nBins],@sum,0);
markerRadius = max(0, round((options.raster_markersize - 1) / 2));
if markerRadius > 0
kernel = ones(2*markerRadius+1);
countImg = conv2(countImg,kernel,'same');
end
density = log1p(countImg);
if max(density(:)) > 0
density = density ./ max(density(:));
end
rgbImg = ones(nBins,nBins,3);
for rgbIdx = 1:3
rgbImg(:,:,rgbIdx) = (1-density) + density .* scatterColor(rgbIdx);
end
hImg = image(linspace(plot_xlim(1),plot_xlim(2),nBins), ...
linspace(plot_ylim(1),plot_ylim(2),nBins), ...
rgbImg);
hImg.HandleVisibility = 'off';
set(gca,'YDir','normal');
uistack(hImg,'bottom');
plot(nan,nan,'.','DisplayName',displayName,'MarkerEdgeColor',scatterColor);
else
scatter(rx_even,rx_odd,5*ones(1,length(D)),'.','DisplayName',displayName,'MarkerEdgeColor',scatterColor);
end
rxLevelColor = scatterColor;
end
scatter(X_(:), Y_(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', rxLevelColor,'DisplayName',[displayName, ' Rx Levels']);
scatter(rx_even,rx_odd,5*ones(1,length(D)),D,'.','DisplayName',['Even/Odd PAM-',num2str(M)],'MarkerEdgeColor',col(2,:));
% colormap(gca,flip(cbrewer2('Spectral',100)))
colormap(gca,'hsv');
scatter(X_(:), Y_(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(6,:),'DisplayName','Statistical Rx Levels');
scatter(X(:), Y(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(4,:),'DisplayName','Tx Levels');
xlim(plot_xlim);
ylim(plot_ylim);
xlim([floor(min(X_(:)))-1, ceil(max(X_(:)))+1]);
ylim([floor(min(Y_(:)))-1, ceil(max(Y_(:)))+1]);
decisionBoundaries = (levels(1:end-1) + levels(2:end)) / 2;
ax = gca;
xticks(levels);
yticks(levels);
xticklabels(compose('%g',levels));
yticklabels(compose('%g',levels));
try
ax.XAxis.MinorTickValues = decisionBoundaries;
ax.YAxis.MinorTickValues = decisionBoundaries;
end
ax.XMinorTick = 'on';
ax.YMinorTick = 'on';
ax.XMinorGrid = 'on';
ax.YMinorGrid = 'on';
ax.GridLineStyle = '--';
ax.MinorGridLineStyle = ':';
ax.GridColor = [0.65 0.65 0.65];
ax.MinorGridColor = [0.35 0.35 0.35];
ax.GridAlpha = 0.35;
ax.MinorGridAlpha = 0.45;
yticks((levels(1:end-1) + levels(2:end)) / 2);
xticks((levels(1:end-1) + levels(2:end)) / 2);
legend
xlabel('even symbols');
ylabel('odd symbols');
axis equal;
grid on;
grid minor;
ax.Layer = 'top';
axis equal; grid on;
end

View File

@@ -4,15 +4,9 @@ arguments
options.postfilter_taps = NaN
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [];
options.colormode (1,1) string {mustBeMember(options.colormode, ["diverging", "qualitative", "paired"])} = "diverging";
options.color = [0.2157 0.4941 0.7216];
end
persistent nextGroupIdx
if isempty(nextGroupIdx)
nextGroupIdx = 0;
end
% Determine the figure number to use or create a new figure
if isnan(options.fignum)
fig = figure; % Create a new figure and get its handle
@@ -21,16 +15,13 @@ end
end
hold on
ax = gca;
linesBefore = findall(ax, 'Type', 'Line');
useAutomaticColor = isempty(options.color);
if useAutomaticColor
options.color = selectEQNoisePSDColor(ax, options.colormode);
end
% N = numel(ax.Children);
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
% Ensure the figure is ready before calling spectrum
eq_noise = eq_noise - mean(eq_noise.signal);
eq_noise.spectrum("displayname", options.displayname, "fignum", fig.Number, "normalizeTo0dB", 0,"color",options.color,"fft_length",4096);
eq_noise.spectrum("displayname", options.displayname, "fignum", fig.Number, "normalizeTo0dB", 1,"color",options.color);
if ~isnan(options.postfilter_taps)
% Hold on to the figure for further plotting
@@ -50,88 +41,7 @@ end
legend('show');
end
if useAutomaticColor
nextGroupIdx = nextGroupIdx + 1;
newLines = getNewLines(ax, linesBefore);
tagEQNoisePSDLines(newLines, nextGroupIdx);
recolorEQNoisePSDLines(ax, options.colormode);
end
xlim([-eq_noise.fs/2* 1e-9 eq_noise.fs/2* 1e-9]);
% ylim([-15, 0]);
end
function color = selectEQNoisePSDColor(ax, colormode)
if colormode == "qualitative"
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
color = cmap(mod(N, size(cmap, 1)) + 1, :);
else
color = [0 0 0];
end
end
function newLines = getNewLines(ax, linesBefore)
linesAfter = findall(ax, 'Type', 'Line');
newLines = linesAfter(~ismember(linesAfter, linesBefore));
end
function tagEQNoisePSDLines(lines, groupIdx)
for lineIdx = 1:numel(lines)
setappdata(lines(lineIdx), 'showEQNoisePSDGroupIdx', groupIdx);
end
end
function recolorEQNoisePSDLines(ax, colormode)
if colormode == "qualitative"
return
end
lines = findall(ax, 'Type', 'Line');
groupIdx = NaN(size(lines));
for lineIdx = 1:numel(lines)
if isappdata(lines(lineIdx), 'showEQNoisePSDGroupIdx')
groupIdx(lineIdx) = getappdata(lines(lineIdx), 'showEQNoisePSDGroupIdx');
end
end
lines = lines(~isnan(groupIdx));
groupIdx = groupIdx(~isnan(groupIdx));
if isempty(lines)
return
end
groups = unique(groupIdx, 'stable');
groups = sort(groups);
cols = getEQNoisePSDColormap(colormode, numel(groups));
for groupColorIdx = 1:numel(groups)
sameGroup = groupIdx == groups(groupColorIdx);
set(lines(sameGroup), 'Color', cols(groupColorIdx, :));
end
end
function cols = getEQNoisePSDColormap(colormode, numColors)
switch colormode
case "paired"
cols = cbrewer2('paired', numColors);
otherwise
cols = getDivergingWithoutBrightCenter(numColors);
end
end
function cols = getDivergingWithoutBrightCenter(numColors)
numBaseColors = max(6, numColors);
centerExcludeFraction = 0.3;
baseCols = cbrewer2('RdYlBu', numBaseColors);
centerIdx = (numBaseColors + 1) / 2;
excludeHalfWidth = centerExcludeFraction * numBaseColors / 2;
keepIdx = find(abs((1:numBaseColors) - centerIdx) > excludeHalfWidth);
sampleIdx = round(linspace(1, numel(keepIdx), numColors));
cols = baseCols(keepIdx(sampleIdx), :);
end

View File

@@ -6,8 +6,7 @@ arguments
options.fs_rx
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [];
options.linestyle = '-';
options.color = [0.2157 0.4941 0.7216];
end
% Determine the figure number to use or create a new figure
@@ -31,52 +30,42 @@ end
ax = gca;
% N = numel(ax.Children);
if isempty(options.color)
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
end
N = sum(arrayfun(@(x) strcmp(x.LineStyle, '-'), ax.Children));
cmap = linspecer(8);
options.color = cmap(mod(N, size(cmap, 1)) + 1, :);
% Ensure the figure is ready before calling spectrum
title('SNR of received Signal')
fft_length = 2^(nextpow2(length(eq_signal))-7);
[s_lin,w] = pwelch(eq_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_tx,"centered","psd","mean");
[n_lin,w_noise] = pwelch(noise_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_rx,"centered","psd","mean");
if numel(w_noise) ~= numel(w) || any(abs(w_noise - w) > max(options.fs_tx,options.fs_rx)*eps)
n_lin = interp1(w_noise,n_lin,w,"linear",NaN);
end
[n_lin,w] = pwelch(noise_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_rx,"centered","psd","mean");
w = w.*1e-9;
snr_lin = s_lin./n_lin;
snr_lin = movmean(snr_lin,10);
snr_dbm = 10*log10(snr_lin);
snr_dbm = 10*log10(s_lin./n_lin);
if isempty(options.displayname)
options.displayname = 'SNR';
end
plot(w,snr_dbm,'DisplayName',options.displayname,'LineWidth',1,'Color',options.color,'LineStyle',options.linestyle);
% yline(mean(snr_dbm),'HandleVisibility','off','Color',options.color,'LineStyle','--','LineWidth',1);
% figure(231)
hold on
plot(w,snr_dbm,'DisplayName','SNR','LineWidth',0.5,'Color',options.color);
yline(mean(snr_dbm),'HandleVisibility','off','Color',options.color);
xlabel("Frequency in GHz");
ylabel("SNR (dB)");
xlim([min(w) max(w)]);
y_min = min(snr_dbm(:));
y_max = max(snr_dbm(:));
y_range = y_max - y_min;
if y_range == 0
y_range = 10;
end
y_margin = 0.05 * y_range;
ylim([y_min - y_margin, y_max + y_margin]);
try
yticks(round(linspace(y_min, y_max, min(10, max(4, ceil(y_range/10))))));
end
grid on;
if isempty(get(gca, 'Legend'))
legend('Interpreter','none');
end
edgetick = 2^(nextpow2(options.fs_tx*1e-9));
ticks = -edgetick:16:edgetick;
xticks(ticks);
[~,b]=min(abs((-edgetick:16:edgetick)-max(w)));
xlim([-ticks(b+1) ticks(b+1)]);
max_snr = ceil(max(snr_dbm)/10)*10;
min_snr = floor(min(snr_dbm)/10)*10;
ylim([min_snr,max_snr]);
yticks(-200:10:100);
grid on; grid minor;
legend('Interpreter','none');
title('Noise of soft decision signal (not MLSE)');
end

View File

@@ -6,7 +6,6 @@ arguments
fs
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.color = [];
end
% Assuming that obj.e contains the final FFE filter coefficients.
@@ -35,24 +34,20 @@ end
end
% Magnitude response (in dB)
% subplot(2,1,1);
subplot(2,1,1);
hold on
if isempty(options.color)
plot(f.*1e-9, H_db,'DisplayName',options.displayname,'LineWidth',1);
else
plot(f.*1e-9, H_db,'DisplayName',options.displayname,'Color',options.color,'LineWidth',1);
end
plot(f.*1e-9, H_db,'DisplayName',options.displayname);
title('(Inverted) Magnitude Response of FFE Filter');
xlabel('Frequency (GHz)');
ylabel('Magnitude (dB)');
grid on;
% % Phase response
% subplot(2,1,2);
% plot(f.*1e-9, unwrap(angle(H)),'DisplayName',options.displayname);
% title('Phase Response of FFE Filter');
% xlabel('Frequency (GHz)');
% ylabel('Phase');
% grid on;
% Phase response
subplot(2,1,2);
plot(f.*1e-9, unwrap(angle(H)),'DisplayName',options.displayname);
title('Phase Response of FFE Filter');
xlabel('Frequency (GHz)');
ylabel('Phase');
grid on;
end

View File

@@ -4,7 +4,6 @@ arguments
ref_symbols
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.max_burst_length (1,1) double = 20
end
% Determine the figure number to use or create a new figure
@@ -19,17 +18,30 @@ end
eq_signal = PAMmapper(M,0).quantize(eq_signal);
end
errpos = find(eq_signal.signal(:) ~= ref_symbols.signal(:));
burst_len = 1:options.max_burst_length;
burst_count = count_error_bursts(errpos, options.max_burst_length);
diff_indices = eq_signal.signal == ref_symbols.signal;
bar(burst_len, burst_count, "DisplayName", options.displayname);
grid on;
xlabel("Error burst length");
ylabel("Count");
title("Error burst count");
if ~isempty(options.displayname)
legend("show");
% Identify the start of new sequences (when the difference is not 1)
sequence_starts = [1, find(diff_indices ~= 1) + 1]; % Include the first index
sequence_ends = [sequence_starts(2:end) - 1, length(errpos)]; % Calculate end indices
% Initialize burst count and print bursts longer than 10
burst_len = 1:10;
burst_count = zeros(length(burst_len),1);
for t = 1:numel(burst_len)
for i = 1:length(sequence_starts)
% Extract current sequence
current_burst = errpos(sequence_starts(i):sequence_ends(i));
% Check if the sequence length matches criterion
if length(current_burst) == burst_len(t)
burst_count(t) = burst_count(t) + 1;
end
end
end
end
burst_symbols = burst_count .* burst_len';
burst_rate = burst_symbols ;%./ length(rx_signal);
end

View File

@@ -4,8 +4,6 @@ arguments
ref_symbols
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.ref_symbol_uncoded = []
options.nbins (1,1) double = 1000
end
if isa(eq_signal,'Signal')
@@ -14,20 +12,6 @@ end
if isa(ref_symbols,'Signal')
ref_symbols = ref_symbols.signal;
end
if isa(options.ref_symbol_uncoded,'Signal')
options.ref_symbol_uncoded = options.ref_symbol_uncoded.signal;
end
eq_signal = eq_signal(:);
ref_symbols = ref_symbols(:);
ref_symbol_uncoded = options.ref_symbol_uncoded(:);
assert(numel(eq_signal) == numel(ref_symbols), ...
'showLevelHistogram:LengthMismatch', ...
'eq_signal and ref_symbols must have the same number of samples.');
assert(isempty(ref_symbol_uncoded) || numel(ref_symbol_uncoded) == numel(eq_signal), ...
'showLevelHistogram:LengthMismatch', ...
'options.ref_symbol_uncoded must have the same number of samples as eq_signal.');
% Determine the figure number to use or create a new figure
if isnan(options.fignum)
@@ -38,89 +22,31 @@ end
eq_signal = max(min(eq_signal,3),-3);
eq_signal = eq_signal .* -1;
%%% histogram
%%% Separate Classes
constellation = unique(ref_symbols);
received_sd = NaN(numel(constellation),length(ref_symbols));
lvlcol = cbrewer2('Paired',numel(constellation)*2);
lvlcol = lvlcol(2:2:end,:);
lvlcol = linspecer(numel(constellation));
% lvlcol = cbrewer2('Set1',numel(constellation));
for lvl = 1:numel(constellation)
%Separate the equalized signal into the
%respective levels based on the actually
%transmitted level!
received_sd(lvl,ref_symbols==constellation(lvl)) = eq_signal(ref_symbols==constellation(lvl));
end
%%% FFE histogram
clf
if isempty(ref_symbol_uncoded)
% Normal mode: split the received signal by the actually
% transmitted reference level.
constellation = unique(ref_symbols);
received_sd = NaN(numel(constellation),numel(ref_symbols));
lvlcol = linspecer(numel(constellation));
lvlcol = cbrewer2('RdBu',numel(constellation)+4);
% remove 4 colors from the middle to avoid the bright central colors
mid = ceil(size(lvlcol,1)/2);
rm_idx = mid + (-1:2); % two before mid and two after (2x2 removal centered)
rm_idx = max(1,min(size(lvlcol,1),rm_idx));
lvlcol(rm_idx,:) = [];
lvlcol = lvlcol(1:numel(constellation),:);
for lvl = 1:numel(constellation)
class_mask = ref_symbols == constellation(lvl);
received_sd(lvl,class_mask) = eq_signal(class_mask);
end
for lvl = 1:numel(constellation)
intermediate = received_sd(lvl,:);
cnt(lvl) = round(numel(intermediate(~isnan(intermediate)))./numel(eq_signal),3).*100;
hold on
warning off
histogram(received_sd(lvl,:),options.nbins, ...
"EdgeAlpha",0, ...
"DisplayName",['Lvl ',num2str(lvl),' ; ',num2str(cnt(lvl)),' '], ...
"FaceColor",lvlcol(lvl,:), ...
"Normalization","pdf");
warning on
end
else
% Plot p(y | x_n): y is the noisy observation and x_n is the
% uncoded PAM class. For duobinary this naturally gives multi-modal
% PDFs because one x_n class can map to multiple DB amplitudes. Each
% DB lobe is drawn separately so the DB-level structure stays visible.
db_constellation = unique(ref_symbols);
classes = unique(ref_symbol_uncoded);
% lvlcol = linspecer(numel(classes));
lvlcol = cbrewer2('RdBu',numel(classes)+4);
% remove 4 colors from the middle to avoid the bright central colors
mid = ceil(size(lvlcol,1)/2);
rm_idx = mid + (-1:2); % two before mid and two after (2x2 removal centered)
rm_idx = max(1,min(size(lvlcol,1),rm_idx));
lvlcol(rm_idx,:) = [];
lvlcol = lvlcol(1:numel(sir_group_labels),:);
for db_lvl = 1:numel(db_constellation)
db_mask = ref_symbols == db_constellation(db_lvl);
mapped_class = mode(ref_symbol_uncoded(db_mask));
[~,class_idx] = min(abs(classes - mapped_class));
cnt = round(nnz(ref_symbol_uncoded == mapped_class)./numel(eq_signal),3).*100;
if db_lvl == findFirstMappedDbLevel(ref_symbols,ref_symbol_uncoded,db_constellation,mapped_class)
display_name = ['p(y|x_',num2str(class_idx),') ; ',num2str(cnt),' '];
display_name = ['p(y|x_',num2str(class_idx),')'];
handle_visibility = "on";
else
display_name = ['p(y|x_',num2str(class_idx),') ; ',num2str(cnt),' '];
display_name = ['p(y|x_',num2str(class_idx),')'];
handle_visibility = "off";
end
weighted_lobe = NaN(size(eq_signal));
weighted_lobe(db_mask) = eq_signal(db_mask);
hold on
warning off
histogram(weighted_lobe,options.nbins, ...
"EdgeAlpha",0, ...
"DisplayName",display_name, ...
"FaceColor",lvlcol(class_idx,:), ...
"HandleVisibility",handle_visibility, ...
"Normalization","pdf");
warning on
end
for lvl = 1:numel(constellation)
intermediate = received_sd(lvl,:);
cnt(lvl) = round(numel(intermediate(~isnan(intermediate)))./length(eq_signal),3).*100;
hold on
warning off
histogram(received_sd(lvl,:),1000,"EdgeAlpha",0,'DisplayName',['Lvl ',num2str(lvl),' ; ',num2str(cnt(lvl)),' '],'FaceColor',lvlcol(lvl,:),'Normalization','pdf');
warning on
end
xlim([-3 3]);
legend
@@ -132,15 +58,3 @@ end
end
function first_db_lvl = findFirstMappedDbLevel(ref_symbols,ref_symbol_uncoded,db_constellation,mapped_class)
first_db_lvl = NaN;
for db_lvl = 1:numel(db_constellation)
db_mask = ref_symbols == db_constellation(db_lvl);
if mode(ref_symbol_uncoded(db_mask)) == mapped_class
first_db_lvl = db_lvl;
return
end
end
end

View File

@@ -1,420 +1,133 @@
function [symbols_for_lvl, avg_for_lvl, xAxisUs, info] = showLevelScatter(rxInput, refSymbols, options)
%SHOWLEVELSCATTER Plot received samples separated by reference PAM level.
% Supports plain numeric vectors, Signal objects, synchronized scope cell
% arrays, and raw unsynchronized Signal input. Raw Signal input is
% synchronized to refSymbols and stitched before plotting.
function [symbols_for_lvl,avg_for_lvl] = showLevelScatter(eq_signal,ref_symbols,options)
arguments
rxInput
refSymbols
options.fignum (1,1) double = NaN
options.displayname (1,:) char = ''
options.f_sym double = []
options.fsym double = []
options.syncFs (1,1) double = 0
options.shiftFs (1,1) double = 0
options.shifts double = []
options.maxOccurences (1,1) double = Inf
options.normalize (1,1) logical = false
options.debug_plots (1,1) logical = false
options.showPlot (1,1) logical = true
options.clear (1,1) logical = true
options.windowLength (1,1) double {mustBePositive, mustBeInteger} = 500
options.xLimits double = []
options.yLimits (1,2) double = [-2.5 2.5]
options.showStdAnnotations (1,1) logical = true
options.scatterAlpha (1,1) double {mustBeGreaterThanOrEqual(options.scatterAlpha, 0), mustBeLessThanOrEqual(options.scatterAlpha, 1)} = 0.2
options.scatterSize (1,1) double {mustBePositive} = 1
options.avgLineMaxPoints (1,1) double {mustBePositive, mustBeInteger} = 1000
options.avgLineSmoothWindow (1,1) double {mustBePositive, mustBeInteger} = 1
eq_signal
ref_symbols
options.fignum (1,1) double = NaN % Default to NaN if not provided
options.displayname (1,:) char = '' % Default to an empty string if not provided
options.f_sym =1e6;
end
fsym = resolveSymbolRate(rxInput, refSymbols, options);
[symbols_for_lvl, avg_for_lvl, xAxisUs, info] = prepareLevelScatterData(rxInput, refSymbols, fsym, options);
plot_shit = 1;
if options.showPlot
info.plot = plotLevelScatter(symbols_for_lvl, avg_for_lvl, refSymbols, xAxisUs, options);
if isa(eq_signal,'Signal')
options.f_sym = eq_signal.fs;
eq_signal = eq_signal.signal;
assert(~isempty(options.f_sym),'No fsym given');
end
if isa(ref_symbols,'Signal')
ref_symbols = ref_symbols.signal;
end
function fsym = resolveSymbolRate(rxInput, refSymbols, options)
if ~isempty(options.fsym)
fsym = options.fsym;
elseif ~isempty(options.f_sym)
fsym = options.f_sym;
elseif isa(refSymbols, "Signal") && ~isempty(refSymbols.fs)
fsym = refSymbols.fs;
elseif isa(rxInput, "Signal") && ~isempty(rxInput.fs)
fsym = rxInput.fs;
elseif iscell(rxInput) && ~isempty(rxInput) && isa(rxInput{1}, "Signal") && ~isempty(rxInput{1}.fs)
fsym = rxInput{1}.fs;
else
fsym = 1e6;
end
end
function [symbols_for_lvl, avg_for_lvl, xAxisUs, info] = prepareLevelScatterData(rxInput, refSymbols, fsym, options)
refSignal = numericSignal(refSymbols);
info = defaultInfo(fsym);
if iscell(rxInput)
[symbols_for_lvl, avg_for_lvl, info] = prepareCellInput(rxInput, refSymbols, fsym, options, info);
elseif isa(rxInput, "Signal")
[symbols_for_lvl, avg_for_lvl, info] = prepareSignalInput(rxInput, refSymbols, fsym, options, info);
else
rxSymbols = numericSignal(rxInput);
[symbols_for_lvl, avg_for_lvl] = levelScatterForOneSequence(rxSymbols, refSignal, options.windowLength);
end
xAxisUs = ((1:size(avg_for_lvl, 2)) / fsym) * 1e6;
end
function info = defaultInfo(fsym)
info = struct();
info.found_sync = true;
info.startSamples = 1;
info.shifts = [];
info.fsym = fsym;
info.shiftFs = fsym;
info.varianceByLevel = [];
info.plot = struct();
end
function [symbols_for_lvl, avg_for_lvl, info] = prepareSignalInput(rxSignal, refSymbols, fsym, options, info)
rxAtSymbolRate = rxSignal.resample("fs_in", rxSignal.fs, "fs_out", fsym);
refSignal = numericSignal(refSymbols);
if numel(rxAtSymbolRate.signal) == numel(refSignal)
rxSymbols = rxAtSymbolRate.signal;
if options.normalize
rxSymbols = normalizeNumericRms(rxSymbols);
end
[symbols_for_lvl, avg_for_lvl] = levelScatterForOneSequence(rxSymbols, refSignal, options.windowLength);
info.varianceByLevel = var(symbols_for_lvl, 0, 2, "omitnan");
return
end
[scopeCell, shifts, shiftFs, foundSync] = synchronizeRawSignal(rxSignal, refSymbols, fsym, options);
info.found_sync = foundSync;
info.shifts = shifts;
info.shiftFs = shiftFs;
if isempty(scopeCell)
symbols_for_lvl = [];
avg_for_lvl = [];
warning("showLevelScatter:NoScopeCells", ...
"No synchronized scope signal occurrences available.");
return
end
[symbols_for_lvl, avg_for_lvl, startSamples] = stitchScopeCells(scopeCell, refSymbols, fsym, shifts, shiftFs, options);
info.startSamples = startSamples;
info.varianceByLevel = var(symbols_for_lvl, 0, 2, "omitnan");
end
function [symbols_for_lvl, avg_for_lvl, info] = prepareCellInput(scopeCell, refSymbols, fsym, options, info)
scopeCell = scopeCell(:);
if isempty(scopeCell)
symbols_for_lvl = [];
avg_for_lvl = [];
info.found_sync = false;
warning("showLevelScatter:NoScopeCells", ...
"No synchronized scope signal occurrences available.");
return
end
shiftFs = options.shiftFs;
if shiftFs <= 0
shiftFs = fsym;
end
[symbols_for_lvl, avg_for_lvl, startSamples] = stitchScopeCells(scopeCell, refSymbols, fsym, options.shifts, shiftFs, options);
info.found_sync = true;
info.shifts = options.shifts;
info.shiftFs = shiftFs;
info.startSamples = startSamples;
info.varianceByLevel = var(symbols_for_lvl, 0, 2, "omitnan");
end
function [scopeCell, shifts, shiftFs, foundSync] = synchronizeRawSignal(rxSignal, refSymbols, fsym, options)
syncFs = options.syncFs;
if syncFs <= 0
syncFs = 2*fsym;
end
syncSignal = rxSignal.resample("fs_in", rxSignal.fs, "fs_out", syncFs);
if options.normalize
syncSignal = syncSignal.normalize("mode", "rms");
end
[~, scopeCell, ~, foundSync, shifts] = syncSignal.tsynch( ...
"reference", refSymbols, ...
"fs_ref", fsym, ...
"debug_plots", options.debug_plots);
if options.shiftFs > 0
shiftFs = options.shiftFs;
else
shiftFs = syncFs;
end
end
function [symbols_for_lvl, avg_for_lvl, startSamples] = stitchScopeCells(scopeCell, refSymbols, fsym, shifts, shiftFs, options)
recordOccurrences = min(numel(scopeCell), options.maxOccurences);
scopeCell = scopeCell(1:recordOccurrences);
refSignal = numericSignal(refSymbols);
startSamples = getStartSamples(shifts, recordOccurrences, shiftFs, fsym, numel(refSignal));
levelScatter = cell(1, recordOccurrences);
levelAverage = cell(1, recordOccurrences);
for occurrenceIdx = 1:recordOccurrences
occurrence = scopeCell{occurrenceIdx};
if isa(occurrence, "Signal")
occurrence = occurrence.resample("fs_out", fsym);
occurrence = occurrence.signal;
end
[levelScatter{occurrenceIdx}, levelAverage{occurrenceIdx}] = ...
levelScatterForOneSequence(occurrence, refSignal, options.windowLength);
end
numLevels = size(levelScatter{1}, 1);
traceLength = max(startSamples(:).' + cellfun(@(x) size(x, 2), levelScatter) - 1);
symbols_for_lvl = NaN(numLevels, traceLength);
avg_for_lvl = NaN(numLevels, traceLength);
for occurrenceIdx = 1:recordOccurrences
writeIdx = startSamples(occurrenceIdx):(startSamples(occurrenceIdx) + size(levelScatter{occurrenceIdx}, 2) - 1);
symbols_for_lvl(:, writeIdx) = levelScatter{occurrenceIdx};
avg_for_lvl(:, writeIdx) = levelAverage{occurrenceIdx};
end
end
function startSamples = getStartSamples(shifts, recordOccurrences, shiftFs, fsym, symbolLength)
if isempty(shifts)
startSamples = ((0:recordOccurrences-1) .* symbolLength) + 1;
return
end
shifts = shifts(:);
if numel(shifts) ~= recordOccurrences
positiveShifts = shifts(shifts >= 0);
if numel(positiveShifts) >= recordOccurrences
shifts = positiveShifts(1:recordOccurrences);
if plot_shit
% Determine the figure number to use or create a new figure
if isnan(options.fignum)
fig = figure; % Create a new figure and get its handle
else
warning("showLevelScatter:ShiftCountMismatch", ...
"Shift count does not match scope cell count. Using sequential stitching.");
startSamples = ((0:recordOccurrences-1) .* symbolLength) + 1;
return
end
end
startSamples = round((shifts(1:recordOccurrences) - shifts(1)) ./ shiftFs .* fsym) + 1;
end
function [symbols_for_lvl, avg_for_lvl] = levelScatterForOneSequence(rxSymbols, refSymbols, windowLength)
rxSymbols = numericSignal(rxSymbols);
refSymbols = numericSignal(refSymbols);
assert(numel(rxSymbols) == numel(refSymbols), ...
'showLevelScatter:LengthMismatch', ...
'rxInput and refSymbols must have the same number of samples after resampling/synchronization.');
levels = unique(refSymbols);
[symbols_for_lvl, levels] = splitByReferenceLevels(rxSymbols, refSymbols, levels);
avg_for_lvl = NaN(numel(levels), numel(refSymbols));
for levelIdx = 1:numel(levels)
levelMask = ~isnan(symbols_for_lvl(levelIdx, :));
levelSamples = symbols_for_lvl(levelIdx, levelMask);
if isempty(levelSamples)
continue
end
smoothWindowLength = min(windowLength, numel(levelSamples));
avg_for_lvl(levelIdx, levelMask) = movmean(levelSamples, smoothWindowLength, 'Endpoints', 'shrink');
avg_for_lvl(levelIdx, :) = interpolateMissingLevelAverage(avg_for_lvl(levelIdx, :));
end
end
function [symbols_for_lvl, levels] = splitByReferenceLevels(rxSymbols, refSymbols, levels)
supportedPamLevels = [2 4 6 8 16];
if ismember(numel(levels), supportedPamLevels)
[symbols_for_lvl, levels] = PAMmapper(numel(levels), 0).splitByReferenceLevels( ...
rxSymbols, refSymbols, ...
"levels", levels);
else
symbols_for_lvl = NaN(numel(levels), numel(refSymbols));
for levelIdx = 1:numel(levels)
levelMask = refSymbols == levels(levelIdx);
symbols_for_lvl(levelIdx, levelMask) = rxSymbols(levelMask);
end
end
end
function plotInfo = plotLevelScatter(symbols_for_lvl, avg_for_lvl, refSymbols, xAxisUs, options)
plotInfo = struct();
if isempty(symbols_for_lvl)
return
end
if isnan(options.fignum)
figHandle = figure;
else
figHandle = figure(options.fignum);
if options.clear
fig = figure(options.fignum); % Use the specified figure number
clf;
end
end
ax = gca;
hold(ax, "on");
numLevels = size(symbols_for_lvl, 1);
cols = cbrewer2("RdBu", numLevels);
% cols = linspecer(numLevels);
xLimits = getXLimits(options.xLimits, xAxisUs, symbols_for_lvl);
if 1
for levelIdx = numLevels:-1:1
scatter(ax, xAxisUs, symbols_for_lvl(levelIdx, :), options.scatterSize, ".", ...
"MarkerEdgeColor", cols(levelIdx, :), ...
"MarkerEdgeAlpha", options.scatterAlpha);
end
end
rx_symbols = eq_signal; %./ rms(eq_signal);
correct_symbols = ref_symbols;
f_sym = options.f_sym;
for levelIdx = 1:numLevels
[xReduced, yReduced] = reduceAverageLineForPlot( ...
xAxisUs, avg_for_lvl(levelIdx, :), ...
options.avgLineMaxPoints, options.avgLineSmoothWindow);
plot(ax, xReduced, yReduced, ...
"LineWidth", 1, ...
"Color", cols(levelIdx, :));
end
col = cbrewer2('Paired',numel(unique(correct_symbols))*2);
ccnt = -1;
% refValues = unique(numericSignal(refSymbols));
% for refIdx = 1:numel(refValues)
% yline(ax, refValues(refIdx), "--", ...
% "Color", [0.35 0.35 0.35], ...
% "LineWidth", 0.75, ...
% "HandleVisibility", "off");
% end
levels = unique(correct_symbols);
symbols_for_lvl = NaN(numel(levels),length(correct_symbols));
start = 1;
ende = length(correct_symbols);
if options.showStdAnnotations
annotateLevelStd(symbols_for_lvl, avg_for_lvl, xAxisUs, options.xLimits);
end
for l = 1:numel(levels)
ccnt = ccnt+2;
xlabel(ax, 'Time in $\mu$s');
ylabel(ax, 'Normalized Amplitude');
xlim(ax, xLimits);
ylim(ax, options.yLimits);
grid(ax, "on");
figure(figHandle);
drawnow;
end
level_amplitude = levels(l);
function [xReduced, yReduced] = reduceAverageLineForPlot(xAxisUs, yTrace, maxPoints, smoothWindow)
valid = isfinite(xAxisUs) & isfinite(yTrace);
xValid = xAxisUs(valid);
yValid = yTrace(valid);
symbols_for_lvl(l,correct_symbols==level_amplitude) = rx_symbols(correct_symbols==level_amplitude);
std_lvl(l) = std(symbols_for_lvl(l,:),'omitnan');
xax_in_sec = ((1:length(correct_symbols)) / f_sym) * 1e6;
if isempty(xValid)
xReduced = [];
yReduced = [];
return
end
[xValid, uniqueIdx] = unique(xValid, "stable");
yValid = yValid(uniqueIdx);
if smoothWindow > 1
yValid = smoothdata(yValid, "movmean", smoothWindow);
end
numOut = min(maxPoints, numel(xValid));
if numOut >= numel(xValid)
xReduced = xValid;
yReduced = yValid;
return
end
xReduced = linspace(xValid(1), xValid(end), numOut);
yReduced = interp1(xValid, yValid, xReduced, "pchip");
end
function xLimits = getXLimits(configuredLimits, xAxisUs, symbols_for_lvl)
if ~isempty(configuredLimits)
xLimits = configuredLimits;
return
end
filledColumns = any(~isnan(symbols_for_lvl), 1);
if ~any(filledColumns)
xLimits = [xAxisUs(1), xAxisUs(end)];
return
end
lastFilledColumn = find(filledColumns, 1, "last");
xMax = xAxisUs(lastFilledColumn);
xLimits = [0, 1*xMax];
end
function annotateLevelStd(symbols_for_lvl, avg_for_lvl, xAxisUs, configuredXLimits)
xLimits = getXLimits(configuredXLimits, xAxisUs, symbols_for_lvl);
xText = xLimits(1) + 0.2*diff(xLimits);
for levelIdx = 1:size(symbols_for_lvl, 1)
levelSamples = symbols_for_lvl(levelIdx, :);
levelStd = std(levelSamples, 0, 2, 'omitnan');
if isnan(levelStd)
continue
if plot_shit
scatter(xax_in_sec(start:ende),symbols_for_lvl(l,start:ende),10,'.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5,'MarkerEdgeColor',col(ccnt,:));
hold on;
end
levelAverage = avg_for_lvl(levelIdx, :);
yText = median(levelAverage(~isnan(levelAverage)), 'omitnan');
if isnan(yText)
yText = median(levelSamples(~isnan(levelSamples)), 'omitnan');
end
std_lvl = round(std_lvl,2);
ccnt = 0;
avg_for_lvl = NaN(numel(levels),length(correct_symbols));
% Add the windowed/ smoothed curves
for l = 1:numel(levels)
ccnt = ccnt+2;
level_amplitude = levels(l);
L = 500;
movmean = 1/L .* movsum(rx_symbols(correct_symbols==level_amplitude),[L/2,L/2], 'Endpoints', 'fill');
avg_for_lvl(l,correct_symbols==level_amplitude) = movmean;
nanx = isnan(avg_for_lvl(l,:));
t = 1:numel(avg_for_lvl(l,:));
avg_for_lvl(l,nanx) = interp1(t(~nanx), avg_for_lvl(l,~nanx), t(nanx));
xax_in_sec = ((1:length(correct_symbols)) / f_sym) * 1e6;
% xax_in_sec = 1:length(correct_symbols);
if plot_shit
plot(xax_in_sec(start:ende),avg_for_lvl(l,start:ende),'Color',col(ccnt,:));
end
label = ['$\sigma^2_',sprintf('%d', levelIdx),'$ = ', sprintf('%.3f', levelStd)];
text(xText, yText, label, ...
'Interpreter', 'latex', ...
'HorizontalAlignment', 'right', ...
'VerticalAlignment', 'middle', ...
'FontSize', 10, ...
'BackgroundColor', 'w', ...
'Margin', 2, ...
'EdgeColor', [0.8 0.8 0.8]);
end
hold on
end
function levelAverage = interpolateMissingLevelAverage(levelAverage)
validSamples = ~isnan(levelAverage);
if nnz(validSamples) == 0
return
elseif nnz(validSamples) == 1
levelAverage(:) = levelAverage(validSamples);
return
if 0
annotation(fig,'textbox',...
[0.660523809523809 0.844444444444448 0.133523809523809 0.0603174603174607],...
'String',['\sigma = ',num2str(std_lvl(4))],...
'LineWidth',1.8,...
'LineStyle','none',...
'FontSize',12,...
'FitBoxToText','off');
% Create textbox
annotation(fig,'textbox',...
[0.667666666666665 0.642857142857147 0.133523809523809 0.0603174603174607],...
'String',['\sigma = ',num2str(std_lvl(3))],...
'LineWidth',1.8,...
'LineStyle','none',...
'FontSize',12,...
'FitBoxToText','off');
% Create textbox
annotation(fig,'textbox',...
[0.671238095238093 0.442857142857148 0.133523809523809 0.0603174603174608],...
'String',['\sigma = ',num2str(std_lvl(2))],...
'LineWidth',1.8,...
'LineStyle','none',...
'FontSize',12,...
'FitBoxToText','off');
% Create textbox
annotation(fig,'textbox',...
[0.670047619047616 0.265079365079371 0.133523809523809 0.0603174603174608],...
'String',['\sigma = ',num2str(std_lvl(1))],...
'LineWidth',1.8,...
'LineStyle','none',...
'FontSize',12,...
'FitBoxToText','off');
end
t = 1:numel(levelAverage);
levelAverage(~validSamples) = interp1(t(validSamples), levelAverage(validSamples), ...
t(~validSamples), 'linear', 'extrap');
if plot_shit
% yline(levels);
xlabel('Time in $\mu$s');
ylabel('Normalized Amplitude');
ylim([-3 3]);
end
function values = numericSignal(signalLike)
if isa(signalLike, "Signal")
values = signalLike.signal;
else
values = signalLike;
end
values = values(:).';
end
function values = normalizeNumericRms(values)
values = values ./ sqrt(mean(values.^2, "omitnan"));
end

View File

@@ -1,35 +0,0 @@
function showMarkovDiagram(sequence,M)
if isa(sequence,'Signal')
sequence = sequence.signal;
end
sequence = sequence(2+mod(1,length(sequence)):end); %filtered sequences often have one "old" sample at idx=1
x = sequence;
levels = sort(unique(x)).'; % or provide known 1x6 level values
[~,ix] = min(abs(x - levels),[],2);
x = levels(ix);
K = numel(levels);
% map to state indices 1..K
[tf, idx] = ismember(x, levels);
idx = idx(:);
from = idx(1:end-1);
to = idx(2:end);
from = idx(1:2:end);
to = idx(2:2:end);
% counts C(from,to)
C = accumarray([from,to], 1, [K K], @sum, 0);
% row-stochastic transition matrix P(to|from)
rowSums = sum(C,2);
P = C ./ max(rowSums,1);
mc = dtmc(P, 'StateNames', string(levels.*PAMmapper(M,0).get_scaling));
figure('Name','Markov Graph (dtmc)');
gp = graphplot(mc, 'ColorEdges',true, 'LabelEdges',true);
end

View File

@@ -1,40 +0,0 @@
function [sep_sig, avg_sig, info] = showMpiLevelScatter(scopeInput, Symbols, options)
%SHOWMPILEVELSCATTER Backward-compatible wrapper around showLevelScatter.
% Prefer showLevelScatter directly for new code. This wrapper keeps older
% MPI call sites working while sharing one plotting/synchronization path.
arguments
scopeInput
Symbols
options.fsym double = []
options.syncFs (1,1) double = 0
options.shiftFs (1,1) double = 0
options.shifts double = []
options.maxOccurences (1,1) double = Inf
options.normalize (1,1) logical = true
options.debug_plots (1,1) logical = false
options.fignum (1,1) double = NaN
options.clear (1,1) logical = true
options.showPlot (1,1) logical = true
options.xLimits double = []
options.yLimits (1,2) double = [-3 3]
options.showStdAnnotations (1,1) logical = true
options.windowLength (1,1) double {mustBePositive, mustBeInteger} = 500
end
[sep_sig, avg_sig, info] = showLevelScatter(scopeInput, Symbols, ...
"fsym", options.fsym, ...
"syncFs", options.syncFs, ...
"shiftFs", options.shiftFs, ...
"shifts", options.shifts, ...
"maxOccurences", options.maxOccurences, ...
"normalize", options.normalize, ...
"debug_plots", options.debug_plots, ...
"fignum", options.fignum, ...
"clear", options.clear, ...
"showPlot", options.showPlot, ...
"xLimits", options.xLimits, ...
"yLimits", options.yLimits, ...
"showStdAnnotations", options.showStdAnnotations, ...
"windowLength", options.windowLength);
end

View File

@@ -1,40 +0,0 @@
function showTransitionProbabilities(sequence)
if isa(sequence,'Signal')
sequence = sequence.signal;
end
sequence = sequence(2+mod(1,length(sequence)):end); %filtered sequences often have one "old" sample at idx=1
x = sequence;
levels = sort(unique(x)).'; % or provide known 1x6 level values
[~,ix] = min(abs(x - levels),[],2);
x = levels(ix);
K = numel(levels);
% map to state indices 1..K
[tf, idx] = ismember(x, levels);
idx = idx(:);
% from = idx(1:end-1);
% to = idx(2:end);
from = idx(1:2:end);
to = idx(2:2:end);
% counts C(from,to)
C = accumarray([from,to], 1, [K K], @sum, 0);
% row-stochastic transition matrix P(to|from)
rowSums = sum(C,2);
P = C ./ max(rowSums,1);
%% 1) HEATMAP (which transitions are more probable?)
figure('Name','Transition Probabilities (to | from)');
h = heatmap(levels, levels, P, 'Colormap', parula, 'ColorbarVisible','on');
colormap(gca,[[1,1,1];flip(cbrewer2('Spectral',100))]);clim([0,ceil(max(P(:))*10)/10]);
h.XLabel = 'From state (level)';
h.YLabel = 'To state (level)';
h.Title = 'P(to | from)';
end

View File

@@ -1,120 +0,0 @@
function output = dsp_runid(run_id, options)
arguments
run_id
options.append_to_db = 0;
options.append_mpi_reduction_db (1,1) logical = false;
options.mpi_reduction_study_name string = "mpi_reduction_v1";
options.mpi_reduction_writer_path string = "";
options.max_occurences = 4;
options.start_occurence = 1;
options.userParameters = struct();
options.database_type
options.dataBase
options.server = "134.245.243.254";
options.port = 3306;
options.user = "silas";
options.password = "silas";
options.load_file_path = struct();
options.storage_path
options.mode
options.recipe = @dsp_scope_signal;
options.debug_plots (1,1) logical = false;
end
try
output = initializeOutput();
database = [];
inputSource = normalizeDspInputSource(options.mode);
if options.append_mpi_reduction_db && strlength(options.mpi_reduction_writer_path) > 0
addpath(options.mpi_reduction_writer_path);
end
if inputSource == "run_id" || options.append_to_db || options.append_mpi_reduction_db
database = DBHandler("dataBase", [options.dataBase], "type", options.database_type, ...
"user", options.user, "password", options.password, ...
"server", options.server, "port", options.port);
end
switch inputSource
case "run_id"
dspInput = loadDspInputFromRunId(run_id, database, options);
case "file_paths"
dspInput = loadDspInputFromFilePaths(run_id, options);
end
num_occurences = length(dspInput.Scpe_cell);
for r = 1:num_occurences
%%%%%%%% CORE EQUALIZATION CALL (Scpe, Symbols, Bits, 'Options') %%%%%%%
dspOutput = options.recipe(dspInput.Scpe_cell{r}, dspInput.Symbols, dspInput.Tx_bits, ...
"fsym", dspInput.fsym, ...
"M", dspInput.M, ...
"duob_mode", dspInput.duob_mode, ...
"dataTable", dspInput.dataTable, ...
"userParameters", options.userParameters, ...
"debug_plots", options.debug_plots);
%%%%%%%% CORE EQUALIZATION CALL %%%%%%%
fieldNames = fieldnames(dspOutput);
for fieldIdx = 1:numel(fieldNames)
fieldName = fieldNames{fieldIdx};
output.(fieldName){r} = dspOutput.(fieldName);
end
if options.append_to_db
appendDspOutputToDatabase(database, run_id, dspOutput);
end
if options.append_mpi_reduction_db
occurrence_idx = options.start_occurence + r - 1;
appendMpiReductionDspOutput(database, run_id, occurrence_idx, dspOutput, options, ...
"study_name", options.mpi_reduction_study_name, ...
"verbose", false);
end
end
catch ME
save('workerError.mat', 'ME');
rethrow(ME);
end
end
function output = initializeOutput()
output.ffe_package = {};
output.dfe_package = {};
output.mlse_package = {};
output.vnle_package = {};
output.dbtgt_package = {};
output.dbenc_package = {};
output.mlmlse_package = {};
end
function inputSource = normalizeDspInputSource(mode)
mode = string(mode);
switch mode
case {"load_run_id", "run_id"}
inputSource = "run_id";
case {"load_files", "file_paths"}
inputSource = "file_paths";
otherwise
error('dsp_runid:UnsupportedMode', ...
'Mode "%s" is not supported. Use "run_id" or "file_paths".', mode);
end
end
function appendDspOutputToDatabase(database, run_id, dspOutput)
packageNames = fieldnames(dspOutput);
for i = 1:numel(packageNames)
package = dspOutput.(packageNames{i});
if isempty(package)
continue
end
database.addProcessingResult(run_id, package.metrics, package.config);
end
end

View File

@@ -1,86 +0,0 @@
function pulseformer = findSignalPulseformer(signalLike)
%FINDSIGNALPULSEFORMER Try to recover TX pulseformer metadata from a signal logbook.
pulseformer = [];
if isempty(signalLike) || ~isprop(signalLike, 'logbook') || isempty(signalLike.logbook)
return
end
if ~ismember('ModifierCopy', signalLike.logbook.Properties.VariableNames)
return
end
modifierCopies = signalLike.logbook.ModifierCopy;
for idx = numel(modifierCopies):-1:1
pulseformer = extractPulseformerCandidate(modifierCopies{idx});
if ~isempty(pulseformer)
return
end
end
end
function pulseformer = extractPulseformerCandidate(candidate)
pulseformer = [];
if isempty(candidate)
return
end
if isa(candidate, 'Pulseformer')
pulseformer = candidate;
return
end
if iscell(candidate)
for cellIdx = 1:numel(candidate)
pulseformer = extractPulseformerCandidate(candidate{cellIdx});
if ~isempty(pulseformer)
return
end
end
return
end
if isstruct(candidate)
if isfield(candidate, 'pulseformer')
pulseformer = extractPulseformerCandidate(candidate.pulseformer);
if ~isempty(pulseformer)
return
end
end
if hasPulseformerFields(candidate)
pulseformer = candidate;
end
return
end
try
if isprop(candidate, 'pulseformer')
pulseformer = extractPulseformerCandidate(candidate.pulseformer);
if ~isempty(pulseformer)
return
end
end
catch
end
try
if hasPulseformerFields(candidate)
pulseformer = candidate;
end
catch
end
end
function tf = hasPulseformerFields(candidate)
tf = false;
if isstruct(candidate)
tf = isfield(candidate, 'pulse') && isfield(candidate, 'pulselength');
return
end
tf = isobject(candidate) && isprop(candidate, 'pulse') && isprop(candidate, 'pulselength');
end

View File

@@ -1,201 +0,0 @@
function [Bits, Symbols, Scpe_cell, found_sync] = loadAndSyncRunSignals(dataTable, options)
%LOADANDSYNCRUNSIGNALS Load and synchronize signal files for one run.
%
% Inputs:
% dataTable - one-row table with run metadata and signal file paths
% options - struct with storage_path, start_occurence and max_occurences
%
% Outputs:
% Bits - transmitted bit reference
% Symbols - transmitted symbol reference
% Scpe_cell - synchronized received signal occurrences
% found_sync - true when a valid synchronization was found
found_sync = 0;
tempLocalStorage = 1;
Scpe_cell = {};
loaded_from_cache = false;
storage_dir = fullfile(prefdir, 'temp_sync_data');
if tempLocalStorage == 1
local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id)));
if exist(local_filename, 'file')
try
cacheData = load(local_filename, 'Bits', 'Symbols', 'Scpe_cell');
if isValidSyncCache(cacheData)
Bits = cacheData.Bits;
Symbols = cacheData.Symbols;
Scpe_cell = cacheData.Scpe_cell;
found_sync = 1;
loaded_from_cache = true;
else
warning('loadAndSyncRunSignals:InvalidSyncCache', ...
'Ignoring incomplete sync cache file "%s".', local_filename);
safeDelete(local_filename);
end
catch
safeDelete(local_filename);
found_sync = 0;
end
end
end
if ~found_sync
Bits = load(composeStoragePath(options.storage_path, dataTable.tx_bits_path));
Bits = Bits.Bits;
M = double(dataTable.pam_level);
fsym = dataTable.symbolrate;
Symbols_mapped = PAMmapper(M,0).map(Bits);
Symbols_mapped.fs = fsym;
Symbols = load(composeStoragePath(options.storage_path, dataTable.tx_symbols_path));
Symbols = Symbols.Symbols;
found_sync = 0;
try
Scpe_load = load(composeStoragePath(options.storage_path, dataTable.rx_sync_path));
Scpe_cell = Scpe_load.S;
[~,~,~,found_sync] = Scpe_cell{1}.tsynch("reference", Symbols, ...
"fs_ref", fsym, ...
"debug_plots", 0);
catch
% Continue with raw data if pre-synchronized data is unavailable.
end
end
if ~found_sync
try
Scpe_sig_raw = load(composeStoragePath(options.storage_path, dataTable.rx_raw_path));
Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw;
Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in", Scpe_sig_raw.fs, "fs_out", 2*fsym);
[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, ...
"fs_ref", fsym, ...
"debug_plots", 0);
catch
% Continue to mapped-symbol fallback if raw data sync fails.
end
end
if ~found_sync && exist('Scpe_sig_raw', 'var')
if length(Symbols_mapped.signal) ~= sum(Symbols_mapped.signal == Symbols.signal)
[~, Scpe_cell, ~, found_sync] = Scpe_sig_raw.tsynch("reference", Symbols_mapped, ...
"fs_ref", fsym, ...
"debug_plots", 0);
end
end
if tempLocalStorage == 1 && found_sync && ~loaded_from_cache
if ~exist(storage_dir, 'dir')
mkdir(storage_dir);
end
max_local_files = 10;
files = dir(fullfile(storage_dir, 'sync_data_run_*.mat'));
if length(files) >= max_local_files
[~, fileOrder] = sort([files.datenum]);
delete(fullfile(storage_dir, files(fileOrder(1)).name));
end
writeSyncCache(local_filename, Bits, Symbols, Scpe_cell);
end
if found_sync
Scpe_cell = selectSyncedOccurrences(Scpe_cell, options);
else
warning('Could not synchronize the received signal with the stored symbols!');
end
end
function path = composeStoragePath(storagePath, relativePath)
relativePath = firstValue(relativePath);
path = char(string(storagePath) + string(relativePath));
if ismac
path = strrep(path,'\','/');
else
path = strrep(path,'/','\');
end
end
function value = firstValue(value)
if iscell(value)
value = value{1};
elseif ~ischar(value) && ~isscalar(value)
value = value(1);
end
end
function Scpe_cell = selectSyncedOccurrences(Scpe_cell, options)
available_occurences = length(Scpe_cell);
start_occurence = floor(getOption(options, 'start_occurence', 1));
max_occurences = floor(getOption(options, 'max_occurences', available_occurences));
if available_occurences < 1
warning('loadAndSyncRunSignals:NoSyncedOccurrences', ...
'Synchronization reported success, but no synced occurrences are available.');
return
end
if start_occurence < 1
error('loadAndSyncRunSignals:InvalidStartOccurrence', ...
'start_occurence must be >= 1.');
end
if max_occurences < 1
Scpe_cell = Scpe_cell(1:0);
return
end
if start_occurence > available_occurences
warning('loadAndSyncRunSignals:StartOccurrenceTooHigh', ...
['Requested start_occurence %d, but only %d synced occurrences are available. ', ...
'Processing the last occurrence only.'], ...
start_occurence, available_occurences);
Scpe_cell = Scpe_cell(available_occurences);
return
end
stop_occurence = min(available_occurences, start_occurence + max_occurences - 1);
Scpe_cell = Scpe_cell(start_occurence:stop_occurence);
end
function value = getOption(options, name, defaultValue)
if isfield(options, name)
value = options.(name);
else
value = defaultValue;
end
end
function valid = isValidSyncCache(cacheData)
valid = isfield(cacheData, 'Bits') && ...
isfield(cacheData, 'Symbols') && ...
isfield(cacheData, 'Scpe_cell') && ...
iscell(cacheData.Scpe_cell) && ...
~isempty(cacheData.Scpe_cell);
end
function writeSyncCache(local_filename, Bits, Symbols, Scpe_cell)
cache_dir = fileparts(local_filename);
temp_filename = [tempname(cache_dir), '.mat'];
try
save(temp_filename, 'Bits', 'Symbols', 'Scpe_cell');
movefile(temp_filename, local_filename, 'f');
catch ME
safeDelete(temp_filename);
rethrow(ME);
end
end
function safeDelete(filename)
if exist(filename, 'file')
try
delete(filename);
catch
% Another parallel worker may already have removed or replaced it.
end
end
end

View File

@@ -0,0 +1,114 @@
function [Bits, Symbols, Scpe_cell, found_sync] = loadAndSyncSignalDataFromDb(dataTable, options)
% LOADSIGNALDATA Loads and synchronizes signal data from storage
%
% Inputs:d
% dataTable - Table with file paths and configuration
% options - Struct with storage_path and max_occurences
%
% Outputs:
% Symbols_mapped - Mapped symbols from bits
% Symbols - Original symbols
% Scpe_cell - Cell array of synchronized signals
% found_sync - Boolean indicating if synchronization was successful
found_sync = 0;
tempLocalStorage = 1;
% Define the fixed storage directory relative to the user's MATLAB preferences directory
storage_dir = fullfile(prefdir, 'temp_sync_data');
% Part A: Check and load from local storage if available-
if tempLocalStorage == 1
local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id)));
if exist(local_filename, 'file')
% Load from local storage and return
try
load(local_filename, 'Bits', 'Symbols', 'Scpe_cell');
found_sync = 1;
return
catch
delete(local_filename);
end
end
end
% If not locally saved, load from storage
if ~found_sync
% Load transmitted bits
Bits = load(fullfile([options.storage_path, char(dataTable.tx_bits_path)]));
Bits = Bits.Bits;
% Map bits to symbols
M = double(dataTable.pam_level);
fsym = dataTable.symbolrate;
Symbols_mapped = PAMmapper(M,0).map(Bits);
Symbols_mapped.fs = fsym;
% Load original symbols
Symbols = load(fullfile([options.storage_path, char(dataTable.tx_symbols_path)]));
Symbols = Symbols.Symbols;
found_sync = 0;
Scpe_cell = {};
% Try to load pre-synchronized data
try
Scpe_load = load(fullfile([options.storage_path, char(dataTable.rx_sync_path)]));
Scpe_cell = Scpe_load.S;
[~,~,~,found_sync] = Scpe_cell{2}.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
catch
% Continue to next method if this fails
end
end
% If not found, try with raw data
if ~found_sync
try
Scpe_sig_raw = load([options.storage_path, char(dataTable.rx_raw_path(1))]);
Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw;
Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in", Scpe_sig_raw.fs, "fs_out", 2*fsym);
[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 1);
catch
% Continue to next method if this fails
end
end
% Last attempt with mapped symbols
if ~found_sync && exist('Scpe_sig_raw', 'var')
if length(Symbols_mapped.signal) ~= sum(Symbols_mapped.signal == Symbols.signal)
[~, Scpe_cell, ~, found_sync] = Scpe_sig_raw.tsynch("reference", Symbols_mapped, "fs_ref", fsym, "debug_plots", 0);
end
end
% Part B: Save to local storage if data was loaded and synced
if tempLocalStorage == 1 && found_sync
% Create directory if it doesn't exist
if ~exist(storage_dir, 'dir')
mkdir(storage_dir);
end
% local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id)));
% List existing files and remove oldest if more than N
max_local_files = 10; % Store up to N files
files = dir(fullfile(storage_dir, 'sync_data_run_*.mat'));
if length(files) >= max_local_files
% Sort by date
[~, idx] = sort([files.datenum]);
% Delete oldest file
delete(fullfile(storage_dir, files(idx(1)).name));
end
% Save current data
save(local_filename, 'Bits', 'Symbols', 'Scpe_cell');
end
% Limit number of occurrences
if found_sync
record_realizations = min(options.max_occurences, length(Scpe_cell));
Scpe_cell = Scpe_cell(1:record_realizations);
else
warning('Could not synchronize the received signal with the stored symbols!');
end
end

View File

@@ -1,97 +0,0 @@
function dspInput = loadDspInputFromFilePaths(run_id, options)
%LOADDSPINPUTFROMFILEPATHS Load explicit signal files and prepare DSP input.
arguments
run_id
options struct
end
Tx_bits = load(textScalar(options.load_file_path.tx_bits_path));
Symbols = load(textScalar(options.load_file_path.tx_symbols_path));
Scpe_sig_raw = load(textScalar(options.load_file_path.rx_raw_path));
Tx_bits = Tx_bits.Bits;
Symbols = Symbols.Symbols;
Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw;
fsym = Symbols.fs;
M = Symbols.logbook.ModifierCopy{1}.M;
duob_mode = Symbols.logbook.ModifierCopy{1}.duobinary_mode;
Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in", Scpe_sig_raw.fs, "fs_out", 2*fsym);
[~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, ...
"fs_ref", fsym, ...
"debug_plots", 1);
if found_sync
Scpe_cell = selectSyncedOccurrences(Scpe_cell, options);
else
warning('Could not synchronize the received signal with the stored symbols!');
end
dspInput = struct();
dspInput.run_id = run_id;
dspInput.dataTable = table();
dspInput.Tx_bits = Tx_bits;
dspInput.Symbols = Symbols;
dspInput.Scpe_cell = Scpe_cell;
dspInput.found_sync = found_sync;
dspInput.fsym = fsym;
dspInput.M = M;
dspInput.duob_mode = duob_mode;
end
function value = textScalar(value)
value = firstValue(value);
value = char(string(value));
end
function value = firstValue(value)
if iscell(value)
value = value{1};
elseif ~ischar(value) && ~isscalar(value)
value = value(1);
end
end
function Scpe_cell = selectSyncedOccurrences(Scpe_cell, options)
available_occurences = length(Scpe_cell);
start_occurence = floor(getOption(options, 'start_occurence', 1));
max_occurences = floor(getOption(options, 'max_occurences', available_occurences));
if available_occurences < 1
warning('loadDspInputFromFilePaths:NoSyncedOccurrences', ...
'Synchronization reported success, but no synced occurrences are available.');
return
end
if start_occurence < 1
error('loadDspInputFromFilePaths:InvalidStartOccurrence', ...
'start_occurence must be >= 1.');
end
if max_occurences < 1
Scpe_cell = Scpe_cell(1:0);
return
end
if start_occurence > available_occurences
warning('loadDspInputFromFilePaths:StartOccurrenceTooHigh', ...
['Requested start_occurence %d, but only %d synced occurrences are available. ', ...
'Processing the last occurrence only.'], ...
start_occurence, available_occurences);
Scpe_cell = Scpe_cell(available_occurences);
return
end
stop_occurence = min(available_occurences, start_occurence + max_occurences - 1);
Scpe_cell = Scpe_cell(start_occurence:stop_occurence);
end
function value = getOption(options, name, defaultValue)
if isfield(options, name)
value = options.(name);
else
value = defaultValue;
end
end

View File

@@ -1,40 +0,0 @@
function dspInput = loadDspInputFromRunId(run_id, database, options)
%LOADDSPINPUTFROMRUNID Query run metadata and prepare canonical DSP input.
arguments
run_id
database
options struct
end
dataTable = queryRunid(run_id, database);
% Load signal files referenced by the run metadata, verify/synchronize the
% received signal, optionally cache the sync result, and cap occurrences.
[Tx_bits, Symbols, Scpe_cell, found_sync] = loadAndSyncRunSignals(dataTable, options);
dspInput = struct();
dspInput.run_id = run_id;
dspInput.dataTable = dataTable;
dspInput.Tx_bits = Tx_bits;
dspInput.Symbols = Symbols;
dspInput.Scpe_cell = Scpe_cell;
dspInput.found_sync = found_sync;
dspInput.fsym = dataTable.symbolrate;
dspInput.M = double(dataTable.pam_level);
dspInput.duob_mode = parseDbMode(dataTable.db_mode);
end
function mode = parseDbMode(rawMode)
if isnumeric(rawMode)
mode = db_mode(rawMode);
return
end
if iscell(rawMode)
rawMode = rawMode{1};
end
mode = db_mode(strrep(string(rawMode), '"', ''));
end

View File

@@ -0,0 +1,32 @@
function Scpe_sig = preprocessSignal(Scpe_sig, Symbols, fsym)
% PREPROCESSSIGNAL Performs standard preprocessing on a signal
%
% Inputs:
% Scpe_sig - Input signal
% Symbols - Reference symbols for synchronization
% fsym - Symbol frequency
%
% Outputs:
% Scpe_sig - Preprocessed signal
% Resample to 2x symbol rate
Scpe_sig = Scpe_sig.resample("fs_out", 2*fsym);
% Synchronize with reference
[Scpe_sig, ~] = Scpe_sig.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", 0);
% Apply Gaussian filter
if 1
Scpe_sig = Filter('filtdegree', 8, "f_cutoff", Symbols.fs.*0.52, ...
"fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
"active", true).process(Scpe_sig);
else
Scpe_sig = Filter('filtdegree', 4, "f_cutoff", Symbols.fs.*0.6, ...
"fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
"active", true).process(Scpe_sig);
end
%Remove DC offset
Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);
end

View File

@@ -1,291 +0,0 @@
function batchResults = runBatch(workerFcn, jobs, options)
%RUNBATCH Execute a list of jobs in serial or parallel.
% batchResults = runBatch(workerFcn, jobs) executes each jobs(i).args cell
% with workerFcn and returns a cell array aligned with the input jobs.
%
% Supported job fields:
% .args cell array of positional arguments for workerFcn
% .label string/char used for progress messages
% .meta arbitrary metadata for wrapper-specific bookkeeping
arguments
workerFcn (1,1) function_handle
jobs (1,:) struct = struct('args', {})
options.mode = processingMode.serial
options.waitbar (1,1) logical = true
options.waitbarMessage (1,1) string = "Processing Jobs..."
options.numWorkers (1,1) double {mustBeNonnegative, mustBeInteger} = 0
options.idleTimeout (1,1) double {mustBePositive} = 300
options.cancelExistingQueue (1,1) logical = true
options.resultHandler = []
options.errorHandler = []
end
mode = normalizeProcessingMode(options.mode);
jobs = normalizeJobs(jobs);
nJobs = numel(jobs);
batchResults = cell(1, nJobs);
jobDurations = nan(1, nJobs);
lastJobDuration = nan;
batchStart = tic;
effectiveWorkerCount = 1;
h = [];
if nJobs == 0
return
end
if options.waitbar
h = waitbar(0, char(options.waitbarMessage));
cleanupWaitbar = onCleanup(@() closeWaitbar(h));
updateWaitbar(options.waitbar, h, 0, nJobs, jobDurations, ...
lastJobDuration, 0, effectiveWorkerCount);
end
switch mode
case processingMode.parallel
pool = setupParallelPool(options.numWorkers, options.idleTimeout, options.cancelExistingQueue);
effectiveWorkerCount = min(pool.NumWorkers, nJobs);
futures = parallel.FevalFuture.empty(nJobs, 0);
for jobIdx = 1:nJobs
fprintf('[%s] Submitted.\n', jobs(jobIdx).label);
futures(jobIdx) = parfeval(pool, @runTimedJob, 3, workerFcn, jobs(jobIdx).args);
end
consumedIdx = false(nJobs, 1);
for completedCount = 1:nJobs
try
[jobIdx, jobResult, jobRuntime, jobError] = fetchNext(futures);
consumedIdx(jobIdx) = true;
jobDurations(jobIdx) = jobRuntime;
lastJobDuration = jobRuntime;
if isempty(jobError)
batchResults{jobIdx} = jobResult;
fprintf('[%s] Completed (%d/%d, runtime %s).\n', ...
jobs(jobIdx).label, completedCount, nJobs, formatDuration(jobRuntime));
if ~isempty(options.resultHandler)
options.resultHandler(jobResult, jobs(jobIdx), jobIdx);
end
else
batchResults{jobIdx} = jobError;
if ~isempty(options.errorHandler)
options.errorHandler(jobError, jobs(jobIdx), jobIdx);
else
defaultErrorHandler(jobError, jobs(jobIdx), jobIdx);
end
end
catch fetchErr
errorJobIdx = findErroredFuture(futures, consumedIdx);
if isempty(errorJobIdx)
rethrow(fetchErr);
end
consumedIdx(errorJobIdx) = true;
errInfo = extractFutureError(futures(errorJobIdx));
batchResults{errorJobIdx} = errInfo;
if ~isempty(options.errorHandler)
options.errorHandler(errInfo, jobs(errorJobIdx), errorJobIdx);
else
defaultErrorHandler(errInfo, jobs(errorJobIdx), errorJobIdx);
end
end
updateWaitbar(options.waitbar, h, completedCount, nJobs, ...
jobDurations, lastJobDuration, toc(batchStart), effectiveWorkerCount);
end
case processingMode.serial
for jobIdx = 1:nJobs
jobStart = tic;
try
fprintf('[%s] Running.\n', jobs(jobIdx).label);
jobResult = workerFcn(jobs(jobIdx).args{:});
jobDurations(jobIdx) = toc(jobStart);
lastJobDuration = jobDurations(jobIdx);
batchResults{jobIdx} = jobResult;
fprintf('[%s] Completed (%d/%d, runtime %s).\n', ...
jobs(jobIdx).label, jobIdx, nJobs, formatDuration(jobDurations(jobIdx)));
if ~isempty(options.resultHandler)
options.resultHandler(jobResult, jobs(jobIdx), jobIdx);
end
catch ME
jobDurations(jobIdx) = toc(jobStart);
lastJobDuration = jobDurations(jobIdx);
batchResults{jobIdx} = ME;
if ~isempty(options.errorHandler)
options.errorHandler(ME, jobs(jobIdx), jobIdx);
else
defaultErrorHandler(ME, jobs(jobIdx), jobIdx);
end
end
updateWaitbar(options.waitbar, h, jobIdx, nJobs, ...
jobDurations, lastJobDuration, toc(batchStart), effectiveWorkerCount);
end
otherwise
error('runBatch:UnknownMode', 'Unknown processing mode "%s".', string(mode));
end
end
function mode = normalizeProcessingMode(mode)
if isa(mode, 'processingMode')
return
end
modeString = string(mode);
switch modeString
case "serial"
mode = processingMode.serial;
case "parallel"
mode = processingMode.parallel;
otherwise
error('runBatch:InvalidMode', 'Unsupported processing mode "%s".', modeString);
end
end
function jobs = normalizeJobs(jobs)
if isempty(jobs)
jobs = struct('args', {}, 'label', {}, 'meta', {});
return
end
for jobIdx = 1:numel(jobs)
if ~isfield(jobs, 'args') || isempty(jobs(jobIdx).args)
jobs(jobIdx).args = {};
end
if ~iscell(jobs(jobIdx).args)
error('runBatch:InvalidArgs', 'jobs(%d).args must be a cell array.', jobIdx);
end
if ~isfield(jobs, 'label') || isempty(jobs(jobIdx).label)
jobs(jobIdx).label = sprintf('Job %d', jobIdx);
else
jobs(jobIdx).label = string(jobs(jobIdx).label);
end
if ~isfield(jobs, 'meta')
jobs(jobIdx).meta = struct();
end
end
end
function [jobResult, jobRuntime, jobError] = runTimedJob(workerFcn, jobArgs)
jobStart = tic;
jobError = [];
try
jobResult = workerFcn(jobArgs{:});
catch ME
jobResult = [];
jobError = ME;
end
jobRuntime = toc(jobStart);
end
function updateWaitbar(useWaitbar, h, completedCount, totalCount, jobDurations, lastJobDuration, elapsedSeconds, effectiveWorkerCount)
if ~useWaitbar || ~isgraphics(h)
return
end
completedDurations = jobDurations(~isnan(jobDurations));
if isempty(completedDurations)
averageJobTimeText = 'calculating...';
lastJobTimeText = 'calculating...';
remainingTimeText = 'calculating...';
else
averageJobSeconds = mean(completedDurations);
remainingJobs = totalCount - completedCount;
remainingSeconds = averageJobSeconds * remainingJobs / max(1, effectiveWorkerCount);
averageJobTimeText = formatDuration(averageJobSeconds);
lastJobTimeText = formatDuration(lastJobDuration);
remainingTimeText = formatDuration(remainingSeconds);
end
message = sprintf(['Completed %d/%d jobs\n' ...
'Elapsed total: %s\n' ...
'Last job time: %s\n' ...
'Avg. time/job: %s\n' ...
'Estimated remaining: %s'], ...
completedCount, totalCount, formatDuration(elapsedSeconds), ...
lastJobTimeText, averageJobTimeText, remainingTimeText);
waitbar(completedCount / totalCount, h, message);
drawnow;
end
function text = formatDuration(seconds)
if isempty(seconds) || isnan(seconds) || isinf(seconds)
text = 'unknown';
return
end
seconds = max(0, seconds);
hours = floor(seconds / 3600);
minutes = floor(mod(seconds, 3600) / 60);
wholeSeconds = floor(mod(seconds, 60));
if hours > 0
text = sprintf('%d:%02d:%02d', hours, minutes, wholeSeconds);
else
text = sprintf('%02d:%02d', minutes, wholeSeconds);
end
end
function closeWaitbar(h)
if isgraphics(h)
delete(h);
end
end
function pool = setupParallelPool(numWorkers, idleTimeout, cancelExistingQueue)
pool = gcp('nocreate');
if isempty(pool)
if numWorkers > 0
pool = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
else
pool = parpool('local', 'IdleTimeout', idleTimeout);
end
elseif numWorkers > 0 && pool.NumWorkers ~= numWorkers
delete(pool);
pool = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
end
if cancelExistingQueue
queue = pool.FevalQueue;
if ~isempty(queue.QueuedFutures) || ~isempty(queue.RunningFutures)
queuedCount = numel(queue.QueuedFutures) + numel(queue.RunningFutures);
cancelAll(queue);
fprintf('Canceled %d unfinished jobs from the current pool queue.\n', queuedCount);
end
end
end
function errorJobIdx = findErroredFuture(futures, consumedIdx)
readMask = arrayfun(@(future) future.Read, futures).';
errorJobIdx = find(readMask & ~consumedIdx, 1);
end
function errInfo = extractFutureError(future)
errInfo = future.Error;
if iscell(errInfo)
errInfo = errInfo{1};
end
end
function defaultErrorHandler(ME, job, jobIndex)
fprintf('[%s | #%d] ERROR [%s]: %s\n', job.label, jobIndex, ME.identifier, ME.message);
for st = ME.stack'
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
end
end

View File

@@ -9,11 +9,9 @@ function [results, wh] = submitJobs(run_ids, dsp_options, submit_mode, submit_op
% submit_options : struct with fields
% .waitbar (logical)
% .wh (DataStorage object)
% .storePackages (string array, optional package whitelist)
%
% results : cell(nJobsPerRunId, nRunIds)
% wh : updated DataStorage. For multiple run_ids, run_id is added as
% the first storage axis.
% wh : updated DataStorage
arguments
run_ids int32 = 0
@@ -21,60 +19,172 @@ arguments
submit_mode processingMode = processingMode.serial
submit_options.waitbar (1,1) logical = true
submit_options.wh = DataStorage(struct())
submit_options.storePackages string = string.empty()
end
% Normalize
run_ids = run_ids(:)';
nRunIds = numel(run_ids);
sweepWh = submit_options.wh;
validateNoRunIdSweepParameter(sweepWh);
nJobsPerRunId = sweepWh.getLastLinIndice();
nJobsPerRunId = submit_options.wh.getLastLinIndice();
totalJobs = nRunIds * nJobsPerRunId;
wh = buildStorageWarehouse(sweepWh, run_ids);
% Preallocate
results = cell(nJobsPerRunId, nRunIds);
jobs = repmat(struct('args', {{}}, 'label', "", 'meta', struct()), 1, totalJobs);
futures = parallel.FevalFuture.empty(totalJobs,0);
jobIndices = zeros(totalJobs,2);
jobCounter = 0;
for r = 1:nRunIds
for k = 1:nJobsPerRunId
jobCounter = jobCounter + 1;
userParameters = buildUserParameters(k, sweepWh);
jobOptions = dsp_options;
jobOptions.userParameters = userParameters;
% Optional waitbar
if submit_options.waitbar
h = waitbar(0, 'Processing Jobs...');
cleanupObj = onCleanup(@() delete(h));
end
jobs(jobCounter).args = [{run_ids(r)}, structToNameValue(jobOptions)];
jobs(jobCounter).label = sprintf('RunID %d, Job %d', run_ids(r), k);
jobs(jobCounter).meta.runIndex = r;
jobs(jobCounter).meta.jobIndex = k;
jobs(jobCounter).meta.sweepJobIndex = k;
jobs(jobCounter).meta.storageJobIndex = buildStorageJobIndex(run_ids(r), userParameters, wh);
jobs(jobCounter).meta.run_id = run_ids(r);
switch submit_mode
case processingMode.parallel
% SET UP POOL & QUEUE
p = setupParallelPool(11, 300); % 10 workers, 300s idle timeout
% === submit all futures ===
jobCounter = 0;
for r = 1:nRunIds
for k = 1:nJobsPerRunId
jobCounter = jobCounter + 1;
jobIndices(jobCounter,:) = [r,k];
opt = buildOptionalVars(k, submit_options.wh);
futures(jobCounter) = parfeval( ...
p, @dsp_runid, 1, ...
run_ids(r), ...
"database_type", dsp_options.database_type, ...
"dataBase", dsp_options.dataBase, ...
"append_to_db", dsp_options.append_to_db, ...
"load_file_path", dsp_options.load_file_path, ...
"max_occurences", dsp_options.max_occurences, ...
"storage_path", dsp_options.storage_path, ...
"mode", dsp_options.mode, ...
"parameters", opt ...
);
fprintf('[RunID %d, Job %d] Submitted to pool.\n', run_ids(r), k);
end
end
% W A I T B A R U P D A T E
if submit_options.waitbar
futureArray = futures;
updateWB = @() waitbar( ...
sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray))/totalJobs, ...
h, sprintf('Completed %d/%d jobs', ...
sum(arrayfun(@(f) strcmp(f.State,'finished'), futureArray)), totalJobs) ...
);
afterEach(futureArray, updateWB, 0);
end
% before the loop
% Keep track of which futures we've already handled:
consumedIdx = false(totalJobs,1);
% fetch in completion order, handling successes and errors
for n = 1:totalJobs
try
% This returns the value AND the linear index in 'futures'
[idx, val] = fetchNext(futures);
duration = futures(idx).RunningDuration;
startDT = futures(idx).StartDateTime;
finishDT = futures(idx).FinishDateTime;
duration = finishDT - startDT;
% Mark it consumed
consumedIdx(idx) = true;
% Map back to (r,k) and store
r = jobIndices(idx,1);
k = jobIndices(idx,2);
fprintf('[%s] JobID %d/%d (%.1f%%) %s RunID %d Subjob %d fetched.\n', ...
datestr(now,'yyyy-mm-dd HH:MM:SS'), ...
r, totalJobs, 100*n/totalJobs,char(duration), ...
run_ids(r), k);
% Update waitbar
if submit_options.waitbar
waitbar(n/totalJobs, h, ...
sprintf('Fetched %d/%d (%.1f% Percent)', n, totalJobs, 100*n/totalJobs));
drawnow; % force the GUI to refresh
end
storeResult(val, k, submit_options.wh);
results{k,r} = val;
catch fetchErr
% fetchNext has already set Read=true on the errored future.
% Find the one Read==true that we have _not_ yet consumed.
readMask = arrayfun(@(f) f.Read, futures);
idxErr = find(readMask & ~consumedIdx', 1);
consumedIdx(idxErr) = true;
% Pull the _real_ exception out of the future object
errInfo = futures(idxErr).Error;
if iscell(errInfo)
origME = errInfo{1};
else
origME = errInfo;
end
% Map back to (r,k) and log
r = jobIndices(idxErr,1);
k = jobIndices(idxErr,2);
handleError(origME, k, run_ids(r));
results{k,r} = origME;
end
end
case processingMode.serial
% SERIAL EXECUTION
jobCounter = 0;
for r = 1:nRunIds
for k = 1:nJobsPerRunId
jobCounter = jobCounter + 1;
optionalVars = buildOptionalVars(k, submit_options.wh);
try
fprintf('[RunID %d, Job %d] Running in linear mode...\n', run_ids(r), k);
val = dsp_runid( run_ids(r), ...
"database_type", dsp_options.database_type, ...
"dataBase", dsp_options.dataBase, ...
"append_to_db", dsp_options.append_to_db, ...
"load_file_path", dsp_options.load_file_path, ...
"max_occurences", dsp_options.max_occurences, ...
"storage_path", dsp_options.storage_path, ...
"mode", dsp_options.mode, ...
"parameters", optionalVars );
fprintf('[RunID %d, Job %d] Completed successfully.\n', run_ids(r), k);
storeResult(val, k, submit_options.wh);
results{k,r} = val;
catch ME
handleError(ME, k, run_ids(r));
results{k,r} = ME;
end
if submit_options.waitbar
waitbar(jobCounter/totalJobs, h, ...
sprintf('Completed %d/%d jobs', jobCounter, totalJobs));
end
end
end
otherwise
error('Unknown submit_mode "%s".', string(submit_mode))
end
linearResults = runBatch(@dsp_runid, jobs, ...
"mode", submit_mode, ...
"waitbar", submit_options.waitbar, ...
"waitbarMessage", "Processing Jobs...", ...
"numWorkers", 11, ...
"idleTimeout", 300, ...
"cancelExistingQueue", true, ...
"resultHandler", @storeResult, ...
"errorHandler", @handleError);
for idx = 1:totalJobs
r = jobs(idx).meta.runIndex;
k = jobs(idx).meta.jobIndex;
results{k, r} = linearResults{idx};
end
wh = submit_options.wh;
%% Local helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function handleError(ME, job, ~)
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', job.meta.run_id, job.meta.jobIndex, ...
function handleError(ME, jobIndex, run_id)
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', run_id, jobIndex, ...
ME.identifier, ME.message);
for st = ME.stack'
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
@@ -82,116 +192,44 @@ end
fprintf('Full report:\n%s\n', getReport(ME,'extended'));
end
function userParameters = buildUserParameters(jobIndex, wh)
userParameters = struct();
function optionalVars = buildOptionalVars(jobIndex, wh)
optionalVars = struct();
if ~isempty(wh.getDimension())
[vals, names] = wh.getPhysIndicesByLinIndex(jobIndex);
for pi = 1:numel(names)
userParameters.(names{pi}) = vals{pi};
optionalVars.(names{pi}) = vals{pi};
end
end
end
function validateNoRunIdSweepParameter(wh)
if isfield(wh.inputParams, "run_id")
error("submitJobs:RunIdParameterConflict", ...
"Do not define userParameters.run_id. submitJobs manages run_id as a storage axis.");
function storeResult(val, jobIndex, wh)
if ~isempty(wh)
wh.addValueToStorageByLinIdx(val.ffe_package, 'ffe_package', jobIndex);
wh.addValueToStorageByLinIdx(val.mlse_package, 'mlse_package', jobIndex);
wh.addValueToStorageByLinIdx(val.vnle_package, 'vnle_package', jobIndex);
wh.addValueToStorageByLinIdx(val.dbtgt_package,'dbtgt_package',jobIndex);
wh.addValueToStorageByLinIdx(val.dbenc_package,'dbenc_package',jobIndex);
wh.addValueToStorageByLinIdx(val.mlmlse_package,'mlmlse_package',jobIndex);
end
end
function storageWh = buildStorageWarehouse(sweepWh, runIds)
if isscalar(runIds)
storageWh = sweepWh;
return
end
storageParameters = struct();
storageParameters.run_id = runIds;
sweepParameterNames = fieldnames(sweepWh.inputParams);
for parameterIdx = 1:numel(sweepParameterNames)
parameterName = sweepParameterNames{parameterIdx};
storageParameters.(parameterName) = sweepWh.inputParams.(parameterName);
end
storageWh = DataStorage(storageParameters);
end
function storageJobIndex = buildStorageJobIndex(runId, userParameters, wh)
storageParameterNames = wh.fn;
if isempty(storageParameterNames)
storageJobIndex = 1;
return
end
storageSubscripts = cell(1, numel(storageParameterNames));
for parameterIdx = 1:numel(storageParameterNames)
parameterName = char(storageParameterNames(parameterIdx));
if strcmp(parameterName, "run_id")
parameterValue = runId;
else
parameterValue = userParameters.(parameterName);
function p = setupParallelPool(numWorkers, idleTimeout)
% Ensure a pool exists at the right size & timeout
p = gcp('nocreate');
if isempty(p) || p.NumWorkers~=numWorkers
if ~isempty(p)
delete(p);
end
storageSubscripts{parameterIdx} = wh.getIndexByPhys(parameterName, parameterValue);
p = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
end
if isscalar(storageSubscripts)
storageJobIndex = storageSubscripts{1};
else
storageJobIndex = sub2ind(wh.getStorageSize(), storageSubscripts{:});
% Cancel anything left in the pool's default queue
q = p.FevalQueue;
if ~isempty(q.QueuedFutures) || ~isempty(q.RunningFutures)
cancelAll(q);
fprintf('Canceled %d unfetched jobs from old queue.\n', ...
numel(q.QueuedFutures)+numel(q.RunningFutures));
end
end
function nameValue = structToNameValue(options)
names = fieldnames(options);
nameValue = cell(1, 2*numel(names));
for i = 1:numel(names)
nameValue{2*i - 1} = string(names{i});
nameValue{2*i} = options.(names{i});
end
end
function storeResult(val, job, ~)
if isempty(wh) || ~isstruct(val)
return
end
storageJobIndex = job.meta.storageJobIndex;
resultFields = fieldnames(val);
for resultIdx = 1:numel(resultFields)
storageName = resultFields{resultIdx};
if ~shouldStore(storageName)
continue
end
if isempty(val.(storageName))
continue
end
ensureStorage(storageName);
wh.addValueToStorageByLinIdx(val.(storageName), storageName, storageJobIndex);
end
end
function tf = shouldStore(storageName)
if isempty(submit_options.storePackages)
tf = true;
return
end
tf = any(string(storageName) == submit_options.storePackages);
end
function ensureStorage(storageName)
if ~isfield(wh.sto, storageName)
wh.addStorage(storageName);
end
end
end

View File

@@ -1,6 +1,6 @@
% Define the precomp path
precomp_path = "W:\labdata\sioe_labor\precomp";
precomp_path = "C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\ECOC_2025_MPI";
precomp_path = "C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\precomp";
% Step 1: Find all valid files (assume .mat files for ChannelFreqResp)
fileList = dir(fullfile(precomp_path, '*.mat'));
fileNames = {fileList.name};

View File

@@ -3,14 +3,10 @@ function burst_count = count_error_bursts(err_pos, max_burst_length)
% max_burst_length: Maximum length for which bursts are counted (e.g., 10)
% Sort the error positions to ensure they're in increasing order
err_pos = sort(err_pos(:).');
err_pos = sort(err_pos);
% Initialize burst_count array to hold the counts for each burst length
burst_count = zeros(1, max_burst_length);
if isempty(err_pos)
return
end
% Find the differences between consecutive error positions
diffs = diff(err_pos);

View File

@@ -0,0 +1,46 @@
% plot_dispersion_models
% ------------------------------------------------------------
% Visualizes exact and linearized chromatic dispersion D(λ)
% around the zero-dispersion wavelength (ZDW).
%
% Inputs:
% lambda0_nm - ZDW in nm
% S0 - dispersion slope at ZDW [ps/(nm^2·km)]
% ------------------------------------------------------------
lambda0_nm = 1310;
S0 = [0.07,0.09]';
% wavelength axis around ZDW
lambda_nm = linspace(lambda0_nm-60, lambda0_nm+60, 400);
% exact dispersion model
D_exact = (S0./4) .* ( ...
lambda_nm - (lambda0_nm^4)./(lambda_nm.^3) );
% linearized model around ZDW
D_lin = S0 .* (lambda_nm - lambda0_nm);
% plot
figure('Color','w'); hold on;
cols = [0.6510 0.8078 0.8902;...
0.1216 0.4706 0.7059;...
0.6980 0.8745 0.5412;...
0.2000 0.6275 0.1725];
% plot(lambda_nm, D_lin(1,:), '-', 'LineWidth',2, 'DisplayName','Linearized model','Color',[cols(1,:)]);
plot(lambda_nm, D_exact(1,:), 'LineWidth',2, 'DisplayName',sprintf('S_0 = 0.07'),'Color',[cols(2,:)]);
% plot(lambda_nm, D_lin(2,:), '-', 'LineWidth',2, 'HandleVisibility','off','Color',[cols(3,:)]);
plot(lambda_nm, D_exact(2,:), 'LineWidth',2, 'HandleVisibility','on', 'DisplayName',sprintf('S_0 = 0.09'),'Color',[cols(4,:)]);
xlabel('Wavelength $\lambda$ [nm]','Interpreter','latex');
ylabel('D($\lambda$) [ps/(nm km)]','Interpreter','latex');
title('Chromatic Dispersion Around the ZDW');
legend('Location','best');
grid on; box on;
xlim([min(lambda_nm) max(lambda_nm)])
ylim([-5 5]);
% mat2tikz_improved('C:\Users\Silas\Documents\6971e0b65b380ca6d71c837f\02_IMDD_System\tikz\dispersion\dispersion')

View File

@@ -0,0 +1,120 @@
function loss_curve_digitized(filename)
% PLOT_WPD_DATASETS Reads and plots scattering data from a CSV file.
% filename: String containing the path to the CSV file (e.g., 'wpd_datasets.csv')
%
% This function expects a CSV with the following structure:
% Row 1: Dataset Names (every 2nd column)
% Row 2: Variable Names (X, Y, X, Y...)
% Row 3+: Numeric Data (potentially with NaNs for unequal lengths)
% --- 1. Import Data ---
if nargin < 1
filename = 'wpd_datasets.csv'; % Default filename
end
% Read the numeric data, skipping the first 2 header lines
% 'TreatAsMissing' ensures empty cells become NaNs
raw_data = readmatrix(filename, 'NumHeaderLines', 2);
% Read the first line separately to parse Dataset Names
fid = fopen(filename, 'r');
if fid == -1
error('Could not open file: %s', filename);
end
header_line = fgetl(fid);
fclose(fid);
% Split header by comma to get names
raw_names = split(header_line, ',');
% Extract non-empty names (assuming names are in col 1, 3, 5...)
dataset_names = raw_names(~cellfun('isempty', raw_names));
% --- 2. Setup Plot ---
figure('Color', 'w', 'Position', [100, 100, 800, 600]);
ax = gca;
hold(ax, 'on');
line_styles = {'-', '-', '-', '-'};
% --- 3. Iterate and Plot Each Dataset ---
num_datasets = length(dataset_names);
for i = 1:num_datasets
% Calculate column indices for X and Y
% Dataset 1: Cols 1,2 | Dataset 2: Cols 3,4 | etc.
col_x = (i-1)*2 + 1;
col_y = (i-1)*2 + 2;
% Extract data
if col_y > size(raw_data, 2)
warning('Data columns missing for dataset %d', i);
break;
end
X = raw_data(:, col_x);
Y = raw_data(:, col_y);
% Remove NaNs (missing data due to unequal lengths)
valid_mask = ~isnan(X) & ~isnan(Y);
X = X(valid_mask);
Y = Y(valid_mask);
[X, sortIdx] = sort(X);
Y = Y(sortIdx);
% 3. Smooth the Data
if numel(X) > 20
if 1
Y = smoothdata(Y, 'sgolay', 15);
else
Y = movmean(Y, 15);
end
end
% Plotting
% Using semilogy because scattering data often spans orders of magnitude
% (Adjust to 'plot' if linear scale is preferred)
p = plot(ax, X, Y, ...
'LineStyle', line_styles{mod(i-1, length(line_styles)) + 1}, ...
'Marker', 'none', ...
'Color', 'black', ...
'LineWidth', 1.5, ...
'MarkerSize', 6, ...
'DisplayName', dataset_names{i});
% Optional: Fill marker faces for better visibility
% p.MarkerFaceColor = p.Color;
% p.MarkerFaceAlpha = 0.3; % Semi-transparent fill
end
% --- 4. Styling and Formatting ---
% Axis Labels (Inferred from typical scattering plots)
xlabel(ax, 'Wavelength (\mu m)', 'FontSize', 12, 'FontWeight', 'bold');
ylabel(ax, 'Intensity / Cross-Section (a.u.)', 'FontSize', 12, 'FontWeight', 'bold');
% Title
title(ax, 'Dataset Comparison', 'FontSize', 14);
% Legend
legend(ax, 'Location', 'best', 'Interpreter', 'none', 'Box', 'on');
% Grid
grid(ax, 'on');
ax.GridAlpha = 0.3;
ax.MinorGridAlpha = 0.1;
% Set Log Scale for Y (likely required for this data type)
set(ax, 'YScale', 'log');
% Enhance axis appearance
set(ax, 'Box', 'on', 'LineWidth', 1.2, 'FontSize', 10);
hold(ax, 'off');
end

View File

@@ -0,0 +1,124 @@
%% ============================================================
% SNR vs Optical Input Power (dBm) Shot vs Thermal vs Combined
% - Generate optical field as sine with target RMS power (verified)
% - Magnitude-square detection -> optical power
% - Photocurrent = Rd * P
% - Add shot noise + thermal noise (white, PSD-based)
% - Compute SNR for: shot-only, thermal-only, combined
% ============================================================
% Constants
k = Constant.Boltzmann;
q = Constant.ElementaryCharge;
% Receiver / PD parameters
T = 20 + 273.15; % K
R = 50; % Ohm (front-end/load)
Rd = 0.7; % A/W (responsivity)
Be = 100e9; % Hz (electrical noise bandwidth)
Id = 0; % A (dark current, optional)
% Sampling for time-domain demo (needs to be >> Be)
fs = 1e12; % Hz
N = 2^14; % samples
t = (0:N-1).'/fs;
% Choose an electrical tone within bandwidth (arbitrary for demo)
f0 = 10e9; % Hz
% Thermal current PSD (two-sided) and variance in Be
Si_th = 4*k*T/R; % A^2/Hz (two-sided)
sigma2_th = Si_th * Be; % A^2
sigma_th = sqrt(sigma2_th); % A_rms
% Optical input power sweep (in dBm)
P_dBm = linspace(-40, 10, 300);
P_W = 10.^((P_dBm - 30)/10); % W
% Pre-allocate results
SNR_shot_dB = zeros(size(P_dBm));
SNR_th_dB = zeros(size(P_dBm));
SNR_tot_dB = zeros(size(P_dBm));
% --- Main loop over optical input power
for ii = 1:numel(P_W)
Pavg = P_W(ii);
% Optical field with RMS power = Pavg:
% Let x(t) be the optical field amplitude such that |x|^2 has mean Pavg.
% Use a sinusoid: x(t) = A*sin(2*pi*f0*t), then mean(|x|^2)=A^2/2.
A = sqrt(2*Pavg); % -> mean(|x|^2) = Pavg
x = A * sin(2*pi*f0*t); % "optical field" (real for simplicity)
% Verify RMS/mean power numerically (optional)
P_meas = mean(abs(x).^2); % should be ~ Pavg
a = P_meas - Pavg;
assert(a<1);
% Magnitude-square detection -> optical power waveform
Popt = abs(x).^2; % W (instantaneous)
% Photocurrent waveform (includes DC + 2f0 component for this demo)
I_sig = Rd * Popt; % A
% Define "signal power" as the mean-squared photocurrent due to signal
% (for this sine-squared waveform, it's OK for a demo SNR definition)
P_sig = mean(I_sig.^2);
% -------- Shot noise (white) --------
% Two-sided PSD: Si_shot = 2*q*(I_photo + I_dark)
% For this demo, use average current to set the white-noise level:
Ibar = mean(I_sig) + Id;
Si_shot = 2*q*Ibar; % A^2/Hz
sigma2_sh = Si_shot * Be; % A^2
sigma_sh = sqrt(sigma2_sh); % A_rms
n_sh = sigma_sh * randn(N,1); % time-domain shot noise
% -------- Thermal noise (white Gaussian) --------
n_th = sigma_th * randn(N,1); % time-domain thermal noise
% Noise powers (mean-square) in time domain
Pn_sh = mean(n_sh.^2);
Pn_th = mean(n_th.^2);
Pn_tot = mean((n_sh + n_th).^2); % ~ Pn_sh + Pn_th (independent)
% SNRs
SNR_shot = P_sig / Pn_sh;
SNR_th = P_sig / Pn_th;
SNR_tot = P_sig / Pn_tot;
SNR_shot_dB(ii) = 10*log10(SNR_shot);
SNR_th_dB(ii) = 10*log10(SNR_th);
SNR_tot_dB(ii) = 10*log10(SNR_tot);
% Optional sanity check (can comment out)
% if ii == 1
% fprintf('Check Pavg target/meas: %.3g W / %.3g W\n', Pavg, P_meas);
% end
end
% Plot comparison
figure(1); clf; hold on;
plot(P_dBm, SNR_shot_dB, 'LineWidth', 1.5);
plot(P_dBm, SNR_th_dB, 'LineWidth', 1.5);
plot(P_dBm, SNR_tot_dB, 'LineWidth', 1.5);
grid on;
xlabel('Optical input power [dBm]');
ylabel('SNR [dB]');
title('SNR vs Optical Input Power: Shot vs Thermal vs Combined');
legend('Shot-noise only','Thermal-noise only','Shot + Thermal','Location','best');
% Print example at 0 dBm
[~,idx0] = min(abs(P_dBm - 0));
fprintf('At 0 dBm:\n');
fprintf(' SNR (shot only) = %.2f dB\n', SNR_shot_dB(idx0));
fprintf(' SNR (thermal only) = %.2f dB\n', SNR_th_dB(idx0));
fprintf(' SNR (combined) = %.2f dB\n', SNR_tot_dB(idx0));
% Also print NEP based on thermal PSD (constant NEP_th)
NEP_th = sqrt(Si_th)/Rd; % W/sqrt(Hz)
fprintf('Thermal NEP = %.3g W/sqrt(Hz)\n', NEP_th);

View File

@@ -0,0 +1,189 @@
Rayleigh,,Experimental,,Infrared Absorption,
X,Y,X,Y,X,Y
0.7066005680911753,3.651009696525016,0.7072188355785799,4.848577786727532,1.4943918372804705,0.009324755400827079
0.7362740977034739,3.1008424465551374,0.7104385926007812,5.059236053617898,1.5870823136443164,0.04795688074913024
0.7704634954089999,2.559836201011587,0.7143024334187478,5.3165954733738054,1.6443739975494367,0.12666181795273013
0.8246453070916075,1.9963763545469397,0.7194723146571098,4.679249634272495,1.6900695253042852,0.3029389206853443
0.8846384359818439,1.4197559292837703,0.7194729766137344,4.646181068667172,1.7344762235109785,0.7194296259968607
0.9375334039861705,1.0762856922437043,0.7227053108118038,4.236869693191961,1.7975573800551148,2.1896107578891244
0.9929980876073115,0.9010620061822204,0.7272205169484065,4.147543538340177,,
1.0420225952274251,0.6977835411732736,0.7291487965959542,4.420849736815974,,
1.0845923638007697,0.5842349714248559,0.7310784001567514,4.645798669234298,,
1.129741777340298,0.4856980392071641,0.7355902965102309,4.71201487210429,,
1.1981066716622326,0.3841608204561696,0.738821306795051,4.358286713153517,,
1.24841405122088,0.31935666463679646,0.7414069093708566,4.059822180897163,,
1.289688370679823,0.2850148847474179,0.742705668268381,3.676053825899206,,
1.3541801567910463,0.23691184007300667,0.7440031032526563,3.376112267508452,,
1.4083573347772815,0.19416797258075097,0.7452965664971838,3.235432943532804,,
1.4618851333147547,0.16723647350379714,0.7459426361628229,3.1898499103097238,,
1.50315548103395,0.15574103754742674,0.7549743723492774,3.0137121806723157,,
1.5386251028515106,0.1419883611511702,0.7620678995388148,2.971117042589562,,
1.596665459699088,0.12316075278891304,0.765291628300764,2.971049114206588,,
1.6437444767994136,0.10759838849626362,0.7756075603390012,2.9708317538173317,,
1.683725994970454,0.09949510555249974,0.7762523060913911,2.9708181693210105,,
1.717258069747722,0.09398483395032568,0.7839919029465676,2.8875658983600925,,
1.7636903552257834,0.08387517688001396,0.7885090949530442,2.767180560538318,,
1.7940013490676008,0.07701567091052414,0.7917348095848672,2.7088647150205,,
,,0.7936723566251598,2.6144535948583694,,
,,0.7962546494178423,2.5233214143921474,,
,,0.8085087904529968,2.417989089048743,,
,,0.8104423657535417,2.435165393892523,,
,,0.8201175237791369,2.3335556930105406,,
,,0.8285065000830756,2.158298098247211,,
,,0.8317335386281479,2.083056634772393,,
,,0.8356046609689853,2.024737963659226,,
,,0.8407639509013534,1.996148150112612,,
,,0.8459232408337212,1.967962031984024,,
,,0.854951005280428,1.9401206794865944,,
,,0.8601116191260452,1.8857864945455538,,
,,0.8691446792257491,1.7565635929668848,,
,,0.8743052930713663,1.7073700288086895,,
,,0.8788211611645935,1.6595617469926904,,
,,0.8826916215488064,1.624580544751916,,
,,0.8891397410293294,1.6130257674984603,,
,,0.896234592132116,1.5678305544227285,,
,,0.903974850943917,1.5131252291764425,,
,,0.90784531132813,1.4812307005435945,,
,,0.9155829223134326,1.470682044636039,,
,,0.9220303798373308,1.4706147972609174,,
,,0.9310574823274131,1.460128389693264,,
,,0.9349259568417521,1.4600883304432553,,
,,0.9433056657529459,1.4913980336424177,,
,,0.9516853746641398,1.5233791328756299,,
,,0.956844002639883,1.512557980118849,,
,,0.9671632444612435,1.4597545460975296,,
,,0.9710370146285795,1.379199997932108,,
,,0.9736206313345113,1.3123773159521164,,
,,0.9774937395452226,1.2487807938448074,,
,,0.9826543533908398,1.213807976266501,,
,,0.9903926263327669,1.1966468122906808,,
,,0.9942630867169798,1.171423198750918,,
,,1.0045796807118417,1.1630595787371243,,
,,1.0097376467309604,1.163017033545686,,
,,1.0219851681998686,1.1963787227370668,,
,,1.0290753856062829,1.2220446908209617,,
,,1.0348774354211667,1.2306917801168409,,
,,1.0471421677623152,1.052809309517664,,
,,1.0510159379296513,0.9947114748787712,,
,,1.0445565651865096,1.1302083245776995,,
,,1.0458487045177878,1.0985863367418764,,
,,1.0542449623445975,0.9398239868494617,,
,,1.0613444471437565,0.8692481092753317,,
,,1.0690886776953055,0.8039684355922487,,
,,1.0768322462902296,0.7488836107091129,,
,,1.0800586228786773,0.7279206840193796,,
,,1.0858633205200596,0.7125673758820761,,
,,1.1013418522737881,0.677981252075927,,
,,1.105210326788127,0.6779626513688453,,
,,1.1193973811672016,0.6589337368401503,,
,,1.1206888585418553,0.6450561487842132,,
,,1.123272475247787,0.613802971613336,,
,,1.129077834845794,0.5966103416145675,,
,,1.1335910551125228,0.5965912453535225,,
,,1.1413266802279516,0.6050805792103379,,
,,1.147134687652457,0.5716821968068484,,
,,1.1581006610960811,0.5401075330241505,,
,,1.167774495208427,0.524964710284852,,
,,1.17357786893656,0.521233298948351,,
,,1.1890511050372914,0.5248855006959243,,
,,1.1974327998183592,0.5248543002000403,,
,,1.2051671010205385,0.5399272739697006,,
,,1.2161264548979165,0.5475977747039503,,
,,1.2225739124218147,0.5475727356323816,,
,,1.2232186581742046,0.5475702317881956,,
,,1.2257923455307669,0.5795255128326332,,
,,1.2264284858470365,0.635494312878344,,
,,1.2264218662807902,0.6822012488404363,,
,,1.2322133247896798,0.7695840678419278,,
,,1.2354304339853828,0.8261273126225834,,
,,1.2386382757883407,0.9793976812064772,,
,,1.2399224716401234,1.0365631680970249,,
,,1.2405632456527655,1.0816189618908738,,
,,1.2457119442791393,1.1944819289049535,,
,,1.2508619668187624,1.3005429730851226,,
,,1.2521488104970433,1.3379536598639075,,
,,1.2586068593269357,1.1943726953174758,,
,,1.2605516878900995,1.0662340854711274,,
,,1.263782036218295,0.9932116176633944,,
,,1.267659116168754,0.9057092045091463,,
,,1.2721796179583538,0.8377104911187728,,
,,1.277345527456968,0.7693377754021644,,
,,1.2818653672899432,0.7166420946785479,,
,,1.2954156193961235,0.639704291832398,,
,,1.298640672071322,0.6306801565443763,,
,,1.3018657247465204,0.6217833222901857,,
,,1.3147579919678183,0.6396165438362177,,
,,1.3179764250767705,0.6769403979817149,,
,,1.3244132912946747,0.7582491296621654,,
,,1.3295613279644238,0.8433295073010272,,
,,1.336000180052202,0.9247376324361347,,
,,1.3430738485430005,1.1278180580973283,,
,,1.3449988184074253,1.2455302076050567,,
,,1.3482112939067554,1.4050952527481475,,
,,1.351423769406086,1.585102197634859,,
,,1.3533487392705106,1.7505418140103943,,
,,1.3565618767264658,1.9608478952847546,,
,,1.3604177740649368,2.243642151072621,,
,,1.3681421459177467,2.5671506730013776,,
,,1.3720059867357133,2.6977396395227498,,
,,1.3700631440424236,2.9583331848749403,,
,,1.3745670969164077,3.267039328784058,,
,,1.3758446732019438,3.711862721055264,,
,,1.377124897313979,4.099294432098773,,
,,1.381620906708467,4.929210899363566,,
,,1.381610315402473,5.521521237053952,,
,,1.382885243861511,6.453816707451638,,
,,1.3828733286422676,7.332602493027707,,
,,1.3835048352621646,8.450022001983974,,
,,1.3847837354609505,9.465318121186383,,
,,1.3892850405084358,10.753821633797266,,
,,1.3950817946703227,11.46214012981349,,
,,1.406041810504325,11.542823048802346,,
,,1.4086254272102567,10.983569572223233,,
,,1.4125144223799593,8.815541223987582,,
,,1.4138145051907332,7.8697993951128655,,
,,1.4164106990725327,6.544455667464655,,
,,1.4170686839574151,5.678973743658009,,
,,1.419664877839215,4.722584406045411,,
,,1.422267691287261,3.6583795017467264,,
,,1.4274475018749921,2.894876829955794,,
,,1.4293982880477776,2.4244991059437035,,
,,1.4319891862765801,2.133892660567146,,
,,1.4365249130685465,1.6766244945281037,,
,,1.442346821582169,1.3648832144532748,,
,,1.44946086942707,1.0800176935856405,,
,,1.4539886527395407,0.9239641429706197,,
,,1.4617467843802068,0.7363233849854021,,
,,1.4701536335130105,0.5623407795094862,,
,,1.477900511891058,0.5055620724926202,,
,,1.4856487141823544,0.44811473438788657,,
,,1.4940482817922873,0.3699994982234866,,
,,1.5005089784486785,0.32105511462339426,,
,,1.5089019264923649,0.2845721176091456,,
,,1.5166481429137875,0.2576601983353233,,
,,1.5179396202884412,0.2522337011156551,,
,,1.5359997828782272,0.23327413205434572,,
,,1.5430966198508878,0.22196482072259285,,
,,1.5469697280615993,0.21120862244291763,,
,,1.5501980905199209,0.20097457800069773,,
,,1.565674636403775,0.1953318790229525,,
,,1.5688977032090996,0.19671762962317987,,
,,1.5785662416684487,0.20236426842871638,,
,,1.5837215598610688,0.2081796533603784,,
,,1.5914545371499988,0.21721757235346942,,
,,1.5991901622654274,0.22030852031094905,,
,,1.6088620105078997,0.21873658192521878,,
,,1.61208309144335,0.22502554836218797,,
,,1.6262602164730557,0.2432590831170723,,
,,1.626903638312196,0.2467330049135315,,
,,1.6281904819904773,0.25383038758817866,,
,,1.6410748057322797,0.28430548752659124,,
,,1.660414530477476,0.29244621369149987,,
,,1.6642816810785659,0.29661578287603696,,
,,1.6707152375133467,0.34423590359793155,,
,,1.6739290369259265,0.3828666146961718,,
,,1.6758540067903511,0.4228270071256609,,
,,1.690028483993558,0.4702407837690221,,
,,1.6964732936909575,0.48374976893430716,,
,,1.6996923887565347,0.5083600695634991,,
,,1.7003318388559272,0.5380344919079171,,
1 Rayleigh Experimental Infrared Absorption
2 X Y X Y X Y
3 0.7066005680911753 3.651009696525016 0.7072188355785799 4.848577786727532 1.4943918372804705 0.009324755400827079
4 0.7362740977034739 3.1008424465551374 0.7104385926007812 5.059236053617898 1.5870823136443164 0.04795688074913024
5 0.7704634954089999 2.559836201011587 0.7143024334187478 5.3165954733738054 1.6443739975494367 0.12666181795273013
6 0.8246453070916075 1.9963763545469397 0.7194723146571098 4.679249634272495 1.6900695253042852 0.3029389206853443
7 0.8846384359818439 1.4197559292837703 0.7194729766137344 4.646181068667172 1.7344762235109785 0.7194296259968607
8 0.9375334039861705 1.0762856922437043 0.7227053108118038 4.236869693191961 1.7975573800551148 2.1896107578891244
9 0.9929980876073115 0.9010620061822204 0.7272205169484065 4.147543538340177
10 1.0420225952274251 0.6977835411732736 0.7291487965959542 4.420849736815974
11 1.0845923638007697 0.5842349714248559 0.7310784001567514 4.645798669234298
12 1.129741777340298 0.4856980392071641 0.7355902965102309 4.71201487210429
13 1.1981066716622326 0.3841608204561696 0.738821306795051 4.358286713153517
14 1.24841405122088 0.31935666463679646 0.7414069093708566 4.059822180897163
15 1.289688370679823 0.2850148847474179 0.742705668268381 3.676053825899206
16 1.3541801567910463 0.23691184007300667 0.7440031032526563 3.376112267508452
17 1.4083573347772815 0.19416797258075097 0.7452965664971838 3.235432943532804
18 1.4618851333147547 0.16723647350379714 0.7459426361628229 3.1898499103097238
19 1.50315548103395 0.15574103754742674 0.7549743723492774 3.0137121806723157
20 1.5386251028515106 0.1419883611511702 0.7620678995388148 2.971117042589562
21 1.596665459699088 0.12316075278891304 0.765291628300764 2.971049114206588
22 1.6437444767994136 0.10759838849626362 0.7756075603390012 2.9708317538173317
23 1.683725994970454 0.09949510555249974 0.7762523060913911 2.9708181693210105
24 1.717258069747722 0.09398483395032568 0.7839919029465676 2.8875658983600925
25 1.7636903552257834 0.08387517688001396 0.7885090949530442 2.767180560538318
26 1.7940013490676008 0.07701567091052414 0.7917348095848672 2.7088647150205
27 0.7936723566251598 2.6144535948583694
28 0.7962546494178423 2.5233214143921474
29 0.8085087904529968 2.417989089048743
30 0.8104423657535417 2.435165393892523
31 0.8201175237791369 2.3335556930105406
32 0.8285065000830756 2.158298098247211
33 0.8317335386281479 2.083056634772393
34 0.8356046609689853 2.024737963659226
35 0.8407639509013534 1.996148150112612
36 0.8459232408337212 1.967962031984024
37 0.854951005280428 1.9401206794865944
38 0.8601116191260452 1.8857864945455538
39 0.8691446792257491 1.7565635929668848
40 0.8743052930713663 1.7073700288086895
41 0.8788211611645935 1.6595617469926904
42 0.8826916215488064 1.624580544751916
43 0.8891397410293294 1.6130257674984603
44 0.896234592132116 1.5678305544227285
45 0.903974850943917 1.5131252291764425
46 0.90784531132813 1.4812307005435945
47 0.9155829223134326 1.470682044636039
48 0.9220303798373308 1.4706147972609174
49 0.9310574823274131 1.460128389693264
50 0.9349259568417521 1.4600883304432553
51 0.9433056657529459 1.4913980336424177
52 0.9516853746641398 1.5233791328756299
53 0.956844002639883 1.512557980118849
54 0.9671632444612435 1.4597545460975296
55 0.9710370146285795 1.379199997932108
56 0.9736206313345113 1.3123773159521164
57 0.9774937395452226 1.2487807938448074
58 0.9826543533908398 1.213807976266501
59 0.9903926263327669 1.1966468122906808
60 0.9942630867169798 1.171423198750918
61 1.0045796807118417 1.1630595787371243
62 1.0097376467309604 1.163017033545686
63 1.0219851681998686 1.1963787227370668
64 1.0290753856062829 1.2220446908209617
65 1.0348774354211667 1.2306917801168409
66 1.0471421677623152 1.052809309517664
67 1.0510159379296513 0.9947114748787712
68 1.0445565651865096 1.1302083245776995
69 1.0458487045177878 1.0985863367418764
70 1.0542449623445975 0.9398239868494617
71 1.0613444471437565 0.8692481092753317
72 1.0690886776953055 0.8039684355922487
73 1.0768322462902296 0.7488836107091129
74 1.0800586228786773 0.7279206840193796
75 1.0858633205200596 0.7125673758820761
76 1.1013418522737881 0.677981252075927
77 1.105210326788127 0.6779626513688453
78 1.1193973811672016 0.6589337368401503
79 1.1206888585418553 0.6450561487842132
80 1.123272475247787 0.613802971613336
81 1.129077834845794 0.5966103416145675
82 1.1335910551125228 0.5965912453535225
83 1.1413266802279516 0.6050805792103379
84 1.147134687652457 0.5716821968068484
85 1.1581006610960811 0.5401075330241505
86 1.167774495208427 0.524964710284852
87 1.17357786893656 0.521233298948351
88 1.1890511050372914 0.5248855006959243
89 1.1974327998183592 0.5248543002000403
90 1.2051671010205385 0.5399272739697006
91 1.2161264548979165 0.5475977747039503
92 1.2225739124218147 0.5475727356323816
93 1.2232186581742046 0.5475702317881956
94 1.2257923455307669 0.5795255128326332
95 1.2264284858470365 0.635494312878344
96 1.2264218662807902 0.6822012488404363
97 1.2322133247896798 0.7695840678419278
98 1.2354304339853828 0.8261273126225834
99 1.2386382757883407 0.9793976812064772
100 1.2399224716401234 1.0365631680970249
101 1.2405632456527655 1.0816189618908738
102 1.2457119442791393 1.1944819289049535
103 1.2508619668187624 1.3005429730851226
104 1.2521488104970433 1.3379536598639075
105 1.2586068593269357 1.1943726953174758
106 1.2605516878900995 1.0662340854711274
107 1.263782036218295 0.9932116176633944
108 1.267659116168754 0.9057092045091463
109 1.2721796179583538 0.8377104911187728
110 1.277345527456968 0.7693377754021644
111 1.2818653672899432 0.7166420946785479
112 1.2954156193961235 0.639704291832398
113 1.298640672071322 0.6306801565443763
114 1.3018657247465204 0.6217833222901857
115 1.3147579919678183 0.6396165438362177
116 1.3179764250767705 0.6769403979817149
117 1.3244132912946747 0.7582491296621654
118 1.3295613279644238 0.8433295073010272
119 1.336000180052202 0.9247376324361347
120 1.3430738485430005 1.1278180580973283
121 1.3449988184074253 1.2455302076050567
122 1.3482112939067554 1.4050952527481475
123 1.351423769406086 1.585102197634859
124 1.3533487392705106 1.7505418140103943
125 1.3565618767264658 1.9608478952847546
126 1.3604177740649368 2.243642151072621
127 1.3681421459177467 2.5671506730013776
128 1.3720059867357133 2.6977396395227498
129 1.3700631440424236 2.9583331848749403
130 1.3745670969164077 3.267039328784058
131 1.3758446732019438 3.711862721055264
132 1.377124897313979 4.099294432098773
133 1.381620906708467 4.929210899363566
134 1.381610315402473 5.521521237053952
135 1.382885243861511 6.453816707451638
136 1.3828733286422676 7.332602493027707
137 1.3835048352621646 8.450022001983974
138 1.3847837354609505 9.465318121186383
139 1.3892850405084358 10.753821633797266
140 1.3950817946703227 11.46214012981349
141 1.406041810504325 11.542823048802346
142 1.4086254272102567 10.983569572223233
143 1.4125144223799593 8.815541223987582
144 1.4138145051907332 7.8697993951128655
145 1.4164106990725327 6.544455667464655
146 1.4170686839574151 5.678973743658009
147 1.419664877839215 4.722584406045411
148 1.422267691287261 3.6583795017467264
149 1.4274475018749921 2.894876829955794
150 1.4293982880477776 2.4244991059437035
151 1.4319891862765801 2.133892660567146
152 1.4365249130685465 1.6766244945281037
153 1.442346821582169 1.3648832144532748
154 1.44946086942707 1.0800176935856405
155 1.4539886527395407 0.9239641429706197
156 1.4617467843802068 0.7363233849854021
157 1.4701536335130105 0.5623407795094862
158 1.477900511891058 0.5055620724926202
159 1.4856487141823544 0.44811473438788657
160 1.4940482817922873 0.3699994982234866
161 1.5005089784486785 0.32105511462339426
162 1.5089019264923649 0.2845721176091456
163 1.5166481429137875 0.2576601983353233
164 1.5179396202884412 0.2522337011156551
165 1.5359997828782272 0.23327413205434572
166 1.5430966198508878 0.22196482072259285
167 1.5469697280615993 0.21120862244291763
168 1.5501980905199209 0.20097457800069773
169 1.565674636403775 0.1953318790229525
170 1.5688977032090996 0.19671762962317987
171 1.5785662416684487 0.20236426842871638
172 1.5837215598610688 0.2081796533603784
173 1.5914545371499988 0.21721757235346942
174 1.5991901622654274 0.22030852031094905
175 1.6088620105078997 0.21873658192521878
176 1.61208309144335 0.22502554836218797
177 1.6262602164730557 0.2432590831170723
178 1.626903638312196 0.2467330049135315
179 1.6281904819904773 0.25383038758817866
180 1.6410748057322797 0.28430548752659124
181 1.660414530477476 0.29244621369149987
182 1.6642816810785659 0.29661578287603696
183 1.6707152375133467 0.34423590359793155
184 1.6739290369259265 0.3828666146961718
185 1.6758540067903511 0.4228270071256609
186 1.690028483993558 0.4702407837690221
187 1.6964732936909575 0.48374976893430716
188 1.6996923887565347 0.5083600695634991
189 1.7003318388559272 0.5380344919079171

View File

@@ -0,0 +1,55 @@
% Gitter für lambda0 und S0
lambda0_vec = linspace(1260,1360,200);
S0_vec = linspace(0.06,0.1,200);
[Lambda0, S0] = meshgrid(lambda0_vec, S0_vec);
% Festen Betriebsparameter
lambda = 1293; % nm
L = 1; % km
% Dispersion berechnen (lineare Näherung)
D = S0 .* ( lambda - Lambda0 ) * L;
D = (S0./4) .* ( lambda - (Lambda0.^4)./(lambda^3) ) * L;
%% 2D-Konturplot nur mit Linien und Text
figure('Color','w');
hold on
% Konturlinien
numLevels = 10;
levels = linspace(min(D(:)), max(D(:)), numLevels);
[C,h] = contour(S0, Lambda0, D, levels, ...
'LineWidth',1.5, ...
'ShowText','on', ...
'LabelFormat','%0.1f');
% cbrewer2-Colormap für die Linien
cmap = cbrewer2('div','RdYlGn', numLevels);
colormap(cmap);
% Achsenlinien
% yline(1310, '--k','ZDW_{mean}','LabelVerticalAlignment','top','LabelHorizontalAlignment','center');
x0 = 0.09;
% xline(x0, '--k','S_{0}','LabelHorizontalAlignment','left');
% Gaussian auf der x-Linie (S0 = 0.09)
mu_zwd = 1310; % nm
sigma_zwd = 2; % nm
zwd_vals = linspace(min(lambda0_vec), max(lambda0_vec), 500);
% PDF berechnen
gauss_pdf = (1/(sigma_zwd*sqrt(2*pi))) * exp(-0.5*((zwd_vals-mu_zwd)/sigma_zwd).^2);
% Normieren und auf eine sichtbare Breite skalieren
scale = 0.005; % passt die Maximal-Auslenkung in x-Richtung an
x_gauss = x0 + (gauss_pdf/max(gauss_pdf)) * scale;
% Plot
% plot(x_gauss, zwd_vals, 'LineWidth',2);
% Achsenbeschriftung & Titel
% Achsenbeschriftung & Titel
xlabel('S0 [ps / nm2 km]', 'FontSize', 12);
ylabel('ZDW [nm]', 'FontSize', 12);
title (sprintf('Dispersion: %d km; %d nm', L, lambda), 'FontSize', 14);
grid on
hold off

Some files were not shown because too many files have changed in this diff Show More