Compare commits
54 Commits
silas_wip
...
b068400cb5
| Author | SHA1 | Date | |
|---|---|---|---|
| b068400cb5 | |||
|
|
8c4edf2490 | ||
|
|
f421348e5b | ||
|
|
ef0a74cb7f | ||
|
|
a6cf742121 | ||
|
|
aa210e5352 | ||
|
|
753bc582b9 | ||
|
|
3cafb06c4f | ||
|
|
1879441999 | ||
|
|
699d146fb5 | ||
|
|
33ec5b3116 | ||
|
|
c4f75b7ec4 | ||
|
|
3603fd7853 | ||
|
|
5c2e27687d | ||
|
|
a91da3b97c | ||
|
|
f883071dec | ||
|
|
56b48095db | ||
|
|
910486f536 | ||
|
|
952178341e | ||
|
|
456fd02186 | ||
|
|
8fd68c0a54 | ||
|
|
5446275a5b | ||
|
|
904eee0604 | ||
| 0ef8129a2c | |||
|
|
20b23dc37e | ||
|
|
5e5648d11a | ||
|
|
10a205f231 | ||
|
|
c135cb76b1 | ||
|
|
d3235a5c9c | ||
|
|
8c55b25112 | ||
|
|
4cef384d15 | ||
|
|
2691b90d65 | ||
|
|
0ae846d3c3 | ||
|
|
0c5ad28f0a | ||
|
|
ed7c68d78b | ||
|
|
f811ab65cd | ||
|
|
c6b902d346 | ||
| 7d856bf124 | |||
| b4e735148e | |||
|
|
b291e0a7bb | ||
|
|
69578b3c43 | ||
|
|
e16806b520 | ||
|
|
76be57515d | ||
|
|
5b90bc11a0 | ||
|
|
0e6be4a584 | ||
|
|
ac374ee5c4 | ||
|
|
96dc0a8b6a | ||
|
|
e88a3359eb | ||
|
|
a592ebefda | ||
|
|
9446dfb888 | ||
|
|
9dccca8ff9 | ||
|
|
0577ffe7f6 | ||
|
|
4c78a1e0b1 | ||
|
|
3676d92b30 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,3 +23,7 @@ codegen/
|
||||
|
||||
|
||||
.mat
|
||||
|
||||
# Local test dashboard status tracking
|
||||
Tests/.last_test_run.json
|
||||
Tests/reports/
|
||||
|
||||
12
.vscode/launch.json
vendored
Normal file
12
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
// 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
344
AGENT_OVERVIEW.md
Normal file
344
AGENT_OVERVIEW.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# 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/`.
|
||||
@@ -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
|
||||
|
||||
@@ -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', 'none', 'LineStyle','-', 'MarkerSize', 0.1);
|
||||
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1);
|
||||
else
|
||||
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', 'none', 'LineStyle','-', 'MarkerSize', 0.1,'Color',options.color);
|
||||
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', '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,12 +200,15 @@ classdef Signal
|
||||
%% Add signals from one signal to another, the first object will sustain
|
||||
function Sum = plus(X,y)
|
||||
|
||||
if isa(y,'Signal')
|
||||
if isa(X,'Signal') && isa(y,'Signal')
|
||||
Sum = X;
|
||||
Sum.signal = X.signal + y.signal;
|
||||
elseif isnumeric(y)
|
||||
elseif isa(X,'Signal') && isnumeric(y)
|
||||
Sum = X;
|
||||
Sum.signal = X.signal + y;
|
||||
elseif isnumeric(X) && isa(y,'Signal')
|
||||
Sum = y;
|
||||
Sum.signal = X + y.signal;
|
||||
end
|
||||
|
||||
end
|
||||
@@ -213,24 +216,42 @@ classdef Signal
|
||||
%% Add signals from one signal to another, the first object will sustain
|
||||
function Diff = minus(X,y)
|
||||
|
||||
if isa(y,'Signal')
|
||||
if isa(X,'Signal') && isa(y,'Signal')
|
||||
Diff = X;
|
||||
Diff.signal = X.signal - y.signal;
|
||||
elseif isnumeric(y)
|
||||
elseif isa(X,'Signal') && 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(y,'Signal')
|
||||
if isa(X,'Signal') && isa(y,'Signal')
|
||||
Product = X;
|
||||
Product.signal = X.signal .* y.signal;
|
||||
elseif isnumeric(y)
|
||||
elseif isa(X,'Signal') && 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
|
||||
@@ -325,7 +346,8 @@ classdef Signal
|
||||
|
||||
else
|
||||
|
||||
obj.signal = resample(obj.signal,options.fs_out,options.fs_in,options.n,options.beta);
|
||||
[p, q] = rat(options.fs_out / options.fs_in);
|
||||
obj.signal = resample(obj.signal,p,q,options.n,options.beta);
|
||||
|
||||
desc = ['resample signal from ', num2str(options.fs_in*1e-9), ' GHz to ', num2str(options.fs_out*1e-9), ' GHz' ];
|
||||
|
||||
@@ -346,10 +368,13 @@ 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 ---
|
||||
@@ -362,7 +387,10 @@ classdef Signal
|
||||
end
|
||||
|
||||
|
||||
if options.normalizeToNyquist == 0
|
||||
useSamplingRateAxis = options.normalizeToSamplingRate ~= 0;
|
||||
useRadPerSampleAxis = options.normalizeToNyquist ~= 0 && ~useSamplingRateAxis;
|
||||
|
||||
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
[p_lin,f_Hz] = pwelch(obj.signal, hanning(options.fft_length), ...
|
||||
options.fft_length/2, options.fft_length, ...
|
||||
obj.fs, "centered", "power", "mean");
|
||||
@@ -371,11 +399,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 mode, pwelch returns rad/sample centered on 0.
|
||||
% We'll keep f_rad for the x-axis in that mode.
|
||||
% In normalized modes, pwelch returns rad/sample centered on 0.
|
||||
% Divide by 2*pi for the f/fs axis where Nyquist is 0.5.
|
||||
end
|
||||
|
||||
% p_lin = movmean(p_lin,4);
|
||||
p_lin = movmean(p_lin,10);
|
||||
|
||||
if options.normalizeTo0dB
|
||||
p_lin = p_lin ./ max(p_lin);
|
||||
@@ -387,7 +415,7 @@ classdef Signal
|
||||
end
|
||||
|
||||
% --- If requested, build wavelength axis from frequency offset ---
|
||||
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
||||
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
c = physconst('LightSpeed'); % [m/s]
|
||||
lambda0_m = options.lambda0_nm*1e-9; % center wavelength [m]
|
||||
f_c = c / lambda0_m; % carrier frequency [Hz]
|
||||
@@ -400,21 +428,36 @@ 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 options.normalizeToNyquist == 0
|
||||
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
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";
|
||||
x_label = "Normalized Frequency [rad/sample]";
|
||||
dc_axis = x_vec;
|
||||
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
|
||||
@@ -422,7 +465,7 @@ classdef Signal
|
||||
p_dbm = p_dbm+options.addDCoffset;
|
||||
|
||||
if options.normalizeToDC
|
||||
[~,min_idx]=min(abs(f_GHz));
|
||||
[~,min_idx]=min(abs(dc_axis));
|
||||
pow_at_dc = p_dbm(min_idx);
|
||||
p_dbm = p_dbm-pow_at_dc;
|
||||
end
|
||||
@@ -430,9 +473,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);
|
||||
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'LineStyle', options.linestyle, 'HandleVisibility', options.HandleVisibility);
|
||||
else
|
||||
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'Color', options.color,'LineStyle',options.linestyle);
|
||||
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'Color', options.color,'LineStyle',options.linestyle, 'HandleVisibility', options.HandleVisibility);
|
||||
end
|
||||
end
|
||||
|
||||
@@ -448,15 +491,25 @@ classdef Signal
|
||||
% Axis labels and limits
|
||||
xlabel(x_label);
|
||||
|
||||
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
||||
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
xlim([min(x_vec) max(x_vec)]);
|
||||
else
|
||||
if options.normalizeToNyquist == 0
|
||||
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
% 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
|
||||
xlim([-pi, pi]);
|
||||
if options.show_onesided
|
||||
xlim([0, pi]);
|
||||
else
|
||||
xlim([-pi, pi]);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -762,11 +815,18 @@ classdef Signal
|
||||
|
||||
pkpos = sort(pkpos);
|
||||
|
||||
% if mean(w) > 15 || mean(p) > 15
|
||||
% return
|
||||
% else
|
||||
% sequenceFound = 1;
|
||||
% end
|
||||
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 options.debug_plots
|
||||
figure(121212);clf
|
||||
@@ -803,6 +863,10 @@ classdef Signal
|
||||
S{c}.logbook = [];
|
||||
end
|
||||
|
||||
if ~isempty(S)
|
||||
obj = S{1};
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
%do nothing when shifts are negative or there are none...
|
||||
@@ -925,9 +989,10 @@ classdef Signal
|
||||
M
|
||||
options.fignum = 100;
|
||||
options.displayname = "";
|
||||
options.mode = 1; %1= histogram method; 2= intuitive "line based" eye
|
||||
end
|
||||
|
||||
mode = 1;
|
||||
mode = options.mode;
|
||||
|
||||
histpoints = 2048; %% verticale resolution
|
||||
histpoints = floor(histpoints/2)*2+1; %% to have the eye digram centered around one point make the vertical resolution uneven
|
||||
@@ -966,8 +1031,9 @@ 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])
|
||||
@@ -975,8 +1041,39 @@ classdef Signal
|
||||
elseif mode == 1
|
||||
% generate eye diagram using histogram
|
||||
|
||||
maxA = max(sig(100:end-100))*1.3;
|
||||
minA = min(sig(100:end-100))*1.3;
|
||||
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 = 0.12;
|
||||
% minA = -0.08;
|
||||
@@ -984,6 +1081,7 @@ 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);
|
||||
@@ -1009,19 +1107,19 @@ classdef Signal
|
||||
if isa(obj,'Opticalsignal')
|
||||
title(['Optical Eye ',options.displayname])
|
||||
ylabel("Power in mW");
|
||||
y_tickstring = string(linspace(maxA.*1e3,minA.*1e3,6));
|
||||
yTickValues = linspace(maxA.*1e3,minA.*1e3,5);
|
||||
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");
|
||||
y_tickstring = string(linspace(maxA,minA,6));
|
||||
yTickValues = linspace(maxA,minA,5);
|
||||
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");
|
||||
y_tickstring = string(linspace(maxA,minA,6));
|
||||
yTickValues = linspace(maxA,minA,5);
|
||||
min_ = min(obj.signal(100:end-100));
|
||||
max_ = abs(max(obj.signal(100:end-100)));
|
||||
end
|
||||
@@ -1062,7 +1160,14 @@ classdef Signal
|
||||
hist_interest_smoth = smooth(hist_interest,20);
|
||||
a = scatter(hist_interest_smoth+posxall,1:length(hist_interest_smoth),4,'.','MarkerEdgeColor','red');
|
||||
|
||||
[pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",10,"NPeaks",M,"MinPeakHeight",30,"MinPeakProminence",10);
|
||||
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);
|
||||
|
||||
scatter(posxall,loc,'red','Marker','x','LineWidth',2);
|
||||
|
||||
@@ -1145,12 +1250,13 @@ classdef Signal
|
||||
end
|
||||
|
||||
|
||||
yticks(linspace(0,histpoints,6));
|
||||
y_tickstring = sprintfc('%.2f', y_tickstring);
|
||||
yticklabels(y_tickstring);
|
||||
yTickPositions = linspace(1,histpoints,numel(yTickValues));
|
||||
yticks(yTickPositions);
|
||||
yticklabels(sprintfc('%.2f', yTickValues));
|
||||
|
||||
xticks(linspace(0,histpoints_horizontal,6))
|
||||
x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12);
|
||||
xTickValues = linspace(0, 2/fsym, 6) .* 1e12;
|
||||
xticks(linspace(1,histpoints_horizontal,numel(xTickValues)))
|
||||
x_tickstring = sprintfc('%.2f', xTickValues);
|
||||
xticklabels(x_tickstring);
|
||||
|
||||
%
|
||||
|
||||
@@ -254,7 +254,7 @@ classdef ChannelFreqResp < handle
|
||||
xlim([0.2 .5*max(obj.faxis)*1e-9]); grid on;
|
||||
|
||||
%%% plot for publication
|
||||
figure(101);
|
||||
figure(1996);
|
||||
hold all;
|
||||
box on;
|
||||
title('Magnitude Freq. Response');
|
||||
|
||||
@@ -462,10 +462,57 @@ classdef PAMmapper
|
||||
|
||||
end
|
||||
|
||||
function [Signal_out] = quantize(obj,Signal_in)
|
||||
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
|
||||
|
||||
constellation = obj.get_levels();
|
||||
constellation = constellation ./ obj.scaling;
|
||||
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 = [];
|
||||
end
|
||||
|
||||
if isempty(options.custom_const)
|
||||
constellation = obj.get_levels();
|
||||
constellation = constellation ./ obj.scaling;
|
||||
else
|
||||
constellation = options.custom_const;
|
||||
end
|
||||
|
||||
issignalclass = 0;
|
||||
if isa(Signal_in,'Signal')
|
||||
@@ -495,10 +542,133 @@ 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();
|
||||
|
||||
bitmap = obj.demap((obj.levels ./ obj.scaling)');
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@@ -136,6 +136,7 @@ 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
|
||||
@@ -148,10 +149,13 @@ classdef PAMsource
|
||||
case db_mode.no_db
|
||||
|
||||
case db_mode.db_precoded
|
||||
symbols = Duobinary().precode(symbols);
|
||||
% symbols = Duobinary().precode(symbols);
|
||||
symbols = Partialresponse().precode(symbols);
|
||||
case db_mode.db_encoded
|
||||
symbols = Duobinary().precode(symbols);
|
||||
symbols = Duobinary().encode(symbols);
|
||||
% symbols = Duobinary().precode(symbols);
|
||||
% symbols = Duobinary().encode(symbols);
|
||||
symbols = Partialresponse().precode(symbols);
|
||||
symbols = Partialresponse().encode(symbols);
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,65 @@
|
||||
classdef Signalgenerator
|
||||
%NAME Summary of this class goes here
|
||||
% Detailed explanation goes here
|
||||
%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);
|
||||
|
||||
properties(Access=public)
|
||||
form
|
||||
length
|
||||
fs
|
||||
fsig
|
||||
dimension
|
||||
M
|
||||
order
|
||||
randkey
|
||||
skip
|
||||
bruijn
|
||||
|
||||
end
|
||||
|
||||
@@ -17,9 +70,15 @@ classdef Signalgenerator
|
||||
|
||||
arguments
|
||||
options.form signalform = signalform.sine
|
||||
options.length double = 1024
|
||||
options.length double = []
|
||||
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
|
||||
|
||||
%
|
||||
@@ -30,6 +89,15 @@ 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)
|
||||
@@ -69,9 +137,15 @@ 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
|
||||
@@ -82,6 +156,361 @@ 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
|
||||
|
||||
@@ -46,7 +46,7 @@ classdef Optical_Demultiplex < handle
|
||||
if isempty(obj.wavelengthplan)
|
||||
obj.wavelengthplan = signalclass_in.lambda; %meter
|
||||
else
|
||||
if all(500e-9 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500e-9) %check if given in nm
|
||||
if all(500 < obj.wavelengthplan) && all(obj.wavelengthplan < 1500) %check if given in nm
|
||||
obj.wavelengthplan = obj.wavelengthplan.*1e-9;
|
||||
end
|
||||
end
|
||||
|
||||
82
Classes/03_electrical/CTLE.m
Normal file
82
Classes/03_electrical/CTLE.m
Normal file
@@ -0,0 +1,82 @@
|
||||
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
|
||||
130
Classes/03_electrical/Electrical_Hybrid.m
Normal file
130
Classes/03_electrical/Electrical_Hybrid.m
Normal file
@@ -0,0 +1,130 @@
|
||||
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
|
||||
31
Classes/03_electrical/Electrical_Trace.m
Normal file
31
Classes/03_electrical/Electrical_Trace.m
Normal file
@@ -0,0 +1,31 @@
|
||||
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
|
||||
|
||||
114
Classes/03_electrical/Electrical_Trace_BiDi.m
Normal file
114
Classes/03_electrical/Electrical_Trace_BiDi.m
Normal file
@@ -0,0 +1,114 @@
|
||||
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
|
||||
|
||||
210
Classes/04_DSP/Coding/Partialresponse.m
Normal file
210
Classes/04_DSP/Coding/Partialresponse.m
Normal file
@@ -0,0 +1,210 @@
|
||||
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
|
||||
394
Classes/04_DSP/Equalizer/Copy_of_VNLE.m
Normal file
394
Classes/04_DSP/Equalizer/Copy_of_VNLE.m
Normal file
@@ -0,0 +1,394 @@
|
||||
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
|
||||
|
||||
@@ -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
@@ -1,351 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
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
|
||||
|
||||
|
||||
492
Classes/04_DSP/Equalizer/FFE_plain.m
Normal file
492
Classes/04_DSP/Equalizer/FFE_plain.m
Normal file
@@ -0,0 +1,492 @@
|
||||
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
|
||||
@@ -1,499 +1,3 @@
|
||||
% 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,
|
||||
@@ -661,8 +165,8 @@ classdef ML_MLSE < handle
|
||||
% EQUALIZE
|
||||
% ==============================================================
|
||||
function [y,y_ref] = equalize(obj,x,d,mu,epochs,N,training)
|
||||
debug = 1;
|
||||
showPlots = 1;
|
||||
debug = 0;
|
||||
showPlots = 0;
|
||||
y = zeros(N,1);
|
||||
nSymbols = ceil(N/obj.sps);
|
||||
|
||||
|
||||
311
Classes/04_DSP/Equalizer/MPI_reduction/FFE_A1.m
Normal file
311
Classes/04_DSP/Equalizer/MPI_reduction/FFE_A1.m
Normal file
@@ -0,0 +1,311 @@
|
||||
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
|
||||
347
Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2Residual.m
Normal file
347
Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2Residual.m
Normal file
@@ -0,0 +1,347 @@
|
||||
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
|
||||
361
Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2TrackedLevels.m
Normal file
361
Classes/04_DSP/Equalizer/MPI_reduction/FFE_A2TrackedLevels.m
Normal file
@@ -0,0 +1,361 @@
|
||||
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
|
||||
752
Classes/04_DSP/Equalizer/MPI_reduction/FFE_DCTracking.m
Normal file
752
Classes/04_DSP/Equalizer/MPI_reduction/FFE_DCTracking.m
Normal file
@@ -0,0 +1,752 @@
|
||||
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
|
||||
@@ -0,0 +1,411 @@
|
||||
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
|
||||
@@ -10,6 +10,7 @@ classdef VNLE < handle
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
e_dc
|
||||
error
|
||||
|
||||
len_tr
|
||||
@@ -18,10 +19,17 @@ 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
|
||||
@@ -42,8 +50,11 @@ 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
|
||||
|
||||
@@ -54,6 +65,7 @@ classdef VNLE < handle
|
||||
|
||||
|
||||
obj.error = 0;
|
||||
obj.e_dc = 0;
|
||||
|
||||
end
|
||||
|
||||
@@ -69,6 +81,13 @@ 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;
|
||||
@@ -148,7 +167,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.' * x_in; % Calculating output of LMS __ * |
|
||||
y(symbol,1) = obj.e_dc + obj.e.' * x_in; % Calculating output of LMS __ * |
|
||||
|
||||
if training
|
||||
err = y(symbol) - d(symbol); % Instantaneous error
|
||||
@@ -164,6 +183,9 @@ 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);
|
||||
@@ -177,12 +199,85 @@ 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
|
||||
|
||||
@@ -103,6 +103,7 @@ classdef MaxVar_Timing_Recovery < handle
|
||||
% y = [y 0];
|
||||
% end
|
||||
data_out.signal = y.';
|
||||
data_out.fs = obj.fsym;
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ classdef DBHandler < handle
|
||||
arguments
|
||||
options.dataBase = "labor_highspeed"; % Default value for pathToDB if not provided
|
||||
options.type = "mysql";
|
||||
options.server = "134.245.243.254";
|
||||
options.server = "192.168.178.192"; % university coffee PC: "134.245.243.254";
|
||||
options.port = 3306;
|
||||
options.user = "silas";
|
||||
options.password = "silas";
|
||||
@@ -47,15 +47,20 @@ classdef DBHandler < handle
|
||||
elseif options.type == "mysql"
|
||||
|
||||
% datasource = "jdbc:mysql://134.245.243.254:3306/labor";
|
||||
|
||||
obj.conn = database( ...
|
||||
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( ...
|
||||
string(obj.dataBase), ... % Database name
|
||||
options.user, ... % Username
|
||||
options.password, ... % Password (or getSecret)
|
||||
"Vendor", "MySQL", ...
|
||||
"Server", options.server, ...
|
||||
"PortNumber", 3306, ...
|
||||
"JDBCDriverLocation", "C:\Users\Silas\Documents\mysql-connector-j-9.3.0\mysql-connector-j-9.3.0.jar");
|
||||
"PortNumber", options.port, ...
|
||||
"JDBCDriverLocation", driverlocation);
|
||||
|
||||
end
|
||||
|
||||
|
||||
400
Classes/DataBaseHandler/sql_query_collection.txt
Normal file
400
Classes/DataBaseHandler/sql_query_collection.txt
Normal file
@@ -0,0 +1,400 @@
|
||||
|
||||
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;
|
||||
@@ -95,14 +95,14 @@ classdef DataStorage < handle
|
||||
|
||||
end
|
||||
|
||||
function addStorage(obj,varName)
|
||||
% add a storage
|
||||
|
||||
storage = cell(obj.dim);
|
||||
|
||||
obj.sto.(string(varName)) = storage;
|
||||
|
||||
end
|
||||
function addStorage(obj,varName)
|
||||
% add a storage
|
||||
|
||||
storage = cell(obj.getStorageSize());
|
||||
|
||||
obj.sto.(string(varName)) = storage;
|
||||
|
||||
end
|
||||
|
||||
function addValueToStorage(obj, valueToStore ,storageVarName, varargin)
|
||||
|
||||
@@ -273,14 +273,18 @@ classdef DataStorage < handle
|
||||
end
|
||||
|
||||
%append to index list :-)
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
end
|
||||
|
||||
% Mapping for single Index
|
||||
@@ -291,24 +295,33 @@ 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));
|
||||
|
||||
% 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
|
||||
% 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
|
||||
end
|
||||
|
||||
function [physStruct, stored_value] = getPhysAndValueByLinIndex(obj, storageVarName, lin_idx)
|
||||
@@ -327,8 +340,14 @@ classdef DataStorage < handle
|
||||
% Initialize an empty structure
|
||||
physStruct = struct();
|
||||
|
||||
% Convert linear index to subscript indices
|
||||
[subscripts{1:numel(obj.dim)}] = ind2sub(obj.dim, lin_idx);
|
||||
% 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
|
||||
|
||||
% Map subscripts to physical values and parameter names for each dimension
|
||||
for i = 1:numel(obj.fn)
|
||||
@@ -343,17 +362,31 @@ 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
|
||||
num_elements = prod(obj.dim);
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
9
Datatypes/channel_model.m
Normal file
9
Datatypes/channel_model.m
Normal file
@@ -0,0 +1,9 @@
|
||||
classdef channel_model < int32
|
||||
|
||||
enumeration
|
||||
awgn (1)
|
||||
awgn_alphad (2)
|
||||
physical (3)
|
||||
end
|
||||
|
||||
end
|
||||
8
Datatypes/db_decoder.m
Normal file
8
Datatypes/db_decoder.m
Normal file
@@ -0,0 +1,8 @@
|
||||
classdef db_decoder < int32
|
||||
|
||||
enumeration
|
||||
sequencedetection (0) % use MLSE for decoding
|
||||
memoryless (1) % use modulo
|
||||
end
|
||||
|
||||
end
|
||||
@@ -5,6 +5,8 @@ classdef signalform < int32
|
||||
sawtooth (2)
|
||||
square (3)
|
||||
noise (4)
|
||||
random (5)
|
||||
prms (6)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
BIN
Documentations/Demystifying Duobinary.pdf
Normal file
BIN
Documentations/Demystifying Duobinary.pdf
Normal file
Binary file not shown.
32
Functions/Channel_structures/awgn_alpha_d_channel.m
Normal file
32
Functions/Channel_structures/awgn_alpha_d_channel.m
Normal file
@@ -0,0 +1,32 @@
|
||||
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
|
||||
43
Functions/Channel_structures/awgn_channel.m
Normal file
43
Functions/Channel_structures/awgn_channel.m
Normal file
@@ -0,0 +1,43 @@
|
||||
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
|
||||
189
Functions/EQ_blocks/duobinary_target.m
Normal file
189
Functions/EQ_blocks/duobinary_target.m
Normal file
@@ -0,0 +1,189 @@
|
||||
function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx_bits, options)
|
||||
|
||||
arguments
|
||||
eq_
|
||||
mlse_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0;
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
options.decoding_mode db_decoder = db_decoder.sequencedetection;
|
||||
end
|
||||
|
||||
|
||||
|
||||
%Duobinary Targeting
|
||||
db_ref_sequence = Duobinary().encode(tx_symbols);
|
||||
db_ref_constellation = unique(db_ref_sequence.signal);
|
||||
[eq_signal, eq_noise] = eq_.process(rx_signal,db_ref_sequence);
|
||||
|
||||
if ~isempty(options.postFFE)
|
||||
[eq_signal,eq_noise] = options.postFFE.process(eq_signal,db_ref_sequence);
|
||||
end
|
||||
%
|
||||
|
||||
switch options.decoding_mode
|
||||
case db_decoder.sequencedetection %MLSE
|
||||
mlse_.DIR = [1,1];
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
mlse_sig_sd = mlse_.process(eq_signal);
|
||||
else
|
||||
[mlse_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);
|
||||
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
|
||||
% combination with duobinary signaling to avoid catastrophic error
|
||||
% behavior (see J.W.M. Bergmans, Digital Baseband Transmission and Recording -> partial response signaling)
|
||||
switch options.precode_mode
|
||||
|
||||
case db_mode.no_db
|
||||
% 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;
|
||||
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);
|
||||
|
||||
[~,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);
|
||||
|
||||
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;
|
||||
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);
|
||||
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');
|
||||
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
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
gmi_mlse = NaN;
|
||||
air_mlse = NaN;
|
||||
else
|
||||
gmi_mlse = GMI_MLSE;
|
||||
air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_mlse ./ log2(double(M));
|
||||
end
|
||||
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));
|
||||
end
|
||||
|
||||
db_results = struct();
|
||||
db_results.metrics = Metricstruct;
|
||||
db_results.metrics.result_id = NaN;
|
||||
db_results.metrics.run_id = NaN;
|
||||
db_results.metrics.eqParam_id = NaN;
|
||||
db_results.metrics.date_of_processing = datetime('now');
|
||||
db_results.metrics.BER = ber_db;
|
||||
db_results.metrics.numBits = bits_db;
|
||||
db_results.metrics.numBitErr = errors_db;
|
||||
db_results.metrics.BER_precoded = ber_db_diff_precoded;
|
||||
db_results.metrics.numBitErr_precoded = errors_db_diff_precoded;
|
||||
db_results.metrics.GMI = gmi_mlse;
|
||||
db_results.metrics.AIR = air_mlse;
|
||||
db_results.metrics.MLSE_dir = mlse_.DIR;
|
||||
db_results.metrics.Alpha = alpha;
|
||||
|
||||
% Create DB results structure
|
||||
db_results.config = Equalizerstruct();
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
db_results.config.eq = jsonencode(eq_);
|
||||
% mlse_.DIR = [];
|
||||
db_results.config.mlse = jsonencode(mlse_);
|
||||
db_results.config.equalizer_structure = int32(equalizer_structure.vnle_db_mlse);
|
||||
db_results.config.comment = 'function: Duobinary tgt. (VNLE -> MLSE)';
|
||||
|
||||
if options.showAnalysis
|
||||
|
||||
eq_signal.eye(eq_signal.fs,M,"fignum",249);
|
||||
|
||||
|
||||
eq_noise = eq_noise - mean(eq_noise.signal);
|
||||
|
||||
rx_signal.spectrum("normalizeTo0dB",1,"fignum",250,"displayname","Rx Spectrum");
|
||||
|
||||
Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",250,"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);
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
function [ffe_results] = ffe(eq_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
function [ffe_results,eq_signal_sd] = ffe(eq_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
% FFE Processes signals through FFE equalizer
|
||||
%
|
||||
% Inputs:
|
||||
@@ -74,13 +74,22 @@ end
|
||||
|
||||
% Create FFE results structure
|
||||
ffe_results = struct();
|
||||
try
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
eq_.b = [];
|
||||
eq_.b2 = [];
|
||||
eq_.b3 = [];
|
||||
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
|
||||
end
|
||||
|
||||
ffe_results.config = Equalizerstruct();
|
||||
@@ -168,6 +177,8 @@ 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);
|
||||
@@ -176,10 +187,11 @@ 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');
|
||||
%
|
||||
@@ -188,6 +200,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
|
||||
211
Functions/EQ_blocks/ffe_db.m
Normal file
211
Functions/EQ_blocks/ffe_db.m
Normal file
@@ -0,0 +1,211 @@
|
||||
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
|
||||
146
Functions/EQ_blocks/preprocessSignal.m
Normal file
146
Functions/EQ_blocks/preprocessSignal.m
Normal file
@@ -0,0 +1,146 @@
|
||||
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
|
||||
@@ -48,6 +48,8 @@ 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
|
||||
@@ -272,34 +274,35 @@ 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", 3388, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
|
||||
showEQNoisePSD(eq_noise, "fignum", 339, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
|
||||
end
|
||||
|
||||
|
||||
tx_symbols.spectrum("displayname",'Equalized Signal','fignum',1234,'normalizeTo0dB',1);
|
||||
tx_symbols.spectrum("displayname",'Equalized Signal','fignum',340,'normalizeTo0dB',1);
|
||||
if ~isempty(postFFE)
|
||||
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 338);
|
||||
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 341);
|
||||
end
|
||||
|
||||
% showEQcoefficients('n1', eq_.e1,'n2', eq_.e2,'n3', eq_.e3, "displayname", 'Coefficients', 'fignum', 339);
|
||||
showEQcoefficients('n1', eq_.e,'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", 340);
|
||||
eq_signal_sd.eye(eq_signal_sd.fs, M, "fignum", 342);
|
||||
|
||||
figure(341); clf;
|
||||
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 341);
|
||||
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 343);
|
||||
|
||||
warning off
|
||||
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 400);
|
||||
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 344);
|
||||
% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401);
|
||||
drawnow;
|
||||
warning on
|
||||
|
||||
|
||||
whitened_noise.spectrum("displayname",'after postfilter','fignum',342);
|
||||
eq_noise.spectrum("displayname",'before postfilter','fignum',342);
|
||||
whitened_noise.spectrum("displayname",'after postfilter','fignum',345);
|
||||
eq_noise.spectrum("displayname",'before postfilter','fignum',345);
|
||||
|
||||
end
|
||||
36
Functions/EQ_recipes/dsp_recipe_minimal.m
Normal file
36
Functions/EQ_recipes/dsp_recipe_minimal.m
Normal file
@@ -0,0 +1,36 @@
|
||||
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
|
||||
206
Functions/EQ_recipes/dsp_scope_signal.m
Normal file
206
Functions/EQ_recipes/dsp_scope_signal.m
Normal file
@@ -0,0 +1,206 @@
|
||||
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
|
||||
404
Functions/EQ_recipes/mpi_recipe.m
Normal file
404
Functions/EQ_recipes/mpi_recipe.m
Normal file
@@ -0,0 +1,404 @@
|
||||
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
|
||||
289
Functions/EQ_recipes/mpi_recipe_dev.m
Normal file
289
Functions/EQ_recipes/mpi_recipe_dev.m
Normal file
@@ -0,0 +1,289 @@
|
||||
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_a2_tracked_levels = 1;
|
||||
run_a2_residual = 1;
|
||||
run_a1 = 1;
|
||||
run_tracking_adaptive = 1;
|
||||
plot_output_signals = 0;
|
||||
|
||||
%% 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;
|
||||
|
||||
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, ...
|
||||
"dc_level_update_blocklength_a2", 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, ...
|
||||
"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", true, ...
|
||||
"dc_tracking_optimization_len", 2^15, ...
|
||||
"dc_tracking_optimization_max_evals", 20, ...
|
||||
"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;
|
||||
|
||||
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
|
||||
@@ -1,319 +0,0 @@
|
||||
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
|
||||
@@ -1,153 +0,0 @@
|
||||
function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx_bits, options)
|
||||
|
||||
arguments
|
||||
eq_
|
||||
mlse_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0;
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
end
|
||||
|
||||
%Duobinary Targeting
|
||||
db_ref_sequence = Duobinary().encode(tx_symbols);
|
||||
db_ref_constellation = unique(db_ref_sequence.signal);
|
||||
[eq_signal, eq_noise] = eq_.process(rx_signal,db_ref_sequence);
|
||||
|
||||
if ~isempty(options.postFFE)
|
||||
[eq_signal,eq_noise] = options.postFFE.process(eq_signal,db_ref_sequence);
|
||||
end
|
||||
|
||||
mlse_.DIR = [1,1];
|
||||
%
|
||||
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
mlse_sig_sd = mlse_.process(eq_signal);
|
||||
else
|
||||
[mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols);
|
||||
end
|
||||
|
||||
mlse_sig_hd = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).quantize(mlse_sig_sd);
|
||||
|
||||
% precoding to mitigate error propagation, most prominently used in
|
||||
% combination with duobinary signaling to avoid catastrophic error
|
||||
% behavior (see J.W.M. Bergmans, Digital Baseband Transmission and Recording -> partial response signaling)
|
||||
switch options.precode_mode
|
||||
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded:
|
||||
|
||||
% A) Emulate diff precoding
|
||||
mlse_sig_hd_precoded = Duobinary().encode(mlse_sig_hd,"M",M);
|
||||
mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded,"M",M);
|
||||
|
||||
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);
|
||||
|
||||
[~,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(mlse_sig_hd);
|
||||
[bits_mlse,errors_mlse,ber_db,~] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
|
||||
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!
|
||||
mlse_sig_hd_decoded = Duobinary().encode(mlse_sig_hd,"M",M);
|
||||
mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded,"M",M);
|
||||
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.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(mlse_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');
|
||||
end
|
||||
|
||||
% M = numel(unique(tx_symbols.signal));
|
||||
rx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_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);
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
gmi_mlse = NaN;
|
||||
air_mlse = NaN;
|
||||
else
|
||||
gmi_mlse = GMI_MLSE;
|
||||
air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_mlse ./ log2(double(M));
|
||||
end
|
||||
|
||||
db_results = struct();
|
||||
db_results.metrics = Metricstruct;
|
||||
db_results.metrics.result_id = NaN;
|
||||
db_results.metrics.run_id = NaN;
|
||||
db_results.metrics.eqParam_id = NaN;
|
||||
db_results.metrics.date_of_processing = datetime('now');
|
||||
db_results.metrics.BER = ber_db;
|
||||
db_results.metrics.numBits = bits_db;
|
||||
db_results.metrics.numBitErr = errors_db;
|
||||
db_results.metrics.BER_precoded = ber_db_diff_precoded;
|
||||
db_results.metrics.numBitErr_precoded = errors_db_diff_precoded;
|
||||
db_results.metrics.GMI = gmi_mlse;
|
||||
db_results.metrics.AIR = air_mlse;
|
||||
db_results.metrics.MLSE_dir = mlse_.DIR;
|
||||
db_results.metrics.Alpha = alpha;
|
||||
|
||||
% Create DB results structure
|
||||
db_results.config = Equalizerstruct();
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
db_results.config.eq = jsonencode(eq_);
|
||||
% mlse_.DIR = [];
|
||||
db_results.config.mlse = jsonencode(mlse_);
|
||||
db_results.config.equalizer_structure = int32(equalizer_structure.vnle_db_mlse);
|
||||
db_results.config.comment = 'function: Duobinary tgt. (VNLE -> MLSE)';
|
||||
|
||||
if options.showAnalysis
|
||||
|
||||
eq_signal.eye(eq_signal.fs,M,"fignum",249);
|
||||
|
||||
|
||||
eq_noise = eq_noise - mean(eq_noise.signal);
|
||||
|
||||
rx_signal.spectrum("normalizeTo0dB",1,"fignum",250,"displayname","Rx Spectrum");
|
||||
|
||||
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;
|
||||
showLevelHistogram(eq_signal, db_ref_sequence, "fignum", 341);
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
320
Functions/EQ_visuals/exportLevelScatterTikz.m
Normal file
320
Functions/EQ_visuals/exportLevelScatterTikz.m
Normal file
@@ -0,0 +1,320 @@
|
||||
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
|
||||
@@ -4,6 +4,12 @@ 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;
|
||||
@@ -22,7 +28,9 @@ if plot_shit
|
||||
fig = figure; % Create a new figure and get its handle
|
||||
else
|
||||
fig = figure(options.fignum); % Use the specified figure number
|
||||
clf;
|
||||
if options.clear
|
||||
clf;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +38,9 @@ end
|
||||
rx_symbols = eq_signal; %./ rms(eq_signal);
|
||||
correct_symbols = ref_symbols;
|
||||
|
||||
col = cbrewer2('Paired',numel(unique(correct_symbols))*2);
|
||||
col = cbrewer2('Paired',numel(unique(correct_symbols))*3);
|
||||
useDefaultColormap = isempty(options.color);
|
||||
scatterColor = options.color;
|
||||
ccnt = -1;
|
||||
|
||||
levels = unique(correct_symbols);
|
||||
@@ -66,20 +76,146 @@ 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;
|
||||
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([floor(min(X_(:)))-1, ceil(max(X_(:)))+1]);
|
||||
ylim([floor(min(Y_(:)))-1, ceil(max(Y_(:)))+1]);
|
||||
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);
|
||||
|
||||
yticks((levels(1:end-1) + levels(2:end)) / 2);
|
||||
xticks((levels(1:end-1) + levels(2:end)) / 2);
|
||||
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(X(:), Y(:), 2, 'x', 'LineWidth', 10, 'MarkerEdgeColor', col(4,:),'DisplayName','Tx Levels');
|
||||
xlim(plot_xlim);
|
||||
ylim(plot_ylim);
|
||||
|
||||
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;
|
||||
legend
|
||||
xlabel('even symbols');
|
||||
ylabel('odd symbols');
|
||||
axis equal; grid on;
|
||||
axis equal;
|
||||
grid on;
|
||||
grid minor;
|
||||
ax.Layer = 'top';
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 = [0.2157 0.4941 0.7216];
|
||||
options.color = [];
|
||||
end
|
||||
|
||||
% Determine the figure number to use or create a new figure
|
||||
@@ -16,12 +16,15 @@ end
|
||||
hold on
|
||||
ax = gca;
|
||||
% 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, :);
|
||||
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
|
||||
|
||||
% Ensure the figure is ready before calling spectrum
|
||||
eq_noise.spectrum("displayname", options.displayname, "fignum", fig.Number, "normalizeTo0dB", 1,"color",options.color);
|
||||
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*2);
|
||||
|
||||
if ~isnan(options.postfilter_taps)
|
||||
% Hold on to the figure for further plotting
|
||||
|
||||
@@ -6,7 +6,8 @@ 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 = [0.2157 0.4941 0.7216];
|
||||
options.color = [];
|
||||
options.linestyle = '-';
|
||||
end
|
||||
|
||||
% Determine the figure number to use or create a new figure
|
||||
@@ -30,42 +31,52 @@ end
|
||||
ax = gca;
|
||||
|
||||
% 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
|
||||
title('SNR of received Signal')
|
||||
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
|
||||
|
||||
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] = pwelch(noise_signal,hanning(fft_length),fft_length/2,fft_length,options.fs_rx,"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
|
||||
|
||||
w = w.*1e-9;
|
||||
snr_dbm = 10*log10(s_lin./n_lin);
|
||||
snr_lin = s_lin./n_lin;
|
||||
snr_lin = movmean(snr_lin,10);
|
||||
snr_dbm = 10*log10(snr_lin);
|
||||
|
||||
% 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);
|
||||
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);
|
||||
xlabel("Frequency in GHz");
|
||||
|
||||
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)]);
|
||||
ylabel("SNR (dB)");
|
||||
xlim([min(w) max(w)]);
|
||||
|
||||
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);
|
||||
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; grid minor;
|
||||
legend('Interpreter','none');
|
||||
title('Noise of soft decision signal (not MLSE)');
|
||||
grid on;
|
||||
if isempty(get(gca, 'Legend'))
|
||||
legend('Interpreter','none');
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ 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.
|
||||
@@ -34,20 +35,24 @@ end
|
||||
end
|
||||
|
||||
% Magnitude response (in dB)
|
||||
subplot(2,1,1);
|
||||
% subplot(2,1,1);
|
||||
hold on
|
||||
plot(f.*1e-9, H_db,'DisplayName',options.displayname);
|
||||
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
|
||||
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
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
@@ -18,30 +19,17 @@ end
|
||||
eq_signal = PAMmapper(M,0).quantize(eq_signal);
|
||||
end
|
||||
|
||||
diff_indices = eq_signal.signal == ref_symbols.signal;
|
||||
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);
|
||||
|
||||
|
||||
% 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
|
||||
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");
|
||||
end
|
||||
|
||||
burst_symbols = burst_count .* burst_len';
|
||||
burst_rate = burst_symbols ;%./ length(rx_signal);
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,8 @@ 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')
|
||||
@@ -12,6 +14,20 @@ 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)
|
||||
@@ -22,31 +38,89 @@ end
|
||||
|
||||
eq_signal = max(min(eq_signal,3),-3);
|
||||
|
||||
%%% 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
|
||||
eq_signal = eq_signal .* -1;
|
||||
|
||||
%%% histogram
|
||||
clf
|
||||
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
|
||||
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
|
||||
end
|
||||
xlim([-3 3]);
|
||||
legend
|
||||
@@ -58,3 +132,15 @@ 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
|
||||
|
||||
|
||||
@@ -1,133 +1,420 @@
|
||||
function [symbols_for_lvl,avg_for_lvl] = showLevelScatter(eq_signal,ref_symbols,options)
|
||||
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.
|
||||
|
||||
arguments
|
||||
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;
|
||||
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
|
||||
end
|
||||
|
||||
plot_shit = 1;
|
||||
fsym = resolveSymbolRate(rxInput, refSymbols, options);
|
||||
[symbols_for_lvl, avg_for_lvl, xAxisUs, info] = prepareLevelScatterData(rxInput, refSymbols, fsym, 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');
|
||||
if options.showPlot
|
||||
info.plot = plotLevelScatter(symbols_for_lvl, avg_for_lvl, refSymbols, xAxisUs, options);
|
||||
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
|
||||
|
||||
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
|
||||
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);
|
||||
else
|
||||
fig = figure(options.fignum); % Use the specified figure number
|
||||
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
|
||||
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);
|
||||
|
||||
rx_symbols = eq_signal; %./ rms(eq_signal);
|
||||
correct_symbols = ref_symbols;
|
||||
f_sym = options.f_sym;
|
||||
if 1
|
||||
for levelIdx = numLevels:-1:1
|
||||
scatter(ax, xAxisUs, symbols_for_lvl(levelIdx, :), options.scatterSize, ".", ...
|
||||
"MarkerEdgeColor", cols(levelIdx, :), ...
|
||||
"MarkerEdgeAlpha", options.scatterAlpha);
|
||||
end
|
||||
end
|
||||
|
||||
col = cbrewer2('Paired',numel(unique(correct_symbols))*2);
|
||||
ccnt = -1;
|
||||
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
|
||||
|
||||
levels = unique(correct_symbols);
|
||||
symbols_for_lvl = NaN(numel(levels),length(correct_symbols));
|
||||
start = 1;
|
||||
ende = length(correct_symbols);
|
||||
% 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
|
||||
|
||||
for l = 1:numel(levels)
|
||||
ccnt = ccnt+2;
|
||||
if options.showStdAnnotations
|
||||
annotateLevelStd(symbols_for_lvl, avg_for_lvl, xAxisUs, options.xLimits);
|
||||
end
|
||||
|
||||
level_amplitude = levels(l);
|
||||
xlabel(ax, 'Time in $\mu$s');
|
||||
ylabel(ax, 'Normalized Amplitude');
|
||||
xlim(ax, xLimits);
|
||||
ylim(ax, options.yLimits);
|
||||
grid(ax, "on");
|
||||
figure(figHandle);
|
||||
drawnow;
|
||||
end
|
||||
|
||||
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;
|
||||
function [xReduced, yReduced] = reduceAverageLineForPlot(xAxisUs, yTrace, maxPoints, smoothWindow)
|
||||
valid = isfinite(xAxisUs) & isfinite(yTrace);
|
||||
xValid = xAxisUs(valid);
|
||||
yValid = yTrace(valid);
|
||||
|
||||
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;
|
||||
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
|
||||
end
|
||||
|
||||
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,:));
|
||||
levelAverage = avg_for_lvl(levelIdx, :);
|
||||
yText = median(levelAverage(~isnan(levelAverage)), 'omitnan');
|
||||
if isnan(yText)
|
||||
yText = median(levelSamples(~isnan(levelSamples)), 'omitnan');
|
||||
end
|
||||
hold on
|
||||
end
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
if plot_shit
|
||||
% yline(levels);
|
||||
xlabel('Time in $\mu$s');
|
||||
ylabel('Normalized Amplitude');
|
||||
ylim([-3 3]);
|
||||
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
|
||||
end
|
||||
|
||||
function levelAverage = interpolateMissingLevelAverage(levelAverage)
|
||||
validSamples = ~isnan(levelAverage);
|
||||
|
||||
if nnz(validSamples) == 0
|
||||
return
|
||||
elseif nnz(validSamples) == 1
|
||||
levelAverage(:) = levelAverage(validSamples);
|
||||
return
|
||||
end
|
||||
|
||||
t = 1:numel(levelAverage);
|
||||
levelAverage(~validSamples) = interp1(t(validSamples), levelAverage(validSamples), ...
|
||||
t(~validSamples), 'linear', 'extrap');
|
||||
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
|
||||
|
||||
35
Functions/EQ_visuals/showMarkovDiagram.m
Normal file
35
Functions/EQ_visuals/showMarkovDiagram.m
Normal file
@@ -0,0 +1,35 @@
|
||||
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
|
||||
40
Functions/EQ_visuals/showMpiLevelScatter.m
Normal file
40
Functions/EQ_visuals/showMpiLevelScatter.m
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
40
Functions/EQ_visuals/showTransitionProbabilities.m
Normal file
40
Functions/EQ_visuals/showTransitionProbabilities.m
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
120
Functions/Job_Processing/dsp_runid.m
Normal file
120
Functions/Job_Processing/dsp_runid.m
Normal file
@@ -0,0 +1,120 @@
|
||||
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
|
||||
86
Functions/Job_Processing/findSignalPulseformer.m
Normal file
86
Functions/Job_Processing/findSignalPulseformer.m
Normal file
@@ -0,0 +1,86 @@
|
||||
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
|
||||
201
Functions/Job_Processing/loadAndSyncRunSignals.m
Normal file
201
Functions/Job_Processing/loadAndSyncRunSignals.m
Normal file
@@ -0,0 +1,201 @@
|
||||
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
|
||||
@@ -1,114 +0,0 @@
|
||||
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
|
||||
97
Functions/Job_Processing/loadDspInputFromFilePaths.m
Normal file
97
Functions/Job_Processing/loadDspInputFromFilePaths.m
Normal file
@@ -0,0 +1,97 @@
|
||||
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
|
||||
40
Functions/Job_Processing/loadDspInputFromRunId.m
Normal file
40
Functions/Job_Processing/loadDspInputFromRunId.m
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
@@ -1,32 +0,0 @@
|
||||
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
|
||||
291
Functions/Job_Processing/runBatch.m
Normal file
291
Functions/Job_Processing/runBatch.m
Normal file
@@ -0,0 +1,291 @@
|
||||
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
|
||||
@@ -9,9 +9,11 @@ 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
|
||||
% wh : updated DataStorage. For multiple run_ids, run_id is added as
|
||||
% the first storage axis.
|
||||
|
||||
arguments
|
||||
run_ids int32 = 0
|
||||
@@ -19,172 +21,60 @@ 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);
|
||||
nJobsPerRunId = submit_options.wh.getLastLinIndice();
|
||||
sweepWh = submit_options.wh;
|
||||
validateNoRunIdSweepParameter(sweepWh);
|
||||
|
||||
nJobsPerRunId = sweepWh.getLastLinIndice();
|
||||
totalJobs = nRunIds * nJobsPerRunId;
|
||||
|
||||
% Preallocate
|
||||
wh = buildStorageWarehouse(sweepWh, run_ids);
|
||||
results = cell(nJobsPerRunId, nRunIds);
|
||||
futures = parallel.FevalFuture.empty(totalJobs,0);
|
||||
jobIndices = zeros(totalJobs,2);
|
||||
jobs = repmat(struct('args', {{}}, 'label', "", 'meta', struct()), 1, totalJobs);
|
||||
|
||||
% Optional waitbar
|
||||
if submit_options.waitbar
|
||||
h = waitbar(0, 'Processing Jobs...');
|
||||
cleanupObj = onCleanup(@() delete(h));
|
||||
end
|
||||
jobCounter = 0;
|
||||
for r = 1:nRunIds
|
||||
for k = 1:nJobsPerRunId
|
||||
jobCounter = jobCounter + 1;
|
||||
userParameters = buildUserParameters(k, sweepWh);
|
||||
jobOptions = dsp_options;
|
||||
jobOptions.userParameters = userParameters;
|
||||
|
||||
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
|
||||
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);
|
||||
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
|
||||
|
||||
wh = submit_options.wh;
|
||||
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
|
||||
|
||||
%% Local helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
function handleError(ME, jobIndex, run_id)
|
||||
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', run_id, jobIndex, ...
|
||||
function handleError(ME, job, ~)
|
||||
fprintf('[RunID %d, Job %d] ERROR [%s]: %s\n', job.meta.run_id, job.meta.jobIndex, ...
|
||||
ME.identifier, ME.message);
|
||||
for st = ME.stack'
|
||||
fprintf(' %s:%d (%s)\n', st.file, st.line, st.name);
|
||||
@@ -192,44 +82,116 @@ wh = submit_options.wh;
|
||||
fprintf('Full report:\n%s\n', getReport(ME,'extended'));
|
||||
end
|
||||
|
||||
function optionalVars = buildOptionalVars(jobIndex, wh)
|
||||
optionalVars = struct();
|
||||
function userParameters = buildUserParameters(jobIndex, wh)
|
||||
userParameters = struct();
|
||||
if ~isempty(wh.getDimension())
|
||||
[vals, names] = wh.getPhysIndicesByLinIndex(jobIndex);
|
||||
for pi = 1:numel(names)
|
||||
optionalVars.(names{pi}) = vals{pi};
|
||||
userParameters.(names{pi}) = vals{pi};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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);
|
||||
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.");
|
||||
end
|
||||
end
|
||||
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);
|
||||
|
||||
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);
|
||||
end
|
||||
p = parpool('local', numWorkers, 'IdleTimeout', idleTimeout);
|
||||
|
||||
storageSubscripts{parameterIdx} = wh.getIndexByPhys(parameterName, parameterValue);
|
||||
end
|
||||
|
||||
% 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));
|
||||
if isscalar(storageSubscripts)
|
||||
storageJobIndex = storageSubscripts{1};
|
||||
else
|
||||
storageJobIndex = sub2ind(wh.getStorageSize(), storageSubscripts{:});
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
% Define the precomp path
|
||||
precomp_path = "C:\Users\Silas\Documents\MATLAB\Datensätze\sioe_labor\precomp";
|
||||
|
||||
precomp_path = "W:\labdata\sioe_labor\precomp";
|
||||
precomp_path = "C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\ECOC_2025_MPI";
|
||||
% Step 1: Find all valid files (assume .mat files for ChannelFreqResp)
|
||||
fileList = dir(fullfile(precomp_path, '*.mat'));
|
||||
fileNames = {fileList.name};
|
||||
|
||||
@@ -3,10 +3,14 @@ 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);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
% 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')
|
||||
@@ -1,120 +0,0 @@
|
||||
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
|
||||
@@ -1,124 +0,0 @@
|
||||
%% ============================================================
|
||||
% 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);
|
||||
@@ -1,189 +0,0 @@
|
||||
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,55 +0,0 @@
|
||||
% 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
|
||||
@@ -1,146 +0,0 @@
|
||||
%% ------------------------------------------------------------
|
||||
% Contour plot: λ_null as function of bandwidth (f_target) and reach (L)
|
||||
% ------------------------------------------------------------
|
||||
|
||||
% Parameters
|
||||
lambda0 = 1310e-9; % [m]
|
||||
S0 = 0.08; % [ps/(nm²·km)]
|
||||
c = physconst('lightspeed');
|
||||
|
||||
% Sweep dimensions
|
||||
f_targets = linspace(50e9, 120e9, 100); % [Hz] (x-axis)
|
||||
L_values = linspace(0.5e3, 10e3, 100); % [m] (y-axis)
|
||||
|
||||
lambda_surface = zeros(numel(L_values), numel(f_targets));
|
||||
Dacc_surface = zeros(numel(L_values), numel(f_targets));
|
||||
|
||||
% Outer loop over fiber length (since L must be scalar)
|
||||
for iL = 1:numel(L_values)
|
||||
L = L_values(iL);
|
||||
[lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_targets, L, lambda0, S0);
|
||||
lambda_vec = 2*abs(lambda0 - lambda_vec);
|
||||
|
||||
if 0
|
||||
fprintf('\n- %d km ------------------------------------\n',L);
|
||||
fprintf(' f_null [GHz] lambda [nm] Dacc [ps/nm]\n');
|
||||
fprintf('----------------------------------------------\n');
|
||||
fprintf('%10.1f %8.2f %+8.3f\n',[f_targets(:)/1e9, lambda_vec(:)*1e9, Dacc_vec(:)].');
|
||||
fprintf('----------------------------------------------\n\n');
|
||||
end
|
||||
|
||||
lambda_surface(iL, :) = lambda_vec; % λ for each f_target
|
||||
Dacc_surface(iL, :) = Dacc_vec; % corresponding accumulated dispersion
|
||||
end
|
||||
|
||||
% Convert for plotting
|
||||
lambda_surface_nm = lambda_surface * 1e9; % [nm]
|
||||
L_km = L_values / 1000; % [km]
|
||||
f_GHz = f_targets / 1e9; % [GHz]
|
||||
|
||||
%% Contour plot
|
||||
figure('Color','w');
|
||||
|
||||
% Define wavelength contour levels [nm]
|
||||
lambda_levels = [1260:10:1290, 1290:5:1300, 1300:2.5:1310];
|
||||
lambda_levels = [100:-20:50, 50:-10:30,30:-5:0];
|
||||
|
||||
% Contour plot
|
||||
contour(f_GHz, L_km, lambda_surface_nm, lambda_levels, ...
|
||||
'LineWidth', 1.5, ...
|
||||
'ShowText', 'on', ...
|
||||
'LabelFormat', '%.0f nm');
|
||||
|
||||
% Colormap and colorbar
|
||||
colormap((cbrewer2('RdYlGn',100)));
|
||||
colorbar;
|
||||
clim([0 100]);
|
||||
|
||||
% Axis formatting
|
||||
xlabel('Signal Bandwidth [GHz]');
|
||||
ylabel('Fiber length [km]');
|
||||
legend('$\Delta \lambda$')
|
||||
|
||||
% X-axis ticks at 56 : 16 : 150 GHz
|
||||
xticks(56:8:150);
|
||||
|
||||
grid on; box on;
|
||||
|
||||
|
||||
%% Optional: overlay accumulated-dispersion contours
|
||||
if 0
|
||||
hold on;
|
||||
[CS, h] = contour(f_GHz, L_km, Dacc_surface, 10, 'k--', 'LineWidth', 0.8);
|
||||
clabel(CS, h, 'Color','k', 'FontSize',8);
|
||||
end
|
||||
|
||||
function [lambda_vec, Dacc_vec] = lambda_for_first_null_full(f_target, L, lambda0, S0)
|
||||
% lambda_for_first_null_full (stable, single-branch + validity checks)
|
||||
% --------------------------------------------------------------------
|
||||
% Computes the wavelength(s) at which the first IM/DD fading null
|
||||
% occurs at frequency/ies f_target using the full dispersion model:
|
||||
%
|
||||
% D(lambda) = (S0/4)*(lambda - lambda0^4 / lambda^3)
|
||||
%
|
||||
% Restricted to the NORMAL-dispersion branch (λ < λ0),
|
||||
% and valid only in the O-band (1260–1360 nm).
|
||||
%
|
||||
% Inputs:
|
||||
% f_target - scalar or vector of target null frequencies [Hz]
|
||||
% L - fiber length [m]
|
||||
% lambda0 - zero-dispersion wavelength (ZDW) [m]
|
||||
% S0 - dispersion slope at ZDW [ps/(nm²·km)]
|
||||
%
|
||||
% Outputs:
|
||||
% lambda_vec - wavelength(s) [m] where first null occurs (clamped to O-band)
|
||||
% Dacc_vec - accumulated dispersion(s) [ps/nm] (NaN if out of valid range)
|
||||
% --------------------------------------------------------------------
|
||||
|
||||
c = physconst('lightspeed');
|
||||
S0_si = S0 * 1e3; % ps/(nm²·km) -> s/(m³)
|
||||
|
||||
% Define O-band boundaries (in meters)
|
||||
lambda_min = 1255e-9;
|
||||
lambda_max = 1361e-9;
|
||||
|
||||
% Force column vector
|
||||
f_target = f_target(:);
|
||||
N = numel(f_target);
|
||||
|
||||
lambda_vec = NaN(N,1);
|
||||
Dacc_vec = NaN(N,1);
|
||||
|
||||
for k = 1:N
|
||||
RHS = c * 0.5 / (f_target(k)^2 * L);
|
||||
|
||||
% Normal-dispersion branch (λ < λ0)
|
||||
fun = @(lambda) -(S0_si/4).*(lambda - (lambda0^4)./(lambda.^3)).*lambda.^2 - RHS;
|
||||
|
||||
% Limit the search to [λ_min, λ0)
|
||||
try
|
||||
lambda_sol = fzero(fun, [lambda_min, lambda0 * 0.999]);
|
||||
catch
|
||||
% If the zero is not within bounds, skip this point
|
||||
lambda_sol = NaN;
|
||||
end
|
||||
|
||||
% Validate solution
|
||||
if isnan(lambda_sol) || lambda_sol < lambda_min || lambda_sol > lambda_max
|
||||
lambda_vec(k) = NaN;
|
||||
Dacc_vec(k) = NaN;
|
||||
continue
|
||||
end
|
||||
|
||||
% Compute D(lambda) and accumulated dispersion
|
||||
D_lambda = (S0_si/4) * (lambda_sol - (lambda0^4)/(lambda_sol^3)) / 1e-6; % ps/(nm·km)
|
||||
Dacc_val = D_lambda * (L/1000); % ps/nm
|
||||
|
||||
% Sanity bound on dispersion (avoid unphysical > ±100 ps/nm)
|
||||
if abs(Dacc_val) > 100
|
||||
lambda_vec(k) = NaN;
|
||||
Dacc_vec(k) = NaN;
|
||||
else
|
||||
lambda_vec(k) = lambda_sol;
|
||||
Dacc_vec(k) = Dacc_val;
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,62 +0,0 @@
|
||||
function tau_error = modifiedGodardTimingRecovery(rx, N, eta, beta)
|
||||
% modifiedGodardTimingRecovery
|
||||
%
|
||||
% This function estimates the symbol timing error using the modified Godard
|
||||
% approach in the frequency domain as described in:
|
||||
%
|
||||
% "Modified Godard Timing Recovery for Non-Integer Oversampling Receivers"
|
||||
% Appl. Sci. 2017, 7, 655. :contentReference[oaicite:0]{index=0}​:contentReference[oaicite:1]{index=1}
|
||||
%
|
||||
% Inputs:
|
||||
% rx - Received time-domain signal (vector)
|
||||
% N - FFT size (should be an even integer)
|
||||
% eta - Effective oversampling factor used for timing recovery (eta > 1)
|
||||
% beta - Roll-off related parameter (0 < beta <= 1)
|
||||
%
|
||||
% Output:
|
||||
% tau_error - Estimated timing error (in sample units)
|
||||
%
|
||||
% Implementation Notes:
|
||||
% 1. The function computes an N-point FFT of the first N samples of rx.
|
||||
% 2. It then determines an offset (Delta) defined as:
|
||||
% offset = round((1 - 1/eta) * N)
|
||||
% 3. To avoid index overflow, the summation is taken over indices k from 1 to
|
||||
% floor(N/2) - offset.
|
||||
% 4. The timing error is estimated as:
|
||||
% tau_error = ( (1+beta)/(2*eta*N - 1) * sum(phase difference) ) / (2*pi)
|
||||
% where the phase difference is (angle(R(k)) - angle(R(k+offset)))
|
||||
%
|
||||
% Make sure that the input signal rx contains at least N samples.
|
||||
|
||||
% Check input length
|
||||
if length(rx) < N
|
||||
error('Input signal length must be at least N.');
|
||||
end
|
||||
|
||||
% Compute the N-point FFT of the first N samples of rx
|
||||
R = fft(rx(1:N), N);
|
||||
|
||||
% Determine the offset based on the oversampling factor (eta)
|
||||
offset = round((1 - 1/eta) * N);
|
||||
|
||||
% Define the summation range to avoid index overflow
|
||||
k_min = 1;
|
||||
k_max = floor(N/2) - offset;
|
||||
if k_max < k_min
|
||||
error('Chosen parameters result in an empty summation range. Adjust N, eta, or beta.');
|
||||
end
|
||||
|
||||
% Compute the sum of phase differences over the selected frequency bins
|
||||
phase_diff_sum = 0;
|
||||
for k = k_min:k_max
|
||||
phase_k = angle(R(k));
|
||||
phase_k_offset = angle(R(k + offset));
|
||||
phase_diff_sum = phase_diff_sum + (phase_k - phase_k_offset);
|
||||
end
|
||||
|
||||
% Normalization factor as per the modified Godard algorithm
|
||||
norm_factor = (1 + beta) / (2 * eta * N - 1);
|
||||
|
||||
% Estimate the timing error in sample units
|
||||
tau_error = (norm_factor * phase_diff_sum) / (2 * pi);
|
||||
end
|
||||
@@ -19,66 +19,85 @@ end
|
||||
ax = gca;
|
||||
|
||||
% ---------------------------------------------------------
|
||||
% Extract *only* real data lines from ax.Children
|
||||
% Extract real data objects from ax.Children
|
||||
% ---------------------------------------------------------
|
||||
children = ax.Children;
|
||||
isLine = arrayfun(@(h) isa(h,'matlab.graphics.chart.primitive.Line'), children);
|
||||
lines = children(isLine);
|
||||
isScatter = arrayfun(@(h) isa(h,'matlab.graphics.chart.primitive.Scatter'), children);
|
||||
|
||||
% Remove helper lines (e.g. yline, fit overlays)
|
||||
lines = lines(~strcmp({lines.Tag},'ConstantLine'));
|
||||
isConstantLine = arrayfun(@(h) isprop(h,'Tag') && strcmp(h.Tag,'ConstantLine'), children);
|
||||
|
||||
n = numel(lines);
|
||||
if n == 0
|
||||
return
|
||||
end
|
||||
dataObjects = children((isLine | isScatter) & ~isConstantLine);
|
||||
n = numel(dataObjects);
|
||||
|
||||
% ---------------------------------------------------------
|
||||
% Fixed color palette (deterministic across figures)
|
||||
% ---------------------------------------------------------
|
||||
try
|
||||
cmap = linspecer(n);
|
||||
catch
|
||||
cmap = lines(n).Color; %#ok<NASGU>
|
||||
cmap = linespecer_fallback(n);
|
||||
if n > 0
|
||||
try
|
||||
cmap = linspecer(n);
|
||||
catch
|
||||
cmap = linespecer_fallback(n);
|
||||
end
|
||||
end
|
||||
|
||||
markers = {'o','s','o','o','^','v','d','>'};
|
||||
lw = 0.8;
|
||||
ms = 4;
|
||||
ms = 2;
|
||||
|
||||
% ---------------------------------------------------------
|
||||
% Apply line + marker styling
|
||||
% Apply line/scatter + marker styling
|
||||
% ---------------------------------------------------------
|
||||
for i = 1:n
|
||||
ln = lines(n-i+1); % preserve plotting order
|
||||
ln.LineWidth = lw;
|
||||
|
||||
if options.setcolors
|
||||
ln.Color = cmap(i,:);
|
||||
end
|
||||
dataObj = dataObjects(n-i+1); % preserve plotting order
|
||||
|
||||
if isa(dataObj,'matlab.graphics.chart.primitive.Line')
|
||||
dataObj.LineWidth = lw;
|
||||
|
||||
if options.setmarkers
|
||||
if strcmp(ln.Marker,'none')
|
||||
ln.Marker = markers{mod(i-1,numel(markers))+1};
|
||||
end
|
||||
ln.MarkerSize = ms;
|
||||
if options.setcolors
|
||||
ln.MarkerEdgeColor = cmap(i,:);
|
||||
dataObj.Color = cmap(i,:);
|
||||
end
|
||||
|
||||
if options.setmarkers
|
||||
if strcmp(dataObj.Marker,'none')
|
||||
dataObj.Marker = markers{mod(i-1,numel(markers))+1};
|
||||
end
|
||||
dataObj.MarkerSize = ms;
|
||||
if options.setcolors
|
||||
dataObj.MarkerEdgeColor = cmap(i,:);
|
||||
end
|
||||
dataObj.MarkerFaceColor = [1 1 1];
|
||||
end
|
||||
elseif isa(dataObj,'matlab.graphics.chart.primitive.Scatter')
|
||||
if options.setcolors
|
||||
dataObj.CData = cmap(i,:);
|
||||
dataObj.MarkerEdgeColor = cmap(i,:);
|
||||
markerFaceColor = dataObj.MarkerFaceColor;
|
||||
hasNoFace = ischar(markerFaceColor) && strcmp(markerFaceColor,'none');
|
||||
if ~hasNoFace
|
||||
dataObj.MarkerFaceColor = cmap(i,:);
|
||||
end
|
||||
end
|
||||
|
||||
if options.setmarkers
|
||||
if strcmp(dataObj.Marker,'none')
|
||||
dataObj.Marker = markers{mod(i-1,numel(markers))+1};
|
||||
end
|
||||
dataObj.SizeData = ms^2;
|
||||
end
|
||||
ln.MarkerFaceColor = [1 1 1];
|
||||
end
|
||||
end
|
||||
|
||||
% ---------------------------------------------------------
|
||||
% Optional smoothing / fitting overlay
|
||||
% ---------------------------------------------------------
|
||||
if options.polyfit
|
||||
if options.polyfit && n > 0
|
||||
hold on
|
||||
for i = 1:n
|
||||
ln = lines(n-i+1);
|
||||
x = ln.XData(:);
|
||||
y = ln.YData(:);
|
||||
dataObj = dataObjects(n-i+1);
|
||||
x = dataObj.XData(:);
|
||||
y = dataObj.YData(:);
|
||||
valid = isfinite(x) & isfinite(y);
|
||||
|
||||
if sum(valid) < options.polyorder+1
|
||||
@@ -112,7 +131,15 @@ if options.polyfit
|
||||
yf = polyval(p,xf);
|
||||
end
|
||||
|
||||
lightcol = ln.Color + 0.4*(1-ln.Color);
|
||||
if isa(dataObj,'matlab.graphics.chart.primitive.Line')
|
||||
basecol = dataObj.Color;
|
||||
else
|
||||
basecol = dataObj.CData(1,:);
|
||||
if numel(basecol) ~= 3
|
||||
basecol = cmap(i,:);
|
||||
end
|
||||
end
|
||||
lightcol = basecol + 0.4*(1-basecol);
|
||||
lightcol(lightcol>1)=1;
|
||||
|
||||
plot(xf,yf,'-','Color',lightcol,...
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
function signal_out = awgn_channel(signal_in)
|
||||
|
||||
signal_out = signal_in;
|
||||
|
||||
end
|
||||
@@ -1,23 +1,45 @@
|
||||
function mat2tikz_improved(filename)
|
||||
function mat2tikz_improved(filename,options)
|
||||
arguments
|
||||
% Default to the path in your example if no argument is provided
|
||||
filename (1,1) string = 'C:\Users\Silas\Documents\Dissertation\00_Examples\tikz\textfig.tikz';
|
||||
options.cleanfigure = false;
|
||||
options.cleanTargetResolution = 600;
|
||||
options.cleanScalePrecision (1,1) double = 1;
|
||||
end
|
||||
if options.cleanfigure
|
||||
cleanfigure( ...
|
||||
'targetResolution', options.cleanTargetResolution, ...
|
||||
'scalePrecision', options.cleanScalePrecision);
|
||||
end
|
||||
cleanfigure;
|
||||
matlab2tikz(char(filename), ...
|
||||
'width','\fwidth', ...
|
||||
'height','\fheight', ...
|
||||
'showInfo',false, ...
|
||||
'extraAxisOptions',{ ...
|
||||
'legend style={font=\footnotesize}', ...
|
||||
'xlabel style={font=\color{white!15!black},font=\small},',...
|
||||
'ylabel style={font=\color{white!15!black},font=\small},',...
|
||||
'width', '\fwidth', ...
|
||||
'height', '\fheight', ...
|
||||
'showInfo', false, ...
|
||||
'extraAxisOptions', { ...
|
||||
'xlabel style={font=\color{white!15!black}\small}', ...
|
||||
'ylabel style={font=\color{white!15!black}\small}', ...
|
||||
'scaled ticks=false', ...
|
||||
'tick label style={/pgf/number format/fixed, /pgf/number format/1000 sep={}}', ...
|
||||
'every axis/.append style={font=\scriptsize}', ...
|
||||
'legend columns=1', ...
|
||||
'every axis/.append style={font=\scriptsize}',...
|
||||
'legend columns=1',...
|
||||
'legend style={at={(0.02,0.98)},font=\footnotesize,draw=black!60,rounded corners=2pt,inner sep=1pt,fill=white,column sep=6pt,anchor= north west}',...
|
||||
'legend style={at={(0.02,0.98)},draw=white!0!white,font=\scriptsize,inner sep=0.1pt,fill=white,column sep=1pt,anchor= north west}',...
|
||||
'every axis/.append style={font=\scriptsize}',...
|
||||
'legend style={at={(0.02,0.98)}, anchor=north west, font=\scriptsize, draw=black!100, rounded corners=2pt, inner sep=1pt, fill=white, column sep=2pt}' ...
|
||||
});
|
||||
% matlab2tikz(char(filename), ...
|
||||
% 'width','\fwidth', ...
|
||||
% 'height','\fheight', ...
|
||||
% 'showInfo',false, ...
|
||||
% 'extraAxisOptions',{ ...
|
||||
% 'legend style={font=\footnotesize}', ...
|
||||
% 'xlabel style={font=\color{white!15!black},font=\small},',...
|
||||
% 'ylabel style={font=\color{white!15!black},font=\small},',...
|
||||
% 'legend columns=1', ...
|
||||
% 'every axis/.append style={font=\scriptsize}',...
|
||||
% 'legend columns=1',...
|
||||
% 'legend style={at={(0.02,0.98)},font=\footnotesize,draw=black!60,rounded corners=2pt,inner sep=1pt,fill=white,column sep=6pt,anchor= north west}',...
|
||||
% 'legend style={at={(0.02,0.98)},draw=white!0!white,font=\scriptsize,inner sep=0.1pt,fill=white,column sep=1pt,anchor= north west}',...
|
||||
% 'every axis/.append style={font=\scriptsize}',...
|
||||
% 'scaled ticks=false', ...
|
||||
% 'tick label style={/pgf/number format/fixed, /pgf/number format/1000 sep={}}' ...
|
||||
% });
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
473
Libs/move_it/prms_move_it.m
Normal file
473
Libs/move_it/prms_move_it.m
Normal file
@@ -0,0 +1,473 @@
|
||||
function [data_out ,state] = prms(data_in, state, para)
|
||||
|
||||
global loop loopmax
|
||||
|
||||
if nargin==0
|
||||
if loop==-1,
|
||||
|
||||
% Abfrage der Ein- und Ausgnge
|
||||
|
||||
noinp=0 ;
|
||||
noout=1;
|
||||
|
||||
% Zuordnung zu den Ausgangsvariablen
|
||||
|
||||
data_out=noinp;
|
||||
state=noout;
|
||||
else
|
||||
|
||||
% Parameterdefinition
|
||||
|
||||
para.bl=128;
|
||||
comment.bl='Block length (Length of output vector)';
|
||||
comment.short.bl='length';
|
||||
comment.type.bl='number';
|
||||
comment.level.bl=1;
|
||||
|
||||
para.rand=0 ;
|
||||
comment.rand='Random (True) or pseudo-random (False)';
|
||||
comment.short.rand='rand/prms';
|
||||
comment.type.rand='boolean';
|
||||
comment.level.rand=1;
|
||||
|
||||
para.dimension=2;
|
||||
comment.dimension='dimension (i.e. number of parallel bit streams)';
|
||||
comment.type.dimension='number';
|
||||
comment.short.dimension='dimension';
|
||||
comment.level.dimension=1;
|
||||
|
||||
para.order=2;
|
||||
comment.order='order of PRMS (needs to be adapted to memory of DUT)';
|
||||
comment.type.order='number';
|
||||
comment.short.order='order';
|
||||
comment.level.order=1;
|
||||
|
||||
para.skip=0;
|
||||
comment.skip='Skip the first n Bits of sequence';
|
||||
comment.type.skip='number';
|
||||
comment.short.skip='skip';
|
||||
comment.level.skip=1;
|
||||
|
||||
para.bruijn=1;
|
||||
comment.bruijn='extends the PRBS sequence of length 2^n-1 to a deBruijn sequence of 2^n';
|
||||
comment.type.bruijn='boolean';
|
||||
comment.short.bruijn='deBruijn';
|
||||
comment.level.bruijn=1;
|
||||
|
||||
para.reset_prms=0;
|
||||
comment.reset_prms='reset the sequence generator every loop';
|
||||
comment.type.reset_prms='boolean';
|
||||
comment.short.reset_prms='Reset every loop';
|
||||
comment.level.reset_prms=1;
|
||||
|
||||
para.method=1;
|
||||
comment.method='false= old method , true = new method';
|
||||
comment.type.method='Fast (c-file)|Slow (Matlab file)';
|
||||
comment.short.method='method';
|
||||
comment.level.method=1;
|
||||
|
||||
data_out=para;
|
||||
state=comment;
|
||||
end;
|
||||
else
|
||||
|
||||
% Initialisierung
|
||||
|
||||
if loop==0,
|
||||
|
||||
% check for correct parameters
|
||||
|
||||
checkskalar(getfield(inputname(2),{1:length(inputname(2))-6}),para,[{'bl'} {'skip'} {'dimension'} {'order'}],...
|
||||
[{'length'} {'skip'} {'dimension'} {'order'}]);
|
||||
checkinteger(getfield(inputname(2),{1:length(inputname(2))-6}),para,[{'bl'} {'skip'} {'dimension'} {'order'}],...
|
||||
[{'length'} {'skip'} {'dimension'} {'order'}]);
|
||||
checkpos(getfield(inputname(2),{1:length(inputname(2))-6}),para,[{'bl'} {'skip'} {'dimension'} {'order'}],...
|
||||
[{'length'} {'skip'} {'dimension'} {'order'}]);
|
||||
|
||||
% determine order of periodicity
|
||||
% initializing of all state parameters
|
||||
state.dimension = para.dimension;
|
||||
state.order = para.order;
|
||||
state.periodicity = para.dimension*para.order;
|
||||
state.bl = para.bl;
|
||||
|
||||
|
||||
state.reset_prms = para.reset_prms;
|
||||
|
||||
if ~para.rand % PRMS
|
||||
%------------------------------------------------------------------
|
||||
% prepare Matrix to map PRBS bits to PRMS values
|
||||
%------------------------------------------------------------------
|
||||
|
||||
% create lookup-table for primitive element
|
||||
if state.dimension>2,
|
||||
% general case
|
||||
bchpolynomial=zeros(1,state.dimension+1);
|
||||
bchpolynomial([1 srgtaps(state.dimension)+1])=1;
|
||||
betatable=fliplr(cyclgen_local(2^state.dimension-1,bchpolynomial).');
|
||||
|
||||
elseif state.dimension==2
|
||||
% special case, by-hand solution
|
||||
betatable=[0 1;1 0;1 1];
|
||||
|
||||
elseif state.dimension==1
|
||||
% special case, by-hand solution
|
||||
betatable=1;
|
||||
|
||||
end;
|
||||
binvec = 2.^(state.dimension-1:-1:0);
|
||||
modulo_mask = 2^state.dimension-1;
|
||||
betatable_sort_forward = sum(repmat(binvec,2^state.dimension-1,1).*betatable,2);
|
||||
[dummy,betatable_sort_inverse] = sort(betatable_sort_forward);
|
||||
betatable_sort_inverse = betatable_sort_inverse-1;
|
||||
|
||||
% perform polynomial division by x+beta
|
||||
|
||||
dividend=zeros(state.dimension+1,2);
|
||||
dividend([state.dimension-srgtaps(state.dimension)+1 end],1)=1;
|
||||
|
||||
h=zeros(state.dimension,2);
|
||||
|
||||
for digit=1:state.dimension,
|
||||
|
||||
% add new coefficient
|
||||
|
||||
h(digit,1:2)=dividend(digit,1:2);
|
||||
dividend(digit,1:2)=[0 0];
|
||||
|
||||
% update dividend
|
||||
|
||||
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;
|
||||
|
||||
% compute new dividend, exclude case of last iteration where
|
||||
% dividend should end up with 0
|
||||
|
||||
if (digit~=state.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;
|
||||
|
||||
% initialize the prms
|
||||
|
||||
|
||||
% init parameters for deBruijn sequence
|
||||
state.bruijn = para.bruijn;
|
||||
state.bruijn_counter = -1;
|
||||
|
||||
if para.method == 1, %new way
|
||||
% Initialize the PRBS registers
|
||||
tmp_srgtaps = srgtaps(state.periodicity);
|
||||
state.srgtaps = zeros(4,1,'int64');
|
||||
|
||||
mat_table = betatable(h(:,2)+1,:);
|
||||
state.mat_table = int64(mat_table * 2.^(0:state.dimension-1)');
|
||||
|
||||
for n=1:length(tmp_srgtaps)
|
||||
state.srgtaps(n)=int64(2^(tmp_srgtaps(n)-1));
|
||||
end
|
||||
|
||||
tmp_reg = int64(2^49-1);
|
||||
|
||||
state.reg = zeros(state.dimension,1,'int64');
|
||||
tmp_reg = prms_c(int32(1),tmp_reg,state.srgtaps);
|
||||
state.reg(1)= tmp_reg;
|
||||
offset = (2^state.periodicity-1)/modulo_mask;
|
||||
|
||||
for n=2:state.dimension
|
||||
tmp_reg = prms_c(int32(offset),tmp_reg,state.srgtaps);
|
||||
|
||||
state.reg(n)= tmp_reg;
|
||||
end
|
||||
|
||||
if para.skip>0
|
||||
[prbs_register, bruijn, bruijn_counter,data_out ]= prms_c(int32(para.skip),...
|
||||
state.reg,...
|
||||
state.srgtaps,...
|
||||
int32(state.order),...
|
||||
state.mat_table,...
|
||||
int32(state.bruijn),...
|
||||
int64(state.bruijn_counter));
|
||||
state.reg = prbs_register;
|
||||
state.bruijn = bruijn;
|
||||
state.bruijn_counter = bruijn_counter;
|
||||
end
|
||||
|
||||
else
|
||||
% Initialize the PRBS registers
|
||||
state.reg = ones(1,state.periodicity);
|
||||
state.srgtaps = srgtaps(state.periodicity);
|
||||
|
||||
state.mat_table = betatable(h(:,2)+1,:);
|
||||
|
||||
% determine indices into past PRBS
|
||||
|
||||
state.PRBS_inx=1+(0:state.dimension-1)*(2^state.periodicity-1)/modulo_mask;
|
||||
|
||||
% fill PRBS_memory by creating the first part of the PRBS
|
||||
[state.reg,state.PRBS_memory]=prms_prbs_init_mex(state.periodicity,state.srgtaps,state.PRBS_inx(end));
|
||||
% init for deBruijn sequence
|
||||
|
||||
% skip first bits
|
||||
skipped=0;
|
||||
PRBS_memory_pointer=0;
|
||||
while skipped<para.skip,
|
||||
|
||||
% consider deBruijn symbol
|
||||
if state.bruijn==2 && skipped>1,
|
||||
skipped = skipped+1;
|
||||
state.bruijn = 1;
|
||||
continue;
|
||||
end;
|
||||
|
||||
% compute current PRMS-symbol
|
||||
|
||||
PRBS_inx=mod(state.PRBS_inx+PRBS_memory_pointer-1,state.PRBS_inx(end))+1;
|
||||
symbol_temp=rem(sum(repmat(double(state.PRBS_memory(PRBS_inx).'),1,state.dimension).*...
|
||||
state.mat_table,1),2).';
|
||||
|
||||
% compute new PRBS_memory
|
||||
|
||||
state.reg=[rem(sum(state.reg(state.srgtaps)),2) state.reg(1:end-1)];
|
||||
state.PRBS_memory(PRBS_memory_pointer+1)=state.reg(end);
|
||||
|
||||
PRBS_memory_pointer=mod(PRBS_memory_pointer+1,state.PRBS_inx(end));
|
||||
skipped=skipped+1;
|
||||
|
||||
% consider deBruijn option
|
||||
|
||||
if state.bruijn==1,
|
||||
if state.bruijn_counter<0,
|
||||
% counter not yet locked
|
||||
if any([~sum(symbol_temp) state.order==1]),
|
||||
% zero symbol detected
|
||||
state.bruijn_counter=state.bruijn_counter-1;
|
||||
if any([state.bruijn_counter== -state.order state.order==1]),
|
||||
% lock counter
|
||||
state.bruijn_counter=2^state.periodicity-1;
|
||||
state.bruijn=2;
|
||||
end;
|
||||
else
|
||||
% no zero, so reset counter
|
||||
state.bruijn_counter=-1;
|
||||
end;
|
||||
else
|
||||
% counter already locked
|
||||
state.bruijn_counter=state.bruijn_counter-1;
|
||||
if state.bruijn_counter==0,
|
||||
% reset counter
|
||||
state.bruijn_counter = 2^state.periodicity-1;
|
||||
state.bruijn=2;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end
|
||||
|
||||
data_out=zeros(state.dimension,state.bl);
|
||||
|
||||
else
|
||||
|
||||
% Execution Stage
|
||||
|
||||
% update status window
|
||||
|
||||
% status(getfield(inputname(2),{1:length(inputname(2))-6}));
|
||||
|
||||
bl = state.bl;
|
||||
|
||||
if ~para.rand % PRMS
|
||||
|
||||
if para.method == 1 % fast implementation
|
||||
|
||||
[prbs_register, bruijn, bruijn_counter,data_out ]= prms_c(int32(state.bl),...
|
||||
state.reg,...
|
||||
state.srgtaps,...
|
||||
int32(state.order),...
|
||||
state.mat_table,...
|
||||
int32(state.bruijn),...
|
||||
int64(state.bruijn_counter));
|
||||
if ~state.reset_prms
|
||||
state.reg = prbs_register;
|
||||
state.bruijn = bruijn;
|
||||
state.bruijn_counter = bruijn_counter;
|
||||
end
|
||||
|
||||
data_out = double(data_out);
|
||||
|
||||
else %old way slow implementation
|
||||
|
||||
if ((loop==1) || ~state.reset_prms)
|
||||
% Initialisierung des Ausgangsvektors
|
||||
data_out = zeros(state.dimension,bl);
|
||||
|
||||
% Successives Berechnen der Ausgangsfolge
|
||||
counter=0;
|
||||
PRBS_memory_pointer=0;
|
||||
|
||||
while counter<bl,
|
||||
|
||||
% check for zero symbol to be inserted for de Bruijn
|
||||
% sequence
|
||||
|
||||
if state.bruijn==2,
|
||||
counter = counter+1;
|
||||
state.bruijn = 1;
|
||||
continue;
|
||||
end;
|
||||
|
||||
% compute current PRMS-symbol
|
||||
|
||||
PRBS_inx=mod(state.PRBS_inx+PRBS_memory_pointer-1,state.PRBS_inx(end))+1;
|
||||
|
||||
data_out(:,counter+1)=rem(sum(repmat(double(state.PRBS_memory(PRBS_inx).'),1,state.dimension).*...
|
||||
state.mat_table,1),2).';
|
||||
|
||||
% compute new PRBS_memory
|
||||
|
||||
state.reg=[rem(sum(state.reg(state.srgtaps)),2) state.reg(1:end-1)];
|
||||
state.PRBS_memory(PRBS_memory_pointer+1)=state.reg(end);
|
||||
|
||||
counter=counter+1;
|
||||
PRBS_memory_pointer=mod(PRBS_memory_pointer+1,state.PRBS_inx(end));
|
||||
|
||||
% consider deBruijn option
|
||||
|
||||
if state.bruijn==1,
|
||||
if state.bruijn_counter<0,
|
||||
% counter not yet locked
|
||||
if any([~sum(data_out(:,counter)) state.order==1]),
|
||||
% zero symbol detected
|
||||
state.bruijn_counter=state.bruijn_counter-1;
|
||||
if any([state.bruijn_counter== -state.order state.order==1]),
|
||||
% lock counter
|
||||
state.bruijn_counter=2^state.periodicity-1;
|
||||
state.bruijn=2;
|
||||
end;
|
||||
else
|
||||
% no zero, so reset counter
|
||||
state.bruijn_counter=-1;
|
||||
end;
|
||||
else
|
||||
|
||||
% counter already locked
|
||||
state.bruijn_counter=state.bruijn_counter-1;
|
||||
if state.bruijn_counter==0,
|
||||
% reset counter
|
||||
state.bruijn_counter = 2^state.periodicity-1;
|
||||
state.bruijn=2;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
% resort PRBS_memory for next block
|
||||
|
||||
state.PRBS_memory=[state.PRBS_memory(PRBS_memory_pointer+1:end) state.PRBS_memory(1:PRBS_memory_pointer)];
|
||||
|
||||
if state.reset_prms
|
||||
state.data_mem = data_out;
|
||||
end
|
||||
else
|
||||
data_out=state.data_mem;
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
data_out = round(rand(para.dimension,bl)) ;
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% A D D I T I O N A L F U N C T I O N S %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% generate feedback taps for shift register %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
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
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% adapted and faster version of generator for cyclic code %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
function code=cyclgen_local(columns,polynomial)
|
||||
rows=log2(columns+1);
|
||||
code=zeros(rows,columns);
|
||||
code(1,1)=1;
|
||||
|
||||
% create columns
|
||||
|
||||
for s=2:columns,
|
||||
code(:,s)=[0;code(1:end-1,s-1)];
|
||||
|
||||
% check for overflow
|
||||
|
||||
if code(end,s-1),
|
||||
code(:,s)=rem(code(:,s)+polynomial(1:end-1).',2);
|
||||
end;
|
||||
end;
|
||||
|
||||
end
|
||||
938
RUN_TESTS.m
Normal file
938
RUN_TESTS.m
Normal file
@@ -0,0 +1,938 @@
|
||||
function app = RUN_TESTS(options)
|
||||
%TEST_DASHBOARD Minimal GUI for test discovery and execution.
|
||||
% test_dashboard() opens a small dashboard that:
|
||||
% - discovers implemented, placeholder, and missing class-backed tests
|
||||
% - lists standalone tests such as integration or helper tests
|
||||
% - runs the selected test file
|
||||
% - runs the existing fast/full test profiles
|
||||
%
|
||||
% app = test_dashboard("Visible", false) builds the dashboard without
|
||||
% showing it. This is useful for non-interactive validation.
|
||||
|
||||
arguments
|
||||
options.Visible (1, 1) logical = true
|
||||
end
|
||||
|
||||
app = struct();
|
||||
app.Inventory = table();
|
||||
app.SelectedRow = [];
|
||||
app.ActiveProfileRowIndices = [];
|
||||
app.LastRunStatusMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
app.LastRunSummaryMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
app.LastRunDateMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
app.LastRunCommitMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
|
||||
testsRoot = fileparts(mfilename("fullpath"));
|
||||
repoRoot = fileparts(testsRoot);
|
||||
reportsRoot = fullfile(testsRoot, 'reports');
|
||||
latestRunFile = fullfile(testsRoot, '.last_test_run.json');
|
||||
|
||||
createUi();
|
||||
refreshInventory();
|
||||
|
||||
if options.Visible
|
||||
app.Figure.Visible = "on";
|
||||
end
|
||||
|
||||
function createUi()
|
||||
app.Figure = uifigure( ...
|
||||
"Name", "IMDD Test Dashboard", ...
|
||||
"Position", [80, 80, 1360, 820], ...
|
||||
"Color", [0.965, 0.973, 0.988], ...
|
||||
"Visible", "off");
|
||||
|
||||
app.Layout = uigridlayout(app.Figure, [3, 1]);
|
||||
app.Layout.RowHeight = {92, "1x", 150};
|
||||
app.Layout.Padding = [14, 14, 14, 14];
|
||||
app.Layout.RowSpacing = 12;
|
||||
|
||||
app.HeaderPanel = uipanel(app.Layout, ...
|
||||
"Title", "Suite Overview", ...
|
||||
"BackgroundColor", [0.985, 0.989, 0.997]);
|
||||
app.HeaderPanel.Layout.Row = 1;
|
||||
|
||||
headerGrid = uigridlayout(app.HeaderPanel, [2, 10]);
|
||||
headerGrid.RowHeight = {28, 32};
|
||||
headerGrid.ColumnWidth = {110, 110, 110, 90, 90, 90, "1x", 120, 120, 120};
|
||||
headerGrid.Padding = [10, 8, 10, 8];
|
||||
|
||||
app.ImplementedLabel = makeMetricLabel(headerGrid, "Implemented", 1);
|
||||
app.PlaceholderLabel = makeMetricLabel(headerGrid, "Placeholder", 2);
|
||||
app.MissingLabel = makeMetricLabel(headerGrid, "Missing", 3);
|
||||
app.PassedLabel = makeMetricLabel(headerGrid, "Passed", 4);
|
||||
app.FailedLabel = makeMetricLabel(headerGrid, "Failed", 5);
|
||||
app.IncompleteLabel = makeMetricLabel(headerGrid, "Incomplete", 6);
|
||||
app.TotalLabel = makeMetricLabel(headerGrid, "Total", 7);
|
||||
|
||||
app.FilterDropDown = uidropdown(headerGrid, ...
|
||||
"Items", {'All', 'Implemented', 'Missing', 'Standalone'}, ...
|
||||
"Value", 'All', ...
|
||||
"ValueChangedFcn", @filterChanged);
|
||||
app.FilterDropDown.Layout.Row = 2;
|
||||
app.FilterDropDown.Layout.Column = 8;
|
||||
|
||||
app.RefreshButton = uibutton(headerGrid, ...
|
||||
"Text", "Refresh", ...
|
||||
"ButtonPushedFcn", @refreshInventoryButtonPushed);
|
||||
app.RefreshButton.Layout.Row = 2;
|
||||
app.RefreshButton.Layout.Column = 9;
|
||||
|
||||
app.OpenButton = uibutton(headerGrid, ...
|
||||
"Text", "Open Selected", ...
|
||||
"ButtonPushedFcn", @openSelectedTest);
|
||||
app.OpenButton.Layout.Row = 2;
|
||||
app.OpenButton.Layout.Column = 10;
|
||||
|
||||
app.TablePanel = uipanel(app.Layout, ...
|
||||
"Title", "Discovered Tests", ...
|
||||
"BackgroundColor", [0.985, 0.989, 0.997]);
|
||||
app.TablePanel.Layout.Row = 2;
|
||||
|
||||
tableGrid = uigridlayout(app.TablePanel, [2, 1]);
|
||||
tableGrid.RowHeight = {36, "1x"};
|
||||
tableGrid.Padding = [10, 8, 10, 10];
|
||||
|
||||
buttonGrid = uigridlayout(tableGrid, [1, 6]);
|
||||
buttonGrid.ColumnWidth = {150, 120, 120, 140, 170, "1x"};
|
||||
buttonGrid.Padding = [0, 0, 0, 0];
|
||||
buttonGrid.ColumnSpacing = 10;
|
||||
|
||||
app.RunSelectedButton = uibutton(buttonGrid, ...
|
||||
"Text", "Run Selected", ...
|
||||
"ButtonPushedFcn", @runSelectedTest);
|
||||
app.RunFastButton = uibutton(buttonGrid, ...
|
||||
"Text", "Run Fast", ...
|
||||
"ButtonPushedFcn", @(~, ~) runProfile("fast"));
|
||||
app.RunFullButton = uibutton(buttonGrid, ...
|
||||
"Text", "Run Full", ...
|
||||
"ButtonPushedFcn", @(~, ~) runProfile("full"));
|
||||
app.RunIntegrationButton = uibutton(buttonGrid, ...
|
||||
"Text", "Run Integration", ...
|
||||
"ButtonPushedFcn", @(~, ~) runProfile("integration"));
|
||||
app.GenerateButton = uibutton(buttonGrid, ...
|
||||
"Text", "Generate Missing Tests", ...
|
||||
"ButtonPushedFcn", @generateMissingTests);
|
||||
|
||||
app.TableHintLabel = uilabel(buttonGrid, ...
|
||||
"Text", "Select a row to run or open the corresponding test file.", ...
|
||||
"HorizontalAlignment", "right", ...
|
||||
"FontColor", [0.28, 0.32, 0.38]);
|
||||
app.TableHintLabel.Layout.Column = 6;
|
||||
|
||||
app.Table = uitable(tableGrid, ...
|
||||
"ColumnName", {"Status", "Last Run", "Result", "Date", "Commit", "Scope", "Target", "Test File", "Tags"}, ...
|
||||
"ColumnEditable", false(1, 9), ...
|
||||
"ColumnSortable", true(1, 9), ...
|
||||
"ColumnWidth", {95, 95, 90, 150, 90, 100, 240, 300, "auto"}, ...
|
||||
"SelectionType", "row", ...
|
||||
"Multiselect", "off", ...
|
||||
"CellSelectionCallback", @tableSelectionChanged, ...
|
||||
"FontName", "Consolas");
|
||||
app.Table.Layout.Row = 2;
|
||||
|
||||
app.LogPanel = uipanel(app.Layout, ...
|
||||
"Title", "Run Log", ...
|
||||
"BackgroundColor", [0.985, 0.989, 0.997]);
|
||||
app.LogPanel.Layout.Row = 3;
|
||||
|
||||
logGrid = uigridlayout(app.LogPanel, [1, 1]);
|
||||
logGrid.Padding = [10, 8, 10, 10];
|
||||
|
||||
app.LogArea = uitextarea(logGrid, ...
|
||||
"Editable", "off", ...
|
||||
"FontName", "Consolas", ...
|
||||
"Value", {'IMDD Test Dashboard ready.'});
|
||||
end
|
||||
|
||||
function label = makeMetricLabel(parent, titleText, columnIdx)
|
||||
label = uilabel(parent, ...
|
||||
"Text", sprintf("%s: --", titleText), ...
|
||||
"FontWeight", "bold", ...
|
||||
"FontSize", 13, ...
|
||||
"FontColor", [0.12, 0.18, 0.26]);
|
||||
label.Layout.Row = 1;
|
||||
label.Layout.Column = columnIdx;
|
||||
end
|
||||
|
||||
function refreshInventoryButtonPushed(~, ~)
|
||||
refreshInventory();
|
||||
end
|
||||
|
||||
function refreshInventory()
|
||||
app.Inventory = buildInventory(repoRoot, testsRoot);
|
||||
app.SelectedRow = [];
|
||||
loadPersistedRunState();
|
||||
updateSummary(app.Inventory);
|
||||
applyFilter();
|
||||
appendLog(sprintf("[%s] Refreshed test inventory.", stamp()));
|
||||
end
|
||||
|
||||
function filterChanged(~, ~)
|
||||
applyFilter();
|
||||
end
|
||||
|
||||
function applyFilter()
|
||||
filtered = app.Inventory;
|
||||
switch string(app.FilterDropDown.Value)
|
||||
case "Implemented"
|
||||
filtered = filtered(filtered.Status == "Implemented", :);
|
||||
case "Missing"
|
||||
filtered = filtered(filtered.Status == "Missing", :);
|
||||
case "Standalone"
|
||||
filtered = filtered(filtered.Scope == "Standalone", :);
|
||||
otherwise
|
||||
% keep all rows
|
||||
end
|
||||
|
||||
app.Table.Data = filtered(:, ["Status", "LastRun", "ResultSummary", "LastRunDate", "LastRunCommit", "Scope", "Target", "TestFile", "Tags"]);
|
||||
applyRowStyles(filtered);
|
||||
end
|
||||
|
||||
function updateSummary(inventory)
|
||||
implementedCount = nnz(inventory.Status == "Implemented");
|
||||
missingCount = nnz(inventory.Status == "Missing");
|
||||
passedCount = nnz(inventory.LastRun == "Passed");
|
||||
failedCount = nnz(inventory.LastRun == "Failed");
|
||||
incompleteCount = nnz(inventory.LastRun == "Incomplete");
|
||||
totalCount = height(inventory);
|
||||
|
||||
app.ImplementedLabel.Text = sprintf("Implemented: %d", implementedCount);
|
||||
app.PlaceholderLabel.Text = "Placeholder: hidden";
|
||||
app.MissingLabel.Text = sprintf("Missing: %d", missingCount);
|
||||
app.PassedLabel.Text = sprintf("Passed: %d", passedCount);
|
||||
app.FailedLabel.Text = sprintf("Failed: %d", failedCount);
|
||||
app.IncompleteLabel.Text = sprintf("Incomplete: %d", incompleteCount);
|
||||
app.TotalLabel.Text = sprintf("Total: %d", totalCount);
|
||||
|
||||
app.PassedLabel.FontColor = metricColor(passedCount > 0, [0.18, 0.56, 0.24]);
|
||||
app.FailedLabel.FontColor = metricColor(failedCount > 0, [0.78, 0.22, 0.22]);
|
||||
app.IncompleteLabel.FontColor = metricColor(incompleteCount > 0, [0.72, 0.48, 0.08]);
|
||||
end
|
||||
|
||||
function applyRowStyles(filtered)
|
||||
removeStyle(app.Table);
|
||||
|
||||
greenStyle = uistyle("BackgroundColor", [0.90, 0.97, 0.92]);
|
||||
amberStyle = uistyle("BackgroundColor", [0.995, 0.95, 0.85]);
|
||||
redStyle = uistyle("BackgroundColor", [0.985, 0.90, 0.90]);
|
||||
blueStyle = uistyle("BackgroundColor", [0.90, 0.94, 0.995]);
|
||||
runningStyle = uistyle("BackgroundColor", [0.89, 0.95, 1.00]);
|
||||
|
||||
for rowIdx = 1:height(filtered)
|
||||
if filtered.LastRun(rowIdx) == "Passed"
|
||||
addStyle(app.Table, greenStyle, "row", rowIdx);
|
||||
elseif filtered.LastRun(rowIdx) == "Failed"
|
||||
addStyle(app.Table, redStyle, "row", rowIdx);
|
||||
elseif filtered.LastRun(rowIdx) == "Incomplete"
|
||||
addStyle(app.Table, amberStyle, "row", rowIdx);
|
||||
elseif filtered.LastRun(rowIdx) == "Running"
|
||||
addStyle(app.Table, runningStyle, "row", rowIdx);
|
||||
elseif filtered.Scope(rowIdx) == "Standalone"
|
||||
addStyle(app.Table, blueStyle, "row", rowIdx);
|
||||
elseif filtered.Status(rowIdx) == "Implemented"
|
||||
addStyle(app.Table, greenStyle, "row", rowIdx);
|
||||
elseif filtered.Status(rowIdx) == "Placeholder"
|
||||
addStyle(app.Table, amberStyle, "row", rowIdx);
|
||||
else
|
||||
addStyle(app.Table, redStyle, "row", rowIdx);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tableSelectionChanged(src, event)
|
||||
if isempty(event.Indices)
|
||||
app.SelectedRow = [];
|
||||
return
|
||||
end
|
||||
|
||||
selectedRow = event.Indices(1);
|
||||
visibleRows = src.Data;
|
||||
selectedTestFile = string(visibleRows.TestFile(selectedRow));
|
||||
|
||||
matchIdx = find(app.Inventory.TestFile == selectedTestFile, 1);
|
||||
if isempty(matchIdx)
|
||||
selectedTarget = string(visibleRows.Target(selectedRow));
|
||||
matchIdx = find(app.Inventory.Target == selectedTarget, 1);
|
||||
end
|
||||
|
||||
app.SelectedRow = matchIdx;
|
||||
end
|
||||
|
||||
function openSelectedTest(~, ~)
|
||||
record = selectedRecord();
|
||||
if isempty(record)
|
||||
uialert(app.Figure, "Select a test row first.", "No Selection");
|
||||
return
|
||||
end
|
||||
|
||||
if strlength(record.TestFile) == 0 || ~isfile(record.TestFile)
|
||||
uialert(app.Figure, "The selected class does not have a test file yet.", "No Test File");
|
||||
return
|
||||
end
|
||||
|
||||
edit(record.TestFile);
|
||||
appendLog(sprintf("[%s] Opened %s", stamp(), record.TestFile));
|
||||
end
|
||||
|
||||
function runSelectedTest(~, ~)
|
||||
record = selectedRecord();
|
||||
if isempty(record)
|
||||
uialert(app.Figure, "Select a test row first.", "No Selection");
|
||||
return
|
||||
end
|
||||
|
||||
if strlength(record.TestFile) == 0 || ~isfile(record.TestFile)
|
||||
uialert(app.Figure, "The selected class does not have a runnable test file yet.", "Missing Test");
|
||||
return
|
||||
end
|
||||
|
||||
titleText = sprintf("Running %s", record.TestFileName);
|
||||
markRowAsRunning(record);
|
||||
runWithProgress(titleText, @() runSingleTestFile(record.TestFile), record);
|
||||
end
|
||||
|
||||
function runProfile(profile)
|
||||
titleText = sprintf("Running profile '%s'", profile);
|
||||
rowIndices = profileRowIndices(profile);
|
||||
app.ActiveProfileRowIndices = rowIndices;
|
||||
markRowsAsRunning(rowIndices);
|
||||
runWithProgress(titleText, @() runProfileInternal(profile, rowIndices), table());
|
||||
app.ActiveProfileRowIndices = [];
|
||||
end
|
||||
|
||||
function generateMissingTests(~, ~)
|
||||
setupRepositoryPath();
|
||||
try
|
||||
summary = generateTests("verbose", false);
|
||||
refreshInventory();
|
||||
appendLog(sprintf("[%s] Generated missing tests: %d new, %d existing skipped, %d classes tracked.", ...
|
||||
stamp(), summary.generated, summary.skippedExisting, summary.classCount));
|
||||
catch err
|
||||
appendLog(sprintf("[%s] generateTests failed.", stamp()));
|
||||
appendLog(getReport(err, "extended", "hyperlinks", "off"));
|
||||
uialert(app.Figure, err.message, "Generate Tests Failed");
|
||||
end
|
||||
end
|
||||
|
||||
function runWithProgress(titleText, runner, record)
|
||||
try
|
||||
[output, results] = runner();
|
||||
appendLog(sprintf("[%s] %s", stamp(), titleText));
|
||||
appendLog(output);
|
||||
updateRunStateFromResults(results, record);
|
||||
writeRunArtifacts(titleText, results, record);
|
||||
catch err
|
||||
appendLog(sprintf("[%s] %s failed.", stamp(), titleText));
|
||||
appendLog(getReport(err, "extended", "hyperlinks", "off"));
|
||||
updateFailureState(record);
|
||||
uialert(app.Figure, err.message, "Run Failed");
|
||||
end
|
||||
end
|
||||
|
||||
function [output, results] = runProfileInternal(profile, rowIndices)
|
||||
setupRepositoryPath();
|
||||
profile = string(profile);
|
||||
|
||||
results = matlab.unittest.TestResult.empty(0, 1);
|
||||
logLines = strings(0, 1);
|
||||
logLines(end + 1, 1) = sprintf("Running profile '%s' with %d test files.", profile, numel(rowIndices));
|
||||
|
||||
for idx = 1:numel(rowIndices)
|
||||
record = app.Inventory(rowIndices(idx), :);
|
||||
if strlength(record.TestFile) == 0 || ~isfile(record.TestFile)
|
||||
continue
|
||||
end
|
||||
|
||||
logLines(end + 1, 1) = sprintf("Running %s", record.TestFileName);
|
||||
drawnow;
|
||||
|
||||
currentResults = runtests(char(record.TestFile));
|
||||
currentResults = currentResults(:);
|
||||
if isempty(results)
|
||||
results = currentResults;
|
||||
else
|
||||
results = [results; currentResults]; %#ok<AGROW>
|
||||
end
|
||||
updateRunStateFromResults(currentResults, record);
|
||||
|
||||
[statusText, summaryText] = summarizeResults(currentResults);
|
||||
logLines(end + 1, 1) = sprintf("Finished %s: %s (%s)", ...
|
||||
record.TestFileName, statusText, summaryText);
|
||||
drawnow;
|
||||
end
|
||||
|
||||
logLines(end + 1, 1) = sprintf("Passed: %d | Failed: %d | Incomplete: %d", ...
|
||||
nnz([results.Passed]), nnz([results.Failed]), nnz([results.Incomplete]));
|
||||
output = strjoin(logLines, newline);
|
||||
end
|
||||
|
||||
function [output, results] = runSingleTestFile(testFile)
|
||||
setupRepositoryPath();
|
||||
results = runtests(char(testFile));
|
||||
output = evalc('disp(results);');
|
||||
end
|
||||
|
||||
function record = selectedRecord()
|
||||
record = table();
|
||||
if isempty(app.SelectedRow)
|
||||
return
|
||||
end
|
||||
|
||||
if app.SelectedRow < 1 || app.SelectedRow > height(app.Inventory)
|
||||
return
|
||||
end
|
||||
|
||||
record = app.Inventory(app.SelectedRow, :);
|
||||
end
|
||||
|
||||
function setupRepositoryPath()
|
||||
addpath(repoRoot);
|
||||
addpath(testsRoot);
|
||||
|
||||
folders = ["Classes", "Functions", "Datatypes", "Libs"];
|
||||
for folder = folders
|
||||
fullFolder = fullfile(repoRoot, folder);
|
||||
if isfolder(fullFolder)
|
||||
addpath(genpath(fullFolder));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function appendLog(textIn)
|
||||
newLines = string(splitlines(string(textIn)));
|
||||
current = string(app.LogArea.Value);
|
||||
current = current(current ~= "");
|
||||
newLines = newLines(newLines ~= "");
|
||||
combined = [current; newLines];
|
||||
if isempty(combined)
|
||||
combined = " ";
|
||||
end
|
||||
app.LogArea.Value = cellstr(combined);
|
||||
drawnow;
|
||||
end
|
||||
|
||||
function markRowAsRunning(record)
|
||||
if isempty(record) || strlength(record.TestFile) == 0
|
||||
return
|
||||
end
|
||||
updateMappedState(char(record.TestFile), 'Running', '...');
|
||||
applyStoredRunState();
|
||||
pause(0.05);
|
||||
end
|
||||
|
||||
function markRowsAsRunning(rowIndices)
|
||||
for idx = 1:numel(rowIndices)
|
||||
key = char(app.Inventory.TestFile(rowIndices(idx)));
|
||||
if ~isempty(key)
|
||||
updateMappedState(key, 'Running', '...');
|
||||
end
|
||||
end
|
||||
applyStoredRunState();
|
||||
pause(0.05);
|
||||
end
|
||||
|
||||
function updateRunStateFromResults(results, record)
|
||||
if ~isempty(record)
|
||||
[statusText, summaryText] = summarizeResults(results);
|
||||
updateMappedState(char(record.TestFile), statusText, summaryText);
|
||||
else
|
||||
resultNames = string({results.Name})';
|
||||
uniqueNames = unique(resultNames);
|
||||
for idx = 1:numel(uniqueNames)
|
||||
currentResults = results(resultNames == uniqueNames(idx));
|
||||
[statusText, summaryText] = summarizeResults(currentResults);
|
||||
targetRows = find(app.Inventory.TestFileName == uniqueNames(idx));
|
||||
for rowIdx = targetRows'
|
||||
updateMappedState(char(app.Inventory.TestFile(rowIdx)), statusText, summaryText);
|
||||
end
|
||||
end
|
||||
end
|
||||
applyStoredRunState();
|
||||
end
|
||||
|
||||
function updateFailureState(record)
|
||||
if ~isempty(record) && strlength(record.TestFile) > 0
|
||||
updateMappedState(char(record.TestFile), 'Failed', 'error');
|
||||
applyStoredRunState();
|
||||
end
|
||||
end
|
||||
|
||||
function updateMappedState(key, statusText, summaryText)
|
||||
if isempty(key)
|
||||
return
|
||||
end
|
||||
app.LastRunStatusMap(key) = char(statusText);
|
||||
app.LastRunSummaryMap(key) = char(summaryText);
|
||||
end
|
||||
|
||||
function applyStoredRunState()
|
||||
for idx = 1:height(app.Inventory)
|
||||
key = char(app.Inventory.TestFile(idx));
|
||||
if isempty(key)
|
||||
continue
|
||||
end
|
||||
if isKey(app.LastRunStatusMap, key)
|
||||
app.Inventory.LastRun(idx) = string(app.LastRunStatusMap(key));
|
||||
app.Inventory.ResultSummary(idx) = string(app.LastRunSummaryMap(key));
|
||||
if isKey(app.LastRunDateMap, key)
|
||||
app.Inventory.LastRunDate(idx) = string(app.LastRunDateMap(key));
|
||||
end
|
||||
if isKey(app.LastRunCommitMap, key)
|
||||
app.Inventory.LastRunCommit(idx) = string(app.LastRunCommitMap(key));
|
||||
end
|
||||
end
|
||||
end
|
||||
updateSummary(app.Inventory);
|
||||
applyFilter();
|
||||
end
|
||||
|
||||
function loadPersistedRunState()
|
||||
app.LastRunStatusMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
app.LastRunSummaryMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
app.LastRunDateMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
app.LastRunCommitMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
|
||||
|
||||
if ~isfile(latestRunFile)
|
||||
return
|
||||
end
|
||||
|
||||
try
|
||||
payload = jsondecode(fileread(latestRunFile));
|
||||
fallbackDate = stringOrEmpty(payload, 'timestamp');
|
||||
fallbackCommit = "";
|
||||
if isfield(payload, 'git')
|
||||
fallbackCommit = stringOrEmpty(payload.git, 'short_commit');
|
||||
end
|
||||
if isfield(payload, 'row_states')
|
||||
rowStates = payload.row_states;
|
||||
for idx = 1:numel(rowStates)
|
||||
key = stringOrEmpty(rowStates(idx), 'test_file');
|
||||
if strlength(key) == 0
|
||||
continue
|
||||
end
|
||||
app.LastRunStatusMap(char(key)) = char(stringOrEmpty(rowStates(idx), 'last_run'));
|
||||
app.LastRunSummaryMap(char(key)) = char(stringOrEmpty(rowStates(idx), 'result_summary'));
|
||||
rowDate = stringOrEmpty(rowStates(idx), 'last_run_date');
|
||||
rowCommit = stringOrEmpty(rowStates(idx), 'last_run_commit');
|
||||
if strlength(rowDate) == 0
|
||||
rowDate = fallbackDate;
|
||||
end
|
||||
if strlength(rowCommit) == 0
|
||||
rowCommit = fallbackCommit;
|
||||
end
|
||||
app.LastRunDateMap(char(key)) = char(rowDate);
|
||||
app.LastRunCommitMap(char(key)) = char(rowCommit);
|
||||
end
|
||||
end
|
||||
applyStoredRunState();
|
||||
catch
|
||||
% Ignore malformed local cache files and rebuild from scratch.
|
||||
end
|
||||
end
|
||||
|
||||
function writeRunArtifacts(runLabel, results, record)
|
||||
if ~isfolder(reportsRoot)
|
||||
mkdir(reportsRoot);
|
||||
end
|
||||
|
||||
gitInfo = collectGitMetadata(repoRoot);
|
||||
runTimestamp = char(datetime("now", "TimeZone", "local", "Format", "yyyy-MM-dd'T'HH:mm:ssXXX"));
|
||||
updateMetadataMaps(results, record, runTimestamp, gitInfo.short_commit);
|
||||
|
||||
report = struct();
|
||||
report.timestamp = runTimestamp;
|
||||
rowStates = inventoryRowStates(app.Inventory);
|
||||
report.run_label = char(runLabel);
|
||||
report.repo_root = repoRoot;
|
||||
report.git = gitInfo;
|
||||
report.totals = struct( ...
|
||||
'passed', nnz([results.Passed]), ...
|
||||
'failed', nnz([results.Failed]), ...
|
||||
'incomplete', nnz([results.Incomplete]));
|
||||
report.method_results = resultEntries(results);
|
||||
report.row_states = rowStates;
|
||||
|
||||
jsonText = jsonencode(report, PrettyPrint=true);
|
||||
writeTextFile(latestRunFile, jsonText);
|
||||
|
||||
reportFile = fullfile(reportsRoot, sprintf('%s_%s.json', ...
|
||||
datestr(now, 'yyyy-mm-dd_HHMMSS'), sanitizeFileLabel(runLabel)));
|
||||
writeTextFile(reportFile, jsonText);
|
||||
appendLog(sprintf("[%s] Saved run report: %s", stamp(), reportFile));
|
||||
end
|
||||
|
||||
function updateMetadataMaps(results, record, runDate, shortCommit)
|
||||
if ~isempty(record) && strlength(record.TestFile) > 0
|
||||
key = char(record.TestFile);
|
||||
app.LastRunDateMap(key) = char(string(runDate));
|
||||
app.LastRunCommitMap(key) = char(string(shortCommit));
|
||||
applyStoredRunState();
|
||||
return
|
||||
end
|
||||
|
||||
if ~isempty(app.ActiveProfileRowIndices)
|
||||
for rowIdx = app.ActiveProfileRowIndices(:)'
|
||||
key = char(app.Inventory.TestFile(rowIdx));
|
||||
if isempty(key)
|
||||
continue
|
||||
end
|
||||
app.LastRunDateMap(key) = char(string(runDate));
|
||||
app.LastRunCommitMap(key) = char(string(shortCommit));
|
||||
end
|
||||
end
|
||||
applyStoredRunState();
|
||||
end
|
||||
|
||||
function rowIndices = profileRowIndices(profile)
|
||||
rowIndices = [];
|
||||
normalizedTags = "," + erase(app.Inventory.Tags, " ") + ",";
|
||||
switch string(profile)
|
||||
case "fast"
|
||||
rowIndices = find(app.Inventory.Status ~= "Missing" & ...
|
||||
contains(normalizedTags, ",fast,") & ...
|
||||
~contains(normalizedTags, ",placeholder,") & ...
|
||||
~contains(normalizedTags, ",hardware,") & ...
|
||||
~contains(normalizedTags, ",slow,"));
|
||||
case "full"
|
||||
rowIndices = find(app.Inventory.Status ~= "Missing" & ...
|
||||
~contains(normalizedTags, ",placeholder,") & ...
|
||||
~contains(normalizedTags, ",hardware,"));
|
||||
case "integration"
|
||||
rowIndices = find(app.Inventory.Status ~= "Missing" & ...
|
||||
contains(normalizedTags, ",integration,") & ...
|
||||
~contains(normalizedTags, ",placeholder,") & ...
|
||||
~contains(normalizedTags, ",hardware,"));
|
||||
case "placeholder"
|
||||
rowIndices = find(contains(normalizedTags, ",placeholder,"));
|
||||
end
|
||||
end
|
||||
|
||||
function textOut = stamp()
|
||||
textOut = char(datetime("now", "Format", "yyyy-MM-dd HH:mm:ss"));
|
||||
end
|
||||
end
|
||||
|
||||
function color = metricColor(hasRuns, activeColor)
|
||||
if hasRuns
|
||||
color = activeColor;
|
||||
else
|
||||
color = [0.48, 0.52, 0.58];
|
||||
end
|
||||
end
|
||||
|
||||
function inventory = buildInventory(repoRoot, testsRoot)
|
||||
repoRoot = char(string(repoRoot));
|
||||
testsRoot = char(string(testsRoot));
|
||||
classesRoot = fullfile(repoRoot, 'Classes');
|
||||
classFiles = collectMFiles(classesRoot);
|
||||
|
||||
records = struct( ...
|
||||
"Status", {}, ...
|
||||
"LastRun", {}, ...
|
||||
"ResultSummary", {}, ...
|
||||
"LastRunDate", {}, ...
|
||||
"LastRunCommit", {}, ...
|
||||
"Scope", {}, ...
|
||||
"Target", {}, ...
|
||||
"TargetFile", {}, ...
|
||||
"TestFile", {}, ...
|
||||
"TestFileName", {}, ...
|
||||
"Tags", {});
|
||||
|
||||
matchedTestFiles = strings(0, 1);
|
||||
|
||||
for idx = 1:numel(classFiles)
|
||||
classFile = fullfile(classFiles(idx).folder, classFiles(idx).name);
|
||||
if ~isClassFile(classFile)
|
||||
continue
|
||||
end
|
||||
|
||||
relativePath = strrep(classFile, [classesRoot, filesep], "");
|
||||
[relativeDir, className] = fileparts(relativePath);
|
||||
relativeDirText = char(join(string(relativeDir), ""));
|
||||
classNameText = char(string(className));
|
||||
testFile = findMatchingTestFile(testsRoot, relativeDirText, classNameText);
|
||||
|
||||
if strlength(testFile) == 0
|
||||
status = "Missing";
|
||||
tags = "";
|
||||
testFileName = "";
|
||||
else
|
||||
source = fileread(testFile);
|
||||
if contains(source, "assumeFail(")
|
||||
continue
|
||||
else
|
||||
matchedTestFiles(end + 1, 1) = testFile; %#ok<AGROW>
|
||||
status = "Implemented";
|
||||
tags = strjoin(extractTestTags(source), ", ");
|
||||
[~, testFileName] = fileparts(testFile);
|
||||
end
|
||||
end
|
||||
|
||||
targetLabel = fullfile('Classes', relativeDirText, [classNameText, '.m']);
|
||||
targetLabel = strrep(targetLabel, "\", "/");
|
||||
|
||||
records(end + 1) = struct( ... %#ok<AGROW>
|
||||
"Status", status, ...
|
||||
"LastRun", "", ...
|
||||
"ResultSummary", "", ...
|
||||
"LastRunDate", "", ...
|
||||
"LastRunCommit", "", ...
|
||||
"Scope", "Class", ...
|
||||
"Target", string(targetLabel), ...
|
||||
"TargetFile", string(classFile), ...
|
||||
"TestFile", string(testFile), ...
|
||||
"TestFileName", string(testFileName), ...
|
||||
"Tags", string(tags));
|
||||
end
|
||||
|
||||
allTestFiles = dir(fullfile(testsRoot, "**", "*_test.m"));
|
||||
for idx = 1:numel(allTestFiles)
|
||||
testFile = fullfile(allTestFiles(idx).folder, allTestFiles(idx).name);
|
||||
if any(strcmpi(testFile, matchedTestFiles))
|
||||
continue
|
||||
end
|
||||
|
||||
source = fileread(testFile);
|
||||
if contains(source, "assumeFail(")
|
||||
continue
|
||||
else
|
||||
status = "Implemented";
|
||||
end
|
||||
|
||||
[~, testFileName] = fileparts(testFile);
|
||||
relativeTest = strrep(testFile, [testsRoot, filesep], "");
|
||||
relativeTest = strrep(relativeTest, "\", "/");
|
||||
tags = strjoin(extractTestTags(source), ", ");
|
||||
|
||||
records(end + 1) = struct( ... %#ok<AGROW>
|
||||
"Status", status, ...
|
||||
"LastRun", "", ...
|
||||
"ResultSummary", "", ...
|
||||
"LastRunDate", "", ...
|
||||
"LastRunCommit", "", ...
|
||||
"Scope", "Standalone", ...
|
||||
"Target", string(relativeTest), ...
|
||||
"TargetFile", "", ...
|
||||
"TestFile", string(testFile), ...
|
||||
"TestFileName", string(testFileName), ...
|
||||
"Tags", string(tags));
|
||||
end
|
||||
|
||||
inventory = struct2table(records);
|
||||
if isempty(inventory)
|
||||
inventory = table(strings(0, 1), strings(0, 1), strings(0, 1), ...
|
||||
strings(0, 1), strings(0, 1), strings(0, 1), strings(0, 1), ...
|
||||
strings(0, 1), strings(0, 1), strings(0, 1), strings(0, 1), ...
|
||||
"VariableNames", ["Status", "LastRun", "ResultSummary", "LastRunDate", ...
|
||||
"LastRunCommit", "Scope", "Target", "TargetFile", "TestFile", ...
|
||||
"TestFileName", "Tags"]);
|
||||
return
|
||||
end
|
||||
|
||||
inventory = inventory(inventory.Status ~= "Placeholder", :);
|
||||
|
||||
statusOrder = containers.Map( ...
|
||||
{'Implemented', 'Missing', 'Placeholder'}, ...
|
||||
{1, 2, 3});
|
||||
scopeOrder = containers.Map({'Class', 'Standalone'}, {1, 2});
|
||||
|
||||
statusRank = zeros(height(inventory), 1);
|
||||
scopeRank = zeros(height(inventory), 1);
|
||||
for idx = 1:height(inventory)
|
||||
statusRank(idx) = statusOrder(char(inventory.Status(idx)));
|
||||
scopeRank(idx) = scopeOrder(char(inventory.Scope(idx)));
|
||||
end
|
||||
|
||||
inventory.statusRank = statusRank;
|
||||
inventory.scopeRank = scopeRank;
|
||||
inventory = sortrows(inventory, {'scopeRank', 'statusRank', 'Target'});
|
||||
inventory = removevars(inventory, {'statusRank', 'scopeRank'});
|
||||
end
|
||||
|
||||
function testFile = findMatchingTestFile(testsRoot, relativeDir, className)
|
||||
testsRoot = char(string(testsRoot));
|
||||
relativeDir = char(join(string(relativeDir), ""));
|
||||
className = char(string(className));
|
||||
testDir = fullfile(testsRoot, relativeDir);
|
||||
testFile = "";
|
||||
if ~isfolder(testDir)
|
||||
return
|
||||
end
|
||||
|
||||
exactFile = fullfile(testDir, [className, '_test.m']);
|
||||
if isfile(exactFile)
|
||||
testFile = string(exactFile);
|
||||
return
|
||||
end
|
||||
|
||||
candidates = dir(fullfile(testDir, '*_test.m'));
|
||||
if isempty(candidates)
|
||||
return
|
||||
end
|
||||
|
||||
candidateNames = string({candidates.name});
|
||||
suffix = ['_', className, '_test.m'];
|
||||
matchMask = endsWith(candidateNames, suffix, "IgnoreCase", true);
|
||||
|
||||
if any(matchMask)
|
||||
firstMatch = candidates(find(matchMask, 1));
|
||||
testFile = string(fullfile(firstMatch.folder, firstMatch.name));
|
||||
end
|
||||
end
|
||||
|
||||
function tags = extractTestTags(source)
|
||||
tokens = regexp(source, "TestTags\s*=\s*\{([^}]*)\}", "tokens", "once");
|
||||
if isempty(tokens)
|
||||
tags = strings(0, 1);
|
||||
return
|
||||
end
|
||||
|
||||
tags = regexp(tokens{1}, "'([^']*)'", "tokens");
|
||||
tags = string(cellfun(@(entry) entry{1}, tags, "UniformOutput", false));
|
||||
end
|
||||
|
||||
function tf = isClassFile(filePath)
|
||||
source = fileread(filePath);
|
||||
tf = contains(source, "classdef");
|
||||
end
|
||||
|
||||
function files = collectMFiles(rootFolder)
|
||||
pathString = genpath(rootFolder);
|
||||
folders = regexp(pathString, pathsep, "split");
|
||||
folders = folders(~cellfun("isempty", folders));
|
||||
|
||||
files = dir(fullfile(rootFolder, "*.m"));
|
||||
files = files([]); %#ok<NASGU>
|
||||
files = dir(fullfile(rootFolder, "*.m"));
|
||||
files = files([]);
|
||||
|
||||
for idx = 1:numel(folders)
|
||||
currentFiles = dir(fullfile(folders{idx}, "*.m"));
|
||||
if isempty(currentFiles)
|
||||
continue
|
||||
end
|
||||
currentFiles = currentFiles(~[currentFiles.isdir]);
|
||||
files = [files; currentFiles]; %#ok<AGROW>
|
||||
end
|
||||
end
|
||||
|
||||
function [statusText, summaryText] = summarizeResults(results)
|
||||
passedCount = nnz([results.Passed]);
|
||||
failedCount = nnz([results.Failed]);
|
||||
incompleteCount = nnz([results.Incomplete]);
|
||||
|
||||
if failedCount > 0
|
||||
statusText = "Failed";
|
||||
elseif incompleteCount > 0
|
||||
statusText = "Incomplete";
|
||||
else
|
||||
statusText = "Passed";
|
||||
end
|
||||
|
||||
summaryText = sprintf('%d/%d/%d', passedCount, failedCount, incompleteCount);
|
||||
end
|
||||
|
||||
function out = stringOrEmpty(structValue, fieldName)
|
||||
if isfield(structValue, fieldName)
|
||||
out = string(structValue.(fieldName));
|
||||
else
|
||||
out = "";
|
||||
end
|
||||
end
|
||||
|
||||
function rows = inventoryRowStates(inventory)
|
||||
rows = repmat(struct( ...
|
||||
'test_file', '', ...
|
||||
'target', '', ...
|
||||
'scope', '', ...
|
||||
'last_run', '', ...
|
||||
'result_summary', '', ...
|
||||
'last_run_date', '', ...
|
||||
'last_run_commit', ''), height(inventory), 1);
|
||||
|
||||
for idx = 1:height(inventory)
|
||||
rows(idx).test_file = char(inventory.TestFile(idx));
|
||||
rows(idx).target = char(inventory.Target(idx));
|
||||
rows(idx).scope = char(inventory.Scope(idx));
|
||||
rows(idx).last_run = char(inventory.LastRun(idx));
|
||||
rows(idx).result_summary = char(inventory.ResultSummary(idx));
|
||||
rows(idx).last_run_date = char(inventory.LastRunDate(idx));
|
||||
rows(idx).last_run_commit = char(inventory.LastRunCommit(idx));
|
||||
end
|
||||
end
|
||||
|
||||
function entries = resultEntries(results)
|
||||
entries = repmat(struct( ...
|
||||
'name', '', ...
|
||||
'passed', false, ...
|
||||
'failed', false, ...
|
||||
'incomplete', false, ...
|
||||
'duration_seconds', 0), numel(results), 1);
|
||||
|
||||
for idx = 1:numel(results)
|
||||
entries(idx).name = char(string(results(idx).Name));
|
||||
entries(idx).passed = results(idx).Passed;
|
||||
entries(idx).failed = results(idx).Failed;
|
||||
entries(idx).incomplete = results(idx).Incomplete;
|
||||
entries(idx).duration_seconds = durationToSeconds(results(idx).Duration);
|
||||
end
|
||||
end
|
||||
|
||||
function gitInfo = collectGitMetadata(repoRoot)
|
||||
gitInfo = struct();
|
||||
gitInfo.commit = strtrim(runCommand(sprintf('git -C "%s" rev-parse HEAD', repoRoot)));
|
||||
gitInfo.short_commit = strtrim(runCommand(sprintf('git -C "%s" rev-parse --short HEAD', repoRoot)));
|
||||
gitInfo.branch = strtrim(runCommand(sprintf('git -C "%s" rev-parse --abbrev-ref HEAD', repoRoot)));
|
||||
statusOutput = runCommand(sprintf('git -C "%s" status --porcelain', repoRoot));
|
||||
gitInfo.dirty = ~isempty(strtrim(statusOutput));
|
||||
end
|
||||
|
||||
function output = runCommand(commandText)
|
||||
[status, output] = system(commandText);
|
||||
if status ~= 0
|
||||
output = '';
|
||||
end
|
||||
end
|
||||
|
||||
function writeTextFile(filePath, content)
|
||||
fid = fopen(filePath, 'w');
|
||||
assert(fid ~= -1, 'Could not write file: %s', filePath);
|
||||
cleaner = onCleanup(@() fclose(fid)); %#ok<NASGU>
|
||||
fprintf(fid, '%s', content);
|
||||
end
|
||||
|
||||
function label = sanitizeFileLabel(labelText)
|
||||
label = regexprep(char(string(labelText)), '[^A-Za-z0-9]+', '_');
|
||||
label = regexprep(label, '^_+|_+$', '');
|
||||
if isempty(label)
|
||||
label = 'run';
|
||||
end
|
||||
end
|
||||
|
||||
function value = durationToSeconds(durationValue)
|
||||
try
|
||||
value = seconds(durationValue);
|
||||
if isa(value, 'duration')
|
||||
value = str2double(char(value));
|
||||
end
|
||||
catch
|
||||
try
|
||||
value = posixtime(datetime(durationValue) - datetime(0, 0, 0));
|
||||
catch
|
||||
durationText = char(string(durationValue));
|
||||
tokens = regexp(durationText, '(\d+):(\d+):([\d\.]+)', 'tokens', 'once');
|
||||
if isempty(tokens)
|
||||
value = NaN;
|
||||
else
|
||||
value = str2double(tokens{1}) * 3600 + ...
|
||||
str2double(tokens{2}) * 60 + ...
|
||||
str2double(tokens{3});
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ~isnumeric(value) || ~isscalar(value)
|
||||
value = NaN;
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,79 @@
|
||||
classdef Electricalsignal_test < matlab.unittest.TestCase
|
||||
% Test class for Electricalsignal
|
||||
classdef Electricalsignal_test < IMDDTestCase
|
||||
methods (Test, TestTags = {'unit', 'fast', 'signals', 'electricalsignal'})
|
||||
function constructorStoresSignalFsAndLogbook(testCase)
|
||||
seed = Signal(1, "fs", 2);
|
||||
seed = seed.logbookentry("electrical seed", seed);
|
||||
|
||||
methods (Test)
|
||||
function testExample(testCase)
|
||||
% Example test case for Electricalsignal
|
||||
% Add your test code here
|
||||
testCase.verifyTrue(true);
|
||||
elec = Electricalsignal([1; -2; 3], "fs", 80e9, "logbook", seed.logbook);
|
||||
|
||||
testCase.verifyClass(elec, 'Electricalsignal');
|
||||
testCase.verifyEqual(elec.signal, [1; -2; 3]);
|
||||
testCase.verifyEqual(elec.fs, 80e9);
|
||||
testCase.verifyEqual(elec.logbook, seed.logbook);
|
||||
end
|
||||
|
||||
function inheritedArithmeticKeepsElectricalsignalTypeAndMetadata(testCase)
|
||||
seed = Signal(0, "fs", 1);
|
||||
seed = seed.logbookentry("lhs metadata", seed);
|
||||
lhs = Electricalsignal([1; 2; 3], "fs", 64e9, "logbook", seed.logbook);
|
||||
rhs = Electricalsignal([4; 5; 6], "fs", 32e9, "logbook", table());
|
||||
|
||||
sumSig = lhs + rhs;
|
||||
|
||||
testCase.verifyClass(sumSig, 'Electricalsignal');
|
||||
testCase.verifyEqual(sumSig.signal, [5; 7; 9]);
|
||||
testCase.verifyEqual(sumSig.fs, lhs.fs);
|
||||
testCase.verifyEqual(sumSig.logbook, lhs.logbook);
|
||||
end
|
||||
|
||||
function informationCastPreservesSignalFsAndLogbook(testCase)
|
||||
seed = Signal(3, "fs", 4);
|
||||
seed = seed.logbookentry("electrical cast", seed);
|
||||
elec = Electricalsignal([2; 0; -2], "fs", 96e9, "logbook", seed.logbook);
|
||||
|
||||
info = elec.Informationsignal("fs", elec.fs, "logbook", elec.logbook);
|
||||
|
||||
testCase.verifyClass(info, 'Informationsignal');
|
||||
testCase.verifyEqual(info.signal, elec.signal);
|
||||
testCase.verifyEqual(info.fs, elec.fs);
|
||||
testCase.verifyEqual(info.logbook, elec.logbook);
|
||||
end
|
||||
|
||||
function power50MatchesInheritedElectricalPowerDefinition(testCase)
|
||||
elec = Electricalsignal([1; -1; 1; -1], "fs", 1, "logbook", table());
|
||||
|
||||
expectedPowerDbm = 10 * log10(mean(abs(elec.signal).^2) / 50) + 30;
|
||||
|
||||
testCase.verifyEqual(elec.power50(), expectedPowerDbm, "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(elec.power50(), elec.power(), "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function csprUsesCarrierToTotalPowerRatioInDb(testCase)
|
||||
elec = Electricalsignal([1; 3], "fs", 1, "logbook", table());
|
||||
|
||||
carrierPowerDbm = pow2db(abs(mean(elec.signal)).^2) + 30;
|
||||
expectedCsprDb = carrierPowerDbm - elec.power();
|
||||
|
||||
testCase.verifyEqual(elec.cspr(), expectedCsprDb, "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function normalizeMilliwattSetsPowerToZeroDbmInto50Ohm(testCase)
|
||||
elec = Electricalsignal([2; -2; 2; -2], "fs", 1, "logbook", table());
|
||||
|
||||
elec = elec.normalize("mode", normalization_mode.milliwatt);
|
||||
|
||||
testCase.verifyEqual(elec.power50(), 0, "AbsTol", 1e-12);
|
||||
testCase.verifyClass(elec, 'Electricalsignal');
|
||||
end
|
||||
|
||||
function setPowerSupportsMilliwattAndDbwTargets(testCase)
|
||||
elec = Electricalsignal([1; -1; 1; -1], "fs", 1, "logbook", table());
|
||||
|
||||
elecMilliwatt = elec.setPower(2, power_notation.mW);
|
||||
elecDbw = elec.setPower(-20, power_notation.dBW);
|
||||
|
||||
testCase.verifyEqual(elecMilliwatt.power50(), 10 * log10(2), "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(elecDbw.power50(), 10, "AbsTol", 1e-12);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,82 @@
|
||||
classdef Informationsignal_test < matlab.unittest.TestCase
|
||||
% Test class for Informationsignal
|
||||
classdef Informationsignal_test < IMDDTestCase
|
||||
methods (Test, TestTags = {'unit', 'fast', 'signals', 'informationsignal'})
|
||||
function constructorWithoutOptionsUsesSignalDefaults(testCase)
|
||||
info = Informationsignal([0; 1; 1; 0]);
|
||||
|
||||
methods (Test)
|
||||
function testExample(testCase)
|
||||
% Example test case for Informationsignal
|
||||
% Add your test code here
|
||||
testCase.verifyTrue(true);
|
||||
testCase.verifyClass(info, 'Informationsignal');
|
||||
testCase.verifyEqual(info.signal, [0; 1; 1; 0]);
|
||||
testCase.verifyEmpty(info.fs);
|
||||
testCase.verifyEmpty(info.logbook);
|
||||
end
|
||||
|
||||
function constructorWithOptionsCopiesFsAndLogbook(testCase)
|
||||
seed = Signal(5, "fs", 2);
|
||||
seed = seed.logbookentry("seed entry", seed);
|
||||
|
||||
info = Informationsignal([1; 0; 1], "fs", 32e9, "logbook", seed.logbook);
|
||||
|
||||
testCase.verifyEqual(info.signal, [1; 0; 1]);
|
||||
testCase.verifyEqual(info.fs, 32e9);
|
||||
testCase.verifyEqual(info.logbook, seed.logbook);
|
||||
end
|
||||
|
||||
function inheritedArithmeticKeepsInformationsignalTypeAndMetadata(testCase)
|
||||
lhs = Informationsignal([1; 2; 3], "fs", 64e9, "logbook", table());
|
||||
rhs = Informationsignal([4; 5; 6], "fs", 1, "logbook", table());
|
||||
|
||||
sumSig = lhs + rhs;
|
||||
|
||||
testCase.verifyClass(sumSig, 'Informationsignal');
|
||||
testCase.verifyEqual(sumSig.signal, [5; 7; 9]);
|
||||
testCase.verifyEqual(sumSig.fs, lhs.fs);
|
||||
testCase.verifyEqual(sumSig.logbook, lhs.logbook);
|
||||
end
|
||||
|
||||
function electricalsignalCastUsesRequestedFsAndLogbook(testCase)
|
||||
seed = Signal(1, "fs", 4);
|
||||
seed = seed.logbookentry("cast logbook", seed);
|
||||
info = Informationsignal([0; 1; 0; 1]);
|
||||
|
||||
elec = info.Electricalsignal("fs", 128e9, "logbook", seed.logbook);
|
||||
|
||||
testCase.verifyClass(elec, 'Electricalsignal');
|
||||
testCase.verifyEqual(elec.signal, info.signal);
|
||||
testCase.verifyEqual(elec.fs, 128e9);
|
||||
testCase.verifyEqual(elec.logbook, seed.logbook);
|
||||
end
|
||||
|
||||
function opticalsignalCastFailsForInformationsignal(testCase)
|
||||
info = Informationsignal([0; 1; 0; 1]);
|
||||
|
||||
didError = false;
|
||||
try
|
||||
info.Opticalsignal( ...
|
||||
"fs", 64e9, ...
|
||||
"logbook", info.logbook, ...
|
||||
"lambda", 1310e-9, ...
|
||||
"nase", 0, ...
|
||||
"polrot", 0);
|
||||
catch ME
|
||||
didError = true;
|
||||
testCase.verifySubstring(ME.message, ...
|
||||
"Cannot convert from information- to opticalsignal.");
|
||||
end
|
||||
|
||||
testCase.verifyTrue(didError, ...
|
||||
"Informationsignal -> Opticalsignal should reject direct conversion.");
|
||||
end
|
||||
|
||||
function electricalToInformationCastPreservesSignalAndMetadata(testCase)
|
||||
seed = Signal(2, "fs", 8);
|
||||
seed = seed.logbookentry("from electrical", seed);
|
||||
elec = Electricalsignal([3; 1; 4], "fs", 96e9, "logbook", seed.logbook);
|
||||
|
||||
info = elec.Informationsignal("fs", elec.fs, "logbook", elec.logbook);
|
||||
|
||||
testCase.verifyClass(info, 'Informationsignal');
|
||||
testCase.verifyEqual(info.signal, elec.signal);
|
||||
testCase.verifyEqual(info.fs, elec.fs);
|
||||
testCase.verifyEqual(info.logbook, elec.logbook);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,64 @@
|
||||
classdef Opticalsignal_test < matlab.unittest.TestCase
|
||||
% Test class for Opticalsignal
|
||||
classdef Opticalsignal_test < IMDDTestCase
|
||||
methods (Test, TestTags = {'unit', 'fast', 'signals', 'optical'})
|
||||
function constructorWiresOpticalMetadata(testCase)
|
||||
opt = makeOpticalsignal([1; 2; 3], 12.5e9, 1310e-9, 0.25, 2);
|
||||
|
||||
methods (Test)
|
||||
function testExample(testCase)
|
||||
% Example test case for Opticalsignal
|
||||
% Add your test code here
|
||||
testCase.verifyTrue(true);
|
||||
testCase.verifyClass(opt, 'Opticalsignal');
|
||||
testCase.verifyEqual(opt.signal, [1; 2; 3]);
|
||||
testCase.verifyEqual(opt.fs, 12.5e9);
|
||||
testCase.verifyEqual(opt.lambda, 1310e-9);
|
||||
testCase.verifyEqual(opt.nase, 0.25);
|
||||
testCase.verifyEqual(opt.polrot, 2);
|
||||
testCase.verifyEqual(height(opt.logbook), 0);
|
||||
end
|
||||
|
||||
function delayBySamplesAndMetersGiveTheSameResult(testCase)
|
||||
fs = physconst('LightSpeed') / 1.4677;
|
||||
opt = makeOpticalsignal([1; 0; -1; 2], fs, 1550e-9, 0, 0);
|
||||
|
||||
[bySamples, delayNs] = opt.delay("delay_samples", 2);
|
||||
[byMeters, delayNm] = opt.delay("delay_meter", 2);
|
||||
|
||||
testCase.verifyEqual(delayNs, 2);
|
||||
testCase.verifyEqual(delayNm, 2);
|
||||
testCase.verifyEqual(bySamples.signal, byMeters.signal);
|
||||
testCase.verifyEqual(bySamples.fs, opt.fs);
|
||||
testCase.verifyClass(bySamples, 'Opticalsignal');
|
||||
testCase.verifyClass(byMeters, 'Opticalsignal');
|
||||
end
|
||||
|
||||
function csprMatchesKnownCarrierToSidebandRatio(testCase)
|
||||
opt = makeOpticalsignal([3; 1; 3; 1], 10, 1550e-9, 0, 0);
|
||||
|
||||
actual = opt.cspr();
|
||||
expected = 10 * log10(abs(mean(opt.signal)).^2 / mean(abs(opt.signal - mean(opt.signal)).^2));
|
||||
|
||||
testCase.verifyEqual(actual, expected, "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function inheritedPlusAndLengthPreserveOpticalSemantics(testCase)
|
||||
left = makeOpticalsignal([1; 2; 3], 5, 1310e-9, 0.1, 1);
|
||||
right = makeOpticalsignal([4; 5; 6], 5, 1310e-9, 0.2, 1);
|
||||
|
||||
sumSig = left + right;
|
||||
|
||||
testCase.verifyClass(sumSig, 'Opticalsignal');
|
||||
testCase.verifyEqual(sumSig.signal, [5; 7; 9]);
|
||||
testCase.verifyEqual(sumSig.fs, left.fs);
|
||||
testCase.verifyEqual(sumSig.lambda, left.lambda);
|
||||
testCase.verifyEqual(sumSig.nase, left.nase);
|
||||
testCase.verifyEqual(sumSig.polrot, left.polrot);
|
||||
testCase.verifyEqual(sumSig.length(), 3);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function opt = makeOpticalsignal(signal, fs, lambda, nase, polrot)
|
||||
base = Signal(signal);
|
||||
opt = Opticalsignal(signal, ...
|
||||
"fs", fs, ...
|
||||
"logbook", base.logbook, ...
|
||||
"lambda", lambda, ...
|
||||
"nase", nase, ...
|
||||
"polrot", polrot);
|
||||
end
|
||||
|
||||
@@ -1,11 +1,164 @@
|
||||
classdef Signal_test < matlab.unittest.TestCase
|
||||
% Test class for Signal
|
||||
classdef Signal_test < IMDDTestCase
|
||||
methods (Test, TestTags = {'unit', 'fast', 'signals'})
|
||||
function constructorStoresSignalAndSamplingRate(testCase)
|
||||
sig = Signal([1; 2; 3], "fs", 64e9);
|
||||
|
||||
methods (Test)
|
||||
function testExample(testCase)
|
||||
% Example test case for Signal
|
||||
% Add your test code here
|
||||
testCase.verifyTrue(true);
|
||||
testCase.verifyEqual(sig.signal, [1; 2; 3]);
|
||||
testCase.verifyEqual(sig.fs, 64e9);
|
||||
testCase.verifyEmpty(sig.logbook);
|
||||
end
|
||||
|
||||
function arithmeticOperatorsSupportSignalOperands(testCase)
|
||||
x = Signal([1; 2; 3]);
|
||||
y = Signal([4; 5; 6]);
|
||||
|
||||
sumSig = x + y;
|
||||
diffSig = y - x;
|
||||
prodSig = x .* y;
|
||||
|
||||
testCase.verifyEqual(sumSig.signal, [5; 7; 9]);
|
||||
testCase.verifyEqual(diffSig.signal, [3; 3; 3]);
|
||||
testCase.verifyEqual(prodSig.signal, [4; 10; 18]);
|
||||
end
|
||||
|
||||
function arithmeticOperatorsSupportNumericOperands(testCase)
|
||||
x = Signal([1; 2; 3]);
|
||||
|
||||
sumSig = x + 2;
|
||||
diffSig = x - 1;
|
||||
prodSig = x .* 3;
|
||||
|
||||
testCase.verifyEqual(sumSig.signal, [3; 4; 5]);
|
||||
testCase.verifyEqual(diffSig.signal, [0; 1; 2]);
|
||||
testCase.verifyEqual(prodSig.signal, [3; 6; 9]);
|
||||
end
|
||||
|
||||
function lengthReturnsSignalLength(testCase)
|
||||
sig = Signal(zeros(11, 1));
|
||||
|
||||
testCase.verifyEqual(sig.length(), 11);
|
||||
end
|
||||
|
||||
function normalizeRmsScalesSignalToUnitPower(testCase)
|
||||
sig = Signal([1; -1; 1; -1]);
|
||||
|
||||
sig = sig.normalize("mode", normalization_mode.rms);
|
||||
|
||||
testCase.verifyEqual(mean(abs(sig.signal).^2), 1, "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function normalizeOneOneMapsSignalToMinusOneToOne(testCase)
|
||||
sig = Signal([2; 4; 6]);
|
||||
|
||||
sig = sig.normalize("mode", normalization_mode.oneone);
|
||||
|
||||
testCase.verifyEqual(sig.signal, [-1; 0; 1], "AbsTol", 1e-12);
|
||||
end
|
||||
|
||||
function logbookEntryAppendsRowAndDescription(testCase)
|
||||
sig = Signal([1; 0; -1], "fs", 1);
|
||||
|
||||
sig = sig.logbookentry("unit-test entry", sig);
|
||||
|
||||
testCase.verifyEqual(height(sig.logbook), 1);
|
||||
testCase.verifyEqual(string(sig.logbook.SignalType(1)), "Signal");
|
||||
testCase.verifyEqual(string(sig.logbook.Description(1)), "unit-test entry");
|
||||
testCase.verifyEqual(string(sig.logbook.ModifierName(1)), "Signal");
|
||||
end
|
||||
|
||||
function resampleWithIdenticalRatesKeepsSignalAndFs(testCase)
|
||||
original = [1; 2; 3; 4];
|
||||
sig = Signal(original, "fs", 8);
|
||||
|
||||
sig = sig.resample("fs_in", 8, "fs_out", 8);
|
||||
|
||||
testCase.verifyEqual(sig.signal, original);
|
||||
testCase.verifyEqual(sig.fs, 8);
|
||||
testCase.verifyEqual(height(sig.logbook), 1);
|
||||
end
|
||||
|
||||
function resampleToNewRateUpdatesSamplingRate(testCase)
|
||||
sig = Signal((1:8).', "fs", 8);
|
||||
|
||||
sig = sig.resample("fs_in", 8, "fs_out", 16);
|
||||
|
||||
testCase.verifyEqual(sig.fs, 16);
|
||||
testCase.verifyNotEmpty(sig.signal);
|
||||
testCase.verifyEqual(height(sig.logbook), 1);
|
||||
end
|
||||
|
||||
function spectrumCanUseSamplingRateNormalizedFrequencyAxis(testCase)
|
||||
fig = figure("Visible", "off");
|
||||
cleanup = onCleanup(@() close(fig));
|
||||
fignum = fig.Number;
|
||||
|
||||
sig = Signal(randn(4096, 1), "fs", 8e9);
|
||||
|
||||
sig.spectrum( ...
|
||||
"fignum", fignum, ...
|
||||
"normalizeToSamplingRate", 1, ...
|
||||
"normalizeTo0dB", 1, ...
|
||||
"fft_length", 512);
|
||||
|
||||
ax = findobj(fig, "Type", "Axes");
|
||||
lineObj = findobj(ax, "Type", "Line");
|
||||
|
||||
testCase.verifyEqual(ax.XLim, [-0.5 0.5], "AbsTol", 1e-12);
|
||||
testCase.verifyEqual(string(ax.XLabel.String), "Normalized Frequency f/fs");
|
||||
testCase.verifyGreaterThanOrEqual(min(lineObj.XData), -0.5);
|
||||
testCase.verifyLessThanOrEqual(max(lineObj.XData), 0.5);
|
||||
testCase.verifyLessThanOrEqual(abs(min(lineObj.XData) + 0.5), 1/512);
|
||||
testCase.verifyLessThanOrEqual(abs(max(lineObj.XData) - 0.5), 1/512);
|
||||
end
|
||||
|
||||
function spectrumCanShowOneSidedSamplingRateNormalizedAxis(testCase)
|
||||
fig = figure("Visible", "off");
|
||||
cleanup = onCleanup(@() close(fig));
|
||||
fignum = fig.Number;
|
||||
|
||||
sig = Signal(randn(4096, 1), "fs", 8e9);
|
||||
|
||||
sig.spectrum( ...
|
||||
"fignum", fignum, ...
|
||||
"normalizeToSamplingRate", 1, ...
|
||||
"show_onesided", true, ...
|
||||
"normalizeTo0dB", 1, ...
|
||||
"fft_length", 512);
|
||||
|
||||
ax = findobj(fig, "Type", "Axes");
|
||||
lineObj = findobj(ax, "Type", "Line");
|
||||
|
||||
testCase.verifyEqual(ax.XLim, [0 0.5], "AbsTol", 1e-12);
|
||||
testCase.verifyGreaterThanOrEqual(min(lineObj.XData), 0);
|
||||
testCase.verifyLessThanOrEqual(max(lineObj.XData), 0.5);
|
||||
testCase.verifyLessThanOrEqual(abs(max(lineObj.XData) - 0.5), 1/512);
|
||||
end
|
||||
|
||||
function castingRoundTripPreservesSignalThroughAllDomains(testCase)
|
||||
infoIn = Informationsignal([0; 1; 1; 0]);
|
||||
|
||||
elecFromInfo = infoIn.Electricalsignal("fs", 64e9, "logbook", infoIn.logbook);
|
||||
optFromElec = elecFromInfo.Opticalsignal( ...
|
||||
"fs", elecFromInfo.fs, ...
|
||||
"logbook", elecFromInfo.logbook, ...
|
||||
"lambda", 1310e-9, ...
|
||||
"nase", 0, ...
|
||||
"polrot", 0);
|
||||
elecFromOpt = optFromElec.Electricalsignal("fs", optFromElec.fs, "logbook", optFromElec.logbook);
|
||||
infoOut = elecFromOpt.Informationsignal("fs", elecFromOpt.fs, "logbook", elecFromOpt.logbook);
|
||||
|
||||
testCase.verifyClass(elecFromInfo, 'Electricalsignal');
|
||||
testCase.verifyClass(optFromElec, 'Opticalsignal');
|
||||
testCase.verifyClass(elecFromOpt, 'Electricalsignal');
|
||||
testCase.verifyClass(infoOut, 'Informationsignal');
|
||||
|
||||
testCase.verifyEqual(infoOut.signal, infoIn.signal);
|
||||
testCase.verifyEqual(elecFromInfo.fs, 64e9);
|
||||
testCase.verifyEqual(optFromElec.fs, 64e9);
|
||||
testCase.verifyEqual(elecFromOpt.fs, 64e9);
|
||||
testCase.verifyEqual(infoOut.fs, 64e9);
|
||||
testCase.verifyEqual(optFromElec.lambda, 1310e-9);
|
||||
testCase.verifyEqual(optFromElec.nase, 0);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
classdef AWG_test < matlab.unittest.TestCase
|
||||
% Test class for AWG
|
||||
classdef AWG_test < IMDDTestCase
|
||||
% Auto-generated placeholder for AWG.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/01_transmit/AWG.m
|
||||
|
||||
methods (Test)
|
||||
function testExample(testCase)
|
||||
% Example test case for AWG
|
||||
% Add your test code here
|
||||
testCase.verifyTrue(true);
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for AWG are not implemented yet.");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
classdef ChannelFreqResp_test < matlab.unittest.TestCase
|
||||
% Test class for ChannelFreqResp
|
||||
classdef ChannelFreqResp_test < IMDDTestCase
|
||||
% Auto-generated placeholder for ChannelFreqResp.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/01_transmit/ChannelFreqResp.m
|
||||
|
||||
methods (Test)
|
||||
function testExample(testCase)
|
||||
% Example test case for ChannelFreqResp
|
||||
% Add your test code here
|
||||
testCase.verifyTrue(true);
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for ChannelFreqResp are not implemented yet.");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
classdef M8196A_test < matlab.unittest.TestCase
|
||||
% Test class for M8196A
|
||||
classdef M8196A_test < IMDDTestCase
|
||||
% Auto-generated placeholder for M8196A.
|
||||
% Target: C:/Users/Silas/Documents/MATLAB/imdd_simulation/Classes/01_transmit/M8196A.m
|
||||
|
||||
methods (Test)
|
||||
function testExample(testCase)
|
||||
% Example test case for M8196A
|
||||
% Add your test code here
|
||||
testCase.verifyTrue(true);
|
||||
methods (Test, TestTags = {'placeholder', 'todo'})
|
||||
function testNotImplemented(testCase)
|
||||
testCase.assumeFail("Tests for M8196A are not implemented yet.");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user