Skip to content

OpenDPD ↔ MATLAB parity: the memory-polynomial DPD

Status: pre-registered; run 1 done — the memory-polynomial parity passes as registered (2026-10-09). Amendment 2 (below, 2026-10-09) registers a small additional comparison with rf.PAmemory; its outcome is recorded after the results of run 1. The registration (rules, data, MathWorks configuration, budgets and the scored/diagnostic split) is committed before any comparison script exists or is run. The commits that follow change only this status line, the run log and the Results section; they do not edit the registration. If the registration itself must change, that is a dated amendment appended below, never an edit in place.

MATLAB users already trust comm.DPDCoefficientEstimator and comm.DPD. OpenDPD's mp_ls is the same model class (memory polynomial, least squares, indirect learning). If the two agree up to the listed conventions, then OpenDPD's baseline can be checked by anyone who has those functions, and a model fitted in OpenDPD (for example on measured data in Studio) can be run by comm.DPD, which is what the planned opendpd-model-v1 export relies on. This document fixes what is compared, with which MathWorks functions and which budgets, before any number exists, and records the outcome afterwards, including everything that does not agree.

Rules

  1. Data, MathWorks configuration and budgets below are fixed before the first run. A run never changes them.
  2. The budgets are numerical-agreement budgets for two implementations of the same least-squares problem. They are loose compared with double-precision round-off (so that conditioning does not decide the verdict) and tight compared with any convention difference, which would produce differences of order one: 1·10⁻⁶ relative where conditioning or a single-precision interface is involved, 1·10⁻⁹ for double against double where neither is. They are not tuned on results.
  3. An item passes if its measured difference is within its budget. An item outside its budget is reported as failed with the numbers; the explanation is a hypothesis until it is shown (a diagnostic run is allowed and is reported next to the failure, never instead of it).
  4. If a comparison disagrees because the MATLAB-side code does not implement the registered procedure (a bug in the check, not a difference between the two tools), the MATLAB code is corrected to the registered procedure and the run is repeated. Every run, including the ones discarded, appears in the run log with the reason. MathWorks options are not changed after seeing a result.
  5. "Diagnostic" items are reported but have no budget and do not decide the verdict.
  6. A scored item that cannot be evaluated (a tool refuses or fails on it) is reported as not evaluable, never as agreement, and the verdict does not pass.
  7. MathWorks sample data is not used or redistributed. All data is generated by the scripts from the repository's own waveform generator and the synthetic PA of tests/fixtures/synthetic.py.

What this can and cannot show

  • With the conventions below identical, both estimators solve the same overdetermined least-squares problem. Agreement on the registered cases means the basis (index order, lag, degree), the gain scaling, the row selection and the way the coefficients are applied are the same in the two tools, in the conditioning range of the registered cases. A disagreement means a convention differs or one implementation has a defect.
  • It does not show that a memory polynomial is a good DPD for any particular amplifier, nor anything about measured data: the amplifier here is a synthetic memory polynomial.
  • It does not cover rank-deficient or very ill-conditioned bases. The unscaled basis of the registered cases has a condition number between about 4·10³ and 3·10⁵ (read from a Python-only exploration before registration, see Data); a degree-9 case (about 3·10⁶ to 1·10⁷) was left out on purpose. OpenDPD's optional rcond truncation has no counterpart in comm.DPDCoefficientEstimator.
  • The OpenDPD side of the scored items calls the compute-core functions that the run service calls (opendpd.core.polynomial.mp_basis, fit_least_squares, PolynomialModel) on double-precision arrays. What the run machinery adds (float32 dataset storage, the gain taken from the train split, the zero-filled rows at every segment start) is quantified by the diagnostics and by the product-path item Q6, not hidden.
  • Not compared, with the reason: cross-term memory polynomial and OpenDPD's GMP — the bases differ (a full cross-term within the memory window against lagging and leading envelope terms with their own ranges), so there is no one-to-one comparison; recursive least squares — OpenDPD has no RLS fit; MATLAB code generation, single precision, fixed-point; every non-polynomial OpenDPD model (the GRU family has no comm.* counterpart); rf.PAmemory — mapping a fitted PA model onto it belongs to the export work and is verified there.

