|
|
|
|
@@ -0,0 +1,835 @@
|
|
|
|
|
% Generalized duobinary partial-response example for PAM-M.
|
|
|
|
|
% Symbol-domain implementation for F(D) = (1 + D)^n, M in {2, 4, 8}.
|
|
|
|
|
|
|
|
|
|
clear; clc;
|
|
|
|
|
|
|
|
|
|
repoRoot = fileparts(fileparts(fileparts(mfilename("fullpath"))));
|
|
|
|
|
addpath(genpath(repoRoot));
|
|
|
|
|
|
|
|
|
|
rng(7);
|
|
|
|
|
M_list = [2,4,8];
|
|
|
|
|
pr_orders = [1,2,3];
|
|
|
|
|
Nsym = 100000;
|
|
|
|
|
userOrder = 17;
|
|
|
|
|
emulate_precoder = 0;
|
|
|
|
|
burst_lengths = 1:50;
|
|
|
|
|
injected_error_count = 100;
|
|
|
|
|
plot_burst_analysis = 1;
|
|
|
|
|
burst_randkey = 140;
|
|
|
|
|
burst_min_gap = 100;
|
|
|
|
|
|
|
|
|
|
fprintf('Generalized duobinary PR burst-resilience demo\n');
|
|
|
|
|
fprintf('emulate_precoder = %d\n', emulate_precoder);
|
|
|
|
|
fprintf('Injected precoder-symbol errors per burst length = %d\n', injected_error_count);
|
|
|
|
|
fprintf('Burst randkey = %d, minimum gap = %d symbols\n', burst_randkey, burst_min_gap);
|
|
|
|
|
|
|
|
|
|
burst_results = struct();
|
|
|
|
|
burst_pattern = buildCombinedBurstPattern( ...
|
|
|
|
|
Nsym, burst_lengths, injected_error_count, ...
|
|
|
|
|
"randkey", burst_randkey, ...
|
|
|
|
|
"min_gap", burst_min_gap);
|
|
|
|
|
|
|
|
|
|
for pr_order = pr_orders
|
|
|
|
|
fprintf('\nF(D) = (1 + D)^%d\n', pr_order);
|
|
|
|
|
|
|
|
|
|
for M = M_list
|
|
|
|
|
[txBits, txSymbols, mapper, d_tx] = generatePamSymbols(M, Nsym, userOrder);
|
|
|
|
|
|
|
|
|
|
if emulate_precoder
|
|
|
|
|
[~, y_amp, pr_alphabet, y_idx, a_ref] = emulatePrecoder(d_tx, M, [], pr_order);
|
|
|
|
|
refBits = demapSymbolIndices(a_ref, txSymbols, mapper, M);
|
|
|
|
|
else
|
|
|
|
|
a_ref = d_tx;
|
|
|
|
|
refBits = txBits;
|
|
|
|
|
u = precode(a_ref, M, [], pr_order);
|
|
|
|
|
[y_amp, pr_alphabet, y_idx] = encode(u, M, [], pr_order);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
a_hat0 = memorylessdecode(y_amp, M, [], pr_order);
|
|
|
|
|
rxBits0 = demapSymbolIndices(a_hat0, txSymbols, mapper, M);
|
|
|
|
|
[~, errors0, ber0] = calc_ber(rxBits0.signal, refBits.signal);
|
|
|
|
|
|
|
|
|
|
assert(isequal(a_hat0, a_ref), ...
|
|
|
|
|
'Noiseless reconstruction failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
assert(errors0 == 0 && ber0 == 0, ...
|
|
|
|
|
'Noiseless BER check failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
assert(numel(pr_alphabet) == (M-1)*2^pr_order + 1, ...
|
|
|
|
|
'Wrong PR alphabet size for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
|
|
|
|
|
results = simulatePrecoderErrorBursts( ...
|
|
|
|
|
a_ref, M, pr_order, txSymbols, mapper, refBits, ...
|
|
|
|
|
burst_pattern);
|
|
|
|
|
|
|
|
|
|
fprintf('M=%d before encode/decode: BER = %.3g (%d/%d bits), symbol errors = %d, bursts = %d, max burst = %d\n', ...
|
|
|
|
|
M, results.input_ber, results.input_bit_errors, results.input_checked_bits, ...
|
|
|
|
|
results.input_symbol_errors, results.input_burst_total, results.input_max_burst_length);
|
|
|
|
|
fprintf('M=%d after encode/decode: BER = %.3g (%d/%d bits), symbol errors = %d, bursts = %d, max burst = %d\n', ...
|
|
|
|
|
M, results.ber, results.bit_errors, results.checked_bits, ...
|
|
|
|
|
results.symbol_errors, results.output_burst_total, results.output_max_burst_length);
|
|
|
|
|
printBurstCountReport(results, M, pr_order);
|
|
|
|
|
|
|
|
|
|
burst_results(pr_order, M).results = results;
|
|
|
|
|
|
|
|
|
|
if plot_burst_analysis
|
|
|
|
|
plotPrecoderBurstAnalysis(results, pr_order, M);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if plot_burst_analysis
|
|
|
|
|
plotBurstSummary(burst_results, pr_orders, M_list);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
% runDuobinaryPartialResponseTests();
|
|
|
|
|
|
|
|
|
|
function pam_levels = getPamLevels(M)
|
|
|
|
|
%GETPAMLEVELS Symmetric PAM amplitudes for natural indices 0...(M-1).
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
pam_levels = -(M-1):2:(M-1);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function [txBits, txSymbols, mapper, a] = generatePamSymbols(M, Nsym, userOrder)
|
|
|
|
|
%GENERATEPAMSYMBOLS Framework-based bit generation and PAM mapping.
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
|
|
|
|
|
txBits = Signalgenerator( ...
|
|
|
|
|
"form", signalform.prms, ...
|
|
|
|
|
"M", M, ...
|
|
|
|
|
"length", Nsym, ...
|
|
|
|
|
"order", userOrder).process();
|
|
|
|
|
|
|
|
|
|
mapper = PAMmapper(M, 0);
|
|
|
|
|
txSymbols = mapper.map(txBits);
|
|
|
|
|
a = amplitudeToSymbolIndex(txSymbols.signal .* mapper.scaling, M);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function rxBits = demapSymbolIndices(a_hat, txSymbolsTemplate, mapper, M)
|
|
|
|
|
%DEMAPSYMBOLINDICES Convert decoded indices back to framework PAM symbols.
|
|
|
|
|
rxSymbols = txSymbolsTemplate;
|
|
|
|
|
rxSymbols.signal = symbolIndexToAmplitude(a_hat, M) ./ mapper.scaling;
|
|
|
|
|
rxBits = mapper.demap(rxSymbols);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function results = simulatePrecoderErrorBursts(a_ref, M, pr_order, txSymbols, mapper, refBits, burst_pattern)
|
|
|
|
|
%SIMULATEPRECODERERRORBURSTS Inject deterministic symbol errors before PR encoding.
|
|
|
|
|
u_ref = precode(a_ref, M, [], pr_order);
|
|
|
|
|
|
|
|
|
|
results = struct();
|
|
|
|
|
results.burst_lengths = burst_pattern.burst_lengths(:);
|
|
|
|
|
results.injected_error_count_per_length = burst_pattern.error_count_per_length;
|
|
|
|
|
results.injected_total_errors = numel(burst_pattern.err_pos);
|
|
|
|
|
results.injected_burst_count = count_error_bursts( ...
|
|
|
|
|
burst_pattern.err_pos, max(burst_pattern.burst_lengths));
|
|
|
|
|
|
|
|
|
|
[u_err, injected_err_pos] = injectSymbolErrorBursts(u_ref, M, burst_pattern);
|
|
|
|
|
u_ref_bits = demapSymbolIndices(u_ref, txSymbols, mapper, M);
|
|
|
|
|
inputBits = demapSymbolIndices(u_err, txSymbols, mapper, M);
|
|
|
|
|
[inputCheckedBits, inputBitErrors, inputBer] = calc_ber( ...
|
|
|
|
|
inputBits.signal, u_ref_bits.signal, "returnErrorLocation", 1);
|
|
|
|
|
input_err_pos = find(u_err(:) ~= u_ref(:));
|
|
|
|
|
input_burst_lengths = getErrorBurstLengths(input_err_pos);
|
|
|
|
|
|
|
|
|
|
[y_amp_err, ~, y_idx_err] = encode(u_err, M, [], pr_order);
|
|
|
|
|
a_hat = memorylessdecode(y_amp_err, M, [], pr_order);
|
|
|
|
|
|
|
|
|
|
rxBits = demapSymbolIndices(a_hat, txSymbols, mapper, M);
|
|
|
|
|
[checkedBits, bitErrors, ber,errorIndice] = calc_ber( ...
|
|
|
|
|
rxBits.signal, refBits.signal, "returnErrorLocation", 1);
|
|
|
|
|
|
|
|
|
|
output_err_pos = find(a_hat(:) ~= a_ref(:));
|
|
|
|
|
output_burst_lengths = getErrorBurstLengths(output_err_pos);
|
|
|
|
|
|
|
|
|
|
results.input_symbol_errors = numel(input_err_pos);
|
|
|
|
|
results.input_symbol_ser = results.input_symbol_errors / numel(a_ref);
|
|
|
|
|
results.input_bit_errors = inputBitErrors;
|
|
|
|
|
results.input_checked_bits = inputCheckedBits;
|
|
|
|
|
results.input_ber = inputBer;
|
|
|
|
|
results.input_burst_count = count_error_bursts( ...
|
|
|
|
|
input_err_pos, max(burst_pattern.burst_lengths));
|
|
|
|
|
results.input_burst_total = numel(input_burst_lengths);
|
|
|
|
|
results.input_max_burst_length = 0;
|
|
|
|
|
if ~isempty(input_burst_lengths)
|
|
|
|
|
results.input_max_burst_length = max(input_burst_lengths);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
results.symbol_errors = numel(output_err_pos);
|
|
|
|
|
results.symbol_ser = results.symbol_errors / numel(a_ref);
|
|
|
|
|
results.bit_errors = bitErrors;
|
|
|
|
|
results.checked_bits = checkedBits;
|
|
|
|
|
results.ber = ber;
|
|
|
|
|
results.output_burst_count = count_error_bursts( ...
|
|
|
|
|
output_err_pos, max(burst_pattern.burst_lengths));
|
|
|
|
|
results.output_burst_total = numel(output_burst_lengths);
|
|
|
|
|
results.output_max_burst_length = 0;
|
|
|
|
|
if ~isempty(output_burst_lengths)
|
|
|
|
|
results.output_max_burst_length = max(output_burst_lengths);
|
|
|
|
|
end
|
|
|
|
|
results.injected_err_pos = injected_err_pos;
|
|
|
|
|
results.output_err_pos = output_err_pos;
|
|
|
|
|
results.last_a_hat = a_hat;
|
|
|
|
|
results.last_a_ref = a_ref;
|
|
|
|
|
results.last_y_idx_err = y_idx_err;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function [u_err, err_pos] = injectSymbolErrorBursts(u_ref, M, burst_pattern)
|
|
|
|
|
%INJECTSYMBOLERRORBURSTS Apply one precomputed burst pattern in one operation.
|
|
|
|
|
validateSymbolVector(u_ref, M, 'u_ref');
|
|
|
|
|
|
|
|
|
|
original_size = size(u_ref);
|
|
|
|
|
u_work = double(u_ref(:));
|
|
|
|
|
N = numel(u_work);
|
|
|
|
|
err_pos = burst_pattern.err_pos(:);
|
|
|
|
|
|
|
|
|
|
if any(err_pos < 1) || any(err_pos > N)
|
|
|
|
|
error('injectSymbolErrorBursts:InvalidPattern', ...
|
|
|
|
|
'burst_pattern.err_pos contains positions outside the sequence.');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
u_err = u_work;
|
|
|
|
|
u_err(err_pos) = mod(u_err(err_pos) + 1, M);
|
|
|
|
|
u_err = reshape(u_err, original_size);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function burst_pattern = buildCombinedBurstPattern(N, burst_lengths, error_count_per_length, options)
|
|
|
|
|
%BUILDCOMBINEDBURSTPATTERN One common sequence containing all burst lengths.
|
|
|
|
|
arguments
|
|
|
|
|
N (1,1) double
|
|
|
|
|
burst_lengths
|
|
|
|
|
error_count_per_length (1,1) double
|
|
|
|
|
options.randkey = []
|
|
|
|
|
options.min_gap (1,1) double = 20
|
|
|
|
|
options.max_placement_attempts (1,1) double = 20000
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if any(burst_lengths < 1) || any(burst_lengths ~= floor(burst_lengths))
|
|
|
|
|
error('buildCombinedBurstPattern:InvalidBurstLengths', ...
|
|
|
|
|
'burst_lengths must contain positive integers.');
|
|
|
|
|
end
|
|
|
|
|
if ~(isscalar(error_count_per_length) && ...
|
|
|
|
|
error_count_per_length == floor(error_count_per_length) && ...
|
|
|
|
|
error_count_per_length >= 1)
|
|
|
|
|
error('buildCombinedBurstPattern:InvalidErrorCount', ...
|
|
|
|
|
'error_count_per_length must be a positive integer.');
|
|
|
|
|
end
|
|
|
|
|
if options.min_gap < 0 || options.min_gap ~= floor(options.min_gap)
|
|
|
|
|
error('buildCombinedBurstPattern:InvalidGap', ...
|
|
|
|
|
'min_gap must be a nonnegative integer.');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
burst_lengths = burst_lengths(:).';
|
|
|
|
|
burst_plan = [];
|
|
|
|
|
burst_label_plan = [];
|
|
|
|
|
|
|
|
|
|
for burst_length = burst_lengths
|
|
|
|
|
remaining = error_count_per_length;
|
|
|
|
|
while remaining > 0
|
|
|
|
|
current_len = min(burst_length, remaining);
|
|
|
|
|
burst_plan(end + 1, 1) = current_len; %#ok<AGROW>
|
|
|
|
|
burst_label_plan(end + 1, 1) = burst_length; %#ok<AGROW>
|
|
|
|
|
remaining = remaining - current_len;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
required_span = sum(burst_plan) + max(0, numel(burst_plan) - 1) * options.min_gap;
|
|
|
|
|
|
|
|
|
|
if required_span > N
|
|
|
|
|
error('buildCombinedBurstPattern:SequenceTooShort', ...
|
|
|
|
|
'Sequence too short for the requested random burst pattern and gap.');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if isempty(options.randkey)
|
|
|
|
|
rs = [];
|
|
|
|
|
else
|
|
|
|
|
rs = RandStream('mt19937ar', 'Seed', options.randkey);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
placement_order = randomPermutation(numel(burst_plan), rs);
|
|
|
|
|
burst_plan = burst_plan(placement_order);
|
|
|
|
|
burst_label_plan = burst_label_plan(placement_order);
|
|
|
|
|
|
|
|
|
|
burst_starts = zeros(numel(burst_plan), 1);
|
|
|
|
|
burst_ends = zeros(numel(burst_plan), 1);
|
|
|
|
|
|
|
|
|
|
for b = 1:numel(burst_plan)
|
|
|
|
|
burst_len = burst_plan(b);
|
|
|
|
|
placed = false;
|
|
|
|
|
|
|
|
|
|
for attempt = 1:options.max_placement_attempts
|
|
|
|
|
start_pos = randomInteger(1, N - burst_len + 1, rs);
|
|
|
|
|
end_pos = start_pos + burst_len - 1;
|
|
|
|
|
|
|
|
|
|
if isBurstPlacementFree(start_pos, end_pos, burst_starts(1:b-1), ...
|
|
|
|
|
burst_ends(1:b-1), options.min_gap)
|
|
|
|
|
burst_starts(b) = start_pos;
|
|
|
|
|
burst_ends(b) = end_pos;
|
|
|
|
|
placed = true;
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if ~placed
|
|
|
|
|
error('buildCombinedBurstPattern:PlacementFailed', ...
|
|
|
|
|
'Could not place all bursts. Reduce min_gap or error count.');
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
[burst_starts, sort_idx] = sort(burst_starts);
|
|
|
|
|
burst_ends = burst_ends(sort_idx);
|
|
|
|
|
burst_label_plan = burst_label_plan(sort_idx);
|
|
|
|
|
|
|
|
|
|
err_pos = [];
|
|
|
|
|
err_label = [];
|
|
|
|
|
for b = 1:numel(burst_starts)
|
|
|
|
|
current_pos = (burst_starts(b):burst_ends(b)).';
|
|
|
|
|
err_pos = [err_pos; current_pos]; %#ok<AGROW>
|
|
|
|
|
err_label = [err_label; repmat(burst_label_plan(b), numel(current_pos), 1)]; %#ok<AGROW>
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
burst_pattern = struct();
|
|
|
|
|
burst_pattern.err_pos = err_pos;
|
|
|
|
|
burst_pattern.err_label = err_label;
|
|
|
|
|
burst_pattern.burst_lengths = burst_lengths;
|
|
|
|
|
burst_pattern.error_count_per_length = error_count_per_length;
|
|
|
|
|
burst_pattern.randkey = options.randkey;
|
|
|
|
|
burst_pattern.min_gap = options.min_gap;
|
|
|
|
|
burst_pattern.burst_starts = burst_starts;
|
|
|
|
|
burst_pattern.burst_ends = burst_ends;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function tf = isBurstPlacementFree(start_pos, end_pos, burst_starts, burst_ends, min_gap)
|
|
|
|
|
%ISBURSTPLACEMENTFREE True when the candidate interval is gap-separated.
|
|
|
|
|
if isempty(burst_starts)
|
|
|
|
|
tf = true;
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
tf = all(end_pos + min_gap < burst_starts | start_pos - min_gap > burst_ends);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function p = randomPermutation(n, rs)
|
|
|
|
|
%RANDOMPERMUTATION randperm with optional RandStream.
|
|
|
|
|
if isempty(rs)
|
|
|
|
|
p = randperm(n);
|
|
|
|
|
else
|
|
|
|
|
p = randperm(rs, n);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function x = randomInteger(lo, hi, rs)
|
|
|
|
|
%RANDOMINTEGER randi with optional RandStream.
|
|
|
|
|
if isempty(rs)
|
|
|
|
|
x = randi([lo hi]);
|
|
|
|
|
else
|
|
|
|
|
x = randi(rs, [lo hi]);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function burst_lengths = getErrorBurstLengths(err_pos)
|
|
|
|
|
%GETERRORBURSTLENGTHS Return lengths of all contiguous error bursts.
|
|
|
|
|
err_pos = sort(err_pos(:).');
|
|
|
|
|
|
|
|
|
|
if isempty(err_pos)
|
|
|
|
|
burst_lengths = [];
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
diffs = diff(err_pos);
|
|
|
|
|
burst_starts = [1, find(diffs > 1) + 1];
|
|
|
|
|
burst_ends = [find(diffs > 1), numel(err_pos)];
|
|
|
|
|
burst_lengths = burst_ends - burst_starts + 1;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function u = precode(a, M, init_state, pr_order)
|
|
|
|
|
%PRECODE Symbol-domain modulo precoder for F(D)=(1+D)^n.
|
|
|
|
|
% h = coefficients of (1 + D)^n
|
|
|
|
|
% u(k) = mod(a(k) - sum_{i=1}^n h(i+1) u(k-i), M)
|
|
|
|
|
if nargin < 4 || isempty(pr_order)
|
|
|
|
|
pr_order = 1;
|
|
|
|
|
end
|
|
|
|
|
if nargin < 3
|
|
|
|
|
init_state = [];
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
validateSymbolVector(a, M, 'a');
|
|
|
|
|
pr_order = validatePrOrder(pr_order);
|
|
|
|
|
state = validateInitState(init_state, M, pr_order);
|
|
|
|
|
|
|
|
|
|
original_size = size(a);
|
|
|
|
|
a_work = double(a(:).');
|
|
|
|
|
|
|
|
|
|
h = getPartialResponseTaps(pr_order);
|
|
|
|
|
u = zeros(size(a_work));
|
|
|
|
|
|
|
|
|
|
for k = 1:numel(a_work)
|
|
|
|
|
% Move the PR recursion to the transmitter.
|
|
|
|
|
feedback = sum(h(2:end) .* state);
|
|
|
|
|
u(k) = mod(a_work(k) - feedback, M);
|
|
|
|
|
state = [u(k) state(1:end-1)];
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
u = reshape(u, original_size);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function [y_amp, pr_alphabet, y_idx] = encode(u, M, init_state, pr_order)
|
|
|
|
|
%ENCODE Apply F(D)=(1+D)^n to an already precoded sequence.
|
|
|
|
|
if nargin < 4 || isempty(pr_order)
|
|
|
|
|
pr_order = 1;
|
|
|
|
|
end
|
|
|
|
|
if nargin < 3
|
|
|
|
|
init_state = [];
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
validateSymbolVector(u, M, 'u');
|
|
|
|
|
pr_order = validatePrOrder(pr_order);
|
|
|
|
|
state = validateInitState(init_state, M, pr_order);
|
|
|
|
|
|
|
|
|
|
original_size = size(u);
|
|
|
|
|
u_work = double(u(:).');
|
|
|
|
|
h = getPartialResponseTaps(pr_order);
|
|
|
|
|
pam_levels = getPamLevels(M);
|
|
|
|
|
pr_alphabet = getDuobinaryAlphabet(M, pr_order);
|
|
|
|
|
|
|
|
|
|
y_idx = zeros(size(u_work));
|
|
|
|
|
y_amp = zeros(size(u_work));
|
|
|
|
|
|
|
|
|
|
for k = 1:numel(u_work)
|
|
|
|
|
pr_state = [u_work(k) state];
|
|
|
|
|
y_idx(k) = sum(h .* pr_state);
|
|
|
|
|
y_amp(k) = sum(h .* pam_levels(pr_state + 1));
|
|
|
|
|
state = pr_state(1:end-1);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
y_idx = reshape(y_idx, original_size);
|
|
|
|
|
y_amp = reshape(y_amp, original_size);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function [a_hat, y_hat_amp, y_hat_idx] = memorylessdecode(r, M, init_state, pr_order)
|
|
|
|
|
%MEMORYLESSDECODE Slice to PR alphabet, then decode modulo M.
|
|
|
|
|
% a_hat(k) = mod(y_hat_idx(k), M)
|
|
|
|
|
if nargin < 4 || isempty(pr_order)
|
|
|
|
|
pr_order = 1;
|
|
|
|
|
end
|
|
|
|
|
if nargin < 3
|
|
|
|
|
init_state = [];
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
pr_order = validatePrOrder(pr_order);
|
|
|
|
|
validateRealVector(r, 'r');
|
|
|
|
|
validateInitState(init_state, M, pr_order);
|
|
|
|
|
|
|
|
|
|
original_size = size(r);
|
|
|
|
|
r_work = double(r(:).');
|
|
|
|
|
|
|
|
|
|
[pr_alphabet, idx_for_alphabet] = getDuobinaryAlphabet(M, pr_order);
|
|
|
|
|
|
|
|
|
|
y_hat_amp = zeros(size(r_work));
|
|
|
|
|
y_hat_idx = zeros(size(r_work));
|
|
|
|
|
|
|
|
|
|
for k = 1:numel(r_work)
|
|
|
|
|
[~, level_pos] = min(abs(r_work(k) - pr_alphabet));
|
|
|
|
|
y_hat_amp(k) = pr_alphabet(level_pos);
|
|
|
|
|
y_hat_idx(k) = idx_for_alphabet(level_pos);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
a_hat = mod(y_hat_idx, M);
|
|
|
|
|
|
|
|
|
|
a_hat = reshape(a_hat, original_size);
|
|
|
|
|
y_hat_amp = reshape(y_hat_amp, original_size);
|
|
|
|
|
y_hat_idx = reshape(y_hat_idx, original_size);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function a_hat = slicePamAmplitude(r, M)
|
|
|
|
|
%SLICEPAMAMPLITUDE Nearest-neighbor PAM slicing to natural symbol indices.
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
validateRealVector(r, 'r');
|
|
|
|
|
|
|
|
|
|
original_size = size(r);
|
|
|
|
|
r_work = double(r(:).');
|
|
|
|
|
pam_levels = getPamLevels(M);
|
|
|
|
|
a_hat = zeros(size(r_work));
|
|
|
|
|
|
|
|
|
|
for k = 1:numel(r_work)
|
|
|
|
|
[~, level_pos] = min(abs(r_work(k) - pam_levels));
|
|
|
|
|
a_hat(k) = level_pos - 1;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
a_hat = reshape(a_hat, original_size);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function plotPrecoderBurstAnalysis(results, pr_order, M)
|
|
|
|
|
%PLOTPRECODERBURSTANALYSIS Compact BER and burst-count visualization.
|
|
|
|
|
figure(300 + 10*pr_order + M);
|
|
|
|
|
clf;
|
|
|
|
|
tiledlayout(2, 1);
|
|
|
|
|
|
|
|
|
|
nexttile;
|
|
|
|
|
bar(results.burst_lengths, results.injected_burst_count);
|
|
|
|
|
grid on;
|
|
|
|
|
xlabel("Injected burst length");
|
|
|
|
|
ylabel("Count");
|
|
|
|
|
title(sprintf("PAM-%d, F(D)=(1+D)^%d", M, pr_order));
|
|
|
|
|
|
|
|
|
|
nexttile;
|
|
|
|
|
bar(1:numel(results.output_burst_count), results.output_burst_count);
|
|
|
|
|
grid on;
|
|
|
|
|
xlabel("Output error burst length");
|
|
|
|
|
ylabel("Count");
|
|
|
|
|
title("Output symbol-error burst count");
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function printBurstCountReport(results, M, pr_order)
|
|
|
|
|
%PRINTBURSTCOUNTREPORT Paste-friendly burst-count report.
|
|
|
|
|
max_len = max([numel(results.input_burst_count), numel(results.output_burst_count)]);
|
|
|
|
|
lengths = 1:max_len;
|
|
|
|
|
input_counts = padCountVector(results.input_burst_count, max_len);
|
|
|
|
|
output_counts = padCountVector(results.output_burst_count, max_len);
|
|
|
|
|
|
|
|
|
|
fprintf('BURST_COUNT_REPORT pr_order=%d M=%d\n', pr_order, M);
|
|
|
|
|
fprintf('length : %s\n', formatIntegerVector(lengths));
|
|
|
|
|
fprintf('before_cnt : %s\n', formatIntegerVector(input_counts));
|
|
|
|
|
fprintf('after_cnt : %s\n', formatIntegerVector(output_counts));
|
|
|
|
|
fprintf('delta_cnt : %s\n', formatIntegerVector(output_counts - input_counts));
|
|
|
|
|
fprintf('before_total=%d before_max=%d after_total=%d after_max=%d\n', ...
|
|
|
|
|
results.input_burst_total, results.input_max_burst_length, ...
|
|
|
|
|
results.output_burst_total, results.output_max_burst_length);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function x = padCountVector(x, target_length)
|
|
|
|
|
%PADCOUNTVECTOR Pad count vector with trailing zeros.
|
|
|
|
|
x = reshape(x, 1, []);
|
|
|
|
|
if numel(x) < target_length
|
|
|
|
|
x(end+1:target_length) = 0;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function s = formatIntegerVector(x)
|
|
|
|
|
%FORMATINTEGERVECTOR Format vector as comma-separated integers.
|
|
|
|
|
x = reshape(x, 1, []);
|
|
|
|
|
if isempty(x)
|
|
|
|
|
s = '';
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
parts = strings(size(x));
|
|
|
|
|
for k = 1:numel(x)
|
|
|
|
|
parts(k) = string(sprintf('%d', x(k)));
|
|
|
|
|
end
|
|
|
|
|
s = char(strjoin(parts, ', '));
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function plotBurstSummary(burst_results, pr_orders, M_list)
|
|
|
|
|
%PLOTBURSTSUMMARY Show BER and output-burst metrics for every M/order pair.
|
|
|
|
|
ber_map = nan(numel(pr_orders), numel(M_list));
|
|
|
|
|
symbol_error_map = nan(size(ber_map));
|
|
|
|
|
max_burst_map = nan(size(ber_map));
|
|
|
|
|
|
|
|
|
|
for row = 1:numel(pr_orders)
|
|
|
|
|
pr_order = pr_orders(row);
|
|
|
|
|
for col = 1:numel(M_list)
|
|
|
|
|
M = M_list(col);
|
|
|
|
|
results = burst_results(pr_order, M).results;
|
|
|
|
|
ber_map(row, col) = results.ber;
|
|
|
|
|
symbol_error_map(row, col) = results.symbol_errors;
|
|
|
|
|
max_burst_map(row, col) = results.output_max_burst_length;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
figure(500);
|
|
|
|
|
clf;
|
|
|
|
|
tiledlayout(1, 3);
|
|
|
|
|
|
|
|
|
|
nexttile;
|
|
|
|
|
imagesc(M_list, pr_orders, ber_map);
|
|
|
|
|
colorbar;
|
|
|
|
|
xlabel("PAM order M");
|
|
|
|
|
ylabel("PR order n");
|
|
|
|
|
title("BER");
|
|
|
|
|
|
|
|
|
|
nexttile;
|
|
|
|
|
imagesc(M_list, pr_orders, symbol_error_map);
|
|
|
|
|
colorbar;
|
|
|
|
|
xlabel("PAM order M");
|
|
|
|
|
ylabel("PR order n");
|
|
|
|
|
title("Symbol errors");
|
|
|
|
|
|
|
|
|
|
nexttile;
|
|
|
|
|
imagesc(M_list, pr_orders, max_burst_map);
|
|
|
|
|
colorbar;
|
|
|
|
|
xlabel("PAM order M");
|
|
|
|
|
ylabel("PR order n");
|
|
|
|
|
title("Max output burst");
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function [u, y_amp, pr_alphabet, y_idx, d_ref] = emulatePrecoder(d_tx, M, init_state, pr_order)
|
|
|
|
|
%EMULATEPRECODER PR precoder emulation by reference relabeling.
|
|
|
|
|
% The available sequence d_tx is interpreted as d_pre.
|
|
|
|
|
% d_ref(k) = sum_i h(i+1)*d_tx(k-i) mod M
|
|
|
|
|
% The waveform itself is generated by the ordinary precode/encode blocks.
|
|
|
|
|
if nargin < 4 || isempty(pr_order)
|
|
|
|
|
pr_order = 1;
|
|
|
|
|
end
|
|
|
|
|
if nargin < 3
|
|
|
|
|
init_state = [];
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
d_ref = precoderEmulationReference(d_tx, M, init_state, pr_order);
|
|
|
|
|
u = precode(d_ref, M, init_state, pr_order);
|
|
|
|
|
[y_amp, pr_alphabet, y_idx] = encode(u, M, init_state, pr_order);
|
|
|
|
|
|
|
|
|
|
assert(isequal(u, d_tx), ...
|
|
|
|
|
'Emulation relabeling did not reproduce the requested precoder output.');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function d_ref = precoderEmulationReference(d_tx, M, init_state, pr_order)
|
|
|
|
|
%PRECODEREMULATIONREFERENCE Reference implied by interpreting d_tx as u.
|
|
|
|
|
% For h(D)=(1+D)^n, d_ref(k)=mod(sum_i h(i+1) d_tx(k-i), M).
|
|
|
|
|
if nargin < 4 || isempty(pr_order)
|
|
|
|
|
pr_order = 1;
|
|
|
|
|
end
|
|
|
|
|
if nargin < 3
|
|
|
|
|
init_state = [];
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
validateSymbolVector(d_tx, M, 'd_tx');
|
|
|
|
|
pr_order = validatePrOrder(pr_order);
|
|
|
|
|
state = validateInitState(init_state, M, pr_order);
|
|
|
|
|
|
|
|
|
|
original_size = size(d_tx);
|
|
|
|
|
d_work = double(d_tx(:).');
|
|
|
|
|
h = getPartialResponseTaps(pr_order);
|
|
|
|
|
d_ref = zeros(size(d_work));
|
|
|
|
|
|
|
|
|
|
for k = 1:numel(d_work)
|
|
|
|
|
pr_state = [d_work(k) state];
|
|
|
|
|
d_ref(k) = mod(sum(h .* pr_state), M);
|
|
|
|
|
state = pr_state(1:end-1);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
d_ref = reshape(d_ref, original_size);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function s = symbolIndexToAmplitude(a, M)
|
|
|
|
|
%SYMBOLINDEXTOAMPLITUDE Map natural PAM indices to amplitudes.
|
|
|
|
|
validateSymbolVector(a, M, 'a');
|
|
|
|
|
pam_levels = getPamLevels(M);
|
|
|
|
|
s = reshape(pam_levels(double(a(:)) + 1), size(a));
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function a = amplitudeToSymbolIndex(s, M)
|
|
|
|
|
%AMPLITUDETOSYMBOLINDEX Map exact PAM amplitudes to natural indices.
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
validateRealVector(s, 's');
|
|
|
|
|
|
|
|
|
|
original_size = size(s);
|
|
|
|
|
s_work = double(s(:).');
|
|
|
|
|
pam_levels = getPamLevels(M);
|
|
|
|
|
a = zeros(size(s_work));
|
|
|
|
|
|
|
|
|
|
for k = 1:numel(s_work)
|
|
|
|
|
match = find(pam_levels == s_work(k), 1);
|
|
|
|
|
if isempty(match)
|
|
|
|
|
error('amplitudeToSymbolIndex:InvalidAmplitude', ...
|
|
|
|
|
'All amplitudes must be valid PAM-%d levels.', M);
|
|
|
|
|
end
|
|
|
|
|
a(k) = match - 1;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
a = reshape(a, original_size);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function runDuobinaryPartialResponseTests()
|
|
|
|
|
%RUNDUOBINARYPARTIALRESPONSETESTS Minimal self-checks.
|
|
|
|
|
fprintf('\nRunning duobinary PR self-tests...\n');
|
|
|
|
|
|
|
|
|
|
testNoiselessReconstruction(2, 1);
|
|
|
|
|
testNoiselessReconstruction(4, 1);
|
|
|
|
|
testNoiselessReconstruction(8, 1);
|
|
|
|
|
testNoiselessReconstruction(4, 2);
|
|
|
|
|
testNoiselessReconstruction(8, 3);
|
|
|
|
|
testFrameworkNoiselessBer();
|
|
|
|
|
testFirstOrderPrecoderEmulation();
|
|
|
|
|
testAlphabetSize();
|
|
|
|
|
testNonzeroInitState();
|
|
|
|
|
|
|
|
|
|
fprintf('All duobinary PR self-tests passed.\n');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function testFrameworkNoiselessBer()
|
|
|
|
|
for M = [2 4 8]
|
|
|
|
|
for pr_order = [1 2 3]
|
|
|
|
|
[txBits, txSymbols, mapper, a] = generatePamSymbols(M, 1024, 9);
|
|
|
|
|
u = precode(a, M, [], pr_order);
|
|
|
|
|
y_amp = encode(u, M, [], pr_order);
|
|
|
|
|
a_hat = memorylessdecode(y_amp, M, [], pr_order);
|
|
|
|
|
rxBits = demapSymbolIndices(a_hat, txSymbols, mapper, M);
|
|
|
|
|
[~, errors, ber] = calc_ber(rxBits.signal, txBits.signal);
|
|
|
|
|
assert(errors == 0 && ber == 0, ...
|
|
|
|
|
'Framework BER check failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
fprintf('PASS: framework Signalgenerator/PAMmapper/calc_ber noiseless BER\n');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function testFirstOrderPrecoderEmulation()
|
|
|
|
|
for M = [2 4 8]
|
|
|
|
|
for pr_order = [1 2 3]
|
|
|
|
|
init_state = mod((1:pr_order) + M - 2, M);
|
|
|
|
|
[~, txSymbols, mapper, d_tx] = generatePamSymbols(M, 1024, 9);
|
|
|
|
|
|
|
|
|
|
[u, y_amp, ~, ~, d_ref] = emulatePrecoder(d_tx, M, init_state, pr_order);
|
|
|
|
|
assert(isequal(u, d_tx), ...
|
|
|
|
|
'Emulated precoder output mismatch for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
|
|
|
|
|
d_hat = memorylessdecode(y_amp, M, init_state, pr_order);
|
|
|
|
|
assert(isequal(d_hat, d_ref), ...
|
|
|
|
|
'Emulated noiseless modulo decoding failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
|
|
|
|
|
refBits = demapSymbolIndices(d_ref, txSymbols, mapper, M);
|
|
|
|
|
rxBits = demapSymbolIndices(d_hat, txSymbols, mapper, M);
|
|
|
|
|
[~, errors, ber] = calc_ber(rxBits.signal, refBits.signal);
|
|
|
|
|
assert(errors == 0 && ber == 0, ...
|
|
|
|
|
'Emulated framework BER failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
fprintf('PASS: precoder emulation and relabeled reference\n');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function testNoiselessReconstruction(M, pr_order)
|
|
|
|
|
a = randi([0 M-1], 1000, 1);
|
|
|
|
|
u = precode(a, M, [], pr_order);
|
|
|
|
|
y_amp = encode(u, M, [], pr_order);
|
|
|
|
|
a_hat = memorylessdecode(y_amp, M, [], pr_order);
|
|
|
|
|
assert(isequal(a_hat, a), ...
|
|
|
|
|
'Noiseless reconstruction failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
fprintf('PASS: noiseless reconstruction for M=%d, order=%d\n', M, pr_order);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function testAlphabetSize()
|
|
|
|
|
for M = [2 4 8]
|
|
|
|
|
for pr_order = [1 2 3]
|
|
|
|
|
u = precode(0:M-1, M, [], pr_order);
|
|
|
|
|
[~, pr_alphabet] = encode(u, M, [], pr_order);
|
|
|
|
|
expected_size = (M-1)*2^pr_order + 1;
|
|
|
|
|
assert(numel(pr_alphabet) == expected_size, ...
|
|
|
|
|
'PR alphabet size failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
fprintf('PASS: PR alphabet has (M-1)*2^order+1 distinct levels\n');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function testNonzeroInitState()
|
|
|
|
|
for M = [2 4 8]
|
|
|
|
|
for pr_order = [1 2 3]
|
|
|
|
|
init_state = mod((1:pr_order) + M - 2, M);
|
|
|
|
|
a = randi([0 M-1], 1, 1000);
|
|
|
|
|
u = precode(a, M, init_state, pr_order);
|
|
|
|
|
y_amp = encode(u, M, init_state, pr_order);
|
|
|
|
|
a_hat = memorylessdecode(y_amp, M, init_state, pr_order);
|
|
|
|
|
assert(isequal(a_hat, a), ...
|
|
|
|
|
'Nonzero init_state failed for M=%d, order=%d.', M, pr_order);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
fprintf('PASS: random-state test with nonzero init_state\n');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function [pr_alphabet, idx_for_alphabet] = getDuobinaryAlphabet(M, pr_order)
|
|
|
|
|
%GETDUOBINARYALPHABET Build amplitude/index lookup tables from PAM sums.
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
pr_order = validatePrOrder(pr_order);
|
|
|
|
|
|
|
|
|
|
h = getPartialResponseTaps(pr_order);
|
|
|
|
|
pam_levels = getPamLevels(M);
|
|
|
|
|
n_state = pr_order + 1;
|
|
|
|
|
n_combinations = M^n_state;
|
|
|
|
|
all_amp = zeros(1, n_combinations);
|
|
|
|
|
idx_to_amp = nan(1, (M-1)*sum(h) + 1);
|
|
|
|
|
|
|
|
|
|
for c = 0:n_combinations-1
|
|
|
|
|
state = zeros(1, n_state);
|
|
|
|
|
tmp = c;
|
|
|
|
|
|
|
|
|
|
for k = 1:n_state
|
|
|
|
|
state(k) = mod(tmp, M);
|
|
|
|
|
tmp = floor(tmp / M);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
y_idx = sum(h .* state);
|
|
|
|
|
y_amp = sum(h .* pam_levels(state + 1));
|
|
|
|
|
|
|
|
|
|
all_amp(c + 1) = y_amp;
|
|
|
|
|
idx_to_amp(y_idx + 1) = y_amp;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
pr_alphabet = unique(all_amp);
|
|
|
|
|
idx_for_alphabet = zeros(size(pr_alphabet));
|
|
|
|
|
|
|
|
|
|
for k = 1:numel(pr_alphabet)
|
|
|
|
|
idx_for_alphabet(k) = find(idx_to_amp == pr_alphabet(k), 1) - 1;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function h = getPartialResponseTaps(pr_order)
|
|
|
|
|
%GETPARTIALRESPONSETAPS Coefficients of F(D)=(1+D)^n.
|
|
|
|
|
pr_order = validatePrOrder(pr_order);
|
|
|
|
|
h = zeros(1, pr_order + 1);
|
|
|
|
|
|
|
|
|
|
for k = 0:pr_order
|
|
|
|
|
h(k + 1) = nchoosek(pr_order, k);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function validatePamOrder(M)
|
|
|
|
|
if ~(isscalar(M) && isnumeric(M) && any(M == [2 4 8]))
|
|
|
|
|
error('validatePamOrder:InvalidM', 'M must be one of [2 4 8].');
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function validateSymbolVector(a, M, name)
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
if ~(isnumeric(a) && isreal(a) && isvector(a))
|
|
|
|
|
error('validateSymbolVector:InvalidVector', '%s must be a real numeric vector.', name);
|
|
|
|
|
end
|
|
|
|
|
if any(a(:) ~= floor(a(:))) || any(a(:) < 0) || any(a(:) > M-1)
|
|
|
|
|
error('validateSymbolVector:InvalidSymbols', ...
|
|
|
|
|
'%s must contain integers in [0, M-1].', name);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function validateRealVector(x, name)
|
|
|
|
|
if ~(isnumeric(x) && isreal(x) && isvector(x))
|
|
|
|
|
error('validateRealVector:InvalidVector', '%s must be a real numeric vector.', name);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function pr_order = validatePrOrder(pr_order)
|
|
|
|
|
if ~(isscalar(pr_order) && isnumeric(pr_order) && isreal(pr_order) && ...
|
|
|
|
|
pr_order == floor(pr_order) && pr_order >= 1)
|
|
|
|
|
error('validatePrOrder:InvalidOrder', ...
|
|
|
|
|
'pr_order must be a positive integer.');
|
|
|
|
|
end
|
|
|
|
|
pr_order = double(pr_order);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function init_state = validateInitState(init_state, M, pr_order)
|
|
|
|
|
validatePamOrder(M);
|
|
|
|
|
pr_order = validatePrOrder(pr_order);
|
|
|
|
|
|
|
|
|
|
if isempty(init_state)
|
|
|
|
|
init_state = zeros(1, pr_order);
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if ~(isnumeric(init_state) && isreal(init_state) && isvector(init_state) && ...
|
|
|
|
|
numel(init_state) == pr_order)
|
|
|
|
|
error('validateInitState:InvalidState', ...
|
|
|
|
|
'init_state must contain pr_order integers in [0, M-1].');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if any(init_state(:) ~= floor(init_state(:))) || ...
|
|
|
|
|
any(init_state(:) < 0) || any(init_state(:) > M-1)
|
|
|
|
|
error('validateInitState:InvalidState', ...
|
|
|
|
|
'init_state must contain pr_order integers in [0, M-1].');
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
init_state = double(reshape(init_state, 1, []));
|
|
|
|
|
end
|