Füge PRMS-Fehlerbehandlung hinzu und implementiere Partialresponse-Kodierung

This commit is contained in:
Silas Oettinghaus
2026-04-23 21:56:47 +02:00
parent 456fd02186
commit 952178341e
3 changed files with 244 additions and 12 deletions

View File

@@ -282,18 +282,29 @@ classdef Signalgenerator
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)
if ~isempty(obj.M)
bits_per_symbol = obj.prms_bits_per_symbol();
prms_order = max(1, floor(obj.order / bits_per_symbol));
end
function bits_per_symbol = prms_bits_per_symbol(obj)
if (~isempty(obj.M) && obj.M == 6) || (isempty(obj.M) && obj.dimension == 5)
% PAM-6 mapping in this repo consumes 5 bits for 2 symbols.
bits_per_symbol = 2.5;
elseif ~isempty(obj.M)
bits_per_symbol = log2(obj.M);
elseif obj.dimension == 5
bits_per_symbol = log2(6);
else
bits_per_symbol = obj.dimension;
end
prms_order = max(1, floor(obj.order / bits_per_symbol));
end
function dimension = mapper_bit_dimension(obj)

View File

@@ -0,0 +1,199 @@
classdef Partialresponse
%PARTIALRESPONSE Generalized symbol-domain partial-response coding.
properties
order = 1
end
methods
function obj = Partialresponse(options)
arguments
options.order (1,1) double = 1
end
obj.order = options.order;
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(~, data, M)
if isempty(M)
M = numel(unique(round(data, 12)));
end
end
function M = resolveEncodedM(obj, data, M)
if isempty(M)
I = numel(unique(round(data, 12)));
if obj.order == 1
M = (I + 1) / 2;
else
error('Partialresponse:NeedM', ...
'Specify M when decoding higher-order partial-response signals.');
end
end
end
function a = amplitudeToIndex(obj, data, M)
levels = obj.pamLevels(M);
scaling = obj.pamScaling(M);
amp = round(data(:) * scaling);
a = (amp + (M - 1)) / 2;
end
function data = indexToPamAmplitude(obj, a, M)
scaling = obj.pamScaling(M);
data = (2*a(:) - (M - 1)) / scaling;
end
function levels = pamLevels(~, M)
levels = -(M-1):2:(M-1);
end
function scaling = pamScaling(~, M)
try
mapper = PAMmapper(M, 0);
scaling = mapper.scaling;
catch ME
error('Partialresponse:UnsupportedM', ...
'Unsupported PAM order for Partialresponse: %s', ME.message);
end
end
function scaling = encodedScaling(~, M, order)
h = arrayfun(@(k) nchoosek(order, k), 0:order);
center = ((M - 1) * sum(h)) / 2;
y = zeros(M^(order+1), 1);
for n = 0:(numel(y)-1)
state = zeros(1, order+1);
tmp = n;
for k = 1:numel(state)
state(k) = mod(tmp, M);
tmp = floor(tmp/M);
end
y(n + 1) = sum(h .* state) - center;
end
scaling = sqrt(mean(y.^2));
end
end
end

View File

@@ -1,9 +1,8 @@
for M = [2 4 8]
bits = Signalgenerator("form", signalform.prms,"M", M,"order", 15).process();
symbols = PAMmapper(M,0).map(bits);
mapper = PAMmapper(M,0);
symbols = mapper.map(bits);
symbols_pre = Duobinary().precode(symbols);
@@ -11,8 +10,31 @@ for M = [2 4 8]
symbols_rx = Duobinary().decode(symbols_db);
% Vergleichen von b(n) und d_dec(n)
bits_rx = PAMmapper(M,0).demap(symbols_rx);
[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",10,"skip_end",10,"returnErrorLocation",1);
disp(['BER: ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]);
end
[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
disp(['Class BER: ',sprintf('%.1E',ber),' - PAM-',num2str(M)]);
symbols_pre_pr = Partialresponse("order",1).precode(symbols,"M",M);
symbols_db_pr = Partialresponse("order",1).encode(symbols_pre_pr,"M",M);
symbols_rx_pr = Partialresponse("order",1).decode(symbols_db_pr,"M",M);
bits_rx_pr = mapper.demap(symbols_rx_pr);
[~,~,ber_pr,~] = calc_ber(bits.signal,bits_rx_pr.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
disp(['Partialresponse BER: ',sprintf('%.1E',ber_pr),' - PAM-',num2str(M)]);
pre_match = isequaln(symbols_pre.signal, symbols_pre_pr.signal);
enc_match = isequaln(symbols_db.signal, symbols_db_pr.signal);
dec_match = isequaln(symbols_rx.signal, symbols_rx_pr.signal);
pre_maxdiff = max(abs(symbols_pre.signal - symbols_pre_pr.signal));
enc_maxdiff = max(abs(symbols_db.signal - symbols_db_pr.signal));
dec_maxdiff = max(abs(symbols_rx.signal - symbols_rx_pr.signal));
disp(['Precode match: ',num2str(pre_match), ...
' | max abs diff: ',sprintf('%.3g',pre_maxdiff)]);
disp(['Encode match: ',num2str(enc_match), ...
' | max abs diff: ',sprintf('%.3g',enc_maxdiff)]);
disp(['Decode match: ',num2str(dec_match), ...
' | max abs diff: ',sprintf('%.3g',dec_maxdiff)]);
disp('---');
end