Conventions, as read from the installed MATLAB code (R2026a Update 5)

These are statements about the code in toolbox/comm/comm/+comm/DPDCoefficientEstimator.m, DPD.m and +internal/+dpd/dpdcore.m, not about the published documentation (which is not installed here and was not consulted). They are the definitions this comparison uses.

topic MathWorks OpenDPD mp_ls
model Coef(i,d) multiplies x(n-(i-1)) · abs(x(n-(i-1)))^(d-1), Coef is MemoryDepth × Degree w[k·Q+q] multiplies x(n-q) · abs(x(n-q))^k, k = 0…K-1, q = 0…Q-1
layout Coef(:) (column-major) lists lag fastest, then degree w lists lag fastest, then degree: the same order, so Coef = reshape(w, Q, K) and Degree = K, MemoryDepth = Q
learning PA_input ≈ Φ(PA_output / g) · Coef is fitted, Coef is then used as the predistorter (indirect learning) x ≈ Φ(y / G) · w is fitted on the measured train split and copied (indirect learning)
gain PA_output is divided by 10^(DesiredAmplitudeGaindB/10) y is divided by G = max(abs(y)) / max(abs(x)) (set_target_gain)
rows only rows with a complete memory (n ≥ MemoryDepth) delays are zero-filled and reset at every segment start (nperseg), so the first Q-1 rows of each segment are included
solver xVec \ target (QR with column pivoting, no column scaling) column-normalised SVD least squares (numpy.linalg.lstsq), cutoff rcond, 0 = machine precision
application zero initial delay state; output sample n uses x(n)…x(n-MemoryDepth+1) zero-filled delays at the start of a segment

Consequences the comparison uses: DesiredAmplitudeGaindB is set to 10·log10(G) so that the two divisors are equal (the property name reads like an amplitude in dB, but the code divides by 10^(g/10); diagnostic D3 shows what the other reading would do); the MATLAB estimator is compared with an OpenDPD fit restricted to the rows with complete memory, and the zero-filled rows are evaluated separately (D2, D4).

Data

The signals of the LTE registration (docs/performance/matlab-parity.md, Signals), generated by the same code: waveform ofdm-lte20-v1, seed 1, 10 subframes, interpolated ×4 to 122.88 MS/s (1 228 800 samples); x is the PA input a · x_4 and y = memory_polynomial_pa(x) the PA output, in double precision (not rounded to float32 unless stated), with a = 0.35 (S4) and a = 0.50 (S5). G = max(abs(y)) / max(abs(x)) over the whole record: 0.5674 (S4) and 0.4030 (S5). Both sides receive identical arrays.

Four cases, each a (record, K, Q) combination:

case record Degree K MemoryDepth Q unscaled condition number of the basis
C1 S4 5 (MathWorks default) 3 (MathWorks default) 4.3·10³
C2 S4 7 5 1.4·10⁵
C3 S5 5 3 6.7·10³
C4 S5 7 5 3.1·10⁵

The condition numbers (2-norm, basis rows with complete memory, y/G) were computed in Python before this registration to choose cases whose conditioning is moderate, so that the budgets below test the conventions and not the arithmetic; no estimator of either tool had been run on this data at that time.

Constructed exact problem. For every case, c_true[k,q] = 0.6^(k+q) · (−1)^k · exp(j(0.35(k+1) + 0.6(q+1))) (k = 0…K-1, q = 0…Q-1, a deliberately asymmetric set so that a transposed or reordered layout cannot agree by accident). With v = y/G, the constructed input is x_syn[n] = Σ c_true[k,q] · v[n-q] · abs(v[n-q])^k, zero before the start of the record, computed in NumPy by a direct double loop in the harness, independently of mp_basis. The fit of x_syn from y has zero residual, so a correct estimator recovers c_true up to the conditioning, with no residual-dependent term.

