Gain.m 13.8 KB
Newer Older
1
2
3
classdef Gain < handle & matlab.mixin.Copyable
%Gain is class to enease implementation of multiple different input 
%for instance control, disturbance, fault inputs or couplings with other
4
5
%systems. Only finite inputs are considered, see dps.Input for inputs for 
%distributed parameter systems.
6
7
8
9
10
11
%It is assumed, that the input signal is multiplied with the input matrices
%from right side, i.e. B * u(t).
%Gain is only one input, for use of multiple input-signals, use Gains!
	
	properties
		% gain matrices
12
		value (:,:);				% value
13
14
		
		% data about gain
15
16
17
18
		inputType (1, 1) string;				% type of input signal, for instance 
		%							% "control", "disturbance", "fault" etc
		outputType (:, 1) string;			% type of output signal, for instance 
		%							% "measurement", "ode", ...
19
20
21
22
23
		lengthInput (1, 1) double;	% number of columns of gain value
		lengthOutput (:, 1) double;	% number of rows of gain value per outputType
	end % properties
	
	properties (Dependent = true)
24
25
		OutputName (:, 1) cell;		% enumerated input type of length lengthOutput
		InputName (1, 1) cell;		% enumerated output type of length lengthInput
26
	end % properties (Dependent = true)
27
28
	
	methods
29
		function obj = Gain(inputType, value, varargin)
30
31
32
33
34
35
			%GAIN object for the gain of a input
			% obj = Gain(INPUTTYPE, VALUE, varargin) initializes an object
			% to specify the gain values of an input. Use INPUTTYPE as char
			% to specify the name of this input and VALUE to define the
			% value of the input.
			%	
36
37
			if nargin > 0
				% read input
38
				myParser = misc.Parser();
39
				myParser.addParameter("outputType", "out", ...
40
					@(v) iscell(v) | ischar(v) | isstring(v));
41
				myParser.addParameter("lengthOutput", size(value, 1), ...
42
43
44
45
					@(v) isvector(v) & isnumeric(v));
				myParser.parse(varargin{:});
				
				obj.inputType = inputType;
46
				obj.value = value;
47
48
49
				if iscell(myParser.Results.outputType)
					obj.outputType = myParser.Results.outputType;
				else
50
					obj.outputType = myParser.Results.outputType;
51
				end
52
53
				
				% set lengths
54
55
56
57
58
59
60
				obj.lengthInput = size(value, 2);
				obj.lengthOutput = myParser.Results.lengthOutput;
				if numel(obj.outputType) > 1
					% check outputType and lengthOutput
					assert(numel(obj.outputType) == numel(obj.lengthOutput));
					assert(sum(obj.lengthOutput) == size(value, 1));
				end
Jakob Gabriel's avatar
Jakob Gabriel committed
61
			end % if nargin > 0
62
63
64
65
		end % Gain() Constructor
		
		function myGains = plus(a, b)
			% combining misc.Gain and misc.Gains
66
67
68
69
			if isempty(a)
				myGains = copy(b);
			elseif isempty(b)
				myGains = copy(a);
70
			elseif isa(b, "misc.Gains")
71
				myGains = b + a;
72
			elseif isa(b, "misc.Gain")
73
				if strcmp(b.inputType, a.inputType)
74
					myGains = parallel(a, b);
75
				else
76
					myGains = misc.Gains(copy(a), copy(b));
77
78
				end
			else
79
				error("misc.Gain can only be added with other Gain or misc.Gains");
80
81
82
			end
		end % plus()
		
83
84
85
86
		function myGains = add(a, b)
			myGains = a + b;
		end % add()
		
Jakob Gabriel's avatar
Jakob Gabriel committed
87
88
89
90
91
92
93
94
95
96
97
		function obj = exchange(obj, newGain)
			if all(strcmp(obj.inputType, newGain.inputType))
				for it = 1 : numel(newGain.outputType)
					thisOutputRows = obj.getOutputRows(newGain.outputType{it});
					if ~isempty(thisOutputRows)
						obj.value(thisOutputRows, :) = newGain.valueOfOutput(newGain.outputType{it});
					end
				end
			end
		end % exchange()
		
98
		function mySeries = series(obj, nextGain)
99
			if isa(nextGain, "misc.Gain")
100
				mySeries = misc.Gain(nextGain.inputType, obj.value * nextGain.value);
101
				
102
			elseif isa(nextGain, "misc.Gains") && (numel(nextGain.inputType) > 0)
103
104
				mySeries = misc.Gain(nextGain.gain(1).inputType, obj.value * nextGain.gain(1).value);
				for it = 2 : numel(nextGain.inputType)
