-
Notifications
You must be signed in to change notification settings - Fork 17
/
PowerAmplifier.m
163 lines (136 loc) · 6.74 KB
/
PowerAmplifier.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
classdef PowerAmplifier
%PowerAmplifier Construct a PA, broadcast throough it, or model a PA.
properties
poly_coeffs %matrix where each column corresponds to memory effects and each row is for a nonlinearity.
order
memory_depth
nmse_of_fit
sample_rate
end
methods
function obj = PowerAmplifier(order, memory_depth)
%POWERAMPLIFIER Construct an instance of this class.
% This will initialize with a basic Parallel Hammerstein PA model
% that was extracted from a WARP board.
%
% Args:
% order: int with PA order. Should be odd. 1, 3, 5, etc.
% memory_depth: int with number of taps in FIR filter
if nargin == 0
order = 7;
memory_depth = 4;
end
if mod(order,2) == 0
error('Order must be odd.');
end
obj.order = order;
obj.memory_depth = memory_depth;
% Default polynomial coeffs derived from a WARP board.
default_poly_coeffs = [ 0.9295 - 0.0001i, 0.2939 + 0.0005i, -0.1270 + 0.0034i, 0.0741 - 0.0018i; % 1st order coeffs
0.1419 - 0.0008i, -0.0735 + 0.0833i, -0.0535 + 0.0004i, 0.0908 - 0.0473i; % 3rd order coeffs
0.0084 - 0.0569i, -0.4610 + 0.0274i, -0.3011 - 0.1403i, -0.0623 - 0.0269i;% 5th order coeffs
0.1774 + 0.0265i, 0.0848 + 0.0613i, -0.0362 - 0.0307i, 0.0415 + 0.0429i]; % 7th order coeffs
% Prune the model to have the desired number of nonlinearities and memory effects.
obj.poly_coeffs = default_poly_coeffs(1:obj.convert_order_to_number_of_coeffs, 1:memory_depth);
obj.sample_rate = 122.88e6;
end
function [y, pa_output_signal] = transmit(obj, in)
%transmit Broadcast the input data using the PA model currently stored in
%the object.
%
% obj.transmit(in) send in through the PH model that is stored in the
% object. It expands the input into a matrix where the columns
% are the different nonlinear branches or delayed versions of the
% nonlinear branches to model the FIR filter. A product can
% be done with the coefficient to get the PA output.
%
% Author: Chance Tarver (2018)
% tarver.chance@gmail.com
%
X = obj.setup_basis_matrix(in);
coeffs = reshape(obj.poly_coeffs.',[],1);
pa_output = X * coeffs;
y = pa_output * norm(pa_output) / norm(pa_output);
pa_output_signal = Signal(pa_output, obj.sample_rate);
end
function obj = make_pa_model(obj, in, out)
%make_pa_model Learn a PA model
% obj.make_pa_model(in, out) finds the best LS fit for a Parallel
% Hammerstein power amplifier. The 'in' is a column vector that is
% the signal that you put into the PA. The 'out' is a column
% vector that is the output of a real PA. This function will
% store the learned coefficients in obj.poly_coeffs. The PA model
% can be used by calling the transmit method.
%
% This method also finds the NMSE of the derived PA model and
% stores it in obj.nmse_of_fit.
%
% The LS regression solution is standard. Can be derrived by
% doing the sum_i [y_i - (beta_0 x_i + beta_! x_i)^2]
% optimization. The PA model is linear with respect to the
% coefficients.
%
% I am using a Regularization. It helps with the condition of the matrix
% http://www.signal.uu.se/Research/PCCWIP/Visbyrefs/Viberg_Visby04.pdf
% I just used a really small lambda.
%
% Author: Chance Tarver (2018)
% tarver.chance@gmail.com
%
%% Construct signal matrix with basis vectors for each nonlinearity
y = out;
X = obj.setup_basis_matrix(in);
%% LS solution to get the optimal coefficients.
%coeffs = (X'*X) \ (X'*y);
lambda = 0.001;
coeffs = (X'*X + lambda*eye(size((X'*X)))) \ (X'*y);
%Reshape for easier to understand matrix of coeffs
coeffs_transpose = reshape(coeffs, [obj.memory_depth, obj.convert_order_to_number_of_coeffs]);
obj.poly_coeffs = coeffs_transpose.';
%% NMSE of the derived PA
model_pa_output = obj.transmit(in);
obj.nmse_of_fit = obj.calculate_nmse(y, model_pa_output);
end
function nmse = calculate_nmse(~, desired, actual)
%calculate_nmse. Calculate the normalized mean square error.
% equivalent to sum (error)2 / sum(desired)^2
nmse = norm(desired - actual)^2 / norm(desired)^2;
end
function X = setup_basis_matrix(obj, x)
%setup_basis_matrix. Setup the basis matrix for the LS learning of
%the PA parameters or for broadcasting through the PA model.
%
% obj.setup_basis_matrix(x)
% Inputs:
% x - column vector of the PA input signal.
% Output:
% X - matrix where each column is the signal, delayed version of
% a signal, signal after going through a nonlinearity, or both.
%
% Author: Chance Tarver (2018)
% tarver.chance@gmail.com
%
number_of_basis_vectors = obj.memory_depth * obj.convert_order_to_number_of_coeffs;
X = zeros(length(x), number_of_basis_vectors);
count = 1;
for i = 1:2:obj.order
branch = x .* abs(x).^(i-1);
for j = 1:obj.memory_depth
delayed_version = zeros(size(branch));
delayed_version(j:end) = branch(1:end - j + 1);
X(:, count) = delayed_version;
count = count + 1;
end
end
end
function number_of_coeffs = convert_order_to_number_of_coeffs(obj, order)
%convert_order_to_number_of_coeffs. Helper function to easily
%convert the order to number of coeffs. We need this because we
%only model odd orders.
if nargin == 1
order = obj.order;
end
number_of_coeffs = (order + 1) / 2;
end
end
end