MathWorks configuration

est = comm.DPDCoefficientEstimator('PolynomialType','Memory polynomial', 'Algorithm','Least squares', ...
    'Degree',K, 'MemoryDepth',Q, 'DesiredAmplitudeGaindB',10*log10(G));
coef = est(PA_input, PA_output);          % PA_input = x (or x_syn), PA_output = y
dpd  = comm.DPD('PolynomialType','Memory polynomial', 'Coefficients', C);
u    = dpd(v);

Items

Scored, for every case C1–C4 (20 items):

id item budget
Q1 comm.DPDCoefficientEstimator on (x_syn, y): ‖Coef(:) − c_true‖ / ‖c_true‖ ≤ 1·10⁻⁶
Q2 OpenDPD (mp_basis(y/G) rows Q-1…, fit_least_squares, rcond 0) on (x_syn, y): the same quantity ≤ 1·10⁻⁶
Q3 comm.DPD with reshape(c_true, Q, K) applied to v = y/G, against x_syn: relative RMS difference ≤ 1·10⁻⁹
Q4 The two predistorters on the measured-style data: comm.DPD with the coefficients the MATLAB estimator returned for (x, y), against mp_basis(x) @ w with the OpenDPD coefficients for (x, y) (rows with complete memory), both applied to x in double: relative RMS difference of the outputs ≤ 1·10⁻⁶
Q5 The same coefficients applied by both tools through their real interfaces: comm.DPD with reshape(w_OpenDPD, Q, K) and the PolynomialModel module (float32 tensor in and out, one segment) applied to x rounded to float32: relative RMS difference ≤ 1·10⁻⁶

Q2 is a numerical self-check of OpenDPD's fit on a problem with a known answer (the data is generated by an independent implementation, but the solver is OpenDPD's). Q1 and Q3 carry the convention comparison: Q3 tests that comm.DPD and OpenDPD's mp_basis define the same polynomial, Q1 that the MathWorks estimator returns coefficients in OpenDPD's layout.

Scored, once (C1 data only, through the product path, product-path item Q6): the S4 pair is imported as a dataset through opendpd.sdk (import_iq, float32 storage, nperseg = 2048), an mp_ls PA run and an mp_ls DPD run with K = 5, Q = 3 are trained, and the coefficients in the DPD run's checkpoint are compared with a fit computed independently from the run's own stored train split with segmented_basis (nperseg 2048) and the gain set_target_gain returns for that split: relative difference ≤ 1·10⁻⁶. Q6 checks that the run service does what the compute core does; it is the bridge between the scored items (core functions) and the diagnostics D2 and D4 (run behaviour).

Diagnostic (no budget), for every case unless stated:

id item
D1 Coefficient difference between the MATLAB estimator and OpenDPD on the measured-style data (x, y): ‖Coef(:) − w‖ / ‖w‖, with the unscaled condition number, the relative residual ρ = ‖r‖ / ‖Φ w‖ of the fit in dB, and the numerical rank OpenDPD reports
D2 Boundary rows: OpenDPD's fit with the segmented basis (nperseg 2048, delays zero-filled and reset every segment, the basis fit_run builds) against the MATLAB estimator: relative coefficient difference and relative RMS difference of the predistorter outputs on x; and the relative residual of each fit in dB
D3 The other reading of the gain setting: the MATLAB estimator with DesiredAmplitudeGaindB = 20·log10(G), against OpenDPD: relative coefficient difference
D4 Product path (C1 data only): the MATLAB estimator on the run's stored train split with the run's gain, against the DPD run's checkpoint coefficients: relative coefficient difference; and OpenDPD's segmented_basis fit minus the full-memory fit on the same split, to show how much of it the zero-filled rows explain

Verdict rule

The memory-polynomial parity passes only if every scored item (Q1–Q5 for C1–C4, and Q6) is within its budget. Otherwise it does not pass as registered, and the report says which items are outside, by how much, and what is known about why. The document draws no conclusion from the diagnostics except to describe them.

Reproduce

python scripts/matlab_parity_dpd.py --work /tmp/parity-dpd --matlab <path to matlab> --write-report

needs the Communications Toolbox. The record is docs/performance/matlab-parity-dpd.json; the Results block below is generated from it. Exit status 1 means the parity did not pass as registered.

Run log

run date change since the previous run outcome
— 2026-10-09 plumbing checks on the first 60 000 samples of each record (--smoke; not the registered data, no report can be written from them) no registered number. They showed that the service refuses a least-squares baseline as a DPD run's PA reference, which led to amendment 1
1 2026-10-09 none after amendment 1 21 of 21 scored items within budget. Record: matlab-parity-dpd.json
2 2026-10-09 amendment 2 only (items R1-R4; nothing of run 1 changes) first attempt stopped by an error of the script, which looked for the fixture one folder too high (a bug of the check, not a result; no number was produced); second attempt: every item within budget, see Results of amendment 2

Amendment 1 (2026-10-09, before run 1)

The registration of Q6 says that an mp_ls PA run and an mp_ls DPD run are trained. The service refuses a least-squares baseline as the PA reference of a DPD run ("a PA-modeling reference, not a DPD surrogate"), which a plumbing check on truncated data (60 000 samples per record, not the registered data, no report written) showed before any registered run. The harness therefore trains a small gradient-trained GRU surrogate (hidden_size 6, one epoch, CPU) as the DPD run's PA reference; the DPD run is the mp_ls run with K = 5, Q = 3 as registered. The platform identifies the postdistorter on measured data and uses the surrogate only for evaluation, so the surrogate does not enter the coefficients that Q6 and D4 compare. No item, budget or case changes.

Amendment 2 (2026-10-09, before run 2): rf.PAmemory

The registration above lists rf.PAmemory among the things not compared and says that mapping a fitted memory-polynomial model onto it belongs to the export work. This amendment registers that comparison. It adds items; it changes none of the items, budgets, data or conclusions of run 1. Run 2 is the first and only run of the items below.

What is known before registering (disclosed). An unregistered exploration on one random signal and on the mp_ls fixture of the toolbox's tests (K = 3, Q = 4) showed that rf.PAmemory(Model='Memory polynomial', CoefficientMatrix=C) with the comm.DPD coefficient layout C = reshape(w, Q, K) agrees with comm.DPD from the Q-th output sample on, and that its first Q − 1 outputs agree with comm.DPD run on the signal preceded by Q − 1 copies of its first sample (largest difference 2.5·10⁻¹⁶). That is: the delay line of rf.PAmemory starts filled with the first input sample, not with zeros. The items below test this as a hypothesis on more cases; they were not tuned to results.

Data. Seeded (rng seeds below), generated by the script; no MathWorks sample data. For every pair (Q, K) with Q ∈ {1, 2, 4, 7} and K ∈ {1, 3, 5}: a complex coefficient matrix C (Q-by-K, real and imaginary parts from randn, scaled by 1/(QK)) and, for each signal seed s ∈ {11, 12, 13}, a complex row-independent test signal of 256 samples (0.3·(randn + 1j·randn)). That is 36 cases. Item R3 uses the mp_ls-dpd package of Matlab/toolbox/tests/data and the same three signal seeds with 128 samples (its segment length).

Items (all scored; budgets are for double against double, expected round-off 10⁻¹⁶; relative means divided by the largest magnitude of the reference output).

Item Comparison Budget
R1 rf.PAmemory(C) applied to [zeros(Q-1,1); x], outputs Q to the end, against comm.DPD(C) applied to x (zero initial state), all 36 cases 1·10⁻¹² relative
R2 The first Q − 1 outputs of rf.PAmemory(C) applied to x alone against the first Q − 1 outputs of comm.DPD(C) applied to x preceded by Q − 1 copies of x(1) (the cases with Q ≥ 2: 27 cases) 1·10⁻¹² relative
R3 opendpd.apply(model, x) of the mp_ls-dpd fixture (one 128-sample segment, so a zero state at the start, single-precision output) against rf.PAmemory(C) with C = model.commCoefficients() applied to [zeros(Q-1,1); x], outputs Q to the end, 3 signals 1·10⁻⁶ relative (the single-precision interface, as for Q5)
R4 rf.PAmemory(C) applied to x(1:128) and then, on the same object, to x(129:256), against one call on all of x (after the pad, i.e. the delay line carries across calls), 36 cases, outputs 129 to 256 1·10⁻¹² relative

Verdict rule. The mapping passes only if every item is within its budget; otherwise the report says which are outside, by how much, and what is known about why.

What this can and cannot show. It shows how the rf.PAmemory System object of this MATLAB release treats the coefficient matrix and its initial delay line, for the memory-polynomial model with the sizes above. It does not cover the RF Blockset amplifier block (a Simulink block that may be implemented differently), the other nonlinearity models of rf.PAmemory (cross-term, generalised memory polynomial), the UnitDelay property at values other than its default, other MATLAB releases, or anything about an amplifier. Reproduce: parityPAmemory in Matlab/toolbox/examples/parity (needs RF Toolbox and Communications Toolbox).

Results

Verdict: the registered memory-polynomial parity passes — 21 of 21 scored items are within budget, with margins of several orders of magnitude (the largest scored difference relative to its budget is Q5, 2.5·10⁻⁸ against 1·10⁻⁶, which is the float32 interface). Run on MATLAB R2026a Update 5 (Communications Toolbox 26.1), Linux. The working tree had uncommitted changes in documentation files under Matlab/toolbox only; no code under test, no script and no .m file was modified.

What the scored items show:

  • The two tools define the same polynomial. comm.DPD with the true coefficients reproduces the independently constructed input to 6·10⁻¹⁶ relative (Q3), in all four cases.
  • Both estimators recover a known answer in the same layout. From the constructed exact problem the MathWorks estimator returns c_true with relative error at most 4.7·10⁻¹³ (Q1) and OpenDPD's fit at most 8.9·10⁻¹⁴ (Q2), including the cases whose unscaled condition number is 1.4·10⁵ and 3.1·10⁵. The layout is Coef = reshape(w, Q, K) with Degree = K and MemoryDepth = Q; an asymmetric c_true rules out a transposed or reordered layout agreeing by accident.
  • On the plant data they give the same predistorter. With the complete-memory rows and DesiredAmplitudeGaindB = 10·log10(G), the coefficient vectors agree to at most 3.9·10⁻¹³ (D1) and the predistorter outputs to at most 4.8·10⁻¹³ relative (Q4).
  • comm.DPD can run OpenDPD's coefficients. Through the PolynomialModel module's float32 interface the outputs differ by 2.5·10⁻⁸ relative (Q5), which is float32 rounding.
  • The run service stores what the compute core computes. The coefficients in the checkpoint of the SDK-trained mp_ls DPD run are identical to the fit recomputed from the run's stored train split (Q6: difference 0).

What the diagnostics add (no budget, so no verdict, but they are the practical content for a MATLAB user):

  • The gain setting must be 10·log10(G). Reading DesiredAmplitudeGaindB as 20·log10(G) (D3) gives coefficients that differ from OpenDPD's by 83 % to 97 %. The estimator divides PA_output by 10^(DesiredAmplitudeGaindB/10); to reproduce OpenDPD's G = max|y| / max|x| the property is 10·log10(G).
  • An OpenDPD run and the MathWorks estimator differ by the zero-filled rows, and by nothing else we could find. OpenDPD includes the first Q-1 rows of every nperseg segment, with delays filled by zeros, because the model is evaluated with exactly that state reset; MathWorks uses only rows with complete memory (D2). With nperseg = 2048 the coefficient vectors differ by 0.6 % and 0.9 % for K = 5, Q = 3, and by 11 % and 19 % for K = 7, Q = 5, where the basis is worse conditioned; the predistorter outputs on the same signal differ by 0.12 % to 0.70 % relative (0.03 % to 0.54 % when the segmented coefficients are applied continuously). The residual of the segmented fit on the complete-memory rows is at most 0.11 dB worse than that of the complete-memory fit (below 0.01 dB for K = 5, Q = 3 on S5). Through the product path (D4, K = 5, Q = 3) the MathWorks estimator differs from the checkpoint by 0.624 %, and the segmented fit differs from the complete-memory fit by 0.623 % on the same split: the difference is accounted for by the zero-filled rows.

Limits of this evidence:

  • One MATLAB release on one platform; a synthetic memory-polynomial amplifier; degrees up to 7 and memory depths up to 5, in a conditioning range of about 4·10³ to 3·10⁵. Nothing here says how the two behave on a rank-deficient or much worse conditioned basis, where OpenDPD's column scaling and rcond cutoff and MathWorks' unscaled QR solve would be expected to diverge.
  • The scored items of the OpenDPD side call the compute-core functions the run service calls. Q6 ties them to one SDK-trained run; it does not test every model configuration the service accepts.
  • Only the memory polynomial is compared. OpenDPD's GMP and the cross-term memory polynomial of MathWorks have different bases; the GRU family has no comm.* counterpart.

What this run does not do: it does not change how mp_ls is fitted. Whether to offer a complete-memory-rows option for MATLAB-comparable coefficients is a decision for the maintainers; it would not change the frozen Arena baselines, which are fitted as they are.

Reproduce: python scripts/matlab_parity_dpd.py --work /tmp/parity-dpd --matlab <path to matlab> --write-report (about two minutes; needs the Communications Toolbox and an OpenDPD checkout). Exit status 1 means the parity did not pass as registered.

Run 1 — 2026-10-09. MATLAB 26.1.0.3346908 (R2026a) Update 5; Communications Toolbox 26.1. Python 3.13.14, NumPy 2.4.4, SciPy 1.18.0, PyTorch 2.13.0+cu132. OpenDPD commit cc5b90792613 with local modifications.

Scored items: 21 of 21 within budget, 0 outside budget, 0 not evaluable — the registered memory-polynomial parity passes.

Scored items

id item difference budget within
Q1 C1 (S4, K=5, Q=3): MATLAB estimator on the exact problem vs c_true 1.779e-14 1e-06 yes
Q2 C1 (S4, K=5, Q=3): OpenDPD fit on the exact problem vs c_true 5.46e-15 1e-06 yes
Q3 C1 (S4, K=5, Q=3): comm.DPD(c_true) on y/G vs the constructed input 2.263e-16 1e-09 yes
Q4 C1 (S4, K=5, Q=3): predistorter outputs, each tool with its own coefficients 1.554e-14 1e-06 yes
Q5 C1 (S4, K=5, Q=3): the same coefficients applied by comm.DPD and by the PolynomialModel module (float32) 2.535e-08 1e-06 yes
Q1 C2 (S4, K=7, Q=5): MATLAB estimator on the exact problem vs c_true 4.745e-13 1e-06 yes
Q2 C2 (S4, K=7, Q=5): OpenDPD fit on the exact problem vs c_true 8.316e-14 1e-06 yes
Q3 C2 (S4, K=7, Q=5): comm.DPD(c_true) on y/G vs the constructed input 3.179e-16 1e-09 yes
Q4 C2 (S4, K=7, Q=5): predistorter outputs, each tool with its own coefficients 2.956e-14 1e-06 yes
Q5 C2 (S4, K=7, Q=5): the same coefficients applied by comm.DPD and by the PolynomialModel module (float32) 2.537e-08 1e-06 yes
Q1 C3 (S5, K=5, Q=3): MATLAB estimator on the exact problem vs c_true 3.164e-14 1e-06 yes
Q2 C3 (S5, K=5, Q=3): OpenDPD fit on the exact problem vs c_true 1.211e-14 1e-06 yes
Q3 C3 (S5, K=5, Q=3): comm.DPD(c_true) on y/G vs the constructed input 3.335e-16 1e-09 yes
Q4 C3 (S5, K=5, Q=3): predistorter outputs, each tool with its own coefficients 2.556e-14 1e-06 yes
Q5 C3 (S5, K=5, Q=3): the same coefficients applied by comm.DPD and by the PolynomialModel module (float32) 2.531e-08 1e-06 yes
Q1 C4 (S5, K=7, Q=5): MATLAB estimator on the exact problem vs c_true 1.246e-13 1e-06 yes
Q2 C4 (S5, K=7, Q=5): OpenDPD fit on the exact problem vs c_true 8.935e-14 1e-06 yes
Q3 C4 (S5, K=7, Q=5): comm.DPD(c_true) on y/G vs the constructed input 5.918e-16 1e-09 yes
Q4 C4 (S5, K=7, Q=5): predistorter outputs, each tool with its own coefficients 4.76e-13 1e-06 yes
Q5 C4 (S5, K=7, Q=5): the same coefficients applied by comm.DPD and by the PolynomialModel module (float32) 2.537e-08 1e-06 yes
Q6 C1 data through the SDK: DPD run checkpoint vs the segmented fit recomputed from the run's train split 0 1e-06 yes

The difference of Q1–Q6 is relative: ‖a − b‖ / ‖b‖ over the flattened complex vectors.

Diagnostics (no budget)

id item value note
D1 C1 (S4, K=5, Q=3): coefficients, MATLAB estimator vs OpenDPD (complete-memory rows) 9.527e-15 unscaled condition number 4.27e+03; OpenDPD fit residual -44.69 dB; rank 15
D2 C1 (S4, K=5, Q=3): coefficients, MATLAB estimator vs OpenDPD segmented fit (nperseg 2048) 0.005697 residual on complete-memory rows (dB): MATLAB -44.69, OpenDPD complete-memory -44.69, OpenDPD segmented -44.68
D2 C1 (S4, K=5, Q=3): predistorter outputs, MATLAB vs OpenDPD segmented fit applied per segment 0.001221 applied continuously instead: 0.000314
D3 C1 (S4, K=5, Q=3): coefficients with DesiredAmplitudeGaindB = 20 log10(G) vs OpenDPD 0.8269 —
D1 C2 (S4, K=7, Q=5): coefficients, MATLAB estimator vs OpenDPD (complete-memory rows) 1.702e-13 unscaled condition number 1.37e+05; OpenDPD fit residual -47.96 dB; rank 35
D2 C2 (S4, K=7, Q=5): coefficients, MATLAB estimator vs OpenDPD segmented fit (nperseg 2048) 0.111 residual on complete-memory rows (dB): MATLAB -47.96, OpenDPD complete-memory -47.96, OpenDPD segmented -47.87
D2 C2 (S4, K=7, Q=5): predistorter outputs, MATLAB vs OpenDPD segmented fit applied per segment 0.001357 applied continuously instead: 0.000774
D3 C2 (S4, K=7, Q=5): coefficients with DesiredAmplitudeGaindB = 20 log10(G) vs OpenDPD 0.93 —
D1 C3 (S5, K=5, Q=3): coefficients, MATLAB estimator vs OpenDPD (complete-memory rows) 1.494e-14 unscaled condition number 6.65e+03; OpenDPD fit residual -27.11 dB; rank 15
D2 C3 (S5, K=5, Q=3): coefficients, MATLAB estimator vs OpenDPD segmented fit (nperseg 2048) 0.008705 residual on complete-memory rows (dB): MATLAB -27.11, OpenDPD complete-memory -27.11, OpenDPD segmented -27.11
D2 C3 (S5, K=5, Q=3): predistorter outputs, MATLAB vs OpenDPD segmented fit applied per segment 0.002114 applied continuously instead: 0.000776
D3 C3 (S5, K=5, Q=3): coefficients with DesiredAmplitudeGaindB = 20 log10(G) vs OpenDPD 0.9166 —
D1 C4 (S5, K=7, Q=5): coefficients, MATLAB estimator vs OpenDPD (complete-memory rows) 3.925e-13 unscaled condition number 3.08e+05; OpenDPD fit residual -28.00 dB; rank 35
D2 C4 (S5, K=7, Q=5): coefficients, MATLAB estimator vs OpenDPD segmented fit (nperseg 2048) 0.1852 residual on complete-memory rows (dB): MATLAB -28.00, OpenDPD complete-memory -28.00, OpenDPD segmented -27.89
D2 C4 (S5, K=7, Q=5): predistorter outputs, MATLAB vs OpenDPD segmented fit applied per segment 0.007003 applied continuously instead: 0.00541
D3 C4 (S5, K=7, Q=5): coefficients with DesiredAmplitudeGaindB = 20 log10(G) vs OpenDPD 0.9712 —
D4 MATLAB estimator on the run's train split vs the checkpoint 0.00624 —
D4 segmented fit minus complete-memory fit on the same split (what the zero-filled rows change) 0.006233 —