105
					mySeries = mySeries + ...
106
						misc.Gain(nextGain.gain(it).inputType, obj.value * nextGain.gain(it).value);
107
108
109
110
				end
			end
		end % series()
		
111
112
113
114
115
116
117
		function myParallel = parallel(obj, varargin)
			myParallel = copy(obj);
			for it = 1 : numel(varargin)
				outputNext = varargin{it};
				% check sizes
				assert((obj.lengthInput == outputNext.lengthInput) ...
					&& strcmp(obj.inputType, outputNext.inputType), ...
118
					"Parallel outputs must be defined for same input length");
119
120
121
122
123
124
125
126
127
128
129
130
				
				for jt = 1 : numel(outputNext.outputType)
					if any(strcmp(myParallel.outputType, outputNext.outputType{jt}))
						
						outputIdx = find(strcmp(myParallel.outputType, outputNext.outputType{jt}));
						selectRowsOfOutput = ...
							sum(myParallel.lengthOutput(1:(outputIdx-1))) + (1 : myParallel.lengthOutput(outputIdx));
						myParallel.value(selectRowsOfOutput, :) = myParallel.value(selectRowsOfOutput, :) ...
							+ outputNext.valueOfOutput(outputNext.outputType{jt});
					else
						myParallel = ...
							misc.Gain(obj.inputType, ...
131
132
133
								[myParallel.value; outputNext.valueOfOutput(outputNext.outputType(jt))], ...
								"outputType", [myParallel.outputType; outputNext.outputType(jt)], ...
								"lengthOutput", [myParallel.lengthOutput; outputNext.lengthOutput(jt)]);
134
135
136
137
138
					end
				end
			end
		end % parallel()
		
Jakob Gabriel's avatar
Jakob Gabriel committed
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
		function newGain = blkdiag(a, b, varargin)
			% blkdiag combines two (or more) misc.Gain objects by combining them blockdiagonally.
			%                                     |A 0 .. 0|
			%     Y = blkdiag(A,B,...)  produces  |0 B .. 0|
			%                                     |0 0 ..  |
			% All misc.Gain objects must have same inputType and outputType.
			assert(isequal(a.inputType, b.inputType), "for blkdiag the inputTypes must be equal");
			assert(isequal(a.outputType, b.outputType), "for blkdiag the outputTypes must be equal");
			newGain = misc.Gain(a.inputType, blkdiag(a.value, b.value), "outputType", a.outputType);
			
			if nargin > 2
				newGain = blkdiag(newGain, varargin{:});
			end
		end % blkdiag()
		
154
		function value = inputType2sumblk(obj)
155
			% returns the string: type(1) + "+" + type(2) + "+" + ...
156
157
158
			value = obj.inputType;
		end % inputType2sumblk()
		
159
160
161
		function mySs = gain2ss(obj, varargin)
			% returns a cell-array containing a ss() representing the gain with the
			% properties InputName and OutputName set according to inputType and
162
163
			% outputType.
			mySs = ss([], [], [], obj.value, ...
164
165
				"InputName", obj.InputName, ...
				"OutputName", obj.OutputName);
166
167
		end % gain2ss()
		
Jakob Gabriel's avatar
Jakob Gabriel committed
168
169
170
171
172
		function thisOutputRows = getOutputRows(obj, outputType)
			outputIdx = find(strcmp(obj.outputType, outputType));
			thisOutputRows = sum(obj.lengthOutput(1:(outputIdx-1))) + (1 : obj.lengthOutput(outputIdx));
		end % getOutputRows()
		
173
174
175
176
		function newObj = gainOfOutput(obj, outputTypes)
			arguments
				obj;
				outputTypes (:, 1) string;
177
			end
178
179
			outputTypes = outputTypes(strcmp(obj.outputType, outputTypes));
			if isempty(outputTypes)
180
181
182
				newObj = misc.Gain();
			else
				newObj = misc.Gain(obj.inputType, ...
183
184
185
						obj.valueOfOutput(outputTypes), ...
						"outputType", outputTypes, ...
						"lengthOutput", obj.lengthOfOutput(outputTypes));
186
			end
187
188
		end % gainOfOutput()
		
Jakob Gabriel's avatar
Jakob Gabriel committed
189
		function thisValue = valueOfOutput(obj, outputNames)
190
191
192
			% obj.valueOfOutput() returns the gain matrix specified by the
			% outputNames in the input parameter. The input outputNames must be a
			% string or char-array or a cell array of strings or char-arrays.
193
194
195
			if ~iscell(outputNames)
				outputNames = {outputNames};
			end
196
			% get gain matrix of first output
Jakob Gabriel's avatar
Jakob Gabriel committed
197
			thisValue = obj.value(obj.getOutputRows(outputNames{1}), :);
198
199
			
			% successivly add gain matrices of further outputs
200
			if numel(outputNames) > 1
Jakob Gabriel's avatar
Jakob Gabriel committed
201
202
203
204
				thisValue = [thisValue; obj.valueOfOutput(outputNames{2:end})];
			end
		end % valueOfOutput(obj, outputNames)
		
205
206
207
208
209
		function obj = removeOutput(obj, outputType)
			myOutputRows = getOutputRows(obj, outputType);
			if ~isempty(myOutputRows)
				myOutputRowsSelector = true(sum(obj.lengthOutput), 1);
				myOutputRowsSelector(myOutputRows) = false;
210
211
212
213
214
215
216
				newLengthOutput = obj.lengthOutput(~strcmp(obj.outputType, outputType));
				newOutputType = obj.outputType(~strcmp(obj.outputType, outputType));
				newValue = obj.value(myOutputRowsSelector, :);
				if isempty(newOutputType)
					obj = misc.Gain();
				else
					obj = misc.Gain(obj.inputType, newValue, ...
217
218
						"outputType", newOutputType, ...
						"lengthOutput", newLengthOutput);
219
220
221
222
				end
% 				obj.value = obj.value(myOutputRowsSelector, :);
% 				obj.outputType = obj.outputType(~strcmp(obj.outputType, outputType));
% 				obj.lengthOutput = lengthOutputBackup(~strcmp(obj.outputType, outputType));
223
224
225
226
				obj.verifySizes();
			end
		end % removeOutput()
		
227
228
229
230
231
		function out = dps.Outputs(obj)
			% convert to dps.Outputs-object
			out = dps.Outputs();
			for it = 1 : numel(obj.outputType)
				out = out.add(dps.Output(...
232
					obj.outputType(it), "input", copy(obj.gainOfOutput(obj.outputType(it)))));
233
234
235
			end % for it = 1 : numel(obj.outputType)
		end % dps.Output
		
Jakob Gabriel's avatar
Jakob Gabriel committed
236
237
238
239
240
241
242
		function thisLength = lengthOfOutput(obj, varargin)
			if nargin > 1
				thisOutputType = varargin{1};
				thisLength = obj.lengthOutput(strcmp(obj.outputType, thisOutputType));
				thisLength = [thisLength; obj.lengthOfOutput(varargin{2:end})];
			else
				thisLength = [];
243
			end
Jakob Gabriel's avatar
Jakob Gabriel committed
244
		end % lengthOfOutput(obj, outputNames)
245
246
247
		
		function result = isempty(obj)
			result = false(size(obj));
248
249
250
251
252
253
254
			if isempty(result) % empty size of obj
				result = true;
			else
				for it = 1 : numel(obj) % non empty size of obj, so check value
					result(it) = isempty(obj(it).value);
				end
			end
255
		end % isempty
256
		
257
		function verifySizes(obj)
258
			assert(isequal([sum(obj.lengthOutput), obj.lengthInput], size(obj.value)));
259
260
		end % verifySizes(obj)
		
261
262
		function obj = strrepOutputType(obj, oldText, newText)
			% replace strings in type
263
264
			assert(ischar(oldText) || isstring(oldText), "oldText must be a char-array or string");
			assert(ischar(newText) || isstring(newText), "newText must be a char-array or string");
Jakob Gabriel's avatar
Jakob Gabriel committed
265
			for it = 1 : numel(obj.outputType)
266
				obj.outputType(it) = strrep(obj.outputType(it), oldText, newText);
267
			end
Jakob Gabriel's avatar
Jakob Gabriel committed
268
269
		end % strrepOutputType()
		
270
271
		function obj = extendOutputType(obj, position, newText)
			% extendOutputType adds a pre- or a postfix to obj.outputType
272
273
			assert(any(strcmp(position, ["front", "back"])));
			assert(ischar(newText) || isstring(newText), "prefix must be a char-array or string");
274
275
			
			for it = 1 : numel(obj.outputType)
276
277
278
279
				if strcmp(position, "front")
					obj.outputType(it) = [newText, obj.outputType(it)];
				elseif strcmp(position, "back")
					obj.outputType(it) = [obj.outputType(it), newText];
280
281
282
283
				end
			end
		end % extendOutputType()
		
Jakob Gabriel's avatar
Jakob Gabriel committed
284
285
		function obj = strrepInputType(obj, oldText, newText)
			% replace strings in type
