├── LICENSE.md ├── README.md ├── comsol ├── P1_damped_time_response.txt ├── P2_damped_time_response.txt ├── P3_damped_time_response.txt ├── extract_matrices_new.asv ├── extract_matrices_new.m ├── formZmatrix.m ├── frequency_response.m ├── frequency_response_P1.txt ├── frequency_response_P1_damped_rayleigh.txt ├── frequency_response_P1_damped_rayleigh_phase.txt ├── frequency_response_P2.txt ├── frequency_response_P2_damped_rayleigh.txt ├── frequency_response_P2_damped_rayleigh_phase.txt ├── frequency_response_and_eigenfrequencies_spring_foundation.asv ├── frequency_response_and_eigenfrequencies_spring_foundation.m ├── frequency_response_and_eigenfrequencies_undamped.mph ├── frequency_response_damped_computations_plot.m ├── frequency_response_damped_rayleigh.m ├── frequency_response_data.m ├── interp_zern.m ├── matrices1.mat ├── matrices2.mat ├── matrices3.mat ├── matrices4.mat ├── matrices5.mat ├── rayleigh_damping.m ├── readme.txt ├── scaling_simulation_check.m ├── seed_code.m ├── time_response_damped_rayleigh.m └── zernfun.m ├── readme.txt └── subspace_identification ├── final_pbsid_identification_no_noise.py ├── final_pbsid_identification_noise.py ├── functionsSID.py ├── matrices1.mat ├── matrices2.mat ├── matrices3.mat ├── matrices4.mat ├── matrices5.mat └── readme.txt /LICENSE.md: -------------------------------------------------------------------------------- 1 | LICENSE AND COPYRIGHT NOTICE 2 | 3 | This code repository and all listed files are the ownership of Dr. Aleksandar Haber. Dr. Aleksandar Haber is referred to as the Author in the sequel. All the files on this repository, including all the supporting files on this repository, are referred to as the material in the sequel. 4 | The Author's contact is ml.mecheng@gmail.com 5 | 6 | (1) You have the right to download, share, fork, and use the material for personal educational use without modifying, upgrading, or integrating the material into other projects. You cannot remix, transform, or build upon the material, meaning you can only share the original work without any adaptations. That is, you have the right to use the material solely in its original and unaltered form. 7 | 8 | (2) The material should not be used for commercial purposes without explicit approval of the Author and/or paying an appropriate fee. 9 | 10 | (3) The material should not be used by engineers working in companies as a tool for their work without the explicit approval of the Author and/or paying an appropriate fee. 11 | 12 | (4) The material should not be used by academic staff (paid researchers, paid grad students, paid post-docs, paid professors, etc.) working in universities or research institutions as a tool in their research or for teaching other people without explicit approval of the Author and/or paying an appropriate fee. 13 | 14 | (5) The material should not be used for producing results in academic papers, engineering reports, books, and similar material without explicit approval of the author and possibly without paying an appropriate fee. 15 | 16 | (6) The material or parts of the material should not be copied and published in other commercial or open-source projects. The material should not be used to build other programs without the explicit approval of the Author and without paying an appropriate fee. 17 | 18 | (7) You must give appropriate credit to the Author and reference the Author and material. The citation and the reference must contain the Author’s name, the link, the name of the repository, and the date of accessing the code. 19 | 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **# Machine-Learning-and-System-Identification-for-Adaptive-Optics** 2 | 3 | **IMPORTANT NOTE: First, thoroughly read the license in the file called LICENSE.md!** 4 | 5 | This repository contains COMSOL, LiveLink, Python and MATLAB codes we used to model a large-scale Deformable Mirror (DM) model and to estimate such a model using 6 | machine learning and subspace identification techniques. 7 | 8 | - The folder "subspace_identification" contains the codes used to identify the DM model using the Subspace Identification (SI) method. 9 | - The folder "comsol" contains the MATLAB, Python, COMSOL, and LiveLink codes used to generate DM models. These models are used for subspace identification and machine learning. 10 | -------------------------------------------------------------------------------- /comsol/extract_matrices_new.asv: -------------------------------------------------------------------------------- 1 | % For the explanation of the purpose of this file, see the "readme.txt" 2 | % file 3 | 4 | import com.comsol.model.* 5 | import com.comsol.model.util.* 6 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 7 | % These are the standard settings, do not alter them 8 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 9 | model = ModelUtil.create('Model'); 10 | model.modelNode.create('comp1'); 11 | model.geom.create('geom1', 2); 12 | model.mesh.create('mesh1', 'geom1'); 13 | model.geom('geom1').create('c1', 'Circle'); 14 | 15 | % we can also manually adjust the radius 16 | plate_radius=1; 17 | model.geom('geom1').feature('c1').set('r',mat2str(plate_radius)); 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | % Here we create a list of points for actuation and spring foundation 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | % here we generate points inside of the circle of radius 1 22 | pts_circle=[]; 23 | % for certain actuator spacing it is good to increase this initial radius 24 | % and later on, the actuators outside 1m radius are neglected 25 | radius_act=1 26 | % this is the actuator spacing 27 | stepp=0.1; 28 | [X,Y]=meshgrid([-radius_act:stepp:radius_act],[-radius_act:stepp:radius_act]) 29 | pts=[X(:) Y(:)]; 30 | indx=1; 31 | [s1,~]=size(pts); 32 | %plot(pts(:,1),pts(:,2),'x'); 33 | for i=1:s1 34 | if sqrt(pts(i,1)^2+pts(i,2)^2)<1 35 | pts_circle(indx,:)=pts(i,:); 36 | indx=indx+1; 37 | end 38 | end 39 | % here you can visualize the points 40 | % plot(pts_circle(:,1),pts_circle(:,2),'x'); 41 | 42 | % Here we define the COMSOL points on the basis of the previously defined 43 | % points 44 | string_pt='pt'; 45 | [s1,s2]=size(pts_circle); 46 | for i=1:s1 47 | chr = int2str(i); 48 | model.geom('geom1').create(strcat(string_pt,chr), 'Point'); 49 | xValue=pts_circle(i,1); 50 | yValue=pts_circle(i,2); 51 | model.geom('geom1').feature(strcat(string_pt,chr)).setIndex('p', mat2str(xValue), 0, 0); 52 | model.geom('geom1').feature(strcat(string_pt,chr)).setIndex('p', mat2str(yValue), 1, 0); 53 | % model.geom('geom1').feature(strcat(string_pt,chr)).setIndex('p', 54 | % '0', 2, 0); % this is for the 3D case- I do not need it 55 | end 56 | model.geom('geom1').run; 57 | model.geom('geom1').run('fin'); 58 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 59 | % end of the point definitions 60 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 61 | % do not alter this 62 | model.material.create('mat1', 'Common', 'comp1'); 63 | model.physics.create('plate', 'Plate', 'geom1'); 64 | 65 | % here, the boundary conditions are created 66 | model.physics('plate').create('fix1', 'Fixed', 1); 67 | model.physics('plate').feature('fix1').selection.set([1 2 3 4]); 68 | 69 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 70 | % Here we define the point loads and the mass spring foundation 71 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 72 | 73 | string_pl='pl'; 74 | 75 | for i=1:s1 76 | chr = int2str(i); 77 | xValue=pts_circle(i,1); 78 | yValue=pts_circle(i,2); 79 | model.physics('plate').create(strcat(string_pl,chr), 'PointLoad', 0); 80 | model.physics('plate').feature(strcat(string_pl,chr)).selection.set(mphselectcoords(model,'geom1',[xValue yValue]','point',... 81 | 'radius',0.003)); 82 | model.physics('plate').feature(strcat(string_pl,chr)).set('Fp', {'0' '0' '1'}); 83 | end 84 | 85 | % set the mass-spring foundation and the point masses 86 | % set the stiffness and damping constants 87 | stiffness = int2str(10^4); 88 | damping = int2str(500); 89 | mass=int2str(0.3); 90 | 91 | % spring-damper foundation 92 | string_spf='spf'; 93 | 94 | % point mass 95 | string_pm='pm'; 96 | for i=1:s1 97 | chr = int2str(i); 98 | xValue=pts_circle(i,1); 99 | yValue=pts_circle(i,2); 100 | model.physics('plate').create(strcat(string_spf,chr), 'SpringFoundation0', 0); 101 | model.physics('plate').feature(strcat(string_spf,chr)).selection.set(mphselectcoords(model,'geom1',[xValue yValue]','point',... 102 | 'radius',0.003)); 103 | model.physics('plate').create(strcat(string_pm,chr), 'PointMass', 0); 104 | model.physics('plate').feature(strcat(string_pm,chr)).selection.set(mphselectcoords(model,'geom1',[xValue yValue]','point',... 105 | 'radius',0.003)); 106 | % model.physics('plate').feature(strcat(string_pl,chr)).set('Fp', {'0' '0' '1'}); 107 | % this was original 108 | model.physics('plate').feature(strcat(string_spf,chr)).set('kSpring', {stiffness; '0'; '0'; '0'; stiffness; '0'; '0'; '0'; stiffness}); 109 | model.physics('plate').feature(strcat(string_spf,chr)).set('DampTot', {damping; '0'; '0'; '0'; damping; '0'; '0'; '0'; damping}); % here I used zero damping, change this in the final model 110 | % model.physics('plate').feature(strcat(string_spf,chr)).set('DampTot',{'1000'; '0'; '0'; '0'; '1000'; '0'; '0'; '0'; '1000'}); 111 | model.physics('plate').feature(strcat(string_pm,chr)).set('pointmass', mass); 112 | end 113 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 114 | % material constants and the mesh size 115 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 116 | % 117 | model.material('mat1').propertyGroup('def').set('youngsmodulus', '9.03*1e10'); 118 | model.material('mat1').propertyGroup('def').set('poissonsratio', '0.24'); 119 | model.material('mat1').propertyGroup('def').set('density', '2530'); 120 | model.physics('plate').prop('d').set('d', '0.003[m]'); 121 | % uncomment this is to disable the 3D formulation 122 | % if the 3D formulation is used, then we have 6 degrees of freedom 123 | % if the 3D formulation is not being used, then we have 3 degrees of 124 | % freedom 125 | model.physics('plate').prop('ShellAdvancedSettings').set('Use3DFormulation', '0'); 126 | model.mesh('mesh1').autoMeshSize(9); 127 | % 9 - extremely coarse 128 | % 8 - extra coarse 129 | model.mesh('mesh1').run; 130 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 131 | model.study.create('std3'); 132 | model.study('std3').create('time', 'Transient'); 133 | model.sol.create('sol3'); 134 | model.sol('sol3').study('std3'); 135 | model.sol('sol3').attach('std3'); 136 | model.sol('sol3').create('st1', 'StudyStep'); 137 | model.sol('sol3').create('v1', 'Variables'); 138 | model.sol('sol3').create('t1', 'Time'); 139 | model.sol('sol3').feature('t1').create('fc1', 'FullyCoupled'); 140 | model.sol('sol3').feature('t1').feature.remove('fcDef'); 141 | model.study('std3').feature('time').set('tlist', 'range(0,0.0001,0.3)'); 142 | model.sol('sol3').attach('std3'); 143 | model.sol('sol3').feature('v1').feature('comp1_u').set('scalemethod', 'manual'); 144 | model.sol('sol3').feature('v1').feature('comp1_u').set('scaleval', '1e-2*2.8284271247461903'); 145 | model.sol('sol3').feature('t1').set('timemethod', 'genalpha'); 146 | model.sol('sol3').feature('t1').set('tlist', 'range(0,0.0001,0.3)'); 147 | model.sol('sol3').runAll; 148 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 149 | % here we extract the matrices 150 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 151 | % extract the matrices and the vectors 152 | MA2 = mphmatrix(model ,'sol3', ... 153 | 'Out', {'K','L', 'M','N','D','E','Kc','Lc', 'Dc','Ec','Null', 'Nullf','ud','uscale'},... 154 | 'initmethod','sol','initsol','zero'); 155 | 156 | % K- stiffness matrix 157 | % L- load vector 158 | % M- constraint vector 159 | % N-constraint Jacobian 160 | % D- damping matrix 161 | % E- mass matrix 162 | % NF-Constraint force Jacobian 163 | % NP-Optimization constraint Jacobian 164 | % MP- Optimizatioin constraint vector 165 | % MLB- lower bound constraint vector 166 | % MUB - upper bound constrainty vector 167 | 168 | % Kc - eliminated stifness matrix 169 | % Dc - eliminated damping matrix 170 | % Ec - eliminated mass matrix 171 | % Null - constraint null-space basis 172 | % Nullf- constraint force null-space basis 173 | % ud - particular solution ud 174 | % uscale-scale vector 175 | 176 | % this is the example how the eliminated solution is being mapped into the 177 | % true solution 178 | % Uc = MA2.Null*(MA2.Kc\MA2.Lc); 179 | % U0 = Uc+MA2.ud; 180 | % U1 = U0.*MA2.uscale; 181 | 182 | [n,n]=size(MA2.Ec) 183 | r=n/3; 184 | % construct the B matrix on the basis of the vector 185 | m=nnz(MA2.Lc); 186 | B=sparse(n,m); 187 | positionsB=find(MA2.Lc~=0); 188 | for i=1:m 189 | B(positionsB(i),i)=1; 190 | end 191 | 192 | % extract the M1,M2, and M3 matrices, THESE MATRICES ARE EXPORTED 193 | M1 = MA2.Ec; 194 | M2 = MA2.Dc; 195 | M3 = MA2.Kc; 196 | 197 | info = mphxmeshinfo(model); 198 | % info.dofs - this tells us about the meaning of the degrees of freedom 199 | info.fieldnames 200 | info.fieldndofs 201 | 202 | info.nodes.dofnames 203 | info.nodes.dofs % 3 \times numbers of nodes matrix, the third row is the row for the w component 204 | info.nodes.coords % coordinates of the nodes 205 | info.dofs.dofnames % This corresponds to 0,1,2, with 2 being the w coordinate 206 | 207 | w_indices=find(info.dofs.nameinds==2); 208 | eliminated_dofs_indices=find(sum(MA2.Null,2)==0); % find the dofs that belong to the boundary domains. 209 | internal_domain_indices=find(sum(MA2.Null,2)>0.5); 210 | 211 | % eliminate the indices 212 | [tmp1,tmp2]=size(info.dofs.nameinds); 213 | source_indices=1:tmp1; 214 | source_indices=source_indices'; 215 | indx=1; 216 | 217 | % new_vector is the vector of internal domain indices after eliminating the 218 | % degrees of freedom that correspond to the boundary 219 | 220 | % original_indices_keep_them are the original indices of the internal 221 | % domain, just for check 222 | 223 | clear original_indices_keep_them; 224 | clear new_vector; 225 | for i=1:numel(source_indices) 226 | if numel(find(eliminated_dofs_indices==source_indices(i)))==0 %if the index is not in the set of the indices that need to be eliminated 227 | offset=numel(find(eliminated_dofs_indices0.5); 227 | 228 | % eliminate the indices 229 | [tmp1,tmp2]=size(info.dofs.nameinds); 230 | source_indices=1:tmp1; 231 | source_indices=source_indices'; 232 | indx=1; 233 | 234 | % new_vector is the vector of internal domain indices after eliminating the 235 | % degrees of freedom that correspond to the boundary 236 | 237 | % original_indices_keep_them are the original indices of the internal 238 | % domain, just for check 239 | 240 | clear original_indices_keep_them; 241 | clear new_vector; 242 | for i=1:numel(source_indices) 243 | if numel(find(eliminated_dofs_indices==source_indices(i)))==0 %if the index is not in the set of the indices that need to be eliminated 244 | offset=numel(find(eliminated_dofs_indicesrad^2 16 | vq(i,j)=NaN; 17 | end 18 | end 19 | end 20 | pcolor(xq,yq,vq), shading interp 21 | axis square, colorbar 22 | %title('Zernike function Z_5^1(r,\theta)') 23 | end -------------------------------------------------------------------------------- /comsol/matrices1.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/comsol/matrices1.mat -------------------------------------------------------------------------------- /comsol/matrices2.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/comsol/matrices2.mat -------------------------------------------------------------------------------- /comsol/matrices3.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/comsol/matrices3.mat -------------------------------------------------------------------------------- /comsol/matrices4.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/comsol/matrices4.mat -------------------------------------------------------------------------------- /comsol/matrices5.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/comsol/matrices5.mat -------------------------------------------------------------------------------- /comsol/rayleigh_damping.m: -------------------------------------------------------------------------------- 1 | % Compute the Rayleigh damping constants 2 | % M_{2}=alpha*M_{1}+beta*M_{3} 3 | % where M_{i} are the system matrices 4 | % and "alpha" and "beta" are the constants that need to be computed 5 | clear 6 | % here we choose the frequencies and damping constants 7 | w1=500; 8 | w2=1000; 9 | zeta1=0.2; 10 | zeta2=0.2; 11 | A=[1 w1^2; 12 | 1 w2^2]; 13 | b=[2*w1*zeta1; 2*w2*zeta2]; 14 | x=inv(A)*b; 15 | alpha=x(1) 16 | beta=x(2) 17 | 18 | 19 | % Check the damping at other frequencies 20 | ww=1:1:10000; 21 | for i=1:10000 22 | zz(i)=0.5*(x(1)*(1/ww(i))+x(2)*ww(i)); 23 | end 24 | plot(ww,zz) -------------------------------------------------------------------------------- /comsol/readme.txt: -------------------------------------------------------------------------------- 1 | These are the source codes to generate data and Deformable Mirror models for the two papers: 2 | 3 | Subspace Identification (SI) paper 4 | Machine Learning (ML) paper 5 | 6 | Explanation of the files: 7 | 8 | - The file "frequency_response_and_eigenfrequencies_undamped.mph" is a COMSOL file used to generate 9 | Figures 1 and 2 in the paper. This file contains both the frequency domain and eigenfrequency studies. 10 | 11 | - The files "frequency_response_P1" and "frequency_response_P2" are the frequency responses at the 12 | corresponding points when the force is acting at the point P1(-0.3,-0.3). This data is used to generate 13 | graphs in Fig. 1b) 14 | 15 | - The file "frequency_response_data.m" contains the vectors of data from the files "frequency_response_P1" and "frequency_response_P2" 16 | 17 | - The file "frequency_response.m" contains the code to generate the plots in Fig.1b) in the SI paper. 18 | 19 | - The file "rayleigh_damping.m" is used to compute the Rayleigh damping constants. 20 | 21 | - The file "frequency_response_and_eigenfrequencies_rayleigh_damping.mph" is the COMSOL file used to compute the Rayleigh damped model. 22 | NOTE- I had to remove this file since it is above 100MB and I cannot post it on GitHub. Contact me if you need this file 23 | 24 | 25 | - The files "Pi_damped_time_response.txt", i=1,2,3 are the time responses of the damped mirror (Rayleigh damping). 26 | 27 | - The file "time_response_damped_rayleigh.m" contains all the time and deformation data extracted from the files "Pi_damped_time_response.txt" 28 | 29 | - The files "frequency_response_Pi_damped_rayleigh", i=1,2 contains the amplitudes 30 | 31 | - The files "frequency_response_Pi_damped_rayleigh_phase" i=1,2 contains the phases 32 | 33 | - The file "frequency_response_damped_rayleigh.m" contains the data originally stored in the files "frequency_response_Pi_damped_rayleigh" and "frequency_response_Pi_damped_rayleigh_phase" 34 | 35 | - The file "frequency_response_damped_computations_plot.m" contains the code used to generate Figure 3. 36 | 37 | - The file "frequency_response_and_eigenfrequencies_spring_foundation.m" was used to generate the Figures 5,6 and 7 in the SI paper. 38 | 39 | NOTE- I had to remove this file since it is above 100MB and I cannot post it on GitHub. Contact me if you need this file 40 | 41 | 42 | - The file "frequency_response_and_eigenfrequencies_spring_foundation.mph" is a COMSOL file used to generate the first version of the file "frequency_response_and_eigenfrequencies_spring_foundation.m". Later on, this MATLAB was significantly modified. 43 | 44 | - The file "extract_matrices_new.m" is used to generate figures 2,3,4 in the ML paper and to extract the system matrices for both papers. 45 | 46 | - The file "zernfun.m" contains a function used to generate Zernike polynomials. 47 | 48 | - The file "interp_zern.m" contains a function that interpolates the Zernike polynomimals and plots a graph. 49 | 50 | - The file "scaling_simulation_check.m" is used to verify that the scaling procedure works. 51 | 52 | - The file "formZmatrix.m" is used to form the Zmap matrix that maps zernike coefficients into displacements 53 | 54 | - The files "matricesi.mat", i=1,2,3,4,5 are extracted matrices for the following parameters: 55 | 56 | matrices1.mat actuator spacing=0.2, radius=1, stiffness=10^4, damping=500, mass=0.3, mesh size=9 57 | matrices2.mat same parameters as in matrices1.mat except mesh size=8 58 | matrices3.mat same parameters as in matrices1.mat except actuator spacing=0.1 59 | matrices4.mat same parameters as in matrices1.mat except stiffness=10^5 and damping=3000 60 | matrices5.mat same parameters as in matrices4 except actuator spacing=0.1 61 | 62 | - The file "seed_code.m" is used to develop the code "extract_matrices.m". Also this files contains many other comments 63 | that explain the codes and it can be used to develop new codes. 64 | 65 | -------------------------------------------------------------------------------- /comsol/scaling_simulation_check.m: -------------------------------------------------------------------------------- 1 | % First try to simulate the unscalled model 2 | load matrices 3 | C=Cr1; 4 | [n,~]=size(M1); 5 | [~,m]=size(B); 6 | [r,~]=size(C); 7 | 8 | % this is the unscalled model 9 | invM1=inv(M1); 10 | A1=[zeros(n,n) eye(n,n); -invM1*M3 -invM1*M2]; 11 | B1= [zeros(n,m); invM1*B]; 12 | C1 = [C zeros(r,n)]; 13 | x0=rand(2*n,1); 14 | 15 | % get the scales 16 | max(sum(M1,2)) 17 | min(sum(M1,2)) 18 | 19 | max(sum(M2,2)) 20 | min(sum(M2,2)) 21 | 22 | max(sum(M3,2)) 23 | min(sum(M3,2)) 24 | 25 | % time scale 26 | c1=10^(-5); 27 | % state scale 28 | c2=10^(-6); 29 | 30 | M1s=M1*c2/(c1^2); 31 | M2s=M2*c2/c1; 32 | M3s=M3*c2; 33 | Cs=C*c2; 34 | 35 | max(sum(M1s,2)) 36 | min(sum(M1s,2)) 37 | 38 | max(sum(M2s,2)) 39 | min(sum(M2s,2)) 40 | 41 | max(sum(M3s,2)) 42 | min(sum(M3s,2)) 43 | 44 | invM1s=inv(M1s); 45 | A1s=[zeros(n,n) eye(n,n); -invM1s*M3s -invM1s*M2s]; 46 | B1s= [zeros(n,m); invM1s*B]; 47 | C1s = [Cs zeros(r,n)]; 48 | 49 | % similarity transformation 50 | SI=[c2*speye(n,n) zeros(n,n); zeros(n,n) (c2/c1)*speye(n,n)] 51 | x0s=inv(SI)*x0; 52 | Toriginal=0.0001; 53 | Adoriginal=inv(eye(2*n,2*n)-Toriginal*A1); 54 | Tscaled=Toriginal/c1; 55 | Adscaled=inv(eye(2*n,2*n)-Tscaled*A1s); 56 | clear Xs 57 | clear Xo 58 | 59 | for i=1:40 60 | if i==1 61 | Xs{1,i}=x0s; 62 | Xo{1,i}=x0; 63 | else 64 | Xs{1,i}= Adscaled*Xs{1,i-1}; 65 | Xo{1,i}= Adoriginal*Xo{1,i-1}; 66 | end 67 | 68 | end 69 | 70 | Xs=cell2mat(Xs); 71 | Xo=cell2mat(Xo); 72 | Xo_fs=SI*Xs; 73 | plot(Xo_fs(400,:),'r'); 74 | hold on 75 | plot(Xo(400,:),'k'); 76 | normest(Xo_fs-Xo)/normest(Xo) 77 | 78 | % descriptor state-space model, unscaled and scaled matrices 79 | E=[eye(size(M1)) zeros(size(M1)); 80 | zeros(size(M1)) M1]; 81 | A=[zeros(size(M1)) eye(size(M1)); -M3 -M2]; 82 | B1=[zeros(size(B)); B]; 83 | C1 = [C zeros(r,n)]; 84 | E=sparse(E) 85 | A=sparse(A) 86 | B1=sparse(B1) 87 | C1=sparse(C1) 88 | 89 | Es=[eye(size(M1s)) zeros(size(M1s)); 90 | zeros(size(M1s)) M1s]; 91 | As=[zeros(size(M1s)) eye(size(M1s)); -M3s -M2s]; 92 | B1=[zeros(size(B)); B]; 93 | C1s = [Cs zeros(r,n)]; 94 | Es=sparse(Es) 95 | As=sparse(As) 96 | B1=sparse(B1) 97 | C1s=sparse(C1s) 98 | 99 | 100 | 101 | % % try another way of scalling the matrices 102 | % [p1,q1,r1]=find(M1); 103 | % [p3,q3,r3]=find(M3); 104 | % 105 | % max(abs(r1)) 106 | % min(abs(r1)) 107 | % 108 | % max(abs(r3)) 109 | % min(abs(r3)) 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /comsol/seed_code.m: -------------------------------------------------------------------------------- 1 | import com.comsol.model.* 2 | import com.comsol.model.util.* 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % These are the standard settings, do not alter them 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | model = ModelUtil.create('Model'); 7 | model.modelNode.create('comp1'); 8 | model.geom.create('geom1', 2); 9 | model.mesh.create('mesh1', 'geom1'); 10 | model.geom('geom1').create('c1', 'Circle'); 11 | 12 | % we can also manually adjust the radius 13 | plate_radius=1; 14 | model.geom('geom1').feature('c1').set('r',mat2str(plate_radius)); 15 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 16 | % Here we create a list of points for actuation and spring foundation 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | % here we generate points inside of the circle of radius 1 19 | pts_circle=[]; 20 | % for certain actuator spacing it is good to increase this initial radius 21 | % and later on, the actuators outside 1m radius are neglected 22 | radius_act=1 23 | % this is the actuator spacing 24 | stepp=0.2; 25 | [X,Y]=meshgrid([-radius_act:stepp:radius_act],[-radius_act:stepp:radius_act]) 26 | pts=[X(:) Y(:)]; 27 | indx=1; 28 | [s1,~]=size(pts); 29 | %plot(pts(:,1),pts(:,2),'x'); 30 | for i=1:s1 31 | if sqrt(pts(i,1)^2+pts(i,2)^2)<1 32 | pts_circle(indx,:)=pts(i,:); 33 | indx=indx+1; 34 | end 35 | end 36 | % here you can visualize the points 37 | % plot(pts_circle(:,1),pts_circle(:,2),'x'); 38 | 39 | % Here we define the COMSOL points on the basis of the previously defined 40 | % points 41 | string_pt='pt'; 42 | [s1,s2]=size(pts_circle); 43 | for i=1:s1 44 | chr = int2str(i); 45 | model.geom('geom1').create(strcat(string_pt,chr), 'Point'); 46 | xValue=pts_circle(i,1); 47 | yValue=pts_circle(i,2); 48 | model.geom('geom1').feature(strcat(string_pt,chr)).setIndex('p', mat2str(xValue), 0, 0); 49 | model.geom('geom1').feature(strcat(string_pt,chr)).setIndex('p', mat2str(yValue), 1, 0); 50 | % model.geom('geom1').feature(strcat(string_pt,chr)).setIndex('p', 51 | % '0', 2, 0); % this is for the 3D case- I do not need it 52 | end 53 | model.geom('geom1').run; 54 | model.geom('geom1').run('fin'); 55 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 56 | % end of the point definitions 57 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 58 | % do not alter this 59 | model.material.create('mat1', 'Common', 'comp1'); 60 | model.physics.create('plate', 'Plate', 'geom1'); 61 | 62 | % here, the boundary conditions are created 63 | model.physics('plate').create('fix1', 'Fixed', 1); 64 | model.physics('plate').feature('fix1').selection.set([1 2 3 4]); 65 | 66 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 67 | % Here we define the point loads and the mass spring foundation 68 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 69 | 70 | string_pl='pl'; 71 | 72 | for i=1:s1 73 | chr = int2str(i); 74 | xValue=pts_circle(i,1); 75 | yValue=pts_circle(i,2); 76 | model.physics('plate').create(strcat(string_pl,chr), 'PointLoad', 0); 77 | model.physics('plate').feature(strcat(string_pl,chr)).selection.set(mphselectcoords(model,'geom1',[xValue yValue]','point',... 78 | 'radius',0.003)); 79 | model.physics('plate').feature(strcat(string_pl,chr)).set('Fp', {'0' '0' '1'}); 80 | end 81 | 82 | % set the mass-spring foundation and the point masses 83 | % set the stiffness and damping constants 84 | stiffness = int2str(10^4); 85 | damping = int2str(500); 86 | mass=int2str(0.3); 87 | 88 | % spring-damper foundation 89 | string_spf='spf'; 90 | 91 | % point mass 92 | string_pm='pm'; 93 | for i=1:s1 94 | chr = int2str(i); 95 | xValue=pts_circle(i,1); 96 | yValue=pts_circle(i,2); 97 | model.physics('plate').create(strcat(string_spf,chr), 'SpringFoundation0', 0); 98 | model.physics('plate').feature(strcat(string_spf,chr)).selection.set(mphselectcoords(model,'geom1',[xValue yValue]','point',... 99 | 'radius',0.003)); 100 | model.physics('plate').create(strcat(string_pm,chr), 'PointMass', 0); 101 | model.physics('plate').feature(strcat(string_pm,chr)).selection.set(mphselectcoords(model,'geom1',[xValue yValue]','point',... 102 | 'radius',0.003)); 103 | % model.physics('plate').feature(strcat(string_pl,chr)).set('Fp', {'0' '0' '1'}); 104 | % this was original 105 | model.physics('plate').feature(strcat(string_spf,chr)).set('kSpring', {stiffness; '0'; '0'; '0'; stiffness; '0'; '0'; '0'; stiffness}); 106 | model.physics('plate').feature(strcat(string_spf,chr)).set('DampTot', {damping; '0'; '0'; '0'; damping; '0'; '0'; '0'; damping}); % here I used zero damping, change this in the final model 107 | % model.physics('plate').feature(strcat(string_spf,chr)).set('DampTot',{'1000'; '0'; '0'; '0'; '1000'; '0'; '0'; '0'; '1000'}); 108 | model.physics('plate').feature(strcat(string_pm,chr)).set('pointmass', mass); 109 | end 110 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 111 | % material constants and the mesh size 112 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 113 | % 114 | model.material('mat1').propertyGroup('def').set('youngsmodulus', '9.03*1e10'); 115 | model.material('mat1').propertyGroup('def').set('poissonsratio', '0.24'); 116 | model.material('mat1').propertyGroup('def').set('density', '2530'); 117 | model.physics('plate').prop('d').set('d', '0.003[m]'); 118 | % uncomment this is to disable the 3D formulation 119 | % if the 3D formulation is used, then we have 6 degrees of freedom 120 | % if the 3D formulation is not being used, then we have 3 degrees of 121 | % freedom 122 | model.physics('plate').prop('ShellAdvancedSettings').set('Use3DFormulation', '0'); 123 | model.mesh('mesh1').autoMeshSize(9); 124 | % 9 - extremely coarse 125 | % 8 - extra coarse 126 | model.mesh('mesh1').run; 127 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 128 | model.study.create('std3'); 129 | model.study('std3').create('time', 'Transient'); 130 | model.sol.create('sol3'); 131 | model.sol('sol3').study('std3'); 132 | model.sol('sol3').attach('std3'); 133 | model.sol('sol3').create('st1', 'StudyStep'); 134 | model.sol('sol3').create('v1', 'Variables'); 135 | model.sol('sol3').create('t1', 'Time'); 136 | model.sol('sol3').feature('t1').create('fc1', 'FullyCoupled'); 137 | model.sol('sol3').feature('t1').feature.remove('fcDef'); 138 | model.study('std3').feature('time').set('tlist', 'range(0,0.0001,0.3)'); 139 | model.sol('sol3').attach('std3'); 140 | model.sol('sol3').feature('v1').feature('comp1_u').set('scalemethod', 'manual'); 141 | model.sol('sol3').feature('v1').feature('comp1_u').set('scaleval', '1e-2*2.8284271247461903'); 142 | model.sol('sol3').feature('t1').set('timemethod', 'genalpha'); 143 | model.sol('sol3').feature('t1').set('tlist', 'range(0,0.0001,0.3)'); 144 | model.sol('sol3').runAll; 145 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 146 | % here we extract the matrices 147 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 148 | % extract the matrices and the vectors 149 | MA2 = mphmatrix(model ,'sol3', ... 150 | 'Out', {'K','L', 'M','N','D','E','Kc','Lc', 'Dc','Ec','Null', 'Nullf','ud','uscale'},... 151 | 'initmethod','sol','initsol','zero'); 152 | 153 | % K- stiffness matrix 154 | % L- load vector 155 | % M- constraint vector 156 | % N-constraint Jacobian 157 | % D- damping matrix 158 | % E- mass matrix 159 | % NF-Constraint force Jacobian 160 | % NP-Optimization constraint Jacobian 161 | % MP- Optimizatioin constraint vector 162 | % MLB- lower bound constraint vector 163 | % MUB - upper bound constrainty vector 164 | 165 | % Kc - eliminated stifness matrix 166 | % Dc - eliminated damping matrix 167 | % Ec - eliminated mass matrix 168 | % Null - constraint null-space basis 169 | % Nullf- constraint force null-space basis 170 | % ud - particular solution ud 171 | % uscale-scale vector 172 | 173 | % this is the example how the eliminated solution is being mapped into the 174 | % true solution 175 | % Uc = MA2.Null*(MA2.Kc\MA2.Lc); 176 | % U0 = Uc+MA2.ud; 177 | % U1 = U0.*MA2.uscale; 178 | 179 | [n,n]=size(MA2.Ec) 180 | r=n/3; 181 | % construct the B matrix on the basis of the vector 182 | m=nnz(MA2.Lc); 183 | B=sparse(n,m); 184 | positionsB=find(MA2.Lc~=0); 185 | for i=1:m 186 | B(positionsB(i),i)=1; 187 | end 188 | 189 | % extract the M1,M2, and M3 matrices, THESE MATRICES ARE EXPORTED 190 | M1 = MA2.Ec; 191 | M2 = MA2.Dc; 192 | M3 = MA2.Kc; 193 | 194 | info = mphxmeshinfo(model); 195 | % info.dofs - this tells us about the meaning of the degrees of freedom 196 | info.fieldnames 197 | info.fieldndofs 198 | 199 | info.nodes.dofnames 200 | info.nodes.dofs % 3 \times numbers of nodes matrix, the third row is the row for the w component 201 | info.nodes.coords % coordinates of the nodes 202 | info.dofs.dofnames % This corresponds to 0,1,2, with 2 being the w coordinate 203 | 204 | w_indices=find(info.dofs.nameinds==2); 205 | eliminated_dofs_indices=find(sum(MA2.Null,2)==0); % find the dofs that belong to the boundary domains. 206 | internal_domain_indices=find(sum(MA2.Null,2)>0.5); 207 | 208 | % eliminate the indices 209 | [tmp1,tmp2]=size(info.dofs.nameinds); 210 | source_indices=1:tmp1; 211 | source_indices=source_indices'; 212 | indx=1; 213 | 214 | % new_vector is the vector of internal domain indices after eliminating the 215 | % degrees of freedom that correspond to the boundary 216 | 217 | % original_indices_keep_them are the original indices of the internal 218 | % domain, just for check 219 | 220 | clear original_indices_keep_them; 221 | clear new_vector; 222 | for i=1:numel(source_indices) 223 | if numel(find(eliminated_dofs_indices==source_indices(i)))==0 %if the index is not in the set of the indices that need to be eliminated 224 | offset=numel(find(eliminated_dofs_indicesn) 103 | error('zernfun:MlessthanN', ... 104 | 'Each M must be less than or equal to its corresponding N.') 105 | end 106 | 107 | if any( r>1 | r<0 ) 108 | error('zernfun:Rlessthan1','All R must be between 0 and 1.') 109 | end 110 | 111 | if ( ~any(size(r)==1) ) || ( ~any(size(theta)==1) ) 112 | error('zernfun:RTHvector','R and THETA must be vectors.') 113 | end 114 | 115 | r = r(:); 116 | theta = theta(:); 117 | length_r = length(r); 118 | if length_r~=length(theta) 119 | error('zernfun:RTHlength', ... 120 | 'The number of R- and THETA-values must be equal.') 121 | end 122 | 123 | % Check normalization: 124 | % -------------------- 125 | if nargin==5 && ischar(nflag) 126 | isnorm = strcmpi(nflag,'norm'); 127 | if ~isnorm 128 | error('zernfun:normalization','Unrecognized normalization flag.') 129 | end 130 | else 131 | isnorm = false; 132 | end 133 | 134 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 135 | % Compute the Zernike Polynomials 136 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 137 | 138 | % Determine the required powers of r: 139 | % ----------------------------------- 140 | m_abs = abs(m); 141 | rpowers = []; 142 | for j = 1:length(n) 143 | rpowers = [rpowers m_abs(j):2:n(j)]; 144 | end 145 | rpowers = unique(rpowers); 146 | 147 | % Pre-compute the values of r raised to the required powers, 148 | % and compile them in a matrix: 149 | % ----------------------------- 150 | if rpowers(1)==0 151 | rpowern = arrayfun(@(p)r.^p,rpowers(2:end),'UniformOutput',false); 152 | rpowern = cat(2,rpowern{:}); 153 | rpowern = [ones(length_r,1) rpowern]; 154 | else 155 | rpowern = arrayfun(@(p)r.^p,rpowers,'UniformOutput',false); 156 | rpowern = cat(2,rpowern{:}); 157 | end 158 | 159 | % Compute the values of the polynomials: 160 | % -------------------------------------- 161 | z = zeros(length_r,length(n)); 162 | for j = 1:length(n) 163 | s = 0:(n(j)-m_abs(j))/2; 164 | pows = n(j):-2:m_abs(j); 165 | for k = length(s):-1:1 166 | p = (1-2*mod(s(k),2))* ... 167 | prod(2:(n(j)-s(k)))/ ... 168 | prod(2:s(k))/ ... 169 | prod(2:((n(j)-m_abs(j))/2-s(k)))/ ... 170 | prod(2:((n(j)+m_abs(j))/2-s(k))); 171 | idx = (pows(k)==rpowers); 172 | z(:,j) = z(:,j) + p*rpowern(:,idx); 173 | end 174 | 175 | if isnorm 176 | z(:,j) = z(:,j)*sqrt((1+(m(j)~=0))*(n(j)+1)/pi); 177 | end 178 | end 179 | % END: Compute the Zernike Polynomials 180 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 181 | 182 | % Compute the Zernike functions: 183 | % ------------------------------ 184 | idx_pos = m>0; 185 | idx_neg = m<0; 186 | 187 | if any(idx_pos) 188 | z(:,idx_pos) = z(:,idx_pos).*cos(theta*m_abs(idx_pos)'); 189 | end 190 | if any(idx_neg) 191 | z(:,idx_neg) = z(:,idx_neg).*sin(theta*m_abs(idx_neg)'); 192 | end 193 | 194 | % EOF zernfun -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | This repository contains COMSOL, LiveLink, Python and MATLAB codes we used to model a large scale Deformable Mirror (DM) model and to estimate such a model using 2 | machine learning and subspace identification techniques. 3 | 4 | - The folder "subspace_identification" containts the codes used to identify the DM model using the Subspace Identification (SI) method. 5 | - The folder "comsol" contains the MATLAB, Python, COMSOL, and LiveLink codes used to generate DM models. These models are used for subspace identification and machine learning. 6 | 7 | NOTE THAT FROM THE FOLDER COMSOL I HAD TO REMOVE TWO FILES 8 | 9 | frequency_response_and_eigenfrequencies_rayleigh_damping.mph 10 | 11 | and 12 | 13 | frequency_response_and_eigenfrequencies_spring_foundation.mph 14 | 15 | DUE TO THE FACT THAT THEY EXCEED 100MB FILE UPLOAD LIMIT. CONTACT ME IF YOU NEED THESE FILES. 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /subspace_identification/final_pbsid_identification_no_noise.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Subspace Identification of a Deformable Mirror Model 4 | Author: Aleksandar Haber 5 | Date: January - November 2019 6 | 7 | 8 | 9 | In this file, we identify a model when the measurements are not corrupted by the measurement noise. 10 | The data generating model has the following form: 11 | 12 | M_{1}\ddot{z}+M_{2}\dot{z}+M_{3}z=B_{f}u (1) 13 | y=C_{f} z (2) 14 | 15 | where z is the displacement vector, u is the control force vector, y is the system output 16 | M_{1}, M_{2}, and M_{3} are the mass, damping, and stiffness matrices 17 | B_{f} is the control matrix 18 | C_{f} is the output matrix 19 | 20 | The model (1)-(2) is written as a descriptor state-space model 21 | 22 | Ed\dot{x}=Ad x +Bd u 23 | y = Cd x 24 | 25 | and discretized using the backward Euler method. The discretized model has the following form 26 | 27 | x_{k} = A x_{k-1} + B u_{k} (3) 28 | y_{k} = C x_{k} (4) 29 | 30 | this is the data-generating model. 31 | 32 | We identify a Kalman innovation state-space model 33 | 34 | x_{k+1}=Aid x_{k}+Bid u_{k}+Kid (y_{k}-Cx_{k}) 35 | y_{k}=Cid x_{k}+e_{k} 36 | 37 | where x_{k}\in \mathbb{R}^{n} is the state, u_{k}\in \mathbb{R}^{r} is the input vector, 38 | y_{k}\in \mathbb{R}^{r}, A,B,K, and C are the system matrices 39 | 40 | In this file, we assume that the output y_{k} of the data generating model is 41 | not corrupted by the measurement noise. 42 | 43 | In the validation step we simulate the model 44 | 45 | x_{k+1}=Aid x_{k}+Bid u_{k} 46 | y_{k}=Cid x_{k} 47 | 48 | that is, we omit the innovation e_{k}=y_{k}-Cx_{k} 49 | 50 | """ 51 | import numpy as np 52 | import matplotlib.pyplot as plt 53 | from numpy.linalg import inv 54 | from scipy import linalg 55 | from scipy.io import loadmat 56 | 57 | 58 | from functionsSID import estimateMarkovParameters 59 | from functionsSID import estimateModel 60 | from functionsSID import estimateInitial 61 | from functionsSID import systemSimulate 62 | from functionsSID import modelError 63 | 64 | 65 | # load the descriptor state-space matrices from the file 66 | matrices = loadmat('matrices1.mat') 67 | 68 | Bf=np.matrix(matrices['B'].toarray()) 69 | Cr1=np.matrix(matrices['Cr1'].toarray()) 70 | M1=np.matrix(matrices['M1'].toarray()) 71 | M2=np.matrix(matrices['M2'].toarray()) 72 | M3=np.matrix(matrices['M3'].toarray()) 73 | Zmap=np.matrix(matrices['Zmap']) 74 | pinvZmap=np.linalg.pinv(Zmap) 75 | Cf=pinvZmap*Cr1; 76 | n=M1.shape[0] 77 | m=Bf.shape[1] 78 | r=Cf.shape[0] 79 | 80 | #define the descriptor state-space matrices 81 | Ed=np.block([ 82 | [np.identity(n), np.zeros(shape=(n,n))], 83 | [np.zeros(shape=(n,n)), M1 ] 84 | ]) 85 | 86 | Ad=np.block([ 87 | [np.zeros(shape=(n,n)), np.identity(n)], 88 | [-M3, -M2] 89 | ]) 90 | 91 | Bd=np.block([ 92 | [np.zeros(shape=(n,m))], 93 | [Bf] 94 | ]) 95 | Cd=np.block([[Cf, np.zeros(shape=(r,n))]]) 96 | 97 | # this is the sampling constant 98 | h=5*10**(-3) 99 | # total time steps used for the identification 100 | time_steps=1200 101 | 102 | Ed=np.asmatrix(Ed) #Unlike numpy.matrix, asmatrix does not make a copy if the input is already a matrix or an ndarray. Equivalent to matrix(data, copy=False). 103 | Ad=np.asmatrix(Ad) 104 | Bd=np.asmatrix(Bd) 105 | Cd=np.asmatrix(Cd) 106 | 107 | # model discretization 108 | invM=inv(Ed-h*Ad) 109 | A=np.matmul(invM,Ed); 110 | B=h*np.matmul(invM,Bd); 111 | C=Cd; 112 | #D=np.asmatrix(np.zeros(shape=(C.shape[0],B.shape[1]))) 113 | 114 | ############################################################################### 115 | # simulate the data generating model to obtain the identification and 116 | # the validation data 117 | ############################################################################### 118 | # initial state for system simulation - used for the identification 119 | x0id=0.1*np.random.randn(A.shape[0],1) 120 | # input sequence that is used for system identification 121 | Uid=np.random.randn(B.shape[1],time_steps) 122 | #Uid=np.ones((B.shape[1],time_steps)) # this is for the step response analysis 123 | # identification output and state 124 | Yid,Xid = systemSimulate(A,B,C,Uid,x0id) 125 | 126 | # initial state for system simulation - used for validation 127 | x0val=0.1*np.random.randn(A.shape[0],1) 128 | # input sequence that is used for system validation 129 | Uval=np.random.randn(B.shape[1],time_steps) 130 | # validation output and state 131 | Yval,Xval = systemSimulate(A,B,C,Uval,x0val) 132 | 133 | #plt.plot(Yid[0,:100],'r') 134 | ############################################################################### 135 | # end of simulation 136 | ############################################################################### 137 | 138 | ############################################################################### 139 | # Estimation of the VARX model 140 | ############################################################################### 141 | # past value 142 | past_value=10 143 | 144 | import time 145 | t0=time.time() 146 | # estimate the Markov parameters 147 | Markov,Z, Y_p_p_l =estimateMarkovParameters(Uid,Yid,past_value) 148 | Error=Y_p_p_l-np.matmul(Markov,Z) 149 | t1=time.time() 150 | print(t1-t0) 151 | ############################################################################### 152 | # Estimation of the final model 153 | ############################################################################### 154 | # identification model order 155 | model_order=40 156 | Aid,Atilde,Bid,Kid,Cid,s_singular,X_p_p_l = estimateModel(Uid,Yid,Markov,Z,past_value,past_value,model_order) 157 | 158 | # open loop validation of x_{k+1}=Ax_{k}+Bu_{k}, y_{k}=Cx_{k} 159 | # estimate the initial state 160 | window=40 # window for estimating the initial state 161 | x0est=estimateInitial(Aid,Bid,Cid,Uval,Yval,window) 162 | 163 | # simulate the open loop model 164 | Yval_prediction,Xval_prediction = systemSimulate(Aid,Bid,Cid,Uval,x0est) 165 | 166 | # compute the errors 167 | relative_error_percentage, vaf_error_percentage, Akaike_error = modelError(Yval,Yval_prediction,r,m,30) 168 | print('Final model relative error %f and VAF value %f' %(relative_error_percentage, vaf_error_percentage)) 169 | 170 | # plot the prediction and the real output 171 | plt.plot(Yval[0,:100],'k',label='Real output') 172 | plt.plot(Yval_prediction[0,:100],'r',label='Prediction') 173 | plt.legend() 174 | plt.xlabel('Time steps') 175 | plt.ylabel('Predicted and real outputs') 176 | plt.show() 177 | 178 | # compute the eigenvalues 179 | eigen_A=linalg.eig(A)[0] 180 | eigen_Aid=linalg.eig(Aid)[0] 181 | 182 | # this is a detailed graph 183 | #plt.figure(figsize=(7,7)) 184 | #plt.plot(eigen_A.real,eigen_A.imag,'or',linewidth=1, markersize=9, label='Original system') 185 | #plt.plot(eigen_Aid.real,eigen_Aid.imag,'xk',linewidth=1, markersize=12, markeredgewidth= 2, label='Identified system') 186 | #plt.title('Eigenvalues of the identified and original systems') 187 | #plt.xlabel('Real part') 188 | #plt.ylabel('Imaginary part') 189 | #plt.legend() 190 | #plt.savefig('eigenvalues.png') 191 | #plt.show() 192 | 193 | # plot the eigenvlaues 194 | plt.figure(figsize=(6,6)) 195 | plt.plot(eigen_A.real,eigen_A.imag,'or',linewidth=1, markersize=9) 196 | plt.plot(eigen_Aid.real,eigen_Aid.imag,'xk',linewidth=1, markersize=13, markeredgewidth= 2) 197 | plt.title('Eigenvalues of the identified and original systems') 198 | plt.xlabel('Real part') 199 | plt.ylabel('Imaginary part') 200 | plt.legend() 201 | #plt.savefig('eigenvalues_h0005_n200.eps') 202 | plt.show() 203 | 204 | 205 | plt.plot(state_order,error,'or-',linewidth=2,markersize=9) 206 | plt.plot(state_order,vaf,'sk-',linewidth=2,markersize=9) 207 | #plt.savefig('vaf_error_0_005.eps') 208 | plt.show() 209 | 210 | # plot the singular values 211 | plt.figure() 212 | plt.plot(s_singular,'xk',markersize=5) 213 | plt.yscale('log') 214 | #plt.savefig('singular_0_005.eps') 215 | plt.show() 216 | 217 | 218 | ############################################################################### 219 | # some results 220 | ############################################################################### 221 | 222 | # discretization constant 5*10**(-3) 223 | # conditions past=10 and future=10, window for estimating the initial state in the validation-40, time_steps=1200 224 | state_order=np.array([5, 20, 35, 50, 65, 80, 95, 110, 125, 140 ]) 225 | error=np.array([62.288115063459834, 24.66615893424684, 10.492955176288715, 3.6205921295767536, 2.4034249963569763, 1.99819849274286, 2.359069435164046, 2.71245398123384, 2.866046159326907, 2.9079129865228794] ) 226 | vaf=np.array([61.201907218411876, 93.91580603430475, 98.89897891668396, 99.86891312631246, 99.94223548286887, 99.960072027836, 99.94434791400076, 99.92642593399688, 99.91785779412608, 99.91544042062812]) 227 | 228 | # discretization constant 1*10**(-3) 229 | # conditions past=15 and future=15, window for estimating the initial state in the validation-60, time_steps=2000 230 | 231 | state_order=np.array([5, 20, 35, 50, 65, 80, 95, 110, 125, 140 ]) 232 | error=np.array([65.32277966448768, 43.03812249190754, 31.907947724703302, 21.05728428675633, 12.921182548541498, 2.9821732090140776, 2.0441328591263916, 1.138414395280608, 0.8674635489534611, 0.8754088706501165]) 233 | vaf=np.array([57.32934456904795, 81.47720012371562, 89.81882871997601, 95.56590778466725, 98.33043041547268, 99.91106642951438, 99.9582152085424, 99.98704012664618, 99.99247506991237, 99.99233659309186]) 234 | 235 | # discretization constant h=0.5*10**(-3) 236 | # conditions past=15 and future=15, window for estimating the initial state in the validation-60, time_steps=2000 237 | state_order=np.array([5, 20, 35, 50, 65, 80, 95, 110, 125, 140 ]) 238 | error=np.array([87.076584, 74.638547, 84.416081, 38.605859, 32.474764, 25.568138, 19.593143, 12.736097, 9.757194, 5.792968 ]) 239 | vaf=np.array([24.176685, 44.290873, 28.739253, 85.095876, 89.453897, 93.462703, 96.161088, 98.377918, 99.047972, 99.664415 ]) 240 | 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /subspace_identification/final_pbsid_identification_noise.py: -------------------------------------------------------------------------------- 1 | """ 2 | Subspace Identification of a Deformable Mirror Model 3 | Author: Aleksandar Haber 4 | Date: January - November 2019 5 | 6 | In this file, we identify a model when the measurements are corrupted by the measurement noise. 7 | The data generating model has the following form: 8 | 9 | M_{1}\ddot{z}+M_{2}\dot{z}+M_{3}z=B_{f}u (1) 10 | y=C_{f} z (2) 11 | 12 | where z is the displacement vector, u is the control force vector, y is the system output 13 | M_{1}, M_{2}, and M_{3} are the mass, damping, and stiffness matrices 14 | B_{f} is the control matrix 15 | C_{f} is the output matrix 16 | 17 | The model (1)-(2) is written as a descriptor state-space model 18 | 19 | Ed\dot{x}=Ad x +Bd u 20 | y = Cd x 21 | 22 | and discretized using the backward Euler method. The discretized model has the following form 23 | 24 | x_{k} = A x_{k-1} + B u_{k} (3) 25 | y_{k} = C x_{k} (4) 26 | 27 | this is the data-generating model. 28 | 29 | We identify a Kalman innovation state-space model 30 | 31 | x_{k+1}=Aid x_{k}+Bid u_{k}+Kid (y_{k}-Cx_{k}) 32 | y_{k}=Cid x_{k}+e_{k} 33 | 34 | where x_{k}\in \mathbb{R}^{n} is the state, u_{k}\in \mathbb{R}^{r} is the input vector, 35 | y_{k}\in \mathbb{R}^{r}, A,B,K, and C are the system matrices 36 | 37 | In this file, we assume that the output y_{k} of the data generating model is 38 | not corrupted by the measurement noise. 39 | 40 | In the validation step we simulate the model 41 | 42 | x_{k+1}=Aid x_{k}+Bid u_{k} 43 | y_{k}=Cid x_{k} 44 | 45 | that is, we omit the innovation e_{k}=y_{k}-Cx_{k} 46 | 47 | """ 48 | import numpy as np 49 | import matplotlib.pyplot as plt 50 | from numpy.linalg import inv 51 | from scipy import linalg 52 | from scipy.io import loadmat 53 | 54 | 55 | from functionsSID import estimateMarkovParameters 56 | from functionsSID import estimateModel 57 | from functionsSID import estimateInitial 58 | from functionsSID import systemSimulate 59 | from functionsSID import modelError 60 | from functionsSID import whiteTest 61 | from functionsSID import portmanteau 62 | from functionsSID import estimateInitial_K 63 | from functionsSID import systemSimulate_Kopen 64 | from functionsSID import systemSimulate_Kclosed 65 | 66 | 67 | # load the descriptor state-space matrices from the file 68 | matrices = loadmat('matrices1.mat') 69 | Bf=np.matrix(matrices['B'].toarray()) 70 | Cr1=np.matrix(matrices['Cr1'].toarray()) 71 | M1=np.matrix(matrices['M1'].toarray()) 72 | M2=np.matrix(matrices['M2'].toarray()) 73 | M3=np.matrix(matrices['M3'].toarray()) 74 | Zmap=np.matrix(matrices['Zmap']) 75 | pinvZmap=np.linalg.pinv(Zmap) 76 | Cf=pinvZmap*Cr1; 77 | n=M1.shape[0] 78 | m=Bf.shape[1] 79 | r=Cf.shape[0] 80 | 81 | #define the descriptor state-space matrices 82 | Ed=np.block([ 83 | [np.identity(n), np.zeros(shape=(n,n))], 84 | [np.zeros(shape=(n,n)), M1 ] 85 | ]) 86 | 87 | Ad=np.block([ 88 | [np.zeros(shape=(n,n)), np.identity(n)], 89 | [-M3, -M2] 90 | ]) 91 | 92 | Bd=np.block([ 93 | [np.zeros(shape=(n,m))], 94 | [Bf] 95 | ]) 96 | Cd=np.block([[Cf, np.zeros(shape=(r,n))]]) 97 | 98 | # this is the sampling constant 99 | h=0.1*10**(-3) 100 | 101 | Ed=np.asmatrix(Ed) #Unlike numpy.matrix, asmatrix does not make a copy if the input is already a matrix or an ndarray. Equivalent to matrix(data, copy=False). 102 | Ad=np.asmatrix(Ad) 103 | Bd=np.asmatrix(Bd) 104 | Cd=np.asmatrix(Cd) 105 | 106 | # model discretization 107 | invM=inv(Ed-h*Ad) 108 | A=np.matmul(invM,Ed); 109 | B=h*np.matmul(invM,Bd); 110 | C=Cd; 111 | #D=np.asmatrix(np.zeros(shape=(C.shape[0],B.shape[1]))) 112 | 113 | # total time steps used for the identification 114 | time_steps=100 115 | 116 | ############################################################################### 117 | # generation of the identification and validation data 118 | ############################################################################### 119 | 120 | # identification data 121 | # initial state for system simulation 122 | x0id=0.1*np.random.randn(A.shape[0],1) 123 | # input sequence for identification 124 | Uid=1*np.random.randn(B.shape[1],time_steps) 125 | #Uid_test=np.ones((B.shape[1],time_steps)) 126 | # outputs witout the noise 127 | Yid_no_noise,Xid = systemSimulate(A,B,C,Uid,x0id) 128 | 129 | # validation data 130 | x0val=0.1*np.random.randn(A.shape[0],1) 131 | Uval=1*np.random.randn(B.shape[1],time_steps) 132 | Yval_no_noise,Xval = systemSimulate(A,B,C,Uval,x0val) 133 | 134 | # generate a measurement noise for the identification sequence 135 | SNR_target=20 # approximate signal to noise ratio 136 | power_signal_no_noise=(1/time_steps)*np.linalg.norm(Yid_no_noise[0,:],2)**2 137 | power_noise=power_signal_no_noise/SNR_target 138 | noise=np.sqrt(power_noise)*np.random.randn(C.shape[0],time_steps) 139 | # add noise to the identification data 140 | Yid=Yid_no_noise+noise 141 | 142 | # generate a measurement noise for the validation sequence 143 | power_signal_no_noise_val=(1/time_steps)*np.linalg.norm(Yval_no_noise[0,:],2)**2 144 | power_noise_val=power_signal_no_noise_val/SNR_target 145 | noise_val=np.sqrt(power_noise_val)*np.random.randn(C.shape[0],time_steps) 146 | # add noise to the validation data 147 | Yval=Yval_no_noise+noise_val 148 | 149 | # check the SNR ratio 150 | power_noise_test=(1/time_steps)*np.linalg.norm(noise[0,:],2)**2 151 | power_singnal_with_noise=(1/time_steps)*np.linalg.norm(Yid[0,:],2)**2 152 | SNR_test=power_singnal_with_noise/power_noise_test 153 | plt.plot(Yid_no_noise[0,:1000],'k') 154 | plt.plot(Yid[0,:1000],'r') 155 | plt.show() 156 | 157 | plt.plot(Yval_no_noise[0,:1000],'k') 158 | plt.plot(Yval[0,:1000],'r') 159 | plt.show() 160 | 161 | 162 | 163 | ############################################################################### 164 | # Estimation of the VARX model 165 | ############################################################################### 166 | # maximal value of the past window 167 | max_past=15 168 | # estimated Markov matrices 169 | Markov_matrices=[] 170 | # Data matrices 171 | Z_matrices=[] 172 | # Akaike value 173 | Akaike_value=[] 174 | time_computations_Markov=[] 175 | 176 | import time 177 | 178 | # search for the "best" value of the past window 179 | for i in np.arange(1,max_past+1): 180 | t0=time.time() 181 | print(i) 182 | Markov_tmp,Z, Y_p_p_l =estimateMarkovParameters(Uid,Yid,i) 183 | Markov=Markov_tmp 184 | Error=Y_p_p_l-np.matmul(Markov,Z) 185 | Cov=(1/(time_steps-i))*np.matmul(Error,Error.T) 186 | #Akaike_value.append(np.log(np.linalg.det(Cov))+(2/time_steps)*(Markov.shape[0]*Markov.shape[1])) # it is not 2/time_steps, since we use less time steps to estimate the model. 187 | Akaike_value.append(np.log(np.linalg.det(Cov))+(2/(time_steps-i))*(Markov.shape[0]*Markov.shape[1])) 188 | Markov_matrices.append(Markov) 189 | Z_matrices.append(Z) 190 | print(Akaike_value[-1]) 191 | t1=time.time() 192 | time_computations_Markov.append(t1-t0) 193 | print(t1-t0) 194 | 195 | 196 | plt.figure() 197 | plt.plot(np.arange(1,max_past+1),Akaike_value, linewidth=2) 198 | plt.xlabel('past horizon - p') 199 | plt.ylabel('AIC(p)') 200 | #plt.savefig('AIC_long1.png') 201 | 202 | 203 | state_order=35 204 | rel_error=[] 205 | vaf_values=[] 206 | past_values=np.array([5]) 207 | 208 | for i in past_values: 209 | # estimate the final model 210 | Aid,Atilde,Bid,Kid,Cid,s_singular,X_p_p_l = estimateModel(Uid,Yid,Markov_matrices[i-1],Z_matrices[i-1],i,i,state_order) 211 | 212 | # open loop validation of x_{k+1}=Ax_{k}+Bu_{k}, y_{k}=Cx_{k} 213 | # estimate the initial state 214 | h=50 215 | # estimate x0 for the open loop model 216 | x0est=estimateInitial(Aid,Bid,Cid,Uval,Yval,h) 217 | # estimate x0 for the open loop innovation model 218 | #x0est=estimateInitial_K(Atilde,Bid,Cid,Kid,Uval,Yval,h) 219 | # simulate the open loop model 220 | Yval_prediction,Xval_prediction = systemSimulate(Aid,Bid,Cid,Uval,x0est) 221 | # simulate the open loop 222 | #Yval_prediction,Xval_prediction = systemSimulate(Aid,Bid,Cid,Uval,x0est) 223 | #Yval_prediction,Xval_prediction = systemSimulate_Kopen(Atilde,Bid,Cid,Kid,Uval,x0est,np.array([Yval[:,0]]).T) 224 | # compute the errors 225 | relative_error_percentage, vaf_error_percentage, Akaike_error = modelError(Yval,Yval_prediction,r,m,15) 226 | rel_error.append(relative_error_percentage) 227 | vaf_values.append(vaf_error_percentage) 228 | print('Final model relative error %f and VAF value %f' %(relative_error_percentage, vaf_error_percentage)) 229 | 230 | 231 | # plot the prediction and the real output 232 | plt.plot(Yval[0,:300],'k') 233 | plt.plot(Yval_prediction[0,:300],'r') 234 | 235 | 236 | ############################################################################### 237 | # error results, open-loop validation on the basis of the simulation of 238 | # x_{k+1}=Ax_{k}+Bu_{k}, y_{k}=Cx_{k} 239 | 240 | error_n_10=np.array([61.73784275640332, 61.68320493872652, 61.647492646400245, 61.61255732666312, 61.61574490443951, 61.57316251025201, 61.61094756838996, 61.68361398744785, 61.696897499816394, 61.75815490423734, 61.832204578716144, 61.90315699410216, 62.09549936650165, 62.27500397515479, 62.65622269600151, 62.68600106409974]) 241 | vaf_n_10=np.array([61.884387717856185, 61.95182228487064, 61.99586650412028, 62.03892779668651, 62.034999798710366, 62.08745658486096, 62.04091139725103, 61.95131765447528, 61.934928388971485, 61.85930302824225, 61.76778476935794, 61.67999154163542, 61.44148958424793, 61.21823879894455, 60.741977574690665, 60.70465270591687]) 242 | 243 | 244 | error_n_30=np.array([54.29065902259113, 54.310708734530145, 54.42981565996065, 54.456898483287944, 54.66798905785879, 54.79713823571187, 54.99453069112724, 55.27202043374658, 55.55293590399011, 55.82291878229903, 56.113411798008215, 56.48611531685258, 56.98720466381795, 57.37048605213823, 58.14694324502029, 58.86527054922467]) 245 | vaf_n_30=np.array([70.52524342892745, 70.50346916753031, 70.37395167222702, 70.3444620758087, 70.11410972369832, 69.97273641176284, 69.75601594062664, 69.450037571715, 69.13871312447166, 68.83801738624847, 68.51285016387152, 68.09318776411232, 67.52458504604127, 67.08627330141411, 66.1893299126039, 65.34879923166584]) 246 | 247 | 248 | error_n_100=np.array([56.198506149096, 56.49937247844585, 56.740770349353184, 56.91186952009547, 57.260475608491014, 57.57523692769132, 57.84927748950521, 58.03033530625045, 58.33566136075643, 58.57956707159448, 58.90476667299885, 59.08706341612691, 59.37562938385709, 59.98400290947469, 60.395507633923586, 60.89887686088258]) 249 | vaf_n_100=np.array([68.4172790661002, 68.07820909541834, 67.80484980161962, 67.61039107727629, 67.21237933089405, 66.85092092720208, 66.53461093942225, 66.32480184244143, 65.9695061360315, 65.68434321704562, 65.30228463199563, 65.08718936858597, 64.74534635270845, 64.01919394956133, 63.52382657640678, 62.9132679708306]) 250 | 251 | 252 | error_n_160=np.array([57.20185251615868, 57.9639935810376, 58.65165978863113, 59.24930587081555, 59.85181341182005, 60.37884829001767, 60.85384708861506, 61.390834540028486, 61.84526924823868, 62.329042473732024, 62.927163008898646, 63.0298987078594, 63.42239053285941, 64.02330998143908, 64.26115652215475, 65.04840488521029]) 253 | vaf_n_160=np.array([67.27948068719631, 66.40175448137433, 65.5998280403867, 64.89519753826542, 64.17760431316677, 63.54394679171031, 62.96809294515455, 62.31165434478845, 61.75162671612864, 61.1509046430771, 60.40172155651498, 60.27231868876983, 59.776003790974656, 59.01015779020562, 58.70503762435128, 57.687050218897504]) 254 | 255 | plt.plot(past_values,error_n_10,'r') 256 | plt.plot(past_values,error_n_30,'b') 257 | plt.plot(past_values,error_n_100,'k') 258 | plt.plot(past_values,error_n_160,'m') 259 | plt.savefig('error_past_value.eps') 260 | plt.show() 261 | 262 | plt.plot(past_values,vaf_n_10,'r') 263 | plt.plot(past_values,vaf_n_30,'b') 264 | plt.plot(past_values,vaf_n_100,'k') 265 | plt.plot(past_values,vaf_n_160,'m') 266 | plt.savefig('vaf_past_value.eps') 267 | plt.show() 268 | 269 | 270 | # plot the prediction and the true value 271 | plt.plot(Yval[0,:200],'k',linewidth=2) 272 | plt.plot(Yval_prediction[0,:200],'r',linewidth=2) 273 | plt.savefig('output_prediction0.eps') 274 | plt.show() 275 | 276 | # residual test 277 | corr_matrices_model_Kopen = whiteTest(Yval,Yval_prediction) 278 | 279 | # Choose the correlation indices 280 | idx1=10 281 | idx2=10 282 | 283 | # extract the correlation vector 284 | vector_corr_Kopen=[] 285 | for matrix in corr_matrices_model_Kopen: 286 | vector_corr_Kopen.append(matrix[idx1,idx2]) 287 | 288 | 289 | # entries that are outside of the two standard-error limits 290 | entries_outside=[x for x in vector_corr_Kopen if np.abs(x) >= (2/(np.sqrt(Yval.shape[1])))] 291 | len(entries_outside)/len(vector_corr_Kopen) 292 | 293 | plt.figure() 294 | plt.plot(vector_corr_Kopen,'b') 295 | plt.plot((2/(np.sqrt(Yval.shape[1])))*np.ones(Yval.shape[1]),'r--') 296 | plt.plot(-(2/(np.sqrt(Yval.shape[1])))*np.ones(Yval.shape[1]),'r--') 297 | plt.xlabel('Lag') 298 | plt.ylabel('Correlation value') 299 | plt.ylim((-0.2,0.2)) 300 | #plt.savefig('corr130_short_modelB.eps') 301 | plt.show() 302 | 303 | # percentages of entries that are outside the bounds 304 | # corr 1-1-0.02530120481927711, corr 1-5 - 0.016867469879518072 305 | # corr 1-15-0.01967871485943775, corr 1-30 - 0.01325301204819277 306 | ############################################################################### 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | ############################################################################### 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | -------------------------------------------------------------------------------- /subspace_identification/functionsSID.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Subspace identification of a Multiple Input Multiple Output (MIMO) state space models of dynamical systems 4 | 5 | Created on Sat Jul 13 23:41:28 2019 6 | @author: Aleksandar Haber 7 | """ 8 | 9 | ############################################################################### 10 | # This function estimates the Markov parameters of the state-space model: 11 | # x_{k+1} = A x_{k} + B u_{k} + Ke(k) 12 | # y_{k} = C x_{k} + e(k) 13 | # The function returns the matrix of the Markov parameters of the model 14 | # Input parameters: 15 | 16 | # "U" - is the input vector of the form U \in mathbb{R}^{m \times timeSteps} 17 | # "Y" - is the output vector of the form Y \in mathbb{R}^{r \times timeSteps} 18 | # "past" is the past horizon 19 | # "future" is the future horizon 20 | # Condition: "future" <= "past" 21 | 22 | # Output parameters: 23 | # The problem beeing solved is 24 | # min_{M_pm1} || Y_p_p_l - M_pm1 Z_0_pm1_l ||_{F}^{2} 25 | # " M_pm1" - matrix of the Markov parameters 26 | # "Z_0_pm1_l" - data matrix used to estimate the Markov parameters, 27 | # this is an input parameter for the "estimateModel()" function 28 | # "Y_p_p_l" is the right-hand side 29 | 30 | 31 | def estimateMarkovParameters(U,Y,past): 32 | import numpy as np 33 | import scipy 34 | from scipy import linalg 35 | 36 | timeSteps=U.shape[1] 37 | m=U.shape[0] 38 | r=Y.shape[0] 39 | l=timeSteps-past-1 40 | 41 | # data matrices for estimating the Markov parameters 42 | Y_p_p_l=np.zeros(shape=(r,l+1)) 43 | Z_0_pm1_l=np.zeros(shape=((m+r)*past,l+1)) # - returned 44 | # the estimated matrix that is returned as the output of the function 45 | M_pm1=np.zeros(shape=(r,(r+m)*past)) # -returned 46 | 47 | 48 | # form the matrices "Y_p_p_l" and "Z_0_pm1_l" 49 | # iterate through columns 50 | for j in range(l+1): 51 | # iterate through rows 52 | for i in range(past): 53 | Z_0_pm1_l[i*(m+r):i*(m+r)+m,j]=U[:,i+j] 54 | Z_0_pm1_l[i*(m+r)+m:i*(m+r)+m+r,j]=Y[:,i+j] 55 | Y_p_p_l[:,j]=Y[:,j+past] 56 | # numpy.linalg.lstsq 57 | #M_pm1=scipy.linalg.lstsq(Z_0_pm1_l.T,Y_p_p_l.T) 58 | M_pm1=np.matmul(Y_p_p_l,linalg.pinv(Z_0_pm1_l)) 59 | 60 | return M_pm1, Z_0_pm1_l, Y_p_p_l 61 | ############################################################################### 62 | # end of function 63 | ############################################################################### 64 | 65 | 66 | 67 | ############################################################################### 68 | # This function estimates the state-space model: 69 | # x_{k+1} = A x_{k} + B u_{k} + Ke(k) 70 | # y_{k} = C x_{k} + e(k) 71 | # Acl= A - KC 72 | 73 | # Input parameters: 74 | 75 | # "U" - is the input matrix of the form U \in mathbb{R}^{m \times timeSteps} 76 | # "Y" - is the output matrix of the form Y \in mathbb{R}^{r \times timeSteps} 77 | # "Markov" - matrix of the Markov parameters returned by the function "estimateMarkovParameters()" 78 | # "Z_0_pm1_l" - data matrix returned by the function "estimateMarkovParameters()" 79 | # "past" is the past horizon 80 | # "future" is the future horizon 81 | # Condition: "future" <= "past" 82 | # "order_estimate" - state order estimate 83 | 84 | # Output parameters: 85 | # the matrices: A,Acl,B,K,C 86 | # s_singular - singular values of the matrix used to estimate the state-sequence 87 | # X_p_p_l - estimated state sequence 88 | 89 | 90 | def estimateModel(U,Y,Markov,Z_0_pm1_l,past,future,order_estimate): 91 | import numpy as np 92 | from scipy import linalg 93 | 94 | timeSteps=U.shape[1] 95 | m=U.shape[0] 96 | r=Y.shape[0] 97 | l=timeSteps-past-1 98 | n=order_estimate 99 | 100 | Qpm1=np.zeros(shape=(future*r,past*(m+r))) 101 | for i in range(future): 102 | Qpm1[i*r:(i+1)*r,i*(m+r):]=Markov[:,:(m+r)*(past-i)] 103 | 104 | 105 | 106 | # estimate the state sequence 107 | Qpm1_times_Z_0_pm1_l=np.matmul(Qpm1,Z_0_pm1_l) 108 | Usvd, s_singular, Vsvd_transpose = np.linalg.svd(Qpm1_times_Z_0_pm1_l, full_matrices=True) 109 | # estimated state sequence 110 | X_p_p_l=np.matmul(np.diag(np.sqrt(s_singular[:n])),Vsvd_transpose[:n,:]) 111 | 112 | 113 | X_pp1_pp1_lm1=X_p_p_l[:,1:] 114 | X_p_p_lm1=X_p_p_l[:,:-1] 115 | 116 | # form the matrices Z_p_p_lm1 and Y_p_p_l 117 | Z_p_p_lm1=np.zeros(shape=(m+r,l)) 118 | Z_p_p_lm1[0:m,0:l]=U[:,past:past+l] 119 | Z_p_p_lm1[m:m+r,0:l]=Y[:,past:past+l] 120 | 121 | Y_p_p_l=np.zeros(shape=(r,l+1)) 122 | Y_p_p_l=Y[:,past:] 123 | 124 | S=np.concatenate((X_p_p_lm1,Z_p_p_lm1),axis=0) 125 | ABK=np.matmul(X_pp1_pp1_lm1,np.linalg.pinv(S)) 126 | 127 | C=np.matmul(Y_p_p_l,np.linalg.pinv(X_p_p_l)) 128 | Acl=ABK[0:n,0:n] 129 | B=ABK[0:n,n:n+m] 130 | K=ABK[0:n,n+m:n+m+r] 131 | A=Acl+np.matmul(K,C) 132 | 133 | 134 | return A,Acl,B,K,C,s_singular,X_p_p_l 135 | ############################################################################### 136 | # end of function 137 | ############################################################################### 138 | 139 | 140 | ############################################################################### 141 | # This function simulates an open loop state-space model: 142 | # x_{k+1} = A x_{k} + B u_{k} 143 | # y_{k} = C x_{k} 144 | # starting from an initial condition x_{0} 145 | 146 | # Input parameters: 147 | # A,B,C - system matrices 148 | # U - the input matrix, its dimensions are \in \mathbb{R}^{m \times simSteps}, where m is the input vector dimension 149 | # Output parameters: 150 | # Y - simulated output - dimensions \in \mathbb{R}^{r \times simSteps}, where r is the output vector dimension 151 | # X - simulated state - dimensions \in \mathbb{R}^{n \times simSteps}, where n is the state vector dimension 152 | 153 | def systemSimulate(A,B,C,U,x0): 154 | import numpy as np 155 | simTime=U.shape[1] 156 | n=A.shape[0] 157 | r=C.shape[0] 158 | X=np.zeros(shape=(n,simTime+1)) 159 | Y=np.zeros(shape=(r,simTime)) 160 | for i in range(0,simTime): 161 | if i==0: 162 | X[:,[i]]=x0 163 | Y[:,[i]]=np.matmul(C,x0) 164 | X[:,[i+1]]=np.matmul(A,x0)+np.matmul(B,U[:,[i]]) 165 | else: 166 | Y[:,[i]]=np.matmul(C,X[:,[i]]) 167 | X[:,[i+1]]=np.matmul(A,X[:,[i]])+np.matmul(B,U[:,[i]]) 168 | 169 | return Y,X 170 | 171 | ############################################################################### 172 | # end of function 173 | ############################################################################### 174 | 175 | ############################################################################### 176 | # This function estimates an initial state x_{0} of the model 177 | # x_{k+1} = A x_{k} + B u_{k} 178 | # y_{k} = C x_{k} 179 | # using the input and output state sequences: {(y_{i}, u_{i})| i=0,1,2,\ldots, h} 180 | # Input parameters: 181 | # "A,B,C" - system matrices 182 | # "U" - is the input matrix of the form U \in mathbb{R}^{m \times timeSteps} 183 | # "Y" - is the output matrix of the form Y \in mathbb{R}^{r \times timeSteps} 184 | # "h" - is the future horizon for the initial state estimation 185 | 186 | # Output parameters: 187 | # "x0_est" 188 | def estimateInitial(A,B,C,U,Y,h): 189 | import numpy as np 190 | n=A.shape[0] 191 | r=C.shape[0] 192 | m=U.shape[0] 193 | 194 | # define the output and input time sequences for estimation 195 | Y_0_hm1=Y[:,0:h] 196 | Y_0_hm1=Y_0_hm1.flatten('F') 197 | Y_0_hm1=Y_0_hm1.reshape((h*r,1)) 198 | 199 | U_0_hm1=U[:,0:h] 200 | U_0_hm1=U_0_hm1.flatten('F') 201 | U_0_hm1=U_0_hm1.reshape((h*m,1)) 202 | 203 | 204 | O_hm1=np.zeros(shape=(h*r,n)) 205 | I_hm1=np.zeros(shape=(h*r,h*m)) 206 | 207 | 208 | for i in range(h): 209 | O_hm1[(i)*r:(i+1)*r,:]=np.matmul(C, np.linalg.matrix_power(A,i)) 210 | if i>0: 211 | for j in range(i-1): 212 | I_hm1[i*r:(i+1)*r,j*m:(j+1)*m]=np.matmul(C,np.matmul(np.linalg.matrix_power(A,i-j-1),B)) 213 | x0_est=np.matmul(np.linalg.pinv(O_hm1),Y_0_hm1-np.matmul(I_hm1,U_0_hm1)) 214 | return x0_est 215 | 216 | ############################################################################### 217 | # end of function 218 | ############################################################################### 219 | 220 | ############################################################################### 221 | # This function computes the prediction performances of estimated models 222 | # Input parameters: 223 | # - "Ytrue" - true system output, dimensions: number of system outputs X time samples 224 | # - "Ypredicted" - output predicted by the model: number of system outputs X time samples 225 | ############################################################################### 226 | def modelError(Ytrue,Ypredicted,r,m,n): 227 | import numpy as np 228 | from numpy import linalg as LA 229 | r=Ytrue.shape[0] 230 | timeSteps=Ytrue.shape[1] 231 | total_parameters=n*(n+m+2*r) 232 | 233 | error_matrix=Ytrue-Ypredicted 234 | Ytrue=Ytrue.flatten('F') 235 | Ytrue=Ytrue.reshape((r*timeSteps,1)) 236 | Ypredicted=Ypredicted.flatten('F') 237 | Ypredicted=Ypredicted.reshape((r*timeSteps,1)) 238 | error=Ytrue-Ypredicted 239 | 240 | relative_error_percentage=(LA.norm(error,2)/LA.norm(Ytrue,2))*100 241 | 242 | vaf_error_percentage = (1 - ((1/timeSteps)*LA.norm(error,2)**2)/((1/timeSteps)*LA.norm(Ytrue,2)**2))*100 243 | vaf_error_percentage=np.maximum(vaf_error_percentage,0) 244 | cov_matrix=(1/(timeSteps))*np.matmul(error_matrix,error_matrix.T) 245 | Akaike_error=np.log(np.linalg.det(cov_matrix))+(2/timeSteps)*(total_parameters) 246 | 247 | return relative_error_percentage, vaf_error_percentage, Akaike_error 248 | 249 | ############################################################################### 250 | # Residual test 251 | ############################################################################### 252 | 253 | def whiteTest(Ytrue,Ypredicted): 254 | import numpy as np 255 | 256 | r=Ytrue.shape[0] 257 | timeSteps=Ytrue.shape[1] 258 | l=timeSteps-10 # l is the total number of autocovariance and autocorrelation matrices 259 | 260 | error_matrix=Ytrue-Ypredicted 261 | 262 | # estimate the mean 263 | error_mean=np.zeros(shape=(r,1)) 264 | for i in range(timeSteps): 265 | error_mean=error_mean+(error_matrix[:,[i]]) 266 | error_mean=(1/timeSteps)*error_mean 267 | 268 | #estimate the autocovariance and autocorrelation matrices (there are two ways, I use the longer one for clarity) 269 | auto_cov_matrices=[] 270 | auto_corr_matrices=[] 271 | 272 | for i in range(l): 273 | tmp_matrix=np.zeros(shape=(r,r)) 274 | for j in np.arange(i,timeSteps): 275 | tmp_matrix=tmp_matrix+np.matmul(error_matrix[:,[j]]-error_mean,(error_matrix[:,[j-i]]-error_mean).T) 276 | tmp_matrix=(1/timeSteps)*tmp_matrix 277 | auto_cov_matrices.append(tmp_matrix) 278 | if i==0: 279 | diag_matrix=np.sqrt(np.diag(np.diag(tmp_matrix))) 280 | diag_matrix=np.linalg.inv(diag_matrix) 281 | tmp_matrix_corr=np.matmul(np.matmul(diag_matrix,tmp_matrix),diag_matrix) 282 | auto_corr_matrices.append(tmp_matrix_corr) 283 | return auto_corr_matrices 284 | 285 | ############################################################################### 286 | # Portmanteau test 287 | ############################################################################### 288 | 289 | def portmanteau(Ytrue,Ypredicted,m_max): 290 | import numpy as np 291 | from scipy import stats 292 | 293 | r=Ytrue.shape[0] 294 | timeSteps=Ytrue.shape[1] 295 | l=timeSteps-10 # l is the total number of autocovariance and autocorrelation matrices 296 | 297 | error_matrix=Ytrue-Ypredicted 298 | 299 | # estimate the mean 300 | error_mean=np.zeros(shape=(r,1)) 301 | for i in range(timeSteps): 302 | error_mean=error_mean+(error_matrix[:,[i]]) 303 | error_mean=(1/timeSteps)*error_mean 304 | 305 | #estimate the autocovariance (there are two ways, I use the longer one for clarity) 306 | auto_cov_matrices=[] 307 | 308 | for i in range(l): 309 | tmp_matrix=np.zeros(shape=(r,r)) 310 | for j in np.arange(i,timeSteps): 311 | tmp_matrix=tmp_matrix+np.matmul(error_matrix[:,[j]]-error_mean,(error_matrix[:,[j-i]]-error_mean).T) 312 | tmp_matrix=(1/timeSteps)*tmp_matrix 313 | auto_cov_matrices.append(tmp_matrix) 314 | 315 | Q=[] 316 | p_value=[] 317 | for i in np.arange(1,m_max+1): 318 | sum=0 319 | for j in np.arange(1,i+1): 320 | sum=sum+(1/(timeSteps-j))*np.trace(np.matmul(auto_cov_matrices[j].T,np.matmul(np.linalg.inv(auto_cov_matrices[0]),np.matmul(auto_cov_matrices[j],np.linalg.inv(auto_cov_matrices[0]))))) 321 | Qtmp=(timeSteps**2)*sum 322 | p_value.append(1-stats.chi2.cdf(Qtmp, (r**2)*i)) 323 | Q.append(Qtmp) 324 | return Q, p_value 325 | 326 | 327 | 328 | 329 | 330 | ############################################################################### 331 | # This function estimates an initial state x_{0} of the model 332 | # x_{k+1} = \tilde{A} x_{k} + B u_{k} + K y_{k} 333 | # y_{k} = C x_{k} 334 | # using the input and output state sequences: {(y_{i}, u_{i})| i=0,1,2,\ldots, h} 335 | # Input parameters: 336 | # "\tilde{A},B,C, K" - system matrices of the Kalman predictor state-space model 337 | # "U" - is the input matrix of the form U \in mathbb{R}^{m \times timeSteps} 338 | # "Y" - is the output matrix of the form Y \in mathbb{R}^{r \times timeSteps} 339 | # "h" - is the future horizon for the initial state estimation 340 | 341 | # Output parameters: 342 | # "x0_est" 343 | def estimateInitial_K(Atilde,B,C,K,U,Y,h): 344 | import numpy as np 345 | 346 | Btilde=np.block([B,K]) 347 | Btilde=np.asmatrix(Btilde) 348 | 349 | n=Atilde.shape[0] 350 | r=C.shape[0] 351 | m=U.shape[0] 352 | m1=r+m 353 | 354 | # define the output and input time sequences for estimation 355 | Y_0_hm1=Y[:,0:h] 356 | Y_0_hm1=Y_0_hm1.flatten('F') 357 | Y_0_hm1=Y_0_hm1.reshape((h*r,1)) 358 | 359 | U_0_hm1=U[:,0:h] 360 | U_0_hm1=U_0_hm1.flatten('F') 361 | U_0_hm1=U_0_hm1.reshape((h*m,1)) 362 | 363 | Z_0_hm1=np.zeros(shape=(h*m1,1)) 364 | for i in range(h): 365 | Z_0_hm1[i*m1:i*m1+m,:]=U_0_hm1[i*m:i*m+m,:] 366 | Z_0_hm1[i*m1+m:i*m1+m1,:]=Y_0_hm1[i*(r):(i+1)*r,:] 367 | 368 | 369 | O_hm1=np.zeros(shape=(h*r,n)) 370 | I_hm1=np.zeros(shape=(h*r,h*m1)) 371 | 372 | 373 | for i in range(h): 374 | O_hm1[(i)*r:(i+1)*r,:]=np.matmul(C, np.linalg.matrix_power(Atilde,i)) 375 | if i>0: 376 | for j in range(i-1): 377 | I_hm1[i*r:(i+1)*r,j*m1:(j+1)*m1]=np.matmul(C,np.matmul(np.linalg.matrix_power(Atilde,i-j-1),Btilde)) 378 | 379 | x0_est=np.matmul(np.linalg.pinv(O_hm1),Y_0_hm1-np.matmul(I_hm1,Z_0_hm1)) 380 | return x0_est 381 | ############################################################################### 382 | # end of function 383 | ############################################################################### 384 | 385 | 386 | 387 | ############################################################################### 388 | # This function performs an open-loop simulation of the state-space model: 389 | # x_{k+1} = Atilde x_{k} + B u_{k} +K y_{k} 390 | # y_{k} = C x_{k} 391 | # starting from an initial condition x_{0} and y_{0} 392 | # Note: 393 | 394 | # Input parameters: 395 | # Atilde,B,C,K - system matrices 396 | # U - the input matrix, its dimensions are \in \mathbb{R}^{m \times simSteps}, where m is the input vector dimension 397 | # Output parameters: 398 | # Y - simulated output - dimensions \in \mathbb{R}^{r \times simSteps}, where r is the output vector dimension 399 | # X - simulated state - dimensions \in \mathbb{R}^{n \times simSteps}, where n is the state vector dimension 400 | 401 | def systemSimulate_Kopen(Atilde,B,C,K,U,x0,y0): 402 | import numpy as np 403 | simTime=U.shape[1] 404 | n=Atilde.shape[0] 405 | r=C.shape[0] 406 | X=np.zeros(shape=(n,simTime+1)) 407 | Y=np.zeros(shape=(r,simTime)) 408 | for i in range(0,simTime): 409 | if i==0: 410 | X[:,[i]]=x0 411 | Y[:,[i]]=y0 412 | X[:,[i+1]]=np.matmul(Atilde,x0)+np.matmul(B,U[:,[i]])+np.matmul(K,y0) 413 | else: 414 | Y[:,[i]]=np.matmul(C,X[:,[i]]) 415 | X[:,[i+1]]=np.matmul(Atilde,X[:,[i]])+np.matmul(B,U[:,[i]])+np.matmul(K,Y[:,[i]]) 416 | 417 | return Y,X 418 | 419 | ############################################################################### 420 | # end of function 421 | ############################################################################### 422 | 423 | 424 | 425 | ############################################################################### 426 | # This function a closed-loop simulation of the state-space model: 427 | # x_{k+1} = Atilde x_{k} + B u_{k} +K y_{k} 428 | # y_{k} = C x_{k} 429 | # starting from an initial condition x_{0} and y_{0} 430 | # Note: 431 | 432 | # Input parameters: 433 | # Atilde,B,C,K - system matrices 434 | # U - the input matrix, its dimensions are \in \mathbb{R}^{m \times simSteps}, where m is the input vector dimension 435 | # Ymeas - the measured output 436 | # Output parameters: 437 | # Y - simulated output - dimensions \in \mathbb{R}^{r \times simSteps}, where r is the output vector dimension 438 | # X - simulated state - dimensions \in \mathbb{R}^{n \times simSteps}, where n is the state vector dimension 439 | 440 | def systemSimulate_Kclosed(Atilde,B,C,K,U,Ymeas,x0): 441 | import numpy as np 442 | simTime=U.shape[1] 443 | n=Atilde.shape[0] 444 | r=C.shape[0] 445 | X=np.zeros(shape=(n,simTime+1)) 446 | Y=np.zeros(shape=(r,simTime)) 447 | for i in range(0,simTime): 448 | if i==0: 449 | X[:,[i]]=x0 450 | Y[:,[i]]=Ymeas[:,[i]] 451 | X[:,[i+1]]=np.matmul(Atilde,x0)+np.matmul(B,U[:,[i]])+np.matmul(K,Ymeas[:,[i]]) 452 | else: 453 | Y[:,[i]]=np.matmul(C,X[:,[i]]) 454 | X[:,[i+1]]=np.matmul(Atilde,X[:,[i]])+np.matmul(B,U[:,[i]])+np.matmul(K,Ymeas[:,[i]]) 455 | 456 | return Y,X 457 | 458 | ############################################################################### 459 | # end of function 460 | ############################################################################### 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | -------------------------------------------------------------------------------- /subspace_identification/matrices1.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/subspace_identification/matrices1.mat -------------------------------------------------------------------------------- /subspace_identification/matrices2.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/subspace_identification/matrices2.mat -------------------------------------------------------------------------------- /subspace_identification/matrices3.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/subspace_identification/matrices3.mat -------------------------------------------------------------------------------- /subspace_identification/matrices4.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/subspace_identification/matrices4.mat -------------------------------------------------------------------------------- /subspace_identification/matrices5.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AleksandarHaber/Machine-Learning-and-System-Identification-for-Adaptive-Optics/9895336ef5b17a4a9e6e5ca31da8df92223742c9/subspace_identification/matrices5.mat -------------------------------------------------------------------------------- /subspace_identification/readme.txt: -------------------------------------------------------------------------------- 1 | The codes used in the Subspace Identification (SI) paper. 2 | 3 | 4 | "final_pbsid_identification_noise.py" - file containing the code used to identify the DM model in the case when the measurements are corrupted by noise 5 | - this file is used to generate the figures 11 and 12 in the SI paper. 6 | 7 | "final_pbsid_identification_no_noise.py" - file containing the code used to identify the DM model in the case when the measurements are not corrupted by noise 8 | - this file is used to generate the figures 8,9,10 in the SI paper. 9 | 10 | "functionsSID.py" - file containing the subspace identification functions. 11 | 12 | 13 | - "matrices1.mat" actuator spacing=0.2, radius=1, stiffness=10^4, damping=500, mass=0.3, mesh size=9 14 | - "matrices2.mat" same parameters as in matrices1.mat except mesh size=8 15 | - "matrices3.mat" same parameters as in matrices1.mat except actuator spacing=0.1 16 | - "matrices4.mat" same parameters as in matrices1.mat except stiffness=10^5 and damping=3000 17 | - "matrices5.mat" same parameters as in matrices4 except actuator spacing=0.1 --------------------------------------------------------------------------------