Cases

case record K Q G unscaled condition number OpenDPD fit residual (dB) rank
C1 S4 5 3 0.567423 4.27e+03 -44.69 15
C2 S4 7 5 0.567423 1.37e+05 -47.96 35
C3 S5 5 3 0.403027 6.65e+03 -27.11 15
C4 S5 7 5 0.403027 3.08e+05 -28.00 35

Results of amendment 2: rf.PAmemory (run 2, 2026-10-09)

Verdict: every registered item is within its budget on MATLAB R2026a Update 5 (RF Toolbox 26.1, Communications Toolbox 26.1), Linux, from parityPAmemory in Matlab/toolbox/examples/parity (it prints this table and computes the verdict from the registered budgets).

Item Cases Worst relative difference (case) Budget
R1 rf.PAmemory on [zeros(Q-1,1); x] against comm.DPD on x 36 4.2·10⁻¹⁶ (Q = 4, K = 5, seed 13) 1·10⁻¹²
R2 first Q − 1 outputs against comm.DPD on x preceded by Q − 1 copies of x(1) 27 1.3·10⁻¹⁶ (Q = 7, K = 1, seed 12) 1·10⁻¹²
R3 opendpd.apply of the mp_ls-dpd fixture against rf.PAmemory with commCoefficients() 3 4.5·10⁻⁸ (seed 11) 1·10⁻⁶
R4 a second call on the same object against one call on the whole signal 36 0 (all 36 identical) 1·10⁻¹²

What this shows, for the rf.PAmemory System object of this release and the memory-polynomial model: the coefficient matrix has the layout of comm.DPD, so the matrix OpenDPD's commCoefficients() returns can be given to rf.PAmemory unchanged (R1, R3); the delay line of rf.PAmemory starts filled with the first input sample, not with zeros, which is the only difference from comm.DPD and from OpenDPD's evaluation of a segment (R2); to reproduce OpenDPD's zero-history segment, prepend Q − 1 zeros and drop the first Q − 1 outputs (R1, R3); and the delay line carries across calls, so a stream can be fed in chunks (R4).

What it does not show: the RF Blockset amplifier block, the other nonlinearity models of rf.PAmemory, the UnitDelay property at values other than its default, other releases, or anything about an amplifier. Two notes on the registration, made here and not edited into it: the phrase "complex row-independent test signal" means independent identically distributed complex Gaussian samples (0.3·(randn + 1j·randn)), and the registration did not fix how the coefficient matrix is drawn, so the script draws it from the seed 1000 + 10·Q + K; neither was changed after seeing a number.