286
287
			assert(ischar(oldText) || isstring(oldText), "oldText must be a char-array or string");
			assert(ischar(newText) || isstring(newText), "newText must be a char-array or string");
Jakob Gabriel's avatar
Jakob Gabriel committed
288
289
			obj.inputType = strrep(obj.inputType, oldText, newText);
		end % strrepInputType()
290
		
291
292
		function obj = extendInputType(obj, position, newText)
			% extendInputType adds a pre- or a postfix to obj.inputType
293
294
			assert(any(strcmp(position, ["front", "back"])));
			assert(ischar(newText) || isstring(newText), "prefix must be a char-array or string");
295
			
296
			if strcmp(position, "front")
297
				obj.inputType = [newText, obj.inputType];
298
			elseif strcmp(position, "back")
299
300
301
302
				obj.inputType = [obj.inputType, newText];
			end
		end % extendInputType()
		
303
304
305
306
		function OutputName = get.OutputName(obj)
			OutputName = cell(sum(obj.lengthOutput), 1); 
			myCounter = 1;
			for jt = 1 : numel(obj.lengthOutput)
307
308
				if (obj.lengthOutput(jt) == 1)
					% no enumeration in this case
309
					OutputName{myCounter} = char(obj.outputType(jt));
310
					myCounter = myCounter + 1;
311
312
				else
					for it = 1 : sum(obj.lengthOutput(jt))
313
						OutputName{myCounter} = [char(obj.outputType(jt)), '(', num2str(it), ')'];
314
315
						myCounter = myCounter + 1;
					end
316
317
318
319
320
321
				end
			end
		end % get.OutputName()
		
		function InputName = get.InputName(obj)
			InputName = cell(sum(obj.lengthInput), 1); 
322
323
			if (obj.lengthInput == 1)
				% no enumeration in this case
324
				InputName{1} = char(obj.inputType);
325
326
			else
				for it = 1 : sum(obj.lengthInput)
327
					InputName{it} = [char(obj.inputType) ,'(', num2str(it), ')'];
328
				end
329
330
331
			end
		end % get.InputName()
		
332
333
334
		function result = isequal(A, B, varargin)
			% isequal compares all parameters of A and B and varargin and returns
			% true if they are equal, and false if not.
335
336
337
338
339
340
			if (numel(A) == 0) || (numel(B) == 0)
				result = (numel(A)==numel(B));
			else
				result = isequal(A.value, B.value) & strcmp(A.inputType, B.inputType) ...
					& all(strcmp(A.outputType, B.outputType));
			end
341
			if result && (nargin > 2)
342
				result = isa(B, "misc.Gain") & B.isequal(varargin{:});
343
344
345
			end
		end % isequal()
		
346
		function [texString, nameValuePairs] = printParameter(obj)
347
348
349
			% Create TeX-code for all non-zero parameter. In this method, the
			% implementation of misc.Gains is used.
			[texString, nameValuePairs] = print(misc.Gains(obj));
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
		end % printParameter
		
		function texString = print(obj, outputType, inputName, leftHandSide)
			% print Print equation to command window as latex compatible string-array
			
			arguments
				obj;
				outputType;
				inputName = obj.inputType(1) + "_{" + obj.inputType(2:end) + "}(t)";
				leftHandSide = "none";
			end % arguments
			
			% put string together
			if isnumeric(leftHandSide)
				if isscalar(leftHandSide) && obj.lengthOutput > 1
					leftHandSide = leftHandSide * ones(obj.lengthOutput, 1);
				end
				leftHandSide = misc.latexChar(leftHandSide);
			end
			if strcmp(leftHandSide, "none")
				myString = "";
			else
				myString = leftHandSide + " &=";
			end
374
			if ~isempty(obj.valueOfOutput(outputType)) && any(obj.valueOfOutput(outputType) ~=0, "all")
Jakob Gabriel's avatar
Jakob Gabriel committed
375
376
377
				myString = myString + misc.latexChar(obj.valueOfOutput(outputType)) ...
					+ " " + inputName;
			end
378
379
380
381
382
383
384
			
			if nargout > 0
				texString = myString;
			else
				misc.printTex(myString);
			end
		end % print()
385
		
386
387
388
389
390
391
392
393
394
395
	end % methods
	
	methods (Access = protected)
		% Override copyElement method:
		function cpObj = copyElement(obj)
			% Make a shallow copy of all properties
			cpObj = copyElement@matlab.mixin.Copyable(obj);
		end
	end % methods (Access = protected)
end