Commit a0c46ed9 authored by Jakob Gabriel's avatar Jakob Gabriel
Browse files

misc.ss: changed implementation to work with string arrays, instead of cells of chars.

parent 30f6ad07
......@@ -280,10 +280,11 @@ classdef Odes < handle & matlab.mixin.Copyable
% get ODE-lines
myOdeString = myOdeString + misc.latexChar(obj.odes.A) + NameValue.stateName;
if any(obj.odes.B ~= 0)
myOdeString = myOdeString + " + ";
if ~misc.iseye(obj.odes.B)
myOdeString = myOdeString + " + " + misc.latexChar(obj.odes.B);
myOdeString = myOdeString + misc.latexChar(obj.odes.B);
end
myOdeString = myOdeString + " + " + misc.latexChar(thisInputName);
myOdeString = myOdeString + misc.latexChar(thisInputName);
end
% get output-lines
......
......@@ -11,33 +11,49 @@ function myStateSpace = addInput2Output(myStateSpace)
% -------------------------------------------------------------------------
% mySimulationModel = ss(1, [1, 2, 3], [1; 2; 3], [1, 0, 0; 0, 0, 0; 0, 0, 0], ...
% 'OutputName', {'x', 'y', 'z'}, 'InputName', {'u(1)', 'u(2)', 'a'});
% mySimulationModel = misc.ss.addInput2Output(mySimulationModel);
% mySimulationModel = misc.ss.addInput2Output(mySimulationModel)
% -------------------------------------------------------------------------
arguments
myStateSpace ss;
end
% read InputName and OutputName
inputNameAll = misc.ss.removeEnumeration(unique(myStateSpace.InputName, 'stable'));
outputNameAll = misc.ss.removeEnumeration(unique(myStateSpace.OutputName, 'stable'));
inputNameAll = misc.ss.removeEnumeration(myStateSpace.InputName);
outputNameAll = misc.ss.removeEnumeration(myStateSpace.OutputName);
% count
inputName = unique(inputNameAll, 'stable');
inputLength = zeros(size(inputName));
for it = 1 : numel(inputName)
inputLength(it) = sum(strcmp(inputNameAll, inputName(it)));
end
outputName = unique(outputNameAll, 'stable');
inputNameCount = cellfun(@(v) sum(strcmp(inputNameAll, v)), inputName);
outputNameCount = cellfun(@(v) sum(strcmp(outputNameAll, v)), outputName);
outputLength = zeros(size(outputName));
for it = 1 : numel(outputLength)
outputLength(it) = sum(strcmp(outputNameAll, outputName(it)));
end
% add feedthrough
isFeedThroughNeccessary = false([numel(inputName), 1]);
for it = 1 : numel(inputName)
if any(strcmp(outputName, inputName{it}))
if any(strcmp(outputName, inputName(it)))
% if the input is already part of the output, assert that it has the same
% size
assert(inputNameCount(it) == outputNameCount(strcmp(outputName, inputName{it})), ...
assert(inputLength(it) == outputLength(strcmp(outputName, inputName(it))), ...
'There is already an output with the same name as an input, but they have different size!?');
isFeedThroughNeccessary(it) = false;
else
% Create a feedthrough and add it to myStateSpace
% feedthrough:
feedthroughTemp = ss([], [], [], eye(inputNameCount(it)), ...
'OutputName', inputName(it), 'InputName', inputName(it));
if inputLength(it) > 1
feedthroughTemp = ss([], [], [], eye(inputLength(it)), ...
"OutputName", inputName(it) + "(" + string(1:1:inputLength(it)).' + ")", ...
"InputName", inputName(it) + "(" + string(1:1:inputLength(it)).' + ")");
else
feedthroughTemp = ss([], [], [], eye(inputLength(it)), ...
"OutputName", inputName(it), ...
"InputName", inputName(it));
end
% connection:
% first myStateSpace is appended, then the input of the feedthrough and the
% state space are interconneted using series()
......@@ -46,20 +62,18 @@ for it = 1 : numel(inputName)
end
end
% create short cut of feedthrough and input
shortCutMatrix = eye(sum(inputNameCount));
shortCutMatrix = eye(sum(inputLength));
shortCutMatrixSelector = true(size(shortCutMatrix));
% remove rows that have now feedthrough
for it = 1 : numel(inputName)
if ~isFeedThroughNeccessary(it)
shortCutMatrixSelector(sum(inputNameCount(1:(it-1)))+(1:inputNameCount(it)), :) ...
= false;
shortCutMatrixSelector(sum(inputLength(1:(it-1)))+(1:inputLength(it)), :) = false;
end
end
shortCutMatrix = reshape(shortCutMatrix(shortCutMatrixSelector), ...
[sum(inputNameCount(isFeedThroughNeccessary)), sum(inputNameCount)]);
shortCut = ss([], [], [], [eye(sum(inputNameCount)); shortCutMatrix]);
shortCut = misc.ss.setSignalName(shortCut, 'input', inputName, inputNameCount);
[sum(inputLength(isFeedThroughNeccessary)), sum(inputLength)]);
shortCut = ss([], [], [], [eye(sum(inputLength)); shortCutMatrix]);
shortCut = misc.ss.setSignalName(shortCut, 'input', inputName, inputLength);
myStateSpace = series(shortCut, myStateSpace);
end
end % addInput2Output()
\ No newline at end of file
......@@ -7,15 +7,15 @@ function resultSs = connect(InputName, OutputName, options, varargin)
% remove empty elements of varargin
% note that empty(ss) does not work, since state space models without inputs are
% considered as empty. Hence, size(v, 1) > 0 is used instead.
if isa(options, 'ltioptions.connect')
if isa(options, "ltioptions.connect")
inputSs = varargin(cellfun(@(v) size(v, 1) > 0, varargin));
else
if isa(options, 'numlti')
if isa(options, "numlti")
inputSs = [{options}, varargin(cellfun(@(v) size(v, 1) > 0, varargin))];
elseif ~isempty(options)
error("third input must be a connectOptions() or a StateSpaceModel");
end
options = connectOptions('Simplify', false);
options = connectOptions("Simplify", false);
end
% clean state-spaces
......@@ -27,18 +27,18 @@ for it = 1 : numel(inputSs)
end
% get InputNames of result
resultInputNames = unique(misc.ss.removeSingularEnumeration(InputName), 'stable');
resultOutputNames = unique(misc.ss.removeSingularEnumeration(OutputName), 'stable');
resultInputNames = unique(misc.ss.removeSingularEnumeration(InputName), "stable");
resultOutputNames = unique(misc.ss.removeSingularEnumeration(OutputName), "stable");
if isempty(resultInputNames)
% as ss/connect does not support empty cell array for inputNames or outputNames, this dummy
% object is added for those cases.
resultInputNames = {"dummy.input"};
resultInputNames = "dummy.input";
resultOutputNames = [resultOutputNames; "dummy.output"];
inputSs = [inputSs, ...
{ss([], [], [], 0, "InputName", {"dummy.input"}, "OutputName", {"dummy.output"})}];
{ss([], [], [], 0, "InputName", "dummy.input", "OutputName", "dummy.output")}];
end
% call built-in function
resultSs = connect(inputSs{:}, resultInputNames, resultOutputNames, options);
end
\ No newline at end of file
end % misc.ss.connect()
\ No newline at end of file
function unenumerateNames = removeEnumeration(enumeratedNames, makeUnique)
% REMOVEENUMERATION removes braces with numbers from every char array that is an
% element of the cell array enumeratedNames. This is for instance useful, to deal
function unenumeratedNames = removeEnumeration(enumeratedNames, makeUnique)
% REMOVEENUMERATION removes braces with numbers from every string that is an
% element of the array enumeratedNames. This is for instance useful, to deal
% with InputName or OutputName properties of large state-space models (ss).
%
% unenumerateNames = removeEnumeration(enumeratedNames) returns the
% enumeratedNames cell-array without enumeration.
% enumeratedNames string-array without enumeration.
%
% unenumerateNames = removeEnumeration(enumeratedNames, true) returns enumeratedNames
% without enumeration, but only unique names. The order of names is not changed.
%
% Examples:
% misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'})
% misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'}, true)
unenumerateNames = cell(size(enumeratedNames));
for it = 1 : numel(enumeratedNames)
namesSplittedTemp = split(enumeratedNames{it}, '(');
unenumerateNames{it} = namesSplittedTemp{1};
% misc.ss.removeEnumeration(["a(1)", "a(2)", "b"])
% misc.ss.removeEnumeration(["a(1)", "a(2)", "b"], true)
arguments
enumeratedNames (:, 1) string;
makeUnique (1, 1) logical = false;
end
if nargin > 1 && makeUnique
unenumerateNames = unique(unenumerateNames, 'stable');
unenumeratedNames = enumeratedNames;
enumeratedIndex = endsWith(enumeratedNames, ")") & contains(enumeratedNames, "(");
if any(enumeratedIndex)
splittedEnumeratedNames = split(enumeratedNames(enumeratedIndex), "(", 2);
unenumeratedNames(enumeratedIndex) = splittedEnumeratedNames(:, 1);
end
end
\ No newline at end of file
if makeUnique
unenumeratedNames = unique(unenumeratedNames, "stable");
end
end % removeEnumeration
\ No newline at end of file
function newNames = removeSingularEnumeration(enumeratedNames)
% REMOVESINGULARENUMERATION removes braces with numbers from char-arrays of singular
% names that are an element of the cell-array enumeratedNames.
% REMOVESINGULARENUMERATION removes braces with numbers from string-arrays of singular
% names that are an element of the array enumeratedNames.
% This is for instance useful, to deal with InputName or OutputName properties of
% large state-space models (ss).
%
% Examples:
% misc.ss.removeSingularEnumeration({'a(1)', 'a(2)', 'b(1)', 'c(1)', 'b', 'c(2)', 'd(1)'})
% misc.ss.removeSingularEnumeration(["a(1)", "a(2)", "b(1)", "c(1)", "b", "c(2)", "d(1)"])
arguments
enumeratedNames (:, 1) string;
end
newNames = enumeratedNames;
for it = 1 : numel(enumeratedNames)
if contains(enumeratedNames{it}, '(1)')
namesSplittedTemp = split(enumeratedNames{it}, '(');
if sum(contains(newNames, [namesSplittedTemp{1}, '('])) == 1
newNames{it} = strrep(enumeratedNames{it}, '(1)', '');
end
end
endWith1Idx = endsWith(enumeratedNames, "(1)");
newNames(endWith1Idx) = strrep(enumeratedNames(endWith1Idx), "(1)", "");
for it = 1 : numel(newNames)
if endWith1Idx(it) && any(strcmp(newNames, newNames(it) + "(2)"))
newNames(it) = newNames(it) + "(1)";
end % if
end
end
\ No newline at end of file
end % misc.ss.removeSingularEnumeration()
\ No newline at end of file
......@@ -18,44 +18,39 @@ function myStateSpace = setSignalName(myStateSpace, signalType, signalNames, sig
% Example:
% -------------------------------------------------------------------------
% mySimulationModel = ss(1, 1, [1; 2; 3], []);
% mySimulationModel = misc.ss.setSignalName(mySimulationModel, 'output', {'a', 'b'}, {2, 1});
% mySimulationModel = misc.ss.setSignalName(mySimulationModel, "output", ["a", "b"], [2, 1]);
% -------------------------------------------------------------------------
% input checks
assert(isa(myStateSpace, "ss"), "1st input must be state space model");
assert(iscell(signalNames) || isstring(signalNames), "signalNames must be a string array or cell array");
assert(iscell(signalLength) || isvector(signalLength), "signalLength must be a cell array or a vector");
if iscell(signalLength)
signalLength = cell2mat(signalLength);
end
if isstring(signalNames)
oldSignalNames = signalNames;
signalNames = cell(size(oldSignalNames));
for it = 1 : numel(oldSignalNames)
signalNames{it} = char(oldSignalNames(it));
end
arguments
myStateSpace ss;
signalType (1, 1) string;
signalNames (:, 1) string;
signalLength (:, 1) double;
end
% input checks
if strcmp(signalType, "input")
assert(size(myStateSpace, 2) == sum([signalLength(:)]), "signalLength does not fit to state space");
assert(size(myStateSpace, 2) == sum(signalLength), "signalLength does not fit to state space");
elseif strcmp(signalType, "output")
assert(size(myStateSpace, 1) == sum([signalLength(:)]), "signalLength does not fit to state space");
assert(size(myStateSpace, 1) == sum(signalLength), "signalLength does not fit to state space");
else
error("signalType must be 'input' or 'output'");
end
assert(numel(signalNames) == numel(signalLength), "signalNames must have same number as signalLength");
% create name cell array
myNewSignalNames = cell(sum([signalLength(:)]), 1);
myCurrentSignal = 1;
for it = 1 : numel(signalNames)
for jt = 1 : signalLength(it)
if signalLength(it) == 1
myNewSignalNames{myCurrentSignal} = signalNames{it};
else
myNewSignalNames{myCurrentSignal} = [signalNames{it}, '(', num2str(jt), ')'];
end
myCurrentSignal = myCurrentSignal + 1;
signalNames = signalNames(signalLength>0);
signalLength = signalLength(signalLength>0);
% create name names
myNewSignalNames = signalNames(1);
if signalLength(1) > 1
myNewSignalNames = myNewSignalNames + "(" + string((1:1:signalLength(1)).') + ")";
end
for it = 2 : numel(signalNames)
if signalLength(it) > 1
myNewSignalNames = [myNewSignalNames; ...
signalNames(it) + "(" + string((1:1:signalLength(it)).') + ")"];
else
myNewSignalNames = [myNewSignalNames; signalNames(it)];
end
end
......@@ -65,5 +60,4 @@ if strcmp(signalType, "input")
else
myStateSpace.OutputName = myNewSignalNames;
end
end
end % misc.ss.setSignalName()
\ No newline at end of file
......@@ -14,13 +14,13 @@ phi1 = exp(1);
p = quantity.Symbolic( misc.ss.setPointChange(phi0, phi1, time.lower, time.upper, n), time);
testCase.verifyEqual(p.at(time.lower), phi0, 'AbsTol', 1e-12);
testCase.verifyEqual(p.at(time.upper), phi1, 'AbsTol', 1e-10);
testCase.verifyEqual(p.at(time.lower), phi0, "AbsTol", 1e-12);
testCase.verifyEqual(p.at(time.upper), phi1, "AbsTol", 1e-10);
for k = 1:n
p_k = diff(p, 't', k);
testCase.verifyEqual(p_k.at(time.lower), 0, 'AbsTol', 1e-7);
testCase.verifyEqual(p_k.at(time.upper), 0, 'AbsTol', 1e-7);
testCase.verifyEqual(p_k.at(time.lower), 0, "AbsTol", 1e-7);
testCase.verifyEqual(p_k.at(time.upper), 0, "AbsTol", 1e-7);
end
p_np1 = diff(p, 't', n + 1);
......@@ -61,8 +61,8 @@ function planPolynomialTrajectory(tc)
plot(t, x - trj.x.on());
disp(x(end,:))
tc.verifyEqual(trj.x.on(), x, 'AbsTol', 2e-2);
tc.verifyEqual(trj.y.on(), y, 'AbsTol', 1e-2);
tc.verifyEqual(trj.x.on(), x, "AbsTol", 2e-2);
tc.verifyEqual(trj.y.on(), y, "AbsTol", 1e-2);
end
......@@ -90,8 +90,8 @@ function testPlanTrajectory(tc)
% plot(t, x);
% disp(x(end,:))
tc.verifyEqual(trj.x.on(), x, 'AbsTol', 2e-2);
tc.verifyEqual(trj.y.on(), y, 'AbsTol', 1e-2);
tc.verifyEqual(trj.x.on(), x, "AbsTol", 2e-2);
tc.verifyEqual(trj.y.on(), y, "AbsTol", 1e-2);
end
......@@ -120,9 +120,9 @@ y1 = lsim(s1, [u; v], time);
y2 = lsim(s2, [w; u], time);
y12 = lsim(s12, [v; u; w], time);
myTol = 1e-6;
tc.verifyEqual(y12(:, 1), y1(:, 1), 'AbsTol', myTol);
tc.verifyEqual(y12(:, 2), y1(:, 2) + y2(:, 2), 'AbsTol', myTol);
tc.verifyEqual(y12(:, 3), y2(:, 1), 'AbsTol', myTol);
tc.verifyEqual(y12(:, 1), y1(:, 1), "AbsTol", myTol);
tc.verifyEqual(y12(:, 2), y1(:, 2) + y2(:, 2), "AbsTol", myTol);
tc.verifyEqual(y12(:, 3), y2(:, 1), "AbsTol", myTol);
end % testParallel()
......@@ -139,7 +139,7 @@ end % testAddState2Output()
function testRemoveSingularEnumeration(tc)
thisResult = misc.ss.removeSingularEnumeration({'a(1)', 'a(2)', 'b(1)', 'c(1)', 'b', 'c(2)', 'd(1)'});
tc.verifyEqual(thisResult, {'a(1)', 'a(2)', 'b', 'c(1)', 'b', 'c(2)', 'd'});
tc.verifyEqual(thisResult, string({'a(1)', 'a(2)', 'b', 'c(1)', 'b', 'c(2)', 'd'}).');
end % testRemoveSingularEnumeration()
......@@ -171,20 +171,23 @@ faultSignal = quantity.Symbolic(sin(sym('t')), t, 'name', 'my.fault');
u = misc.ss.combineInputSignals(myStateSpace, t.grid, 'disturbance', disturbanceSignal);
y = quantity.Discrete(lsim(myStateSpace, u.on(), t.grid), t, 'name', 'y');
odeResiduum = - y.diff("t") - y + 2*disturbanceSignal;
tc.verifyEqual(odeResiduum.abs.median(), 0, 'AbsTol', 5e-4);
tc.verifyEqual(odeResiduum.abs.median(), 0, "AbsTol", 5e-4);
% case 2: fault and disturbance
u2 = misc.ss.combineInputSignals(myStateSpace, t.grid, 'disturbance', disturbanceSignal, ...
'my.fault', faultSignal);
y2 = quantity.Discrete(lsim(myStateSpace, u2.on(), t.grid), t, 'name', 'y');
odeResiduum = - y2.diff("t") - y2 + 2*disturbanceSignal + 3*faultSignal;
tc.verifyEqual(odeResiduum.abs.median(), 0, 'AbsTol', 5e-4);
tc.verifyEqual(odeResiduum.abs.median(), 0, "AbsTol", 5e-4);
end
function testRemoveEnumeration(tc)
tc.verifyEqual(misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'}), {'a', 'a', 'b'});
tc.verifyEqual(misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'}, false), {'a', 'a', 'b'});
tc.verifyEqual(misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'}, true), {'a', 'b'});
tc.verifyEqual(misc.ss.removeEnumeration("asdf(1)"), "asdf");
tc.verifyEqual(misc.ss.removeEnumeration("asdf"), "asdf");
tc.verifyEqual(misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'}), ["a"; "a"; "b"]);
tc.verifyEqual(misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'}, false), ["a"; "a"; "b"]);
tc.verifyEqual(misc.ss.removeEnumeration({'a(1)', 'a(2)', 'b'}, true), ["a"; "b"]);
tc.verifyEqual(misc.ss.removeEnumeration(["a(1)"; "a(2)"; "b"], true), ["a"; "b"]);
end
function testRemoveInputOfOutput(tc)
......@@ -238,13 +241,13 @@ end % testAddInput2Output
function testChangeSignalName(tc)
% change output
mySimulationModel = ss(1, 1, [1; 2; 3;], [], 'InputName', 'e');
mySimulationModel = misc.ss.setSignalName(mySimulationModel, 'output', {'a', 'b'}, {2, 1});
mySimulationModel = misc.ss.setSignalName(mySimulationModel, 'output', {'a', 'b'}, [2, 1]);
mySimulationModel = misc.ss.changeSignalName(mySimulationModel, {'a'}, {'c'});
tc.verifyEqual(mySimulationModel.OutputName, {'c(1)'; 'c(2)'; 'b'});
% change both and combine input
mySimulationModel2 = ss(1, [1, 2], [1; 2; 3], [], 'InputName', {'d', 'e'});
mySimulationModel2 = misc.ss.setSignalName(mySimulationModel2, 'output', {'a', 'b'}, {2, 1});
mySimulationModel2 = misc.ss.setSignalName(mySimulationModel2, 'output', {'a', 'b'}, [2, 1]);
mySimulationModel2 = misc.ss.changeSignalName(mySimulationModel2, {'a', 'd'}, {'c', 'e'});
tc.verifyEqual(mySimulationModel2.OutputName, {'c(1)'; 'c(2)'; 'b'});
tc.verifyEqual(mySimulationModel2.InputName, {'e'});
......@@ -261,10 +264,14 @@ end % testChangeSignalName
function testSetSignalName(tc)
mySimulationModel = ss(1, [1, 0], [1; 2; 3], []);
mySimulationModel = misc.ss.setSignalName(mySimulationModel, 'output', {'a', 'b'}, {2, 1});
mySimulationModel = misc.ss.setSignalName(mySimulationModel, 'input', {'c'}, {2});
mySimulationModel2 = misc.ss.setSignalName(mySimulationModel, 'output', ["a", "b"], [2, 1]);
mySimulationModel2 = misc.ss.setSignalName(mySimulationModel2, 'input', "c", 2);
mySimulationModel = misc.ss.setSignalName(mySimulationModel, 'output', {'a', 'b'}, [2, 1]);
mySimulationModel = misc.ss.setSignalName(mySimulationModel, 'input', {'c'}, 2);
tc.verifyEqual(mySimulationModel.OutputName, {'a(1)'; 'a(2)'; 'b'})
tc.verifyEqual(mySimulationModel.InputName, {'c(1)'; 'c(2)'})
tc.verifyEqual(mySimulationModel.OutputName, mySimulationModel2.OutputName)
tc.verifyEqual(mySimulationModel.InputName, mySimulationModel2.InputName)
end % testSetSignalName
function testSimulationOuput2Quantity(tc)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment