├── JacobianAlgFunc.m ├── JacobianFunc.m ├── JacobianFunc15i.m ├── JacobianFuncAGC15i.m ├── JacobianFuncControl.m ├── JacobianFuncControl15i.m ├── NDAE_control.m ├── NDAE_reduced_control.m ├── NDAE_reduced_control_2.m ├── NDAE_reduced_control_input_constr.m ├── NDAE_reduced_control_minimized.m ├── ODE_LQR_reduced_control.m ├── README.md ├── case9.m ├── checkAlgebraicSS.m ├── checkGeneratorSS.m ├── checkLinearDAEClassFunctionsSS.m ├── checkNonlinearDAEClassFunctions.m ├── checkNonlinearDAEClassFunctionsSS.m ├── checkPowerFlowSolution.m ├── computeAlgebraicJacobianMatrix.m ├── computeJacobianMatrix.m ├── controlInput.m ├── controlInputAGC.m ├── d3m9bm_seb.m ├── getGenInputVal.m ├── getGenSteadyStateVal.m ├── getICDynamicVariables.m ├── getInitCondAfterDisturbance.m ├── getJacobianMatrix.m ├── getJacobianMatrixFsolve.m ├── getLinearDAEmat.m ├── getNetworkDescriptionMATPOWER.m ├── getNetworkDescriptionPST.m ├── getSteadyStateValues.m ├── getSteadyStateValuesFsolve.m ├── main_prog_case9.m ├── nonlin_ps_DAE_class.m ├── plot_scenario1_case9.m ├── powerNetwork_NDAE.m ├── powerNetwork_NDAE_AGC.m ├── psgen_class.m └── shade.m /JacobianAlgFunc.m: -------------------------------------------------------------------------------- 1 | function [Jac_sparse] = JacobianAlgFunc(state,delta0plus,E0plus,pd0plus,qd0plus) 2 | % Get the Jacobian matrix for the algebraic equations 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/31/2021 5 | 6 | %Load power network's object 7 | filename = 'PNobj.mat'; 8 | load(filename); 9 | 10 | %Jacobian function name 11 | name_jacobian = sprintf('Jac_alg_%s',case_sys.casename); 12 | 13 | %Construct function handle for Jacobian matrix 14 | jac_handle = str2func(name_jacobian); 15 | 16 | %Reshape vectors 17 | pgs = state(1:case_sys.nG,1); 18 | qgs = state(case_sys.nG+1:2*case_sys.nG,1); 19 | vs = state(2*case_sys.nG+1:2*case_sys.nG+case_sys.nN,1); 20 | thes = state(2*case_sys.nG+case_sys.nN+1:end,1); 21 | 22 | %Compute the Jacobian matrix 23 | Jac_sparse = feval(jac_handle,delta0plus,E0plus,pgs,qgs,vs,thes); 24 | 25 | end -------------------------------------------------------------------------------- /JacobianFunc.m: -------------------------------------------------------------------------------- 1 | function [Jac_sparse] = JacobianFunc(t,state) 2 | % Get the Jacobian matrix 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/30/2021 5 | 6 | %Load power network's object 7 | filename = 'PNobj.mat'; 8 | load(filename); 9 | 10 | %Jacobian function name 11 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 12 | 13 | %Construct function handle for Jacobian matrix 14 | jac_handle = str2func(name_jacobian); 15 | 16 | %Reshape vectors 17 | dels = state(1:case_sys.nG,1); 18 | oms = state(case_sys.nG+1:2*case_sys.nG,1); 19 | Eps = state(2*case_sys.nG+1:3*case_sys.nG,1); 20 | Tms = state(3*case_sys.nG+1:4*case_sys.nG,1); 21 | pgs = state(4*case_sys.nG+1:5*case_sys.nG,1); 22 | qgs = state(5*case_sys.nG+1:6*case_sys.nG,1); 23 | vs = state(6*case_sys.nG+1:6*case_sys.nG+case_sys.nN,1); 24 | thes = state(6*case_sys.nG+1+case_sys.nN:end,1); 25 | 26 | %Compute the Jacobian matrix 27 | Jac_sparse = feval(jac_handle,dels,oms,Eps,Tms,pgs,qgs,vs,thes); 28 | 29 | end -------------------------------------------------------------------------------- /JacobianFunc15i.m: -------------------------------------------------------------------------------- 1 | function [JacMat,JacMatprime] = JacobianFunc15i(t,state,stateprime) 2 | % Get the Jacobian matrix 3 | % Author: Sebastian A. Nugroho 4 | % Date: 2/14/2021 5 | 6 | %Load power network's object 7 | filename = 'PNobj.mat'; 8 | load(filename); 9 | 10 | %Load the mass smatrix 11 | filename = 'MassMatrix.mat'; 12 | load(filename); 13 | 14 | %Jacobian function name 15 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 16 | 17 | %Construct function handle for Jacobian matrix 18 | jac_handle = str2func(name_jacobian); 19 | 20 | %Reshape vectors 21 | dels = state(1:case_sys.nG,1); 22 | oms = state(case_sys.nG+1:2*case_sys.nG,1); 23 | Eps = state(2*case_sys.nG+1:3*case_sys.nG,1); 24 | Tms = state(3*case_sys.nG+1:4*case_sys.nG,1); 25 | pgs = state(4*case_sys.nG+1:5*case_sys.nG,1); 26 | qgs = state(5*case_sys.nG+1:6*case_sys.nG,1); 27 | vs = state(6*case_sys.nG+1:6*case_sys.nG+case_sys.nN,1); 28 | thes = state(6*case_sys.nG+1+case_sys.nN:end,1); 29 | 30 | %Compute the Jacobian matrix 31 | JacMat = feval(jac_handle,dels,oms,Eps,Tms,pgs,qgs,vs,thes); 32 | 33 | JacMatprime = -Mass; 34 | 35 | end -------------------------------------------------------------------------------- /JacobianFuncAGC15i.m: -------------------------------------------------------------------------------- 1 | function [JacMat,JacMatprime] = JacobianFuncAGC15i(t,state,stateprime) 2 | % Get the Jacobian matrix 3 | % Author: Sebastian A. Nugroho 4 | % Date: 2/21/2021 5 | 6 | %Load power network's object 7 | filename = 'PNobj.mat'; 8 | load(filename); 9 | 10 | %Jacobian function name 11 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 12 | name_jacobian0 = sprintf('Jac_u_%s',case_sys.casename); 13 | name_jacobian1 = sprintf('Jac_Ad_d_%s',case_sys.casename); 14 | name_jacobian2 = sprintf('Jac_Ad_a_%s',case_sys.casename); 15 | name_jacobian4 = sprintf('Jac_Aa_d_%s',case_sys.casename); 16 | name_jacobian5 = sprintf('Jac_Aa_a_%s',case_sys.casename); 17 | 18 | %Construct function handle for Jacobian matrix 19 | jac_handle = str2func(name_jacobian); 20 | jac_handle0 = str2func(name_jacobian0); 21 | jac_handle1 = str2func(name_jacobian1); 22 | jac_handle2 = str2func(name_jacobian2); 23 | jac_handle4 = str2func(name_jacobian4); 24 | jac_handle5 = str2func(name_jacobian5); 25 | 26 | %Reshape vectors 27 | dels = state(1:case_sys.nG,1); 28 | oms = state(case_sys.nG+1:2*case_sys.nG,1); 29 | Eps = state(2*case_sys.nG+1:3*case_sys.nG,1); 30 | Tms = state(3*case_sys.nG+1:4*case_sys.nG,1); 31 | pgs = state(4*case_sys.nG+1:5*case_sys.nG,1); 32 | qgs = state(5*case_sys.nG+1:6*case_sys.nG,1); 33 | vs = state(6*case_sys.nG+1:6*case_sys.nG+case_sys.nN,1); 34 | thes = state(6*case_sys.nG+1+case_sys.nN:end,1); 35 | 36 | %Compute the matrices 37 | Ad_d = full(feval(jac_handle1,dels,oms,Eps,Tms,pgs,qgs,vs,thes)); 38 | Ad_a = full(feval(jac_handle2,dels,oms,Eps,Tms,pgs,qgs,vs,thes)); 39 | Aa_d = full(feval(jac_handle4,dels,oms,Eps,Tms,pgs,qgs,vs,thes)); 40 | Aa_a = full(feval(jac_handle5,dels,oms,Eps,Tms,pgs,qgs,vs,thes)); 41 | 42 | %Additional terms due to AGC 43 | dfydy = -case_sys.Kg; 44 | dfydw = -(case_sys.Kg/case_sys.nG)*(1./case_sys.Rd + case_sys.D)'; 45 | dfydpg = case_sys.Kg*ones(1,case_sys.nG); 46 | add_terms_row = [zeros(1,case_sys.nG) dfydw zeros(1,2*case_sys.nG) dfydy ... 47 | dfydpg zeros(1,1*case_sys.nG) zeros(1,2*case_sys.nN)]; 48 | add_terms_col1 = zeros(4*case_sys.nG,1); 49 | add_terms_col2 = zeros(2*case_sys.nG+2*case_sys.nN,1); 50 | 51 | %Compute the Jacobian matrix 52 | Jac_full = [Ad_d add_terms_col1 Ad_a; add_terms_row; Aa_d add_terms_col2 Aa_a]; 53 | Jac_sparse = sparse(Jac_full); 54 | 55 | temp = feval(jac_handle0,case_sys.EFd0,case_sys.Tr0); 56 | temp(end+1,:) = zeros(1,2*case_sys.nG); 57 | 58 | Jac_sparse2 = temp*sparse(case_sys.Kagcfull); 59 | 60 | JacMat = Jac_sparse + Jac_sparse2; 61 | 62 | %Load the mass matrix 63 | filename = 'MassMatrixAGC.mat'; 64 | load(filename); 65 | 66 | JacMatprime = -MassAGC; 67 | 68 | end -------------------------------------------------------------------------------- /JacobianFuncControl.m: -------------------------------------------------------------------------------- 1 | function [JacMat] = JacobianFuncControl(t,state) 2 | % Get the Jacobian matrix 3 | % Author: Sebastian A. Nugroho 4 | % Date: 2/2/2021 5 | 6 | %Load power network's object 7 | filename = 'PNobj.mat'; 8 | load(filename); 9 | 10 | %Jacobian function name 11 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 12 | name_jacobian2 = sprintf('Jac_u_%s',case_sys.casename); 13 | 14 | %Construct function handle for Jacobian matrix 15 | jac_handle = str2func(name_jacobian); 16 | jac_handle2 = str2func(name_jacobian2); 17 | 18 | %Reshape vectors 19 | dels = state(1:case_sys.nG,1); 20 | oms = state(case_sys.nG+1:2*case_sys.nG,1); 21 | Eps = state(2*case_sys.nG+1:3*case_sys.nG,1); 22 | Tms = state(3*case_sys.nG+1:4*case_sys.nG,1); 23 | pgs = state(4*case_sys.nG+1:5*case_sys.nG,1); 24 | qgs = state(5*case_sys.nG+1:6*case_sys.nG,1); 25 | vs = state(6*case_sys.nG+1:6*case_sys.nG+case_sys.nN,1); 26 | thes = state(6*case_sys.nG+1+case_sys.nN:end,1); 27 | 28 | %Compute the Jacobian matrix 29 | Jac_sparse = feval(jac_handle,dels,oms,Eps,Tms,pgs,qgs,vs,thes); 30 | 31 | temp = feval(jac_handle2,case_sys.EFd0,case_sys.Tr0); 32 | 33 | Jac_sparse2 = temp*case_sys.Ksparse; 34 | 35 | JacMat = Jac_sparse + Jac_sparse2; 36 | 37 | end -------------------------------------------------------------------------------- /JacobianFuncControl15i.m: -------------------------------------------------------------------------------- 1 | function [JacMat,JacMatprime] = JacobianFuncControl15i(t,state,stateprime) 2 | % Get the Jacobian matrix 3 | % Author: Sebastian A. Nugroho 4 | % Date: 2/2/2021 5 | 6 | %Load power network's object 7 | filename = 'PNobj.mat'; 8 | load(filename); 9 | 10 | %Jacobian function name 11 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 12 | name_jacobian2 = sprintf('Jac_u_%s',case_sys.casename); 13 | 14 | %Construct function handle for Jacobian matrix 15 | jac_handle = str2func(name_jacobian); 16 | jac_handle2 = str2func(name_jacobian2); 17 | 18 | %Reshape vectors 19 | dels = state(1:case_sys.nG,1); 20 | oms = state(case_sys.nG+1:2*case_sys.nG,1); 21 | Eps = state(2*case_sys.nG+1:3*case_sys.nG,1); 22 | Tms = state(3*case_sys.nG+1:4*case_sys.nG,1); 23 | pgs = state(4*case_sys.nG+1:5*case_sys.nG,1); 24 | qgs = state(5*case_sys.nG+1:6*case_sys.nG,1); 25 | vs = state(6*case_sys.nG+1:6*case_sys.nG+case_sys.nN,1); 26 | thes = state(6*case_sys.nG+1+case_sys.nN:end,1); 27 | 28 | %Compute the Jacobian matrix 29 | Jac_sparse = feval(jac_handle,dels,oms,Eps,Tms,pgs,qgs,vs,thes); 30 | 31 | temp = feval(jac_handle2,case_sys.EFd0,case_sys.Tr0); 32 | 33 | Jac_sparse2 = temp*sparse(case_sys.Kdfull); 34 | 35 | JacMat = Jac_sparse + Jac_sparse2; 36 | 37 | %Load the mass matrix 38 | filename = 'MassMatrix.mat'; 39 | load(filename); 40 | 41 | JacMatprime = -Mass; 42 | 43 | end -------------------------------------------------------------------------------- /NDAE_control.m: -------------------------------------------------------------------------------- 1 | function [K] = NDAE_control(sys) 2 | % Computing a stabilizing controller gain matrix for NDAE 3 | % Bring the states to the zero equilibrium 4 | % Author: Sebastian A. Nugroho 5 | % Date: 2/2/2021 6 | 7 | %% Construct an orthogonal complement 8 | %Find an orthogonal complement of E 9 | sys.E_comp = null(sys.E'); 10 | 11 | %Resize 12 | sys.E_comp = sys.E_comp(:,1:sys.nx-sys.re)'; 13 | 14 | %Check if it is an orthogonal complement of E 15 | I_E = sys.E_comp*sys.E_comp'; 16 | if (nnz(I_E - eye(size(I_E,1))) == 0) && (nnz(sys.E_comp*sys.E) == 0) 17 | disp('An orthogonal complement is found.'); 18 | else 19 | disp('An orthogonal complement is NOT found.'); 20 | return; 21 | end 22 | 23 | %% Controller Design 24 | %Constants for positive definiteness 25 | eps1 = 1e-12; %eps; %1e-16; 26 | 27 | %Solve the LMI 28 | %Optimization variables 29 | X = sdpvar(sys.nx,sys.nx,'symmetric'); 30 | Y = sdpvar(sys.nx-sys.re,sys.nx); 31 | W = sdpvar(sys.nu,sys.nx); 32 | beta = sdpvar(1); 33 | 34 | %Objective functions 35 | obj = 1*(norm(X,2) + norm(Y,2) + norm(W,2)); 36 | 37 | F1 = [sys.A*X*sys.E + sys.E'*X*sys.A' + sys.A*sys.E_comp'*Y + Y'*sys.E_comp*sys.A' + sys.Bu*W + W'*sys.Bu' + beta*sys.G*sys.G' sys.E'*X*sys.H' + Y'*sys.E_comp*sys.H'; ... 38 | sys.H*X*sys.E + sys.H*sys.E_comp'*Y -beta*eye(sys.nx)] + eps1*eye(2*sys.nx) <= 0; 39 | 40 | F2 = [X >= eps1*eye(sys.nx), beta >= eps1]; 41 | 42 | %YALMIP settings 43 | ops = sdpsettings('verbose',1,'solver','mosek','showprogress',1); 44 | 45 | %Solve the optimization problem 46 | disp('Solving LMIs...'); 47 | tt = tic; 48 | sol = optimize([F1, F2], obj, ops); 49 | elapsedTime = toc(tt); 50 | disp('Done!'); 51 | fprintf(1, 'Computation time: %f seconds', elapsedTime); 52 | fprintf(1, '\nYALMIP status: %s', sol.info); 53 | fprintf('\n'); 54 | 55 | %Values 56 | Y = value(Y); 57 | X = value(X); 58 | W = value(W); 59 | beta = value(beta); 60 | 61 | %Replace NaN with zeros 62 | Y(isnan(Y)) = 0; 63 | X(isnan(Y)) = 0; 64 | W(isnan(Y)) = 0; 65 | 66 | %Matrices 67 | Qinv = inv(X*sys.E + sys.E_comp'*Y); 68 | K = W*Qinv ; 69 | 70 | end -------------------------------------------------------------------------------- /NDAE_reduced_control.m: -------------------------------------------------------------------------------- 1 | function [Kd,K] = NDAE_reduced_control(sys) 2 | % Computing a stabilizing controller gain matrix for NDAE 3 | % Controller is ud = Kd*xd 4 | % Bring the states to the zero equilibrium 5 | % Author: Sebastian A. Nugroho 6 | % Date: 2/13/2021 7 | 8 | %% Controller Design 9 | %Constants for positive definiteness 10 | eps1 = 1e-15; 11 | eps2 = 1e-15; 12 | 13 | %Solve the LMI 14 | %Optimization variables 15 | X1 = sdpvar(sys.nxd,sys.nxd,'symmetric'); 16 | X2 = sdpvar(sys.nxa,sys.nxd); 17 | Y1 = sdpvar(sys.nxa,sys.nxd); 18 | Y2 = sdpvar(sys.nxa,sys.nxa); 19 | W = sdpvar(sys.nud,sys.nxd); 20 | sigma = sdpvar(1); 21 | 22 | %Objective functions 23 | obj = []; %1e-4*(norm(W,2)); 24 | 25 | F1 = [sys.Ed'*X1*sys.Ad' + sys.Ad*X1*sys.Ed + sys.Ed'*W'*sys.Bud' + sys.Bud*W*sys.Ed + sigma*sys.Gd*sys.Gd' ... %1st row 26 | sys.Ed'*X2'*sys.Aa' + Y1'*sys.Aa' sys.Ed'*X1*sys.Hd' sys.Ed'*X2'*sys.Ha' + Y1'*sys.Ha' ; ... %1st row 27 | sys.Aa*X2*sys.Ed + sys.Aa*Y1 Y2'*sys.Aa' + sys.Aa*Y2 + sigma*sys.Ga*sys.Ga' zeros(sys.nxa,sys.nxd) Y2'*sys.Ha' ; ... 28 | sys.Hd*X1*sys.Ed zeros(sys.nxd,sys.nxa) -sigma*eye(sys.nxd) zeros(sys.nxd,sys.nxa); ... 29 | sys.Ha*X2*sys.Ed + sys.Ha*Y1 sys.Ha*Y2 zeros(sys.nxa,sys.nxd) -sigma*eye(sys.nxa)] + eps1*eye(2*sys.nxd + 2*sys.nxa) <= 0; 30 | 31 | F2 = [X1 >= eps2*eye(sys.nxd), sigma >= eps1]; 32 | 33 | %YALMIP settings 34 | ops = sdpsettings('verbose',1,'solver','mosek','showprogress',1); 35 | 36 | %Solve the optimization problem 37 | disp('Solving LMIs...'); 38 | tt = tic; 39 | sol = optimize([F1, F2], obj, ops); 40 | elapsedTime = toc(tt); 41 | disp('Done!'); 42 | fprintf(1, 'Computation time: %f seconds', elapsedTime); 43 | fprintf(1, '\nYALMIP status: %s', sol.info); 44 | fprintf('\n'); 45 | 46 | %Values 47 | Y1 = value(Y1); 48 | X1 = value(X1); 49 | W = value(W); 50 | sigma = value(sigma); 51 | 52 | %Replace NaN with zeros 53 | Y1(isnan(Y1)) = 0; 54 | X1(isnan(X1)) = 0; 55 | W(isnan(W)) = 0; 56 | 57 | %Matrices 58 | Kd = W*inv(X1) ; 59 | K = [Kd zeros(sys.nud,sys.nxa)]; 60 | 61 | %Compute aux matrices 62 | EdX1 = sys.Ed'*inv(sys.Ed)*inv(X1); 63 | 64 | end -------------------------------------------------------------------------------- /NDAE_reduced_control_2.m: -------------------------------------------------------------------------------- 1 | function [Kd,K] = NDAE_reduced_control_2(sys) 2 | % Computing a stabilizing controller gain matrix for NDAE, version 2 3 | % Controller is ud = Kd*xd 4 | % Bring the states to the zero equilibrium 5 | % Author: Sebastian A. Nugroho 6 | % Date: 3/6/2021 7 | 8 | %% Controller Design 9 | %Constants for positive definiteness 10 | eps1 = 1e-13; 11 | eps2 = 1e-13; 12 | 13 | %Constants 14 | beta1 = 1e-4; 15 | beta2 = 1e-4; 16 | 17 | %Solve the LMI 18 | %Optimization variables 19 | X1 = sdpvar(sys.nxd,sys.nxd,'symmetric'); 20 | X2 = sdpvar(sys.nxa,sys.nxd); 21 | Y1 = sdpvar(sys.nxa,sys.nxd); 22 | Y2 = sdpvar(sys.nxa,sys.nxa); 23 | W = sdpvar(sys.nud,sys.nxd); 24 | % beta1 = sdpvar(1); 25 | % beta2 = sdpvar(1); 26 | sigma = sdpvar(1); 27 | 28 | %Objective functions 29 | obj = 1*1e-4*(norm(W,2)); % + beta1 + beta2; 30 | 31 | % F1 = [sys.Ed*X1*sys.Ad' + sys.Ad*X1*sys.Ed' + sys.Ed*W'*sys.Bud' + sys.Bud*W*sys.Ed' + sigma*sys.Gd*sys.Gd' ... %1st row 32 | % sys.Ed*X2'*sys.Aa' + Y1'*sys.Aa' sys.Ed*X1*sys.Hd' sys.Ed*X2'*sys.Ha' + Y1'*sys.Ha' ; ... %1st row 33 | % sys.Aa*X2*sys.Ed' + sys.Aa*Y1 Y2'*sys.Aa' + sys.Aa*Y2 + sigma*sys.Ga*sys.Ga' zeros(sys.nxa,sys.nxd) Y2'*sys.Ha' ; ... 34 | % sys.Hd*X1*sys.Ed' zeros(sys.nxd,sys.nxa) -beta1*sigma*eye(sys.nxd) zeros(sys.nxd,sys.nxa); ... 35 | % sys.Ha*X2*sys.Ed' + sys.Ha*Y1 sys.Ha*Y2 zeros(sys.nxa,sys.nxd) -beta2*sigma*eye(sys.nxa)] + eps1*eye(2*sys.nxd + 2*sys.nxa) <= 0; 36 | 37 | % F1 = [sys.Ed*X1*sys.Ad' + sys.Ad*X1*sys.Ed' + sys.Ed*W'*sys.Bud' + sys.Bud*W*sys.Ed' ... %1st row 38 | % sys.Ed*X2'*sys.Aa' + Y1'*sys.Aa' sys.Ed*X1*sys.Hd' sys.Ed*X2'*sys.Ha' + Y1'*sys.Ha' sys.Gd zeros(sys.nxd,sys.nga); ... %1st row 39 | % sys.Aa*X2*sys.Ed' + sys.Aa*Y1 Y2'*sys.Aa' + sys.Aa*Y2 zeros(sys.nxa,sys.nxd) Y2'*sys.Ha' zeros(sys.nxa,sys.ngd) sys.Ga; ... 40 | % sys.Hd*X1*sys.Ed' zeros(sys.nxd,sys.nxa) -beta1*eye(sys.nxd) zeros(sys.nxd,sys.nxa) zeros(sys.nxd,sys.ngd+sys.nga); ... 41 | % sys.Ha*X2*sys.Ed' + sys.Ha*Y1 sys.Ha*Y2 zeros(sys.nxa,sys.nxd) -beta2*eye(sys.nxa) zeros(sys.nxa,sys.ngd+sys.nga); ... 42 | % sys.Gd' zeros(sys.ngd,sys.nxa+sys.nxd+sys.nxa) -eye(sys.ngd) zeros(sys.ngd,sys.nga); ... 43 | % zeros(sys.nga,sys.nxd) sys.Ga' zeros(sys.nga,sys.nxd+sys.nxa+sys.ngd) -eye(sys.nga)] + eps1*eye(2*sys.nxd + 2*sys.nxa + sys.ngd + sys.nga) <= 0; 44 | 45 | % F1 = [sys.Ed*X1*sys.Ad' + sys.Ad*X1*sys.Ed' + sys.Ed*W'*sys.Bud' + sys.Bud*W*sys.Ed' ... %1st row 46 | % sys.Ed*X2'*sys.Aa' + Y1'*sys.Aa' sys.Ed*X1*sys.Hd' sys.Ed*X2'*sys.Ha' + Y1'*sys.Ha' sigma*sys.Gd zeros(sys.nxd,sys.nga); ... %1st row 47 | % sys.Aa*X2*sys.Ed' + sys.Aa*Y1 Y2'*sys.Aa' + sys.Aa*Y2 zeros(sys.nxa,sys.nxd) Y2'*sys.Ha' zeros(sys.nxa,sys.ngd) sigma*sys.Ga; ... 48 | % sys.Hd*X1*sys.Ed' zeros(sys.nxd,sys.nxa) -beta1*sigma*eye(sys.nxd) zeros(sys.nxd,sys.nxa) zeros(sys.nxd,sys.ngd+sys.nga); ... 49 | % sys.Ha*X2*sys.Ed' + sys.Ha*Y1 sys.Ha*Y2 zeros(sys.nxa,sys.nxd) -beta2*sigma*eye(sys.nxa) zeros(sys.nxa,sys.ngd+sys.nga); ... 50 | % sigma*sys.Gd' zeros(sys.ngd,sys.nxa+sys.nxd+sys.nxa) -sigma*eye(sys.ngd) zeros(sys.ngd,sys.nga); ... 51 | % zeros(sys.nga,sys.nxd) sigma*sys.Ga' zeros(sys.nga,sys.nxd+sys.nxa+sys.ngd) -sigma*eye(sys.nga)] + eps1*eye(2*sys.nxd + 2*sys.nxa + sys.ngd + sys.nga) <= 0; 52 | 53 | F2 = [X1 >= eps2*eye(sys.nxd), sigma >= eps1]; %beta1 >= eps1, beta2 >= eps1]; 54 | 55 | %YALMIP settings 56 | ops = sdpsettings('verbose',1,'solver','mosek','showprogress',1); 57 | 58 | %Solve the optimization problem 59 | disp('Solving LMIs...'); 60 | tt = tic; 61 | sol = optimize([F1, F2], obj, ops); 62 | elapsedTime = toc(tt); 63 | disp('Done!'); 64 | fprintf(1, 'Computation time: %f seconds', elapsedTime); 65 | fprintf(1, '\nYALMIP status: %s', sol.info); 66 | fprintf('\n'); 67 | 68 | %Values 69 | Y1 = value(Y1); 70 | X1 = value(X1); 71 | W = value(W); 72 | beta1 = value(beta1); 73 | beta2 = value(beta2); 74 | 75 | %Replace NaN with zeros 76 | Y1(isnan(Y1)) = 0; 77 | X1(isnan(X1)) = 0; 78 | W(isnan(W)) = 0; 79 | 80 | %Matrices 81 | Kd = W*inv(X1) ; 82 | K = [Kd zeros(sys.nud,sys.nxa)]; 83 | 84 | %Compute aux matrices 85 | EdX1 = sys.Ed'*inv(sys.Ed)*inv(X1); 86 | 87 | end -------------------------------------------------------------------------------- /NDAE_reduced_control_input_constr.m: -------------------------------------------------------------------------------- 1 | function [Kd,K] = NDAE_reduced_control_input_constr(sys,xd0) 2 | % Computing a stabilizing controller gain matrix for NDAE 3 | % Controller is ud = Kd*xd 4 | % The control input is constrained 5 | % Bring the states to the zero equilibrium 6 | % Author: Sebastian A. Nugroho 7 | % Date: 2/13/2021 8 | 9 | %% Controller Design 10 | %Constants for positive definiteness 11 | eps1 = 1e-12; 12 | 13 | %Tuning parameters 14 | mu_bar = 1e+12; %for Case 9 15 | R = 1e-4*eye(sys.nud); 16 | umax = 1e+5; 17 | % mu_bar = 1e+13; %for Case 14 18 | % R = 1e-4*eye(sys.nud); 19 | % umax = 1e+7; 20 | 21 | %Solve the LMI 22 | %Optimization variables 23 | X1 = sdpvar(sys.nxd,sys.nxd,'symmetric'); 24 | X2 = sdpvar(sys.nxa,sys.nxd); 25 | Y1 = sdpvar(sys.nxa,sys.nxd); 26 | Y2 = sdpvar(sys.nxa,sys.nxa); 27 | W = sdpvar(sys.nud,sys.nxd); 28 | sigma = sdpvar(1); 29 | 30 | %Objective functions 31 | obj = []; %1e-4*(norm(W,2)); 32 | 33 | F1 = [sys.Ed'*X1*sys.Ad' + sys.Ad*X1*sys.Ed + sys.Ed'*W'*sys.Bud' + sys.Bud*W*sys.Ed + sigma*sys.Gd*sys.Gd' ... %1st row 34 | sys.Ed'*X2'*sys.Aa' + Y1'*sys.Aa' sys.Ed'*X1*sys.Hd' sys.Ed'*X2'*sys.Ha' + Y1'*sys.Ha' ; ... %1st row 35 | sys.Aa*X2*sys.Ed + sys.Aa*Y1 Y2'*sys.Aa' + sys.Aa*Y2 + sigma*sys.Ga*sys.Ga' zeros(sys.nxa,sys.nxd) Y2'*sys.Ha' ; ... 36 | sys.Hd*X1*sys.Ed zeros(sys.nxd,sys.nxa) -sigma*eye(sys.nxd) zeros(sys.nxd,sys.nxa); ... 37 | sys.Ha*X2*sys.Ed + sys.Ha*Y1 sys.Ha*Y2 zeros(sys.nxa,sys.nxd) -sigma*eye(sys.nxa)] + eps1*eye(2*sys.nxd + 2*sys.nxa) <= 0; 38 | 39 | F2 = [X1 >= eps1*eye(sys.nxd), sigma >= eps1]; 40 | 41 | F3 = [-mu_bar*eye(sys.nxd) eye(sys.nxd); eye(sys.nxd) -X1*sys.Ed*inv(sys.Ed)'] + eps1*eye(2*sys.nxd) <= 0; 42 | 43 | F4 = [-(umax/(mu_bar*norm(xd0,2)))*X1*sys.Ed*inv(sys.Ed)' inv(sys.Ed)*sys.Ed'*W'; W*sys.Ed*inv(sys.Ed)' -inv(R)] + eps1*eye(sys.nxd + sys.nud) <= 0; 44 | 45 | %YALMIP settings 46 | ops = sdpsettings('verbose',1,'solver','mosek','showprogress',1); 47 | 48 | %Solve the optimization problem 49 | disp('Solving LMIs...'); 50 | tt = tic; 51 | sol = optimize([F1, F2, F3, F4], obj, ops); 52 | elapsedTime = toc(tt); 53 | disp('Done!'); 54 | fprintf(1, 'Computation time: %f seconds', elapsedTime); 55 | fprintf(1, '\nYALMIP status: %s', sol.info); 56 | fprintf('\n'); 57 | 58 | %Values 59 | Y1 = value(Y1); 60 | X1 = value(X1); 61 | W = value(W); 62 | sigma = value(sigma); 63 | 64 | %Replace NaN with zeros 65 | Y1(isnan(Y1)) = 0; 66 | X1(isnan(X1)) = 0; 67 | W(isnan(W)) = 0; 68 | 69 | %Matrices 70 | Kd = W*inv(X1) ; 71 | K = [Kd zeros(sys.nud,sys.nxa)]; 72 | 73 | end -------------------------------------------------------------------------------- /NDAE_reduced_control_minimized.m: -------------------------------------------------------------------------------- 1 | function [Kdnew,K,elapsedTime] = NDAE_reduced_control_minimized(sys) 2 | % Computing a stabilizing controller gain matrix for NDAE 3 | % Controller is ud = Kd*xd 4 | % Bring the states to the zero equilibrium 5 | % Author: Sebastian A. Nugroho 6 | % Date: 2/13/2021 7 | 8 | %% Controller Design 9 | %Constants for positive definiteness 10 | eps1 = 1e-15; 11 | eps2 = 1e-15; 12 | 13 | %Solve the LMI 14 | %Optimization variables 15 | X1 = sdpvar(sys.nxd,sys.nxd,'symmetric'); 16 | X2 = sdpvar(sys.nxa,sys.nxd); 17 | Y1 = sdpvar(sys.nxa,sys.nxd); 18 | Y2 = sdpvar(sys.nxa,sys.nxa); 19 | W = sdpvar(sys.nud,sys.nxd); 20 | sigma = sdpvar(1); 21 | 22 | %Objective functions 23 | obj = 1e-4*(norm(W,2)); 24 | 25 | % F1 = [sys.Ed'*X1*sys.Ad' + sys.Ad*X1*sys.Ed + sys.Ed'*W'*sys.Bud' + sys.Bud*W*sys.Ed + sigma*sys.Gd*sys.Gd' ... %1st row 26 | % sys.Ed'*X2'*sys.Aa' + Y1'*sys.Aa' sys.Ed'*X1*sys.Hd' sys.Ed'*X2'*sys.Ha' + Y1'*sys.Ha' ; ... %1st row 27 | % sys.Aa*X2*sys.Ed + sys.Aa*Y1 Y2'*sys.Aa' + sys.Aa*Y2 + sigma*sys.Ga*sys.Ga' zeros(sys.nxa,sys.nxd) Y2'*sys.Ha' ; ... 28 | % sys.Hd*X1*sys.Ed zeros(sys.nxd,sys.nxa) -sigma*eye(sys.nxd) zeros(sys.nxd,sys.nxa); ... 29 | % sys.Ha*X2*sys.Ed + sys.Ha*Y1 sys.Ha*Y2 zeros(sys.nxa,sys.nxd) -sigma*eye(sys.nxa)] + eps1*eye(2*sys.nxd + 2*sys.nxa) <= 0; 30 | 31 | F1 = [sys.Ed*X1*sys.Ad' + sys.Ad*X1*sys.Ed' + sys.Ed*W'*sys.Bud' + sys.Bud*W*sys.Ed' + sigma*sys.Gd*sys.Gd' ... %1st row 32 | sys.Ed*X2'*sys.Aa' + Y1'*sys.Aa' sys.Ed*X1*sys.Hd' sys.Ed*X2'*sys.Ha' + Y1'*sys.Ha' ; ... %1st row 33 | sys.Aa*X2*sys.Ed' + sys.Aa*Y1 Y2'*sys.Aa' + sys.Aa*Y2 + sigma*sys.Ga*sys.Ga' zeros(sys.nxa,sys.nxd) Y2'*sys.Ha' ; ... 34 | sys.Hd*X1*sys.Ed' zeros(sys.nxd,sys.nxa) -sigma*eye(sys.nxd) zeros(sys.nxd,sys.nxa); ... 35 | sys.Ha*X2*sys.Ed' + sys.Ha*Y1 sys.Ha*Y2 zeros(sys.nxa,sys.nxd) -sigma*eye(sys.nxa)] + eps1*eye(2*sys.nxd + 2*sys.nxa) <= 0; 36 | 37 | F2 = [X1 >= eps2*eye(sys.nxd), sigma >= eps1]; 38 | 39 | %YALMIP settings 40 | ops = sdpsettings('verbose',1,'solver','mosek','showprogress',1); 41 | 42 | %Solve the optimization problem 43 | disp('Solving LMIs...'); 44 | tt = tic; 45 | sol = optimize([F1, F2], obj, ops); 46 | elapsedTime = toc(tt); 47 | disp('Done!'); 48 | fprintf(1, 'Computation time: %f seconds', elapsedTime); 49 | fprintf(1, '\nYALMIP status: %s', sol.info); 50 | fprintf('\n'); 51 | 52 | %Values 53 | Y1 = value(Y1); 54 | X1 = value(X1); 55 | W = value(W); 56 | sigma = value(sigma); 57 | 58 | %Replace NaN with zeros 59 | Y1(isnan(Y1)) = 0; 60 | X1(isnan(X1)) = 0; 61 | W(isnan(W)) = 0; 62 | 63 | %Matrices 64 | Kd = W*inv(X1) 65 | Kdmag = abs(Kd); 66 | Kdmag(Kdmag<1e-6) = 0; 67 | [rId, cId] = find(Kdmag); 68 | Kdnew = zeros(size(Kd)); 69 | for i = 1:length(rId) 70 | Kdnew(rId(i),cId(i)) = Kd(rId(i),cId(i)); 71 | end 72 | K = [Kdnew zeros(sys.nud,sys.nxa)]; 73 | 74 | %Compute aux matrices 75 | EdX1 = sys.Ed'*inv(sys.Ed)*inv(X1); 76 | 77 | end -------------------------------------------------------------------------------- /ODE_LQR_reduced_control.m: -------------------------------------------------------------------------------- 1 | function [Kd,K,elapsedTime] = ODE_LQR_reduced_control(sys,x0) 2 | % Computing a stabilizing controller gain matrix for NDAE 3 | % Based on LQR and linearized ODE power networks 4 | % Bring the states to the zero equilibrium 5 | % Author: Sebastian A. Nugroho 6 | % Date: 2/19/2021 7 | 8 | %% Controller Design 9 | %Constants for positive definiteness 10 | eps1 = 1e-9; 11 | eps2 = 1e-9; 12 | 13 | %Cost matrices 14 | Q = 1e-1*eye(sys.nxlin); 15 | R = 1e-1*eye(sys.nulin); 16 | 17 | %Solve the LMI 18 | %Optimization variables 19 | P = sdpvar(sys.nxlin,sys.nxlin,'symmetric'); 20 | gamma = sdpvar(1); 21 | 22 | %Objective functions 23 | obj = gamma; 24 | 25 | F1 = [sys.Alin*P + P*sys.Alin' - sys.Bulin*inv(R)*sys.Bulin' P; P -inv(Q)] + eps1*eye(2*sys.nxlin) <= 0; 26 | 27 | F2 = [gamma x0'; x0 P] - eps1*eye(sys.nxlin + 1) >= 0; 28 | 29 | F3 = [P >= eps2*eye(sys.nxlin), gamma >= eps2]; 30 | 31 | %YALMIP settings 32 | ops = sdpsettings('verbose',1,'solver','mosek','showprogress',1); 33 | 34 | %Solve the optimization problem 35 | disp('Solving LMIs...'); 36 | tt = tic; 37 | sol = optimize([F1, F2, F3], obj, ops); 38 | elapsedTime = toc(tt); 39 | disp('Done!'); 40 | fprintf(1, 'Computation time: %f seconds', elapsedTime); 41 | fprintf(1, '\nYALMIP status: %s', sol.info); 42 | fprintf('\n'); 43 | 44 | %Values 45 | P = value(P); 46 | gamma = value(gamma); 47 | 48 | %Replace NaN with zeros 49 | P(isnan(P)) = 0; 50 | gamma(isnan(gamma)) = 0; 51 | 52 | %Matrices 53 | Kd = -inv(R)*sys.Bulin'*inv(P); 54 | K = [Kd zeros(sys.nud,sys.nxa)]; 55 | 56 | 57 | 58 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi_Machine_DAE_Control 2 | The codes correspond to our paper on load frequency control for power systems DAE which is accessible here https://arxiv.org/abs/2104.05957. The main program is the main code to run the simulation on the 3-machine, 9-bus system. To run it, you'll need MATPOWER to solve the power flow problem, PST at least for the description of generator parameters, and YALMIP with MOSEK solver to solve LMIs. 3 | -------------------------------------------------------------------------------- /case9.m: -------------------------------------------------------------------------------- 1 | function mpc = case9 2 | %CASE9 Power flow data for 9 bus, 3 generator case. 3 | % Please see CASEFORMAT for details on the case file format. 4 | % 5 | % Based on data from p. 70 of: 6 | % 7 | % Chow, J. H., editor. Time-Scale Modeling of Dynamic Networks with 8 | % Applications to Power Systems. Springer-Verlag, 1982. 9 | % Part of the Lecture Notes in Control and Information Sciences book 10 | % series (LNCIS, volume 46) 11 | % 12 | % which in turn appears to come from: 13 | % 14 | % R.P. Schulz, A.E. Turner and D.N. Ewart, "Long Term Power System 15 | % Dynamics," EPRI Report 90-7-0, Palo Alto, California, 1974. 16 | 17 | % MATPOWER 18 | 19 | %% MATPOWER Case Format : Version 2 20 | mpc.version = '2'; 21 | 22 | %%----- Power Flow Data -----%% 23 | %% system MVA base 24 | mpc.baseMVA = 100; 25 | 26 | %% bus data 27 | % bus_i type Pd Qd Gs Bs area Vm Va baseKV zone Vmax Vmin 28 | mpc.bus = [ 29 | 1 3 0 0 0 0 1 1 0 345 1 1.1 0.9; 30 | 2 2 0 0 0 0 1 1 0 345 1 1.1 0.9; 31 | 3 2 0 0 0 0 1 1 0 345 1 1.1 0.9; 32 | 4 1 0 0 0 0 1 1 0 345 1 1.1 0.9; 33 | % 5 1 90 30 0 0 1 1 0 345 1 1.1 0.9; 34 | % 6 1 0 0 0 0 1 1 0 345 1 1.1 0.9; 35 | % 7 1 100 35 0 0 1 1 0 345 1 1.1 0.9; 36 | % 8 1 0 0 0 0 1 1 0 345 1 1.1 0.9; 37 | % 9 1 125 50 0 0 1 1 0 345 1 1.1 0.9; 38 | 5 1 90 30 0 0 1 1 0 345 1 1.1 0.9; 39 | 6 1 0 0 0 0 1 1 0 345 1 1.1 0.9; 40 | 7 1 100 35 0 0 1 1 0 345 1 1.1 0.9; 41 | 8 1 0 0 0 0 1 1 0 345 1 1.1 0.9; 42 | 9 1 125 50 0 0 1 1 0 345 1 1.1 0.9; 43 | ]; 44 | 45 | %% generator data 46 | % bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin Pc1 Pc2 Qc1min Qc1max Qc2min Qc2max ramp_agc ramp_10 ramp_30 ramp_q apf 47 | mpc.gen = [ 48 | 1 72.3 27.03 300 -300 1.04 100 1 250 10 0 0 0 0 0 0 0 0 0 0 0; 49 | 2 163 6.54 300 -300 1.025 100 1 300 10 0 0 0 0 0 0 0 0 0 0 0; 50 | 3 85 -10.95 300 -300 1.025 100 1 270 10 0 0 0 0 0 0 0 0 0 0 0; 51 | ]; 52 | 53 | %% branch data 54 | % fbus tbus r x b rateA rateB rateC ratio angle status angmin angmax 55 | mpc.branch = [ 56 | 1 4 0 0.0576 0 250 250 250 0 0 1 -360 360; 57 | 4 5 0.017 0.092 0.158 250 250 250 0 0 1 -360 360; 58 | 5 6 0.039 0.17 0.358 150 150 150 0 0 1 -360 360; 59 | 3 6 0 0.0586 0 300 300 300 0 0 1 -360 360; 60 | 6 7 0.0119 0.1008 0.209 150 150 150 0 0 1 -360 360; 61 | 7 8 0.0085 0.072 0.149 250 250 250 0 0 1 -360 360; 62 | 8 2 0 0.0625 0 250 250 250 0 0 1 -360 360; 63 | 8 9 0.032 0.161 0.306 250 250 250 0 0 1 -360 360; 64 | 9 4 0.01 0.085 0.176 250 250 250 0 0 1 -360 360; 65 | ]; 66 | 67 | %%----- OPF Data -----%% 68 | %% generator cost data 69 | % 1 startup shutdown n x1 y1 ... xn yn 70 | % 2 startup shutdown n c(n-1) ... c0 71 | mpc.gencost = [ 72 | 2 1500 0 3 0.11 5 150; 73 | 2 2000 0 3 0.085 1.2 600; 74 | 2 3000 0 3 0.1225 1 335; 75 | ]; 76 | -------------------------------------------------------------------------------- /checkAlgebraicSS.m: -------------------------------------------------------------------------------- 1 | function [isAlgSSok] = checkAlgebraicSS(case_sys,pd0,qd0) 2 | % Check the steady-state conditions on all algebraic equations 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/29/2020 5 | 6 | %Evaluate function 7 | [val1,val2] = case_sys.f_gen_alg(case_sys.delta0,case_sys.Ep0,case_sys.pg0,case_sys.qg0,case_sys.v0,case_sys.theta0); 8 | 9 | [valGp,valGq,valLp,valLq] = case_sys.f_pf(case_sys.pg0,case_sys.qg0,case_sys.v0,case_sys.theta0); 10 | 11 | if abs(sum([sum([val1 val2]) sum([valGp+pd0(case_sys.gen_set) valGq+qd0(case_sys.gen_set)]) ... 12 | sum([valLp+pd0(case_sys.load_set) valLq+qd0(case_sys.load_set)])])) < 1e-6 13 | disp('Algebraic steady-state equations satisfied.'); 14 | isAlgSSok = 0; 15 | else 16 | disp('Algebraic steady-state equations NOT satisfied.'); 17 | isAlgSSok = 1; 18 | end 19 | 20 | end -------------------------------------------------------------------------------- /checkGeneratorSS.m: -------------------------------------------------------------------------------- 1 | function [isGenSSok] = checkGeneratorSS(case_sys) 2 | % Check the steady-state conditions on generators 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/29/2020 5 | 6 | %Evaluate function 7 | [delta_dot,omega_dot,Ep_dot,Tm_dot] = case_sys.f_gen_dyn(case_sys.delta0,case_sys.w0*ones(case_sys.nG,1),... 8 | case_sys.Ep0,case_sys.TM0,case_sys.EFd0,case_sys.Tr0,case_sys.pg0,... 9 | case_sys.v0,case_sys.theta0); 10 | 11 | if abs(sum([delta_dot + omega_dot + Ep_dot + Tm_dot])) < 1e-6 12 | disp('Generator steady-state equations satisfied.'); 13 | isGenSSok = 0; 14 | else 15 | disp('Generator steady-state equations NOT satisfied.'); 16 | isGenSSok = 1; 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /checkLinearDAEClassFunctionsSS.m: -------------------------------------------------------------------------------- 1 | function [isLinDAEok] = checkLinearDAEClassFunctionsSS(sys,Xss,Uss) 2 | % Check the linear DAE for the steady state values 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % Assumption: generator buses not connected to loads (fixed) 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | % Using the power balance equations 7 | % Author: Sebastian A. Nugroho 8 | % Date: 8/4/2020 9 | 10 | %Generator dynamics test 11 | gen_dyn_err = full(sys.A_sp)*Xss + full(sys.B_sp)*Uss; 12 | 13 | if max(gen_dyn_err) < 1e-1 14 | disp('Nonlinear DAE equations satisfied.'); 15 | isLinDAEok = 1; 16 | else 17 | disp('Nonlinear DAE equations NOT satisfied.'); 18 | isLinDAEok = 0; 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /checkNonlinearDAEClassFunctions.m: -------------------------------------------------------------------------------- 1 | function [isNonlinDAEok] = checkNonlinearDAEClassFunctions(case_sys) 2 | % Check the nonlinear DAE 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % Assumption: generator buses not connected to loads (fixed) 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | % Using the power balance equations 7 | % Author: Sebastian A. Nugroho 8 | % Date: 7/23/2020 9 | 10 | %Initialize vectors 11 | X = []; 12 | Ig = []; 13 | U = []; 14 | VRG0 = case_sys.vR0(case_sys.gen_set); 15 | VIG0 = case_sys.vI0(case_sys.gen_set); 16 | Vg = []; 17 | for i = 1:case_sys.nG 18 | X = [X; case_sys.deltaG0(i); case_sys.w0v(i); case_sys.EQIp0(i); case_sys.EDIp0(i)]; 19 | Ig = [Ig; real(case_sys.IDQG0(i)); imag(case_sys.IDQG0(i))]; 20 | U = [U; case_sys.TM0(i); case_sys.EFD0(i)]; 21 | Vg = [Vg; VRG0(i); VIG0(i)]; 22 | end 23 | 24 | VRL0 = case_sys.vR0(case_sys.load_set); 25 | VIL0 = case_sys.vI0(case_sys.load_set); 26 | Vl = []; 27 | for i = 1:case_sys.nL 28 | Vl = [Vl; VRL0(i); VIL0(i)]; 29 | end 30 | 31 | %Generator dynamics test 32 | gen_dyn_err = case_sys.f_gen_dyn(X,Ig,U); 33 | 34 | %Generator algebraic equations test 35 | gen_alg_err = case_sys.f_gen_alg(X,Ig,Vg); 36 | 37 | %Power balance equations test 38 | pwr_blc_err = case_sys.f_pf_P(X,Ig,Vg,Vl); 39 | 40 | if max(gen_dyn_err) + sum(gen_alg_err) + sum(pwr_blc_err) < 1e-1 41 | disp('Nonlinear DAE equations satisfied.'); 42 | isNonlinDAEok = 1; 43 | else 44 | disp('Nonlinear DAE equations NOT satisfied.'); 45 | isNonlinDAEok = 0; 46 | end 47 | 48 | end -------------------------------------------------------------------------------- /checkNonlinearDAEClassFunctionsSS.m: -------------------------------------------------------------------------------- 1 | function [isNonlinDAEok,res] = checkNonlinearDAEClassFunctionsSS(case_sys,Xss,Igss,Vgss,Vlss,Uss) 2 | % Check the nonlinear DAE for the steady state values 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % Assumption: generator buses not connected to loads (fixed) 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | % Using the power balance equations 7 | % Author: Sebastian A. Nugroho 8 | % Date: 8/4/2020 9 | 10 | %Generator dynamics test 11 | gen_dyn_err = case_sys.f_gen_dyn(Xss,Igss,Uss); 12 | 13 | %Generator algebraic equations test 14 | gen_alg_err = case_sys.f_gen_alg(Xss,Igss,Vgss); 15 | 16 | %Power balance equations test 17 | pwr_blc_err = case_sys.f_pf_P(Xss,Igss,Vgss,Vlss); 18 | 19 | if sum(gen_dyn_err) + sum(gen_alg_err) + sum(pwr_blc_err) < 1e-1 20 | disp('Nonlinear DAE equations satisfied.'); 21 | isNonlinDAEok = 1; 22 | else 23 | disp('Nonlinear DAE equations NOT satisfied.'); 24 | isNonlinDAEok = 0; 25 | end 26 | 27 | %residue 28 | res = sum(gen_dyn_err) + sum(gen_alg_err) + sum(pwr_blc_err); 29 | 30 | end -------------------------------------------------------------------------------- /checkPowerFlowSolution.m: -------------------------------------------------------------------------------- 1 | function [isPFok] = checkPowerFlowSolution(v0,theta0,pg0,qg0,pl0,ql0,gen_set,load_set,Ybus,nN,nG,nL) 2 | % Check the solution of power flow 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % Assumption: generator buses not connected to loads (fixed) 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | % Using the power balance equations 7 | % Author: Sebastian A. Nugroho 8 | % Date: 7/22/2020 9 | 10 | %Initialize value 11 | Gdiff = 0; 12 | Ldiff = 0; 13 | 14 | %Compute 15 | for i = 1:nN 16 | if ismember(i,gen_set) %Compute generator buses 17 | temp = 0; 18 | for j = 1:nN 19 | temp = temp + v0(i)*v0(j)*abs(full(Ybus(i,j)))*exp(1i*(theta0(i) - theta0(j) - angle(full(Ybus(i,j))))); 20 | end 21 | idx = find(gen_set == i); 22 | Gdiff = Gdiff + (pg0(idx)+1i*qg0(idx)-pl0(i)-1i*ql0(i)-temp); 23 | elseif ismember(i,load_set) %Compute load buses 24 | temp = 0; 25 | for j = 1:nN 26 | temp = temp + v0(i)*v0(j)*abs(full(Ybus(i,j)))*exp(1i*(theta0(i) - theta0(j) - angle(full(Ybus(i,j))))); 27 | end 28 | Ldiff = Ldiff + (-pl0(i)-1i*ql0(i)-temp); 29 | end 30 | end 31 | 32 | if abs(real(Ldiff+Gdiff) + imag(Ldiff+Gdiff)) < 1e-6 33 | disp('Power flow equations satisfied.'); 34 | isPFok = 0; 35 | else 36 | disp('Power flow equations NOT satisfied.'); 37 | isPFok = 1; 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /computeAlgebraicJacobianMatrix.m: -------------------------------------------------------------------------------- 1 | function [isJacobianMade] = computeAlgebraicJacobianMatrix(case_sys) 2 | % Get the Jacobian matrix with constant inputs for the algebraic equations 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/31/2021 5 | 6 | %Jacobian function name 7 | name_Jacobian_m = sprintf('Jac_alg_%s.m',case_sys.casename); 8 | 9 | %Check if the Jacobian function file exists. If it does not, build one 10 | if ~isfile(name_Jacobian_m) 11 | %Construct the Jacobian matrix 12 | 13 | %Declare symbolic variables 14 | dels = sym('dels', [case_sys.nG 1], 'real'); %delta 15 | % oms = sym('oms', [case_sys.nG 1], 'real'); %omega 16 | Eps = sym('Eps', [case_sys.nG 1], 'real'); %Eprime 17 | % Tms = sym('Tms', [case_sys.nG 1], 'real'); %Tm 18 | pgs = sym('pgs', [case_sys.nG 1], 'real'); %Pg 19 | qgs = sym('qgs', [case_sys.nG 1], 'real'); %Qg 20 | vs = sym('vs', [case_sys.nN 1], 'real'); %v 21 | thes = sym('thes', [case_sys.nN 1], 'real'); %theta 22 | 23 | %Create auxiliary functions 24 | fgenalg = case_sys.f_gen_alg_compact(dels,Eps,pgs,qgs,vs,thes); 25 | fpf = case_sys.f_pf_loads_compact(pgs,qgs,vs,thes,case_sys.pl0,case_sys.ql0); 26 | 27 | %Start computing the Jacobians 28 | % dfg2ddel = jacobian(fgenalg,dels); 29 | % dfg2doms = jacobian(fgenalg,oms); 30 | % dfg2deps = jacobian(fgenalg,Eps); 31 | % dfg2dtms = jacobian(fgenalg,Tms); 32 | dfg2dpgs = jacobian(fgenalg,pgs); 33 | dfg2dqgs = jacobian(fgenalg,qgs); 34 | dfg2dvs = jacobian(fgenalg,vs); 35 | dfg2dthes = jacobian(fgenalg,thes); 36 | % dpfddel = jacobian(fpf,dels); 37 | % dpfdoms = jacobian(fpf,oms); 38 | % dpfdeps = jacobian(fpf,Eps); 39 | % dpfdtms = jacobian(fpf,Tms); 40 | dpfdpgs = jacobian(fpf,pgs); 41 | dpfdqgs = jacobian(fpf,qgs); 42 | dpfdvs = jacobian(fpf,vs); 43 | dpfdthes = jacobian(fpf,thes); 44 | 45 | %Construct the overall Jacobian matrix of the algebraic equations with respect to the states 46 | Jmx = [dfg2dpgs dfg2dqgs dfg2dvs dfg2dthes; ... 47 | dpfdpgs dpfdqgs dpfdvs dpfdthes]; 48 | 49 | %Save the Jacobian matrix into a file 50 | name_jacobian = sprintf('Jac_alg_%s',case_sys.casename); 51 | Jmx_handler = matlabFunction(Jmx,'File',name_jacobian,'Sparse',true,... 52 | 'Vars',{dels,Eps,pgs,qgs,vs,thes}); 53 | 54 | %Output 55 | disp('Jacobian matrix does not exist.'); 56 | disp('A new one has been created.'); 57 | isJacobianMade = 0; 58 | else 59 | Jmx_handler = []; 60 | name_jacobian = sprintf('Jac_alg_%s',case_sys.casename); 61 | 62 | %Output 63 | disp('Jacobian matrix exists.'); 64 | isJacobianMade = 1; 65 | end 66 | 67 | end -------------------------------------------------------------------------------- /computeJacobianMatrix.m: -------------------------------------------------------------------------------- 1 | function [isJacobianMade] = computeJacobianMatrix(case_sys,alwaysCreate) 2 | % Get the Jacobian matrix with constant inputs 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/29/2021 5 | 6 | %Jacobian function name 7 | name_Jacobian_m = sprintf('Jac_%s.m',case_sys.casename); 8 | name_Jacobian_m2 = sprintf('Jac_u_%s.m',case_sys.casename); 9 | name_Jacobian_m3 = sprintf('Jac_alg_%s.m',case_sys.casename); 10 | 11 | %Check if the Jacobian function file exists. If it does not, build one 12 | if (~isfile(name_Jacobian_m) && ~isfile(name_Jacobian_m2) && ~isfile(name_Jacobian_m3)) || alwaysCreate 13 | %Construct the Jacobian matrix 14 | 15 | %Declare symbolic variables 16 | dels = sym('dels', [case_sys.nG 1], 'real'); %delta 17 | oms = sym('oms', [case_sys.nG 1], 'real'); %omega 18 | Eps = sym('Eps', [case_sys.nG 1], 'real'); %Eprime 19 | Tms = sym('Tms', [case_sys.nG 1], 'real'); %Tm 20 | pgs = sym('pgs', [case_sys.nG 1], 'real'); %Pg 21 | qgs = sym('qgs', [case_sys.nG 1], 'real'); %Qg 22 | vs = sym('vs', [case_sys.nN 1], 'real'); %v 23 | thes = sym('thes', [case_sys.nN 1], 'real'); %theta 24 | Efds = sym('Efds', [case_sys.nG 1], 'real'); %Efd 25 | Trs = sym('Trs', [case_sys.nG 1], 'real'); %Tr 26 | pds = sym('prs', [case_sys.nN 1], 'real'); %Pd 27 | qds = sym('qrs', [case_sys.nN 1], 'real'); %Qd 28 | % ys = sym('ys', [1 1], 'real'); %y 29 | 30 | %Create auxiliary functions 31 | fgendyn = case_sys.f_gen_dyn_compact(dels,oms,Eps,Tms,Efds,Trs,pgs,vs,thes); 32 | % fgendynAGC = case_sys.f_gen_dyn_AGC(dels,oms,Eps,Tms,Efds,Trs,pgs,vs,thes,ys); 33 | fgenalg = case_sys.f_gen_alg_compact(dels,Eps,pgs,qgs,vs,thes); 34 | fpf = case_sys.f_pf_loads_compact(pgs,qgs,vs,thes,pds,qds); 35 | 36 | %Start computing the Jacobians with respect to the states 37 | dfg1ddel = jacobian(fgendyn,dels); 38 | dfg1doms = jacobian(fgendyn,oms); 39 | dfg1deps = jacobian(fgendyn,Eps); 40 | dfg1dtms = jacobian(fgendyn,Tms); 41 | dfg1dpgs = jacobian(fgendyn,pgs); 42 | dfg1dqgs = jacobian(fgendyn,qgs); 43 | dfg1dvs = jacobian(fgendyn,vs); 44 | dfg1dthes = jacobian(fgendyn,thes); 45 | % dfg1dys = jacobian(fgendynAGC(end,1),ys); 46 | dfg2ddel = jacobian(fgenalg,dels); 47 | dfg2doms = jacobian(fgenalg,oms); 48 | dfg2deps = jacobian(fgenalg,Eps); 49 | dfg2dtms = jacobian(fgenalg,Tms); 50 | dfg2dpgs = jacobian(fgenalg,pgs); 51 | dfg2dqgs = jacobian(fgenalg,qgs); 52 | dfg2dvs = jacobian(fgenalg,vs); 53 | dfg2dthes = jacobian(fgenalg,thes); 54 | dfg2dpds = jacobian(fgenalg,pds); 55 | dfg2dqds = jacobian(fgenalg,qds); 56 | dpfddel = jacobian(fpf,dels); 57 | dpfdoms = jacobian(fpf,oms); 58 | dpfdeps = jacobian(fpf,Eps); 59 | dpfdtms = jacobian(fpf,Tms); 60 | dpfdpgs = jacobian(fpf,pgs); 61 | dpfdqgs = jacobian(fpf,qgs); 62 | dpfdpds = jacobian(fpf,pds); 63 | dpfdqds = jacobian(fpf,qds); 64 | dpfdvs = jacobian(fpf,vs); 65 | dpfdthes = jacobian(fpf,thes); 66 | 67 | %Jacobians for dynamics with AGC 68 | 69 | %Start computing the Jacobians with respect to the inputs 70 | dfg1defd = jacobian(fgendyn,Efds); 71 | dfg1dtr = jacobian(fgendyn,Trs); 72 | 73 | %Construct the overall Jacobian matrix of the NDAE with respect to the states 74 | Jmx = [dfg1ddel dfg1doms dfg1deps dfg1dtms dfg1dpgs dfg1dqgs dfg1dvs dfg1dthes; ... 75 | dfg2ddel dfg2doms dfg2deps dfg2dtms dfg2dpgs dfg2dqgs dfg2dvs dfg2dthes; ... 76 | dpfddel dpfdoms dpfdeps dpfdtms dpfdpgs dpfdqgs dpfdvs dpfdthes]; 77 | 78 | %Construct the Jacobian matrix of generator's dynamics with respect to the inputs 79 | Jmu = [dfg1defd dfg1dtr; zeros(2*case_sys.nG,2*case_sys.nG); zeros(2*case_sys.nN,2*case_sys.nG)]; 80 | 81 | %Construct the overall Jacobian matrix of the algebraic equations with respect to the states 82 | Jmx2 = [dfg2dpgs dfg2dqgs dfg2dvs dfg2dthes; ... 83 | dpfdpgs dpfdqgs dpfdvs dpfdthes]; 84 | 85 | %Compute Jacobians for the linearized DAE model 86 | Ad_d = [dfg1ddel dfg1doms dfg1deps dfg1dtms]; 87 | Ad_a = [dfg1dpgs dfg1dqgs dfg1dvs dfg1dthes]; 88 | Bd = [dfg1defd dfg1dtr]; 89 | Aa_d = [dfg2ddel dfg2doms dfg2deps dfg2dtms; dpfddel dpfdoms dpfdeps dpfdtms]; 90 | Aa_a = [dfg2dpgs dfg2dqgs dfg2dvs dfg2dthes; dpfdpgs dpfdqgs dpfdvs dpfdthes]; 91 | Bq = [dfg2dpds dfg2dqds; dpfdpds dpfdqds]; 92 | 93 | %Save the Jacobian matrix into a file 94 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 95 | matlabFunction(Jmx,'File',name_jacobian,'Sparse',true,... 96 | 'Vars',{dels,oms,Eps,Tms,pgs,qgs,vs,thes}); 97 | 98 | %Save the Jacobian matrix into a file 99 | name_jacobian = sprintf('Jac_u_%s',case_sys.casename); 100 | matlabFunction(Jmu,'File',name_jacobian,'Sparse',true,... 101 | 'Vars',{Efds,Trs}); 102 | 103 | %Save the Jacobian matrix into a file 104 | name_jacobian = sprintf('Jac_alg_%s',case_sys.casename); 105 | matlabFunction(Jmx2,'File',name_jacobian,'Sparse',true,... 106 | 'Vars',{dels,Eps,pgs,qgs,vs,thes}); 107 | 108 | %Save the Jacobian matrix into a file 109 | name_jacobian = sprintf('Jac_Ad_d_%s',case_sys.casename); 110 | matlabFunction(Ad_d,'File',name_jacobian,'Sparse',true,... 111 | 'Vars',{dels,oms,Eps,Tms,pgs,qgs,vs,thes}); 112 | 113 | %Save the Jacobian matrix into a file 114 | name_jacobian = sprintf('Jac_Ad_a_%s',case_sys.casename); 115 | matlabFunction(Ad_a,'File',name_jacobian,'Sparse',true,... 116 | 'Vars',{dels,oms,Eps,Tms,pgs,qgs,vs,thes}); 117 | 118 | %Save the Jacobian matrix into a file 119 | name_jacobian = sprintf('Jac_Bd_%s',case_sys.casename); 120 | matlabFunction(Bd,'File',name_jacobian,'Sparse',true,... 121 | 'Vars',{Efds,Trs}); 122 | 123 | %Save the Jacobian matrix into a file 124 | name_jacobian = sprintf('Jac_Aa_d_%s',case_sys.casename); 125 | matlabFunction(Aa_d,'File',name_jacobian,'Sparse',true,... 126 | 'Vars',{dels,oms,Eps,Tms,pgs,qgs,vs,thes}); 127 | 128 | %Save the Jacobian matrix into a file 129 | name_jacobian = sprintf('Jac_Aa_a_%s',case_sys.casename); 130 | matlabFunction(Aa_a,'File',name_jacobian,'Sparse',true,... 131 | 'Vars',{dels,oms,Eps,Tms,pgs,qgs,vs,thes}); 132 | 133 | %Save the Jacobian matrix into a file 134 | name_jacobian = sprintf('Jac_Bq_%s',case_sys.casename); 135 | matlabFunction(Bq,'File',name_jacobian,'Sparse',true,... 136 | 'Vars',{}); 137 | 138 | %Output 139 | disp('Jacobian matrix does not exist.'); 140 | disp('A new one has been created.'); 141 | isJacobianMade = 0; 142 | else 143 | Jmx_handler = []; 144 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 145 | 146 | %Output 147 | disp('Jacobian matrix exists.'); 148 | isJacobianMade = 1; 149 | end 150 | 151 | end -------------------------------------------------------------------------------- /controlInput.m: -------------------------------------------------------------------------------- 1 | function [u] = controlInput(case_sys,sys,initial_input,state,K) 2 | % Construct control inputs 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/30/2021 5 | 6 | u = initial_input + K*(state(1:sys.nxd) - case_sys.x0); 7 | 8 | end -------------------------------------------------------------------------------- /controlInputAGC.m: -------------------------------------------------------------------------------- 1 | function [u] = controlInputAGC(case_sys,sys,initial_input,state,K,x0d) 2 | % Construct control inputs 3 | % Author: Sebastian A. Nugroho 4 | % Date: 2/22/2021 5 | 6 | feedback = K*(state(1:sys.nxd+1) - x0d); 7 | Kpart = K(:,4*case_sys.nG+1); 8 | 9 | u = [initial_input(1:case_sys.nG,1) + feedback(1:case_sys.nG,1); ... 10 | initial_input(case_sys.nG+1:end,1) + Kpart(case_sys.nG+1:end,1)*state(4*case_sys.nG+1,1)]; 11 | 12 | end -------------------------------------------------------------------------------- /d3m9bm_seb.m: -------------------------------------------------------------------------------- 1 | % A 3-machine 9-bus system from Chow's book pp.70 2 | % data3m9b.m 3 | % motor load at buses 7 and 9 4 | 5 | % bus data format 6 | % bus: number, voltage(pu), angle(degree), p_gen(pu), q_gen(pu), 7 | % p_load(pu), q_load(pu),G shunt,B shunt, bus_type 8 | % bus_type - 1, swing bus 9 | % - 2, generator bus (PV bus) 10 | % - 3, load bus (PQ bus) 11 | 12 | bus = [... 13 | % 1 1.04 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1; 14 | % 2 1.02533 0.00 1.63 0.00 0.00 0.00 0.00 0.00 2; 15 | % 3 1.02536 0.00 0.85 0.00 0.00 0.00 0.00 0.00 2; 16 | % 4 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 3; 17 | % 5 1.00 0.00 0.00 0.00 0.90 0.30 0.00 0.00 3; 18 | % 6 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 3; 19 | % 7 1.00 0.00 0.00 0.00 1.00 0.35 0.00 0.00 3; 20 | % 8 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 3; 21 | % 9 1.00 0.00 0.00 0.00 1.25 0.50 0.00 0.00 3]; 22 | 1 1.04 0.00 0.716 0.27 0.00 0.00 0.00 0.00 1; 23 | 2 1.025 9.3 1.63 0.067 0.00 0.00 0.00 0.00 2; 24 | 3 1.025 4.7 0.85 -0.109 0.00 0.00 0.00 0.00 2; 25 | 4 1.026 -2.2 0.00 0.00 0.00 0.00 0.00 0.00 3; 26 | 5 0.996 -4.0 0.00 0.00 1.25 0.5 0.00 0.00 3; 27 | 6 1.013 -3.7 0.00 0.00 0.90 0.3 0.00 0.00 3; 28 | 7 1.026 3.7 0.00 0.00 0.00 0.00 0.00 0.00 3; 29 | 8 1.016 0.7 0.00 0.00 1.00 0.35 0.00 0.00 3; 30 | 9 1.032 2.0 0.00 0.00 0.00 0.00 0.00 0.00 3]; 31 | 32 | 33 | % line data format 34 | % line: from bus, to bus, resistance(pu), reactance(pu), 35 | % line charging(pu), tap ratio 36 | 37 | line = [... 38 | % 1 4 0.0 0.0576 0. 1. 0. ; 39 | % 4 5 0.017 0.092 0.158 1. 0. ; 40 | % 5 6 0.039 0.17 0.358 1. 0. ; 41 | % 3 6 0.0 0.0586 0. 1. 0. ; 42 | % 6 7 0.0119 0.1008 0.209 1. 0. ; 43 | % 7 8 0.0085 0.072 0.149 1. 0. ; 44 | % 8 2 0.0 0.0625 0. 1. 0. ; 45 | % 8 9 0.032 0.161 0.306 1. 0. ; 46 | % 9 4 0.01 0.085 0.176 1. 0. ]; 47 | 1 4 0.0 0.0576 0. 1. 0. ; 48 | 4 5 0.01 0.085 0.176 1. 0. ; 49 | 5 7 0.032 0.161 0.306 1. 0. ; 50 | 4 6 0.017 0.092 0.158 1. 0. ; 51 | 6 9 0.039 0.17 0.358 1. 0. ; 52 | 7 8 0.0085 0.072 0.149 1. 0. ; 53 | 3 9 0.0 0.0586 0. 1. 0. ; 54 | 8 9 0.0119 0.1008 0.209 1. 0. ; 55 | 2 7 0.00 0.0625 0. 1. 0. ]; 56 | 57 | 58 | % Machine data format 59 | % Machine data format 60 | % 1. machine number, 61 | % 2. bus number, 62 | % 3. base mva, 63 | % 4. leakage reactance x_l(pu), 64 | % 5. resistance r_a(pu), 65 | % 6. d-axis sychronous reactance x_d(pu), 66 | % 7. d-axis transient reactance x'_d(pu), 67 | % 8. d-axis subtransient reactance x"_d(pu), 68 | % 9. d-axis open-circuit time constant T'_do(sec), 69 | % 10. d-axis open-circuit subtransient time constant 70 | % T"_do(sec), 71 | % 11. q-axis sychronous reactance x_q(pu), 72 | % 12. q-axis transient reactance x'_q(pu), 73 | % 13. q-axis subtransient reactance x"_q(pu), 74 | % 14. q-axis open-circuit time constant T'_qo(sec), 75 | % 15. q-axis open circuit subtransient time constant 76 | % T"_qo(sec), 77 | % 16. inertia constant H(sec), 78 | % 17. damping coefficient d_o(pu), 79 | % 18. dampling coefficient d_1(pu), 80 | % 19. bus number 81 | % 82 | % note: all the following machines use electro-mechanical model 83 | mac_con = [ ... 84 | 1 1 100 0.000 0.000 0. 0.0608 0 0 0 0 0 0 0 0 23.64 9.6 0 1; 85 | 2 2 100 0.000 0.000 0. 0.1198 0 0 0 0 0 0 0 0 6.4 2.5 0 2; 86 | 3 3 100 0.000 0.000 0. 0.1813 0 0 0 0 0 0 0 0 3.01 1.0 0 3]; 87 | % 1 1 100 0.000 0.000 0. 0.0608 0 0 0 0 0 0 0 0 23.64 0.0 0 1; 88 | % 2 2 100 0.000 0.000 0. 0.1198 0 0 0 0 0 0 0 0 6.4 0.0 0 2; 89 | % 3 3 100 0.000 0.000 0. 0.1813 0 0 0 0 0 0 0 0 3.01 0.0 0 3]; 90 | 91 | 92 | % induction motor data 93 | % 1. Motor Number 94 | % 2. Bus Number 95 | % 3. Motor MVA Base 96 | % 4. rs pu 97 | % 5. xs pu - stator leakage reactance 98 | % 6. Xm pu - magnetizing reactance 99 | % 7. rr pu 100 | % 8. xr pu - rotor leakage reactance 101 | % 9. H s - motor plus load inertia constant 102 | % 15. fraction of bus power drawn by motor 103 | % ind_con = [ ... 104 | % 1 7 25. .001 .01 3 .009 .01 2. 0 0 0 0 0 .15 105 | % 2 9 25. .001 .01 3 .009 .01 2. 0 0 0 0 0 .15 106 | % ]; 107 | % Motor Load Data 108 | % format for motor load data - mld_con 109 | % 1 motor number 110 | % 2 bus number 111 | % 3 stiction load pu on motor base (f1) 112 | % 4 stiction load coefficient (i1) 113 | % 5 external load pu on motor base(f2) 114 | % 6 external load coefficient (i2) 115 | % 116 | % load has the form 117 | % tload = f1*slip^i1 + f2*(1-slip)^i2 118 | % mld_con = [ ... 119 | % 1 7 .1 1 .7 2 120 | % 2 9 .1 1 .7 2 121 | % ]; 122 | 123 | %Switching file defines the simulation control 124 | % row 1 col1 simulation start time (s) (cols 2 to 6 zeros) 125 | % col7 initial time step (s) 126 | % row 2 col1 fault application time (s) 127 | % col2 bus number at which fault is applied 128 | % col3 bus number defining far end of faulted line 129 | % col4 zero sequence impedance in pu on system base 130 | % col5 negative sequence impedance in pu on system base 131 | % col6 type of fault - 0 three phase 132 | % - 1 line to ground 133 | % - 2 line-to-line to ground 134 | % - 3 line-to-line 135 | % - 4 loss of line with no fault 136 | % - 5 loss of load at bus 137 | % col7 time step for fault period (s) 138 | % row 3 col1 near end fault clearing time (s) (cols 2 to 6 zeros) 139 | % col7 time step for second part of fault (s) 140 | % row 4 col1 far end fault clearing time (s) (cols 2 to 6 zeros) 141 | % col7 time step for fault cleared simulation (s) 142 | % row 5 col1 time to change step length (s) 143 | % col7 time step (s) 144 | % 145 | % 146 | % 147 | % row n col1 finishing time (s) (n indicates that intermediate rows may be inserted) 148 | 149 | sw_con = [... 150 | % 0 0 0 0 0 0 0.005;%sets intitial time step 151 | % 0 0 0 0 0 0 1/30;%sets intitial time step 152 | % 0.1 7 5 0 0 0 0.0025; %apply three phase fault at bus 4, on line 7-5 153 | % 0.1 7 5 0 0 0 1/30; %apply three phase fault at bus 4, on line 7-5 154 | % 0.2 0 0 0 0 0 1/30; %clear fault at bus 7 155 | % 0.27 0 0 0 0 0 1/30; %clear fault at bus 7 156 | % 0.3 0 0 0 0 0 1/30; %clear remote end 157 | % 0.35 0 0 0 0 0 1/30; %clear remote end 158 | % 0.50 0 0 0 0 0 0.005; % increase time step 159 | % 0.50 0 0 0 0 0 1/30; % increase time step 160 | % 5.0 0 0 0 0 0 1/30]; % end simulation 161 | 162 | % 0 0 0 0 0 0 1/30;%sets intitial time step 163 | % 0.1 4 5 0 0 0 1/30; %apply three phase fault at bus 4, on line 4-5 164 | % 0.15 0 0 0 0 0 1/30; %clear remote end 165 | % 0.2 0 0 0 0 0 1/30; % increase time step 166 | % 60 0 0 0 0 0 0]; % end simulation 167 | 168 | 0 0 0 0 0 0 1/30;%sets intitial time step 169 | 1.1 4 5 0 0 0 1/30; %apply three phase fault at bus 4, on line 4-5 170 | 1.15 0 0 0 0 0 1/30; %clear remote end 171 | 1.2 0 0 0 0 0 1/30; % increase time step 172 | 20 0 0 0 0 0 0]; % end simulation 173 | 174 | -------------------------------------------------------------------------------- /getGenInputVal.m: -------------------------------------------------------------------------------- 1 | function [EFd0,Tr0] = getGenInputVal(v0,theta0,delta0,E0,TM0,gen_set,xd,xdp) 2 | % Get generator's input values at equilibrium 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/28/2021 5 | 6 | EFd0 = (xd./xdp).*E0 - ((xd-xdp)./xdp).*v0(gen_set).*cos(delta0-theta0(gen_set)); 7 | Tr0 = TM0; 8 | 9 | end -------------------------------------------------------------------------------- /getGenSteadyStateVal.m: -------------------------------------------------------------------------------- 1 | function [delta0,E0,TM0] = getGenSteadyStateVal(v0,theta0,pg0,qg0,gen_set,xq,xdp,nG) 2 | % Get generator's steady-state values at equilibrium 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/27/2021 5 | 6 | %Solve the nonlinear function 7 | problem.options = optimoptions('fsolve','Display','off'); 8 | problem.objective = @(x)genStateFun(x,v0,theta0,pg0,qg0,gen_set,xq,xdp,nG); 9 | problem.x0 = zeros(2*nG,1); 10 | problem.solver='fsolve'; 11 | x = fsolve(problem); 12 | 13 | %Extract the results 14 | delta0 = x(1:2:2*nG); 15 | E0 = x(2:2:2*nG); 16 | 17 | %Compute the remaining values: TMi 18 | TM0 = pg0; 19 | 20 | function val = genStateFun(x,v0,theta0,pg0,qg0,gen_set,xq,xdp,nG) 21 | % The variable x is constructed as x = [x1, x2, ..., x_nG] 22 | % Where x_i = [delta0_i, E0_i]; 23 | 24 | v0red = v0(gen_set); 25 | theta0red = theta0(gen_set); 26 | for i = 1:nG 27 | idx_i = (i-1)*2; 28 | val(idx_i+1) = -pg0(i) + (1/xdp(i))*x(idx_i+2)*v0red(i)*sin(x(idx_i+1)-theta0red(i)) ... 29 | - ((xq(i)-xdp(i))/(2*xdp(i)*xq(i)))*v0red(i)^2*sin(2*(x(idx_i+1)-theta0red(i))); 30 | val(idx_i+2) = -qg0(i) + (1/xdp(i))*x(idx_i+2)*v0red(i)*cos(x(idx_i+1)-theta0red(i)) ... 31 | - ((xq(i)+xdp(i))/(2*xdp(i)*xq(i)))*v0red(i)^2 ... 32 | - ((xq(i)-xdp(i))/(2*xdp(i)*xq(i)))*v0red(i)^2*cos(2*(x(idx_i+1)-theta0red(i))); 33 | end 34 | end 35 | 36 | end -------------------------------------------------------------------------------- /getICDynamicVariables.m: -------------------------------------------------------------------------------- 1 | function [iG,deltaG,IDQG,VDQG,EDIp,EQIp,EFD,TM,W0] = getICDynamicVariables(v0,theta0,pg0,qg0,pl0,ql0,gen_set,Rs,xd,xq,xdp,xqp,w0,nG) 2 | % Get power system's dynamic variables initial condition values 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % Assumption: generator buses not connected to loads (fixed) 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | % According to the steps described in Sauer's book "Power System Dynamics 7 | % and Stability" Section 7.6.3 8 | % Author: Sebastian A. Nugroho 9 | % Date: 7/22/2020 10 | 11 | %Initialization 12 | v0gen = v0(gen_set); 13 | theta0gen = theta0(gen_set); 14 | pg0gen = pg0; 15 | qg0gen = qg0; 16 | pl0gen = pl0(gen_set); 17 | ql0gen = ql0(gen_set); 18 | 19 | %Step 1: get generator current 20 | iG = (pg0gen + pl0gen -1i.*(qg0gen + ql0gen))./conj((v0gen.*exp(1i*theta0gen))); 21 | 22 | %Step 2: get generator current angle delta 23 | deltaG = angle(v0gen.*exp(1i*theta0gen) + (Rs + 1i*xq).*iG); 24 | 25 | %Step 3: get generator current and voltage 26 | IDQG = abs(iG).*exp(1i*(angle(iG) - deltaG + (pi/2)*ones(nG,1))); 27 | VDQG = v0gen.*exp(1i*(theta0gen - deltaG + (pi/2)*ones(nG,1))); 28 | 29 | %Step 4: compute Ed_prime 30 | EDIp1 = real(VDQG) + Rs.*real(IDQG) - xqp.*imag(IDQG); 31 | EDIp2 = (xq-xqp).*imag(IDQG); 32 | 33 | %Check for discrepancy 34 | if (sum(EDIp1-EDIp2)) < 1e-6 35 | EDIp = EDIp1; 36 | else 37 | EDIp = (EDIp1 + EDIp2)./2; 38 | end 39 | 40 | %Step 5: compute Eq_prime 41 | EQIp = imag(VDQG) + Rs.*imag(IDQG) + xdp.*real(IDQG); 42 | 43 | %Step 6: compute Efd 44 | EFD = EQIp + (xd - xdp).*real(IDQG); 45 | 46 | %Step 7: compute Tm 47 | TM = EDIp.*real(IDQG) + EQIp.*imag(IDQG) + (xqp - xdp).*real(IDQG).*imag(IDQG); 48 | 49 | %Synchronous frequency 50 | W0 = w0*ones(nG,1); 51 | 52 | end -------------------------------------------------------------------------------- /getInitCondAfterDisturbance.m: -------------------------------------------------------------------------------- 1 | function [pg0plus,qg0plus,v0plus,theta0plus] = getInitCondAfterDisturbance(case_sys,delta0plus,E0plus,pd0plus,qd0plus) 2 | % Get initial conditions after disturbance occurs 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/31/2021 5 | 6 | options = optimoptions('fsolve','Display','Iter','Algorithm','levenberg-marquardt','InitDamping',0.5, 'ScaleProblem','jacobian',... 7 | 'SpecifyObjectiveGradient',true,'MaxIterations',150,'MaxFunctionEvaluations',300,'OptimalityTolerance',1e-6); 8 | 9 | [res,fval,flag] = fsolve(@(x)genStateFun(x,delta0plus,E0plus,pd0plus,qd0plus),... 10 | [case_sys.pg0; case_sys.qg0; case_sys.v0; case_sys.theta0],options); 11 | 12 | pg0plus = res(1:case_sys.nG,1); 13 | qg0plus = res(case_sys.nG+1:2*case_sys.nG,1); 14 | v0plus = res(2*case_sys.nG+1:2*case_sys.nG+case_sys.nN,1); 15 | theta0plus = res(2*case_sys.nG+case_sys.nN+1:end,1); 16 | 17 | if ((flag == 1) || (flag == 2) || (flag == 3) || (flag == 4)) && abs(sum(fval)) < 1e-6 18 | disp('Initial conditions after disturbance found.'); 19 | else 20 | disp('Initial conditions after disturbance NOT found.'); 21 | end 22 | 23 | function [val,JacVal] = genStateFun(x,delta0plus,E0plus,pd0plus,qd0plus) 24 | %The variable x is constructed as x = [pg, qg, v, theta] 25 | 26 | Pg = x(1:case_sys.nG,1); 27 | Qg = x(case_sys.nG+1:2*case_sys.nG,1); 28 | v = x(2*case_sys.nG+1:2*case_sys.nG+case_sys.nN,1); 29 | theta = x(2*case_sys.nG+case_sys.nN+1:end,1); 30 | 31 | [out1] = case_sys.f_gen_alg_compact(delta0plus,E0plus,Pg,Qg,v,theta); 32 | 33 | [out2] = case_sys.f_pf_loads_compact(Pg,Qg,v,theta,pd0plus,qd0plus); 34 | 35 | val = [out1; out2]; 36 | 37 | [JacVal] = JacobianAlgFunc(x,delta0plus,E0plus,pd0plus,qd0plus); 38 | 39 | end 40 | 41 | end -------------------------------------------------------------------------------- /getJacobianMatrix.m: -------------------------------------------------------------------------------- 1 | function [isJacobianmade] = getJacobianMatrix(case_sys) 2 | % Get the Jacobian matrix with constant inputs for Newton's method 3 | % Author: Sebastian A. Nugroho 4 | % Date: 7/24/2020 5 | 6 | %Jacobian function name 7 | name_jacobian_m = sprintf('Jac_%s.m',case_sys.casename); 8 | 9 | %Check if the Jacobian function file exists. If it does not, build one 10 | if ~isfile(name_jacobian_m) 11 | %Declare symbolic variables 12 | Xs = sym('Xs', [4*case_sys.nG 1], 'real'); 13 | Igs = sym('Igs', [2*case_sys.nG 1], 'real'); 14 | Vgs = sym('Vgs', [2*case_sys.nG 1], 'real'); 15 | Vls = sym('Vls', [2*case_sys.nL 1], 'real'); 16 | 17 | %Construct U for constant inputs 18 | U = zeros(2*case_sys.nG,1); 19 | for i = 1:case_sys.nG 20 | idx_U = (i-1)*2; 21 | U(idx_U+1) = case_sys.TM0(i); 22 | U(idx_U+2) = case_sys.EFD0(i); 23 | end 24 | 25 | %Redefine function 26 | fx = case_sys.F_P_Jac(Xs,Igs,Vgs,Vls,U); 27 | 28 | %Compute the Jacobian matrix 29 | Jmx1 = jacobian(fx,Xs); 30 | Jmx2 = jacobian(fx,Igs); 31 | Jmx3 = jacobian(fx,Vgs); 32 | Jmx4 = jacobian(fx,Vls); 33 | Jmx = [Jmx1 Jmx2 Jmx3 Jmx4]; 34 | 35 | %Save the Jacobian matrix into a file 36 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 37 | Jmx_handler = matlabFunction(Jmx,'File',name_jacobian,'Sparse',true,'Vars',{Xs,Igs,Vgs,Vls}); 38 | 39 | %Output 40 | disp('Jacobian matrix does not exist.'); 41 | disp('A new one has been created.'); 42 | isJacobianmade = 1; 43 | else 44 | Jmx_handler = []; 45 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 46 | 47 | %Output 48 | disp('Jacobian matrix exists.'); 49 | isJacobianmade = 0; 50 | end 51 | 52 | % %Redefine base function 53 | % function [out] = fx_base(X,Ig,Vg,Vl,U) 54 | % out = case_sys.F_P_Jac(X,Ig,Vg,Vl,U); 55 | % end 56 | 57 | end -------------------------------------------------------------------------------- /getJacobianMatrixFsolve.m: -------------------------------------------------------------------------------- 1 | function [isJacobianmade] = getJacobianMatrixFsolve(case_sys) 2 | % Get the Jacobian matrix with constant inputs for Newton's method 3 | % Author: Sebastian A. Nugroho 4 | % Date: 8/13/2020 5 | 6 | %Jacobian function name 7 | name_jacobian_m = sprintf('Jac_Fsolve_%s.m',case_sys.casename); 8 | 9 | %Check if the Jacobian function file exists. If it does not, build one 10 | if ~isfile(name_jacobian_m) 11 | %Declare symbolic variables 12 | Xs = sym('Xs', [4*case_sys.nG 1], 'real'); 13 | Igs = sym('Igs', [2*case_sys.nG 1], 'real'); 14 | Vgs = sym('Vgs', [2*case_sys.nG 1], 'real'); 15 | Vls = sym('Vls', [2*case_sys.nL 1], 'real'); 16 | 17 | %Construct U for constant inputs 18 | U = zeros(2*case_sys.nG,1); 19 | for i = 1:case_sys.nG 20 | idx_U = (i-1)*2; 21 | U(idx_U+1) = case_sys.TM0(i); 22 | U(idx_U+2) = case_sys.EFD0(i); 23 | end 24 | 25 | %Redefine function 26 | fx = case_sys.F_all(Xs,Igs,Vgs,Vls,U); 27 | 28 | %Compute the Jacobian matrix 29 | Jmx1 = jacobian(fx,Xs); 30 | Jmx2 = jacobian(fx,Igs); 31 | Jmx3 = jacobian(fx,Vgs); 32 | Jmx4 = jacobian(fx,Vls); 33 | Jmx = [Jmx1 Jmx2 Jmx3 Jmx4]; 34 | 35 | %Save the Jacobian matrix into a file 36 | name_jacobian = sprintf('Jac_Fsolve_%s',case_sys.casename); 37 | Jmx_handler = matlabFunction(Jmx,'File',name_jacobian,'Sparse',true,'Vars',{Xs,Igs,Vgs,Vls}); 38 | 39 | %Output 40 | disp('Jacobian matrix does not exist.'); 41 | disp('A new one has been created.'); 42 | isJacobianmade = 1; 43 | else 44 | Jmx_handler = []; 45 | name_jacobian = sprintf('Jac_Fsolve_%s',case_sys.casename); 46 | 47 | %Output 48 | disp('Jacobian matrix exists.'); 49 | isJacobianmade = 0; 50 | end 51 | 52 | 53 | end 54 | -------------------------------------------------------------------------------- /getLinearDAEmat.m: -------------------------------------------------------------------------------- 1 | function [Ad_d,Ad_a,Aa_d,Aa_a,Bd,Bq] = getLinearDAEmat(case_sys) 2 | % Get the matrices for the linearized DAE around the equilibrium 3 | % Author: Sebastian A. Nugroho 4 | % Date: 2/18/2021 5 | 6 | %Save the Jacobian matrix into a file 7 | name_jacobian1 = sprintf('Jac_Ad_d_%s',case_sys.casename); 8 | name_jacobian2 = sprintf('Jac_Ad_a_%s',case_sys.casename); 9 | name_jacobian3 = sprintf('Jac_Bd_%s',case_sys.casename); 10 | name_jacobian4 = sprintf('Jac_Aa_d_%s',case_sys.casename); 11 | name_jacobian5 = sprintf('Jac_Aa_a_%s',case_sys.casename); 12 | name_jacobian6 = sprintf('Jac_Bq_%s',case_sys.casename); 13 | 14 | %Construct function handle for Jacobian matrix 15 | jac_handle1 = str2func(name_jacobian1); 16 | jac_handle2 = str2func(name_jacobian2); 17 | jac_handle3 = str2func(name_jacobian3); 18 | jac_handle4 = str2func(name_jacobian4); 19 | jac_handle5 = str2func(name_jacobian5); 20 | jac_handle6 = str2func(name_jacobian6); 21 | 22 | %Auxiliary variable 23 | w0 = case_sys.w0*ones(case_sys.nG,1); 24 | 25 | %Compute the matrices 26 | Ad_d = feval(jac_handle1,case_sys.delta0,w0,case_sys.Ep0,case_sys.TM0,... 27 | case_sys.pg0,case_sys.qg0,case_sys.v0,case_sys.theta0); 28 | Ad_a = feval(jac_handle2,case_sys.delta0,w0,case_sys.Ep0,case_sys.TM0,... 29 | case_sys.pg0,case_sys.qg0,case_sys.v0,case_sys.theta0); 30 | Aa_d = feval(jac_handle4,case_sys.delta0,w0,case_sys.Ep0,case_sys.TM0,... 31 | case_sys.pg0,case_sys.qg0,case_sys.v0,case_sys.theta0); 32 | Aa_a = feval(jac_handle5,case_sys.delta0,w0,case_sys.Ep0,case_sys.TM0,... 33 | case_sys.pg0,case_sys.qg0,case_sys.v0,case_sys.theta0); 34 | Bd = feval(jac_handle3,case_sys.EFd0,case_sys.Tr0); 35 | Bq = feval(jac_handle6); 36 | 37 | end -------------------------------------------------------------------------------- /getNetworkDescriptionMATPOWER.m: -------------------------------------------------------------------------------- 1 | function [nN,nG,nL,Ybus,Gbus,Bbus,node_set,gen_set,load_set,... 2 | Ysh,Cf,Ct,Cg,Yff,Yft,Ytf,Ytt,Sbase,Yf,Yt] = getNetworkDescriptionMATPOWER(net_desc) 3 | % Get power system's network description from MATPOWER 4 | % Code is developed from 'networkParams.m' by Hafez Bazrafshan 5 | % Input: net_desc is obtained from running loadcase function from MATPOWER 6 | % Outputs: 7 | % 1. nN is the number of nodes (or buses) 8 | % 2. nG is the number of generator buses 9 | % 3. nL is the number of load buses 10 | % 4. Ybus: is the complex bus admittance matrix of size(nN,nN) 11 | % 5. Gbus: is the real part of Ybus. 12 | % 6. Bbus: is the imaginary part of Ybus. 13 | % 7. node_set: is the set of node labels for the network and has size(nN,1) 14 | % 8. gen_set: is the set of generator labels in the network and has size(nG,1) 15 | % 9. load_set: is the set of load labels with no generators and has size(nL,1) 16 | % 10. Ysh: is the matrix of node shunt admittances (diagonal) of size(nN,nN) 17 | % 11. Cf: is the from matrix of size(B,N), where B is the number of branches 18 | % 12. Ct: is the to matrix of size(B,N), where B is the number of branches 19 | % 13. Cg: is the generator indicator matrix of size(nN,nG) 20 | % 14. Sbase: base MVA 21 | % 15. Yf: from branch admittance matrix 22 | % 16. Yt: to branch admittance matrix 23 | % Author: Sebastian A. Nugroho 24 | % Date: 7/22/2020 25 | 26 | %Labels 27 | NodeLabels=union(net_desc.branch(:,1),net_desc.branch(:,2)); 28 | GenLabels=net_desc.gen(:,1); 29 | LoadLabels=setdiff(NodeLabels,GenLabels); 30 | 31 | %Number of nodes 32 | nN=length(NodeLabels); 33 | nG=length(GenLabels); 34 | nL=length(LoadLabels); 35 | 36 | %Set of nodes, generators, and loads 37 | node_set=(1:nN).'; 38 | gen_set=zeros(nG,1); 39 | for ii=1:nG 40 | gen_set(ii)=find(NodeLabels==GenLabels(ii)); 41 | end 42 | load_set=zeros(nL,1); 43 | for ii=1:nL 44 | load_set(ii)=find(NodeLabels==LoadLabels(ii)); 45 | end 46 | 47 | %Construct bus and branch admittance matrices 48 | FromNodes=net_desc.branch(:,1); 49 | ToNodes=net_desc.branch(:,2); 50 | B=length(FromNodes); % number of branches 51 | 52 | FromNodesIndices=zeros(B,1); 53 | ToNodesIndices=zeros(B,1); 54 | 55 | for ii=1:B 56 | FromNodesIndices(ii)=find(NodeLabels==FromNodes(ii)); 57 | ToNodesIndices(ii)=find(NodeLabels==ToNodes(ii)); 58 | end 59 | 60 | Cf=sparse(B,nN); 61 | Ct=sparse(B,nN); 62 | 63 | Cf(sub2ind([B nN], (1:B).', FromNodesIndices))=1; 64 | Ct(sub2ind([B nN], (1:B).', ToNodesIndices))=1; 65 | 66 | rmn_vec=net_desc.branch(:,3); 67 | xmn_vec=net_desc.branch(:,4); 68 | zmn_vec=rmn_vec+i*xmn_vec; 69 | ymn_vec=1./zmn_vec; 70 | gmn_vec=real( ymn_vec); 71 | bmn_vec=imag(ymn_vec); 72 | bcmn_vec=net_desc.branch(:,5); 73 | taumn_vec=net_desc.branch(:,9); 74 | taumn_vec(taumn_vec==0)=1; 75 | alphamn_vec=net_desc.branch(:,10); 76 | gs_vec=net_desc.bus(:,5); 77 | bs_vec=net_desc.bus(:,6); 78 | ys_vec=(gs_vec+i*bs_vec)/net_desc.baseMVA; % vector of nodal shunt admittances 79 | 80 | YffVec=(gmn_vec+i*(bmn_vec+bcmn_vec./2))./(taumn_vec.^2); 81 | YftVec=-(gmn_vec+i*bmn_vec)./(taumn_vec.*exp(-i*degrees2radians(alphamn_vec))); 82 | YtfVec=-(gmn_vec+i*bmn_vec)./(taumn_vec.*exp(i*degrees2radians(alphamn_vec))); 83 | YttVec=gmn_vec+i*(bmn_vec+bcmn_vec/2); 84 | 85 | Yff=diag(sparse(YffVec)); 86 | Yft=diag(sparse(YftVec)); 87 | Ytf=diag(sparse(YtfVec)); 88 | Ytt=diag(sparse(YttVec)); 89 | Ysh=diag(sparse(ys_vec)); 90 | 91 | Yf = Yff*Cf + Yft*Ct; 92 | Yt = Ytf*Cf+Ytt*Ct; 93 | Ybus=Cf.'*Yff*Cf+Cf.'*Yft*Ct+Ct.'*Ytf*Cf+Ct.'*Ytt*Ct+Ysh; 94 | Gbus=real(Ybus); 95 | Bbus=imag(Ybus); 96 | 97 | Cg=sparse(nN,nG); 98 | 99 | Cg(sub2ind([nN nG], gen_set, (1:nG).'))=1; 100 | 101 | Sbase = net_desc.baseMVA; 102 | % omega0 = 2*pi*60; %60 Hz 103 | 104 | %Check bus and branch admittance matrices from MATPOWER function 105 | disp('Check MATPOWER data.') 106 | [Ybusm, Yfm, Ytm] = makeYbus(net_desc); 107 | 108 | if ~isequal(Ybus,Ybusm) 109 | disp('Ybus not equal. Use from MATPOWER.') 110 | else 111 | disp('Ybus OK.') 112 | end 113 | 114 | if ~isequal(Yf,Yfm) 115 | disp('Yf not equal. Use from MATPOWER.') 116 | else 117 | disp('Yf OK.') 118 | end 119 | 120 | if ~isequal(Yt,Ytm) 121 | disp('Yt not equal. Use from MATPOWER.') 122 | else 123 | disp('Yt OK.') 124 | end 125 | 126 | function [phaseOut] = degrees2radians(phaseIn) 127 | %DEGREES2RADIANS converts degrees to radians 128 | % degrees2radians( phaseIn ) converts the angle phaseIn in degrees to its 129 | % equivalent in radians. 130 | % Created by Hafez Bazrafshan 8/26/2016 131 | phaseOut=-pi+(pi/180)*(phaseIn+180); 132 | end 133 | 134 | end -------------------------------------------------------------------------------- /getNetworkDescriptionPST.m: -------------------------------------------------------------------------------- 1 | function [w0,M,D,xq,xd,xqp,xdp,Tq0p,Td0p,Rs,Sbase2] = getNetworkDescriptionPST(net_desc) 2 | % Get power system's network description from PST 3 | % input: net_desc is the case file name for PST 4 | % outputs: 5 | % 1. w0 is the synchronous frequency 6 | % 2. M; %Rotor's inertia constant 7 | % 3. D; %Damping coefficient 8 | % 4. xq; %Direct axis synchronous reactance in q-axis 9 | % 5. xd; %Direct axis synchronous reactance in d-axis 10 | % 6. xqp; %Direct axis transient reactance in q-axis 11 | % 7. xdp; %Direct axis transient reactance in d-axis 12 | % 8. Tq0p; %Open circuit time constant in q-axis 13 | % 9. Td0p; %Open circuit time constant in d-axis 14 | % 10. Rs; %Stator resistance 15 | % 11. Sbase2: base MVA from PST 16 | % Author: Sebastian A. Nugroho 17 | % Date: 7/22/2020 18 | 19 | %Load data 20 | lfile =length(net_desc); 21 | % strip off .m and convert to lower case 22 | net_desc = lower(net_desc(1:lfile-2)); 23 | eval(net_desc); 24 | 25 | %Assign generator data 26 | sys_freq = 60; %60 Hz 27 | w0 = 2*pi*sys_freq; %rad/sec 28 | % w0 = 1; %pu 29 | % mac_pot = 100/mac_con(mac_num,3); 30 | M = 0.01*mac_con(:,16); 31 | %M = mac_con(:,16)/(pi*60); 32 | xd = 1.2*mac_con(:,6); 33 | xdp = mac_con(:,7); 34 | xq = mac_con(:,11); 35 | xqp = mac_con(:,12); 36 | Td0p = mac_con(:,9); 37 | Tq0p = 2*mac_con(:,14); 38 | D = 0.2*mac_con(:,17); 39 | Rs = mac_con(:,5); 40 | Sbase2 = mac_con(:,3); 41 | 42 | end -------------------------------------------------------------------------------- /getSteadyStateValues.m: -------------------------------------------------------------------------------- 1 | function [Z_k,X_k,Ig_k,Vg_k,Vl_k,U] = getSteadyStateValues(case_sys) 2 | % Get the steady state values using Newton's method based on TI method 3 | % Author: Sebastian A. Nugroho 4 | % Date: 7/24/2020 5 | 6 | %Jacobian function name 7 | name_jacobian_m = sprintf('Jac_%s.m',case_sys.casename); 8 | name_jacobian = sprintf('Jac_%s',case_sys.casename); 9 | 10 | %Construct function handle for Jacobian matrix 11 | jac_handle = str2func(name_jacobian); 12 | 13 | %Bus voltages 14 | vR0g = case_sys.vR0(case_sys.gen_set); 15 | vI0g = case_sys.vI0(case_sys.gen_set); 16 | vR0l = case_sys.vR0(case_sys.load_set); 17 | vI0l = case_sys.vI0(case_sys.load_set); 18 | 19 | %Create Z0: vector of initial conditions 20 | %Construct X0,Ig0,Vg0,U 21 | X0 = zeros(4*case_sys.nG,1); 22 | Ig0 = zeros(2*case_sys.nG,1); 23 | Vg0 = zeros(2*case_sys.nG,1); 24 | U = zeros(2*case_sys.nG,1); 25 | for i = 1:case_sys.nG 26 | idx_X = (i-1)*4; 27 | idx_Ig = (i-1)*2; 28 | idx_Vg = (i-1)*2; 29 | idx_U = (i-1)*2; 30 | X0(idx_X+1) = case_sys.deltaG0(i); 31 | X0(idx_X+2) = case_sys.w0v(i); 32 | X0(idx_X+3) = case_sys.EQIp0(i); 33 | X0(idx_X+4) = case_sys.EDIp0(i); 34 | Ig0(idx_Ig+1) = real(case_sys.IDQG0(i)); 35 | Ig0(idx_Ig+2) = imag(case_sys.IDQG0(i)); 36 | Vg0(idx_Vg+1) = vR0g(i); 37 | Vg0(idx_Vg+2) = vI0g(i); 38 | U(idx_U+1) = case_sys.TM0(i); 39 | U(idx_U+2) = case_sys.EFD0(i); 40 | end 41 | Vl0 = zeros(2*case_sys.nL,1); 42 | for i = 1:case_sys.nL 43 | idx_Vl = (i-1)*2; 44 | Vl0(idx_Vl+1) = vR0l(i); 45 | Vl0(idx_Vl+2) = vI0l(i); 46 | end 47 | Z0 = [X0; Ig0; Vg0; Vl0]; 48 | 49 | %Check if the Jacobian function file exists. If it does not, exit 50 | if isfile(name_jacobian_m) 51 | 52 | %Set tolerance for termination 53 | Eps_tol = 10^-6; 54 | 55 | %Stopping condition 56 | stop = false; 57 | 58 | %Start the Newton's iteration 59 | k = 1; 60 | while ~stop 61 | %Set prior values for Z 62 | if k == 1 63 | Z_p = Z0; 64 | X_p = X0; 65 | Ig_p = Ig0; 66 | Vg_p = Vg0; 67 | Vl_p = Vl0; 68 | X_p2 = X0; 69 | Ig_p2 = Ig0; 70 | end 71 | 72 | %Compute the Jacobian matrix at k-1 73 | J_p_sparse = feval(jac_handle,X_p,Ig_p,Vg_p,Vl_p); 74 | 75 | %Evaluate the negative of F at k-1 76 | min_F_p_sparse = -case_sys.F_P(X_p,Ig_p,Vg_p,Vl_p,X_p2,Ig_p2,U); 77 | 78 | %Solve for Delta_Zk with linsolve 79 | Delta_Zk = linsolve(full(J_p_sparse),min_F_p_sparse); 80 | 81 | %Update Z 82 | Z_k = Z_p + Delta_Zk; 83 | 84 | %Convert to variables 85 | X_k = Z_k(1:4*case_sys.nG); 86 | Ig_k = Z_k(4*case_sys.nG+1:(4+2)*case_sys.nG); 87 | Vg_k = Z_k((4+2)*case_sys.nG+1:(4+2+2)*case_sys.nG); 88 | Vl_k = Z_k((4+2+2)*case_sys.nG+1:end); 89 | 90 | %Check stopping condition 91 | if norm(Delta_Zk,2) < Eps_tol %Stop 92 | stop = true; 93 | else 94 | X_p2 = X_p; 95 | Ig_p2 = Ig_p; 96 | Z_p = Z_k; 97 | X_p = X_k; 98 | Ig_p = Ig_k; 99 | Vg_p = Vg_k; 100 | Vl_p = Vl_k; 101 | k = k + 1; 102 | end 103 | end 104 | else 105 | Z_k = Z0; 106 | X_k = X0; 107 | Ig_k = Ig0; 108 | Vg_k = Vg0; 109 | Vl_k = Vl0; 110 | end 111 | 112 | end -------------------------------------------------------------------------------- /getSteadyStateValuesFsolve.m: -------------------------------------------------------------------------------- 1 | function [Z_ss,X_ss,Ig_ss,Vg_ss,Vl_ss,U] = getSteadyStateValuesFsolve(case_sys) 2 | % Get the steady state values using Newton's method using Fsolve 3 | % Author: Sebastian A. Nugroho 4 | % Date: 8/13/2020 5 | 6 | %Jacobian function name 7 | name_jacobian_m = sprintf('Jac_Fsolve_%s.m',case_sys.casename); 8 | name_jacobian = sprintf('Jac_Fsolve_%s',case_sys.casename); 9 | 10 | %Construct function handle for Jacobian matrix 11 | jac_handle = str2func(name_jacobian); 12 | 13 | %Bus voltages 14 | vR0g = case_sys.vR0(case_sys.gen_set); 15 | vI0g = case_sys.vI0(case_sys.gen_set); 16 | vR0l = case_sys.vR0(case_sys.load_set); 17 | vI0l = case_sys.vI0(case_sys.load_set); 18 | 19 | %Create Z0: vector of initial conditions 20 | %Construct X0,Ig0,Vg0,U 21 | X0 = zeros(4*case_sys.nG,1); 22 | Ig0 = zeros(2*case_sys.nG,1); 23 | Vg0 = zeros(2*case_sys.nG,1); 24 | U = zeros(2*case_sys.nG,1); 25 | for i = 1:case_sys.nG 26 | idx_X = (i-1)*4; 27 | idx_Ig = (i-1)*2; 28 | idx_Vg = (i-1)*2; 29 | idx_U = (i-1)*2; 30 | X0(idx_X+1) = case_sys.deltaG0(i); 31 | X0(idx_X+2) = case_sys.w0v(i); 32 | X0(idx_X+3) = case_sys.EQIp0(i); 33 | X0(idx_X+4) = case_sys.EDIp0(i); 34 | Ig0(idx_Ig+1) = real(case_sys.IDQG0(i)); 35 | Ig0(idx_Ig+2) = imag(case_sys.IDQG0(i)); 36 | Vg0(idx_Vg+1) = vR0g(i); 37 | Vg0(idx_Vg+2) = vI0g(i); 38 | U(idx_U+1) = case_sys.TM0(i); 39 | U(idx_U+2) = case_sys.EFD0(i); 40 | end 41 | Vl0 = zeros(2*case_sys.nL,1); 42 | for i = 1:case_sys.nL 43 | idx_Vl = (i-1)*2; 44 | Vl0(idx_Vl+1) = vR0l(i); 45 | Vl0(idx_Vl+2) = vI0l(i); 46 | end 47 | Z0 = [X0; Ig0; Vg0; Vl0]; 48 | 49 | %Fsolve options 50 | options = optimoptions('fsolve','Algorithm','trust-region','SpecifyObjectiveGradient',true,... 51 | 'UseParallel',false,'FunctionTolerance',1.0000e-12,'MaxIter',10000,'StepTolerance', 1.0000e-12,... 52 | 'MaxFunctionEvaluations',1e4); 53 | 54 | %Solve using Fsolve 55 | [Z_ss,fval] = fsolve(@(z)fun_all(z), [Z0], options); 56 | 57 | %Show fval 58 | fprintf(1, 'Show fval norm: %f',norm(fval,2)); 59 | fprintf('\n'); 60 | 61 | %Results 62 | X_ss = Z_ss(1:4*case_sys.nG); 63 | Ig_ss = Z_ss(4*case_sys.nG+1:(4+2)*case_sys.nG); 64 | Vg_ss = Z_ss((4+2)*case_sys.nG+1:(4+2+2)*case_sys.nG); 65 | Vl_ss = Z_ss((4+2+2)*case_sys.nG+1:end); 66 | 67 | function [fz,jacobianz] = fun_all(z) 68 | % function [fz] = fun_all(z) 69 | Xs = z(1:4*case_sys.nG); 70 | Igs = z(4*case_sys.nG+1:(4+2)*case_sys.nG); 71 | Vgs = z((4+2)*case_sys.nG+1:(4+2+2)*case_sys.nG); 72 | Vls = z((4+2+2)*case_sys.nG+1:end); 73 | fz = case_sys.F_all(Xs,Igs,Vgs,Vls,U); 74 | jacobianz = feval(jac_handle,Xs,Igs,Vgs,Vls); 75 | jacobianz = jacobianz'; 76 | end 77 | 78 | end -------------------------------------------------------------------------------- /main_prog_case9.m: -------------------------------------------------------------------------------- 1 | % Main program - Case 9 2 | % Author: Sebastian A. Nugroho 3 | % Date: 1/25/2020 4 | 5 | clear 6 | close all 7 | 8 | %Add necessary paths 9 | addpath(genpath('C:\Users\antim\Dropbox\DAE Power Systems\Code\MATPOWER\matpower-master\matpower-master')) 10 | addpath(genpath('C:\Users\antim\Dropbox\DAE Power Systems\Code\PST')) 11 | addpath(genpath('C:\Users\antim\Documents\YALMIP-master\YALMIP-master')) 12 | addpath(genpath('C:\Users\lzg572\Dropbox\MATPOWER\matpower-master\matpower-master')) 13 | addpath(genpath('C:\Users\lzg572\Dropbox\PST')) 14 | addpath(genpath('C:\Users\antim\Dropbox\MATPOWER\matpower-master\matpower-master')) 15 | addpath(genpath('C:\Users\antim\Dropbox\PST')) 16 | 17 | %Filename 18 | filename2 = 'Case9matrixDAE.mat'; 19 | filename2raw = 'Case9matrixDAE'; 20 | casenameraw = 'Case9'; 21 | 22 | %% Simulation settings 23 | 24 | %Need to simulate power systems at steady-state? 25 | isSimulSS = 0; 26 | 27 | %Need to simulate power systems after disturbance? 28 | isSimulDist15s = 0; 29 | 30 | %Need to simulate power systems after disturbance? 31 | isSimulDist15i = 1; 32 | 33 | %Need to stabilize power systems after disturbance? 34 | isRunDist15s = 0; 35 | 36 | %Need to stabilize power systems after disturbance? 37 | isRunDist15i = 1; 38 | 39 | %Need to stabilize power systems after disturbance? 40 | isRunDistLQR15i = 1; 41 | 42 | %Simulation time 43 | tspan1 = 0:0.01:100; 44 | 45 | %% Specify renewable and load characteristics 46 | RenMean = 0.2; %20 percent 47 | % RenVariance = 0.01; 48 | RenDisturbance = -0.04; %Low 49 | LoadDisturbance = 0.04; %Low 50 | distChar = 'low'; 51 | % RenDisturbance = -0.08; %Moderate 52 | % LoadDisturbance = 0.08; %Moderate 53 | % distChar = 'moderate'; 54 | % RenDisturbance = -0.12; %High 55 | % LoadDisturbance = 0.12; %High 56 | % distChar = 'high'; 57 | % RenDisturbance = -0.4; %Ultra 58 | % LoadDisturbance = 0.4; %Ultra 59 | % distChar = 'ultra'; 60 | 61 | %% Set power system test case 62 | %for MATPOWER 63 | CaseFileNameMATPOWER = 'case9'; %3 machine 9 bus system 64 | %for PST 65 | CaseFileNamePST = 'data3m9b_seb.m'; %3 machine 9 bus system 66 | 67 | PauseTime = 1; 68 | 69 | %% Get steady state data from MATPOWER 70 | disp('Get data from MATPOWER.') 71 | %loadcase 72 | network_desc_matpower = loadcase(CaseFileNameMATPOWER); 73 | 74 | %Get steady state network description from MATPOWER 75 | [nN,nG,nL,Ybus,Gbus,Bbus,node_set,gen_set,load_set,... 76 | Ysh,Cf,Ct,Cg,Yff,Yft,Ytf,Ytt,Sbase,Yf,Yt] = getNetworkDescriptionMATPOWER(network_desc_matpower); 77 | 78 | %Define index mapping 79 | GenSetMap = [gen_set (1:nG)']; 80 | LoadSetMap = [load_set (1:nL)']; 81 | 82 | %% Get generator parameters from PST 83 | disp('Get data from PST.') 84 | %Get generator parameters from PST 85 | [w0,M,D,xq,xd,xqp,xdp,Tq0p,Td0p,Rs,Sbase2] = getNetworkDescriptionPST(CaseFileNamePST); 86 | 87 | %Chest valve time constants 88 | % Tch = 0.23*ones(nG,1); %works 89 | % Tch = 0.21*ones(nG,1); 90 | Tch = 0.2*ones(nG,1); 91 | 92 | %Regulation constants 93 | % Rd = 0.07*ones(nG,1).*(2*pi); %works 94 | % Rd = 0.07*ones(nG,1).*(2*pi); 95 | Rd = 0.02*ones(nG,1).*(2*pi); 96 | 97 | %% Specify the disturbances on renewables and loads 98 | %Assigning 20 % of load at each node to wind generation 99 | %Reducing the overall generation by 20 % 100 | disp('Assigning perturbations to renewables.'); 101 | ren_set = find(or(network_desc_matpower.bus(:,3)>0, network_desc_matpower.bus(:,4)>0)); 102 | pl0 = network_desc_matpower.bus(:,3)./Sbase; % third column of bus matrix is real power demand 103 | ql0 = network_desc_matpower.bus(:,4)./Sbase; % fourth column of bus matrix is reactive power demand 104 | pr0 = zeros(size(pl0)); 105 | qr0 = zeros(size(ql0)); 106 | pr0(ren_set) = RenMean.*pl0(ren_set); 107 | pd0 = pl0 - pr0; 108 | qd0 = ql0 - qr0; 109 | network_desc_matpower.bus(:,3) = (pd0).*Sbase; 110 | network_desc_matpower.bus(:,4) = (qd0).*Sbase; 111 | 112 | %Number of renewables 113 | nR = length(ren_set); 114 | 115 | %Nonzero loads 116 | load_set_nonz = ren_set; 117 | nLN = length(load_set_nonz); 118 | 119 | %Matrices for mapping renewables and loads 120 | Cr = sparse(nN,nR); 121 | Cr(sub2ind([nN nR], ren_set, (1:nR).')) = 1; 122 | Cl = sparse(nN,nLN); 123 | Cl(sub2ind([nN nLN], load_set_nonz, (1:nLN).')) = 1; 124 | 125 | %% Get initial values for the state and input variables 126 | 127 | %MATPOWER settings for solving power flow 128 | MatpowerOptions = mpoption('model', 'AC', 'pf.alg', 'NR', 'verbose', 0, 'out.all',1,'pf.enforce_q_lims',1,'pf.tol',1e-9); 129 | 130 | disp('Solve the power flow to get initial condition and steady state values.'); 131 | 132 | %Run the power flow solver via MATPOWER 133 | pf_result = runpf(network_desc_matpower,MatpowerOptions); 134 | 135 | %Get initial conditions data from MATPOWER 136 | v0 = pf_result.bus(:,8); % eighth column of bus matrix is voltage magnitude solution 137 | theta0 = degrees2radians(pf_result.bus(:,9)); % nineth column of bus matrix is voltage phase solution 138 | pg0 = pf_result.gen(:,2)./Sbase; % second column of gen matrix is real power set points (or solution for slack bus) 139 | qg0 = pf_result.gen(:,3)./Sbase; % third column of gen matrix is reactive power solutions 140 | 141 | %Get difference power 142 | % pd0 = network_desc_matpower.bus(:,3)./Sbase; % third column of bus matrix is real power demand 143 | % qd0 = network_desc_matpower.bus(:,4)./Sbase; % fourth column of bus matrix is reactive power demand 144 | 145 | %Sanity check #1 146 | %Check power flow solutions from MATPOWER 147 | [isPFok] = checkPowerFlowSolution(v0,theta0,pg0,qg0,pd0,qd0,gen_set,load_set,Ybus,nN,nG,nL); 148 | pause(PauseTime); 149 | 150 | %% Get generator's steady state values 151 | disp('Get generator steady state values.') 152 | [delta0,E0,TM0] = getGenSteadyStateVal(v0,theta0,pg0,qg0,gen_set,xq,xdp,nG); 153 | 154 | %% Get generator's inputs at steady state 155 | disp('Get generator inputs.') 156 | [EFd0,Tr0] = getGenInputVal(v0,theta0,delta0,E0,TM0,gen_set,xd,xdp); 157 | 158 | %% Assign initial conditions, states, and inputs (at steady-state) 159 | disp('Assign initial conditions.') 160 | 161 | %Select order 162 | order_type = 0; 163 | 164 | if order_type == 1 %State-space structure 165 | x0 = zeros(4*nG,1); 166 | a0 = zeros(2*nG,1); 167 | u0 = zeros(2*nG,1); 168 | for i = 1:nG 169 | idx_i = (i-1)*2; 170 | idx_iG = (i-1)*4; 171 | x0(idx_iG+1) = delta0(i); 172 | x0(idx_iG+2) = w0; 173 | x0(idx_iG+3) = E0(i); 174 | x0(idx_iG+4) = TM0(i); 175 | u0(idx_i+1) = EFd0(i); 176 | u0(idx_i+2) = Tr0(i); 177 | a0(idx_i+1) = pg0(i); 178 | a0(idx_i+2) = qg0(i); 179 | end 180 | V0 = zeros(2*nN,1); 181 | V0(1:2:2*nN) = v0; 182 | V0(2:2:2*nN) = theta0; 183 | else %Simpler structure 184 | x0 = [delta0; w0*ones(nG,1); E0; TM0]; 185 | a0 = [pg0; qg0]; 186 | u0 = [EFd0; Tr0]; 187 | V0 = [v0; theta0]; 188 | end 189 | 190 | %% Create objects for all generators 191 | disp('Create objects for all generators.') 192 | 193 | Genset = {}; 194 | if length(M) == nG 195 | for i = 1:nG 196 | %Convert generator parameters with the right base MVA (please check if 197 | %this is correct) 198 | M_t = M(i)*Sbase2(i)/Sbase; 199 | D_t = D(i)*Sbase/Sbase2(i); 200 | xq_t = xq(i)*Sbase/Sbase2(i); 201 | xd_t = xd(i)*Sbase/Sbase2(i); 202 | xqp_t = xqp(i)*Sbase/Sbase2(i); 203 | xdp_t = xdp(i)*Sbase/Sbase2(i); 204 | Rs_t = Rs(i)*Sbase/Sbase2(i); 205 | Genset{i} = psgen_class(w0,M_t,D_t,xq_t,xd_t,xqp_t,xdp_t,Tq0p(i),Td0p(i),... 206 | Tch(i),Rd(i),Rs_t,GenSetMap(i,1),GenSetMap(i,2),Sbase2(i)); 207 | end 208 | end 209 | 210 | %% Create a nonlinear DAE system object 211 | disp('Create a nonlinear DAE system object.') 212 | 213 | case_sys = nonlin_ps_DAE_class(casenameraw,nN,nG,nL,[],Ybus,Gbus,Bbus,Yf,Yt,Cg,node_set,M,D,xq,xd,xqp,xdp,Tq0p,Td0p,Rs,Tch,Rd,... 214 | gen_set,load_set,Sbase,w0,Genset,v0,theta0,pg0,qg0,pl0,ql0,pd0,qd0,pr0,qr0,GenSetMap,LoadSetMap,isPFok,delta0,E0,TM0,EFd0,Tr0,x0,u0,a0,V0,... 215 | Cr,Cl,ren_set,load_set_nonz,nR,nLN); 216 | 217 | %Save object 218 | filename = 'PNobj.mat'; 219 | save(filename,'case_sys'); 220 | 221 | %Sanity check #2 222 | %Check generator's steady-state solutions 223 | [isGenSSok] = checkGeneratorSS(case_sys); 224 | pause(PauseTime); 225 | 226 | %Sanity check #3 227 | %Check algebraic constraints steady-state solutions 228 | [isAlgSSok] = checkAlgebraicSS(case_sys,pd0,qd0); 229 | pause(PauseTime); 230 | 231 | %% Setting Up AGC 232 | %AGC constants 233 | Kg = 10^3; %Integrator constant 234 | Kpart = pg0/sum(pg0); %Participation factor 235 | 236 | %Update AGC parameters 237 | case_sys = case_sys.updateAGCparameters(Kg,Kpart); 238 | 239 | %Save the updated object 240 | filename = 'PNobj.mat'; 241 | save(filename,'case_sys'); 242 | 243 | %% Create Jacobian matrices to improve computational efficiency 244 | alwaysCreate = 0; 245 | [isJacobianMade] = computeJacobianMatrix(case_sys,alwaysCreate); 246 | 247 | %% Create the Mass matrix 248 | Mass = blkdiag(eye(4*case_sys.nG),zeros(2*case_sys.nG,2*case_sys.nG),zeros(2*case_sys.nN,2*case_sys.nN)); 249 | 250 | %Save the mass matrix 251 | filename = 'MassMatrix.mat'; 252 | save(filename,'Mass'); 253 | 254 | %% Create the Mass matrix for AGC 255 | MassAGC = blkdiag(eye(4*case_sys.nG + 1),zeros(2*case_sys.nG,2*case_sys.nG),zeros(2*case_sys.nN,2*case_sys.nN)); 256 | 257 | %Save the mass matrix 258 | filename = 'MassMatrixAGC.mat'; 259 | save(filename,'MassAGC'); 260 | 261 | %% Simulate the steady-state conditions of the power network 262 | 263 | %ODE solver settings 264 | options1 = odeset('Mass',Mass,'MassSingular','yes','MStateDependence','none', ... 265 | 'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 266 | 267 | %Include Jacobian 268 | options1.Jacobian = @JacobianFunc; 269 | 270 | %Initial conditions 271 | x_pre0 = [case_sys.x0; case_sys.a0; case_sys.V0]; 272 | 273 | %Solve the nonlinear DAE without noise 274 | if isSimulSS == 0 275 | [t1,x_pre] = ode15s(@(t1,x_pre)powerNetwork_NDAE(t1,x_pre,case_sys.u0,pd0,qd0,case_sys),tspan1,x_pre0,options1); 276 | end 277 | 278 | %% Create step disturbance 279 | pl0plus = pl0; 280 | ql0plus = ql0; 281 | pr0plus = pr0; 282 | qr0plus = qr0; 283 | pr0plus(ren_set) = pr0(ren_set) + RenDisturbance.*pl0(ren_set); 284 | pl0plus = pl0plus + LoadDisturbance.*pl0; 285 | pd0plus = pl0plus - pr0plus; 286 | qd0plus = ql0plus - qr0plus; 287 | 288 | %% Initialization after disturbance 289 | delta0plus = delta0; 290 | E0plus = E0; 291 | TM0plus = TM0; 292 | 293 | %% Solve for initial conditions for generator power and bus voltage 294 | [pg0plus,qg0plus,v0plus,theta0plus] = getInitCondAfterDisturbance(case_sys,delta0plus,E0plus,pd0plus,qd0plus); 295 | 296 | %% Update again initial conditions and disturbances to object 297 | case_sys = case_sys.updateInitCondAfterDist(delta0plus,E0plus,TM0plus,pd0plus,qd0plus,pl0plus,ql0plus,pr0plus,qr0plus,... 298 | pg0plus,qg0plus,v0plus,theta0plus); 299 | 300 | %Save the updated object 301 | filename = 'PNobj.mat'; 302 | save(filename,'case_sys'); 303 | 304 | %% Simulate the power network after step disturbance due to power mismatch with ode15s 305 | 306 | %Setting up initial conditions for simulation 307 | x0plus = [delta0plus; w0*ones(case_sys.nG,1); E0plus; TM0plus]; 308 | a0plus = [pg0plus; qg0plus]; 309 | V0plus = [v0plus; theta0plus]; 310 | 311 | %Initial conditions 312 | x_preD0 = [x0plus; a0plus; V0plus]; 313 | 314 | %Compute initial conditions for derivative 315 | [initdot] = case_sys.f_gen_dyn_compact(case_sys.delta0plus,case_sys.omega0plus,case_sys.E0plus,case_sys.TM0plus,... 316 | u0(1:2:2*case_sys.nG),u0(2:2:2*case_sys.nG),case_sys.pg0plus,case_sys.v0plus,case_sys.theta0plus); 317 | xdot0plus0 = zeros(size(x_preD0)); 318 | xdot0plus0(1:4*case_sys.nG,1) = initdot; 319 | 320 | %ODE solver settings 321 | options2 = odeset('Mass',Mass,'MassSingular','yes','MStateDependence','none', ... 322 | 'RelTol',1e-7,'AbsTol',1e-6,'Stats','on','InitialSlope',xdot0plus0); 323 | 324 | %Include Jacobian 325 | options2.Jacobian = @JacobianFunc; 326 | 327 | %Solve the nonlinear DAE without noise 328 | if isSimulDist15s == 1 329 | [t2,x_pre2] = ode15s(@(t2,x_pre2)powerNetwork_NDAE(t2,x_pre2,u0,pd0plus,qd0plus,case_sys),tspan1,x_preD0,options2); 330 | plot(t2,x_pre2(:,4)) 331 | end 332 | 333 | %% Simulate the power network after step disturbance due to power mismatch with ode15i 334 | 335 | %ODE solver settings 336 | options3decic = odeset('Jacobian',@JacobianFunc15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 337 | 338 | %Compute initial conditions 339 | [x_pre20,x_pre2p0] = decic(@(t,x_res,x_resp)f_NDAE_implicit(t,x_res,x_resp,u0,pd0plus,qd0plus,Mass,case_sys)... 340 | ,0,x_preD0,[],xdot0plus0,[],options3decic); 341 | 342 | %ODE solver settings 343 | options3 = odeset('Jacobian',@JacobianFunc15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 344 | 345 | %Solve the nonlinear DAE without noise 346 | if isRunDist15i == 1 347 | [t2,x_pre2] = ode15i(@(t4,x_pre2,x_pre2dot)f_NDAE_implicit(t4,x_pre2,x_pre2dot,u0,pd0plus,qd0plus,Mass,case_sys),... 348 | tspan1,x_pre20,x_pre2p0,options3); 349 | plot(t2,x_pre2(:,4)) 350 | end 351 | 352 | %% Get matrices for nonlinear DAE 353 | sys.E = blkdiag(case_sys.E_tilD(),zeros(2*case_sys.nG+2*case_sys.nN,2*case_sys.nG+2*case_sys.nN)); 354 | sys.A = blkdiag(case_sys.A_tilD(),case_sys.A_tilA()); 355 | sys.G = blkdiag(case_sys.G_tilD(),case_sys.G_tilA(),eye(2*case_sys.nN)); 356 | sys.Bu = [case_sys.B_tilU(); zeros(2*case_sys.nG+2*case_sys.nN,2*case_sys.nG)]; 357 | sys.Bq = [zeros(2*case_sys.nG,2*case_sys.nR+2*case_sys.nLN); case_sys.B_tilQ()]; 358 | sys.F = [case_sys.F_tilS(); zeros(2*case_sys.nN,1)]; 359 | 360 | %Smaller matrices 361 | sys.Ed = case_sys.E_tilD(); 362 | sys.Ad = case_sys.A_tilD(); 363 | sys.Aa = case_sys.A_tilA(); 364 | sys.Gd = case_sys.G_tilD(); 365 | sys.Ga = blkdiag(case_sys.G_tilA(),eye(2*case_sys.nN)); 366 | sys.Bud = case_sys.B_tilU(); 367 | 368 | %Get matrices for linear DAE 369 | [Ad_d,Ad_a,Aa_d,Aa_a,Bd,Bq] = getLinearDAEmat(case_sys); 370 | 371 | %Get matrices for linear ODE 372 | sys.Alin = full(Ad_d-Ad_a*inv(Aa_a)*Aa_d); 373 | sys.Bulin = full(Bd); 374 | sys.Bqlin = sys.Bq; 375 | 376 | %Matrices size 377 | sys.nx = size(sys.A,1); 378 | sys.nu = size(sys.Bu,2); 379 | sys.nq = size(sys.Bq,2); 380 | sys.ng = size(sys.G,2); 381 | sys.re = rank(sys.E); 382 | sys.nxd = size(sys.Ad,1); 383 | sys.nxa = size(sys.Aa,1); 384 | sys.ngd = size(sys.Gd,2); 385 | sys.nga = size(sys.Ga,2); 386 | sys.nud = size(sys.Bud,2); 387 | sys.red = rank(sys.Ed); 388 | sys.nxlin = size(sys.Alin,1); 389 | sys.nulin = size(sys.Bulin,2); 390 | 391 | %Bounds on the nonlinearity 392 | sys.H = 2.0*eye(sys.nx); 393 | sys.Hd = sqrt(2*eye(sys.nxd)); 394 | sys.Ha = sqrt(2*eye(sys.nxa)); 395 | 396 | %% Compute a stabilizing controller gain matrix 397 | %Nonlinear DAE control 398 | %[K] = NDAE_control(sys); Kd = K(:,1:4*case_sys.nG); % becasue we are designing controller for xd only. 399 | % [Kd,K] = NDAE_reduced_control(sys); 400 | % [Kd,K] = NDAE_reduced_control_2(sys); 401 | [Kd,K,ndae_lmi_time] = NDAE_reduced_control_minimized(sys); 402 | % [Kd,K] = NDAE_reduced_control_input_constr(sys,x0plus); 403 | 404 | %LQR design 405 | [Kdlqr,Klqr,lqr_lmi_time] = ODE_LQR_reduced_control(sys,x0plus); 406 | 407 | %Construct K for AGC with the aid from NDAE control/LQR for the exciter dynamics 408 | Kdagc = Kdlqr; 409 | Kdagc(case_sys.nG+1:end,1:end) = zeros(case_sys.nG,size(Kdagc,2)); 410 | temp = [zeros(case_sys.nG,1); Kpart]; 411 | Kdagc = [Kdagc temp]; 412 | Kagc = [Kdagc zeros(sys.nud,sys.nxa)]; 413 | 414 | %Update controller 415 | case_sys = case_sys.updateControlGain(Kd,K,Kdlqr,Klqr,Kdagc,Kagc); 416 | 417 | %Save the updated object 418 | filename = 'PNobj.mat'; 419 | save(filename,'case_sys'); 420 | 421 | %% Simulate the power network after step disturbance with controller 422 | 423 | %ODE solver settings 424 | options4 = odeset('Mass',sys.E,'MassSingular','yes','MStateDependence','none', ... 425 | 'RelTol',1e-6,'AbsTol',1e-6,'Stats','on'); 426 | 427 | %Include Jacobian 428 | options4.Jacobian = @JacobianFuncControl; 429 | 430 | %Compute initial inputs after disturbance 431 | u0plus = controlInput(case_sys,sys,u0,x_preD0,Kd); 432 | 433 | %Compute initial conditions for derivative 434 | [initdot] = case_sys.f_gen_dyn_compact(case_sys.delta0plus,case_sys.omega0plus,case_sys.E0plus,case_sys.TM0plus,... 435 | u0plus(1:2:2*case_sys.nG),u0plus(2:2:2*case_sys.nG),case_sys.pg0plus,case_sys.v0plus,case_sys.theta0plus); 436 | xdot0plus = zeros(size(x_preD0)); 437 | xdot0plus(1:4*case_sys.nG,1) = initdot; 438 | options4.InitialSlope = xdot0plus; 439 | 440 | %Solve the nonlinear DAE with control without noise 441 | if isRunDist15s == 1 442 | [t3,x_res] = ode15s(@(t3,x_res)powerNetwork_NDAE(t3,x_res,controlInput(case_sys,sys,u0,x_res,Kd),... 443 | pd0plus,qd0plus,case_sys),tspan1,x_preD0,options4); 444 | stateidx = 4; 445 | h(1) = figure; 446 | plot(t2,x_pre2(:,stateidx),'r',t3,x_res(:,stateidx),'b--'); 447 | h(2) = figure ; 448 | plot(t2,x_pre2(:,stateidx+1),'r',t3,x_res(:,stateidx+1),'b--'); 449 | h(3) = figure ; 450 | plot(t2,x_pre2(:,stateidx+2),'r',t3,x_res(:,stateidx+2),'b--'); 451 | end 452 | 453 | %% Simulate the power network after step disturbance with controller with ode15i 454 | 455 | %ODE solver settings 456 | options5decic = odeset('Jacobian',@JacobianFuncControl15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 457 | 458 | %Compute initial conditions 459 | [x_out0,x_outp0] = decic(@(t,x_res,x_resp)f_NDAE_implicit(t,x_res,x_resp,controlInput(case_sys,sys,u0,x_preD0,Kd),pd0plus,qd0plus,Mass,case_sys)... 460 | ,0,x_preD0,[],xdot0plus,[],options5decic); 461 | 462 | %ODE solver settings 463 | options5 = odeset('Jacobian',@JacobianFuncControl15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 464 | 465 | %Solve the ODE 466 | if isRunDist15i == 1 467 | [t4,x_res2] = ode15i(@(t4,x_res2,x_resdot)f_NDAE_implicit(t4,x_res2,x_resdot,controlInput(case_sys,sys,u0,x_res2,Kd),pd0plus,qd0plus,Mass,case_sys),... 468 | tspan1,x_out0,x_outp0,options5); 469 | end 470 | 471 | %% Simulate the LQR-based power network after step disturbance with controller with ode15i 472 | 473 | %Compute initial inputs after disturbance 474 | u0plusLQR = controlInput(case_sys,sys,u0,x_preD0,Kdlqr); 475 | 476 | %Compute initial conditions for derivative 477 | [initdot] = case_sys.f_gen_dyn_compact(case_sys.delta0plus,case_sys.omega0plus,case_sys.E0plus,case_sys.TM0plus,... 478 | u0plusLQR(1:2:2*case_sys.nG),u0plusLQR(2:2:2*case_sys.nG),case_sys.pg0plus,case_sys.v0plus,case_sys.theta0plus); 479 | xdot0plusLQR = zeros(size(x_preD0)); 480 | xdot0plusLQR(1:4*case_sys.nG,1) = initdot; 481 | 482 | %ODE solver settings 483 | options5decic = odeset('Jacobian',@JacobianFuncControl15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 484 | 485 | %Compute initial conditions 486 | [x_out0,x_outp0] = decic(@(t,x_res,x_resp)f_NDAE_implicit(t,x_res,x_resp,controlInput(case_sys,sys,u0,x_preD0,Kdlqr),pd0plus,qd0plus,Mass,case_sys)... 487 | ,0,x_preD0,[],xdot0plusLQR,[],options5decic); 488 | 489 | %ODE solver settings 490 | options5 = odeset('Jacobian',@JacobianFuncControl15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 491 | 492 | %Solve the ODE 493 | if isRunDistLQR15i == 1 494 | [t5,x_res3] = ode15i(@(t5,x_res3,x_resdot)f_NDAE_implicit(t5,x_res3,x_resdot,controlInput(case_sys,sys,u0,x_res3,Kdlqr),pd0plus,qd0plus,Mass,case_sys),... 495 | tspan1,x_out0,x_outp0,options5); 496 | end 497 | 498 | %% Simulate the AGC-LQR-based power network after step disturbance with controller with ode15i 499 | 500 | %Compute y0 501 | y0 = 0; %ones(1,case_sys.nG)*(case_sys.Pg0plus-case_sys.Pg0); 502 | 503 | %Setting up initial conditions for simulation 504 | x0plusAGC = [delta0plus; w0*ones(case_sys.nG,1); E0plus; TM0plus; y0]; 505 | 506 | %Initial conditions 507 | x_preD0AGC = [x0plusAGC; a0plus; V0plus]; 508 | 509 | %Compute initial inputs after disturbance 510 | u0plusAGC = controlInputAGC(case_sys,sys,u0,x_preD0AGC,Kdagc,x0plusAGC); 511 | 512 | %Compute initial conditions for derivative 513 | [initdot] = case_sys.f_gen_dyn_AGC(case_sys.delta0plus,case_sys.omega0plus,case_sys.E0plus,case_sys.TM0plus,... 514 | u0plusAGC(1:2:2*case_sys.nG),u0plusAGC(2:2:2*case_sys.nG),case_sys.pg0plus,case_sys.v0plus,case_sys.theta0plus,y0); 515 | xdot0plusAGC = zeros(size(x_preD0AGC)); 516 | xdot0plusAGC(1:4*case_sys.nG + 1,1) = initdot; 517 | 518 | %ODE solver settings 519 | % options6decic = odeset('Jacobian',@JacobianFuncAGC15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 520 | options6decic = odeset('RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 521 | 522 | %Compute initial conditions 523 | [x_out0,x_outp0] = decic(@(t,x_res,x_resp)f_NDAE_AGC_implicit(t,x_res,x_resp,controlInputAGC(case_sys,sys,u0,x_preD0AGC,Kdagc,x0plusAGC),... 524 | pd0plus,qd0plus,MassAGC,case_sys),0,x_preD0AGC,[],xdot0plusAGC,[],options6decic); 525 | 526 | %ODE solver settings 527 | % options6 = odeset('Jacobian',@JacobianFuncAGC15i,'RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 528 | options6 = odeset('RelTol',1e-7,'AbsTol',1e-6,'Stats','on'); 529 | 530 | %Solve the ODE 531 | if isRunDistLQR15i == 1 532 | [t6,x_res4] = ode15i(@(t6,x_res4,x_resdot)f_NDAE_AGC_implicit(t6,x_res4,x_resdot,controlInputAGC(case_sys,sys,u0,x_res4,Kdagc,x0plusAGC),... 533 | pd0plus,qd0plus,MassAGC,case_sys),tspan1,x_out0,x_outp0,options6); 534 | end 535 | 536 | stateidx = 4; 537 | h(7) = figure; 538 | plot(t2,x_pre2(:,stateidx),'r',t4,x_res2(:,stateidx),'b--',t5,x_res3(:,stateidx),'g-.',t6,x_res4(:,stateidx),'k:'); 539 | legend('no control','NDAE','LQR','AGC'); 540 | set(gcf,'color','w'); 541 | h(8) = figure ; 542 | plot(t2,x_pre2(:,stateidx+1),'r',t4,x_res2(:,stateidx+1),'b--',t5,x_res3(:,stateidx+1),'g-.',t6,x_res4(:,stateidx+1),'k:'); 543 | legend('no control','NDAE','LQR','AGC'); 544 | set(gcf,'color','w'); 545 | h(9) = figure ; 546 | plot(t2,x_pre2(:,stateidx+2),'r',t4,x_res2(:,stateidx+2),'b--',t5,x_res3(:,stateidx+2),'g-.',t6,x_res4(:,stateidx+2),'k:'); 547 | legend('no control','NDAE','LQR','AGC'); 548 | set(gcf,'color','w'); 549 | 550 | % %% Save Simulation Data 551 | % file_name_data = sprintf('Data_scenario2_%s_dist_%s.mat',casenameraw,distChar); 552 | % save(file_name_data,'casenameraw','case_sys','sys','t2','x_pre2','t4','x_res2','t5','x_res3','t6','x_res4',... 553 | % 'Kd','K','Kdlqr','Klqr','Kdagc','Kagc','ndae_lmi_time','lqr_lmi_time','distChar','RenDisturbance','LoadDisturbance'); 554 | 555 | %% Collection of Functions 556 | 557 | function fx = f_NDAE_AGC_implicit(t,x,x_dot,u,pd,qd,Mass,case_sys) 558 | %Dynamics 559 | fx = -Mass*x_dot + powerNetwork_NDAE_AGC(t,x,u,pd,qd,case_sys); 560 | end 561 | 562 | function fx = f_NDAE_implicit(t,x,x_dot,u,pd,qd,Mass,case_sys) 563 | %Dynamics 564 | fx = -Mass*x_dot + powerNetwork_NDAE(t,x,u,pd,qd,case_sys); 565 | end 566 | 567 | function [phaseOut] = degrees2radians(phaseIn) 568 | %DEGREES2RADIANS converts degrees to radians 569 | % degrees2radians( phaseIn ) converts the angle phaseIn in degrees to its 570 | % equivalent in radians. 571 | % Created by Hafez Bazrafshan 8/26/2016 572 | phaseOut=-pi+(pi/180)*(phaseIn+180); 573 | end -------------------------------------------------------------------------------- /nonlin_ps_DAE_class.m: -------------------------------------------------------------------------------- 1 | classdef nonlin_ps_DAE_class 2 | % Class for power systems DAE 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/28/2021 5 | 6 | properties 7 | %Network parameters 8 | casename; %Power system case name 9 | nN; %Number of buses (N = G + L) 10 | nG; %Number of generator buses 11 | nL; %Number of load buses 12 | nM; %Number of buses with PMU measurements 13 | Ybus; %Bus admittance matrix 14 | Gbus; 15 | Bbus; 16 | Yf; %From branch admittance matrix 17 | Yt; %To branch admittance matrix 18 | Yft; %branch admittance matrix 19 | node_set; %The set of node labels for the network and has size(nN,1) 20 | gen_set; %The set of generator labels in the network and has size(nG,1) 21 | load_set; %The set of load labels with no generators and has size(nL,1) (may include zero loads) 22 | Sbase; %Base MVA 23 | w0; %Synchronous frequency 24 | Cg; %Generators indicator matrix of size(N,G) 25 | Cr; %Renewables indicator matrix of size(N,R) 26 | Cl; %Loads indicator matrix of size(N,L) 27 | ren_set; %The set of renewables 28 | load_set_nonz; %The set of NONZERO loads 29 | nR; %Number of renewables 30 | nLN; %Number of NONZERO loads 31 | 32 | Genset; %Set of generators data 33 | 34 | %Generator parameters 35 | M; 36 | D; 37 | xq; 38 | xd; 39 | xqp; 40 | xdp; 41 | Tq0p; 42 | Td0p; 43 | Rs; 44 | Tch; 45 | Rd; 46 | 47 | %Initial conditions 48 | v0; 49 | theta0; 50 | pg0; 51 | qg0; 52 | pl0; 53 | ql0; 54 | pd0; 55 | qd0; 56 | pr0; 57 | qr0; 58 | 59 | %Set mapping 60 | GenSetMap; 61 | LoadSetMap; 62 | 63 | %PF solutions 64 | isPFok; 65 | 66 | %Generator dynamic IC 67 | delta0; 68 | Ep0; 69 | TM0; 70 | EFd0; 71 | Tr0; 72 | 73 | %DAE initial conditions (at steady-state) 74 | x0; 75 | a0; 76 | V0; 77 | u0; 78 | 79 | %Initial conditions after disturbance 80 | pd0plus; 81 | qd0plus; 82 | delta0plus; 83 | omega0plus; 84 | E0plus; 85 | TM0plus; 86 | pg0plus; 87 | qg0plus; 88 | v0plus; 89 | theta0plus; 90 | 91 | %Renewables and loads after disturbance 92 | pl0plus; 93 | ql0plus; 94 | pr0plus; 95 | qr0plus; 96 | 97 | %Stabilizing controller gain 98 | K; 99 | Ksparse; 100 | Kd; 101 | Kdfull; 102 | Klqr; 103 | Klqrfull; 104 | Kagc; 105 | Kagcfull; 106 | 107 | %AGC constants 108 | Kg; %Integrator gain 109 | Kpart; %Vector of participation factors 110 | 111 | end 112 | methods 113 | %Object constructor 114 | function obj = nonlin_ps_DAE_class(casename,nN,nG,nL,nM,Ybus,Gbus,Bbus,Yf,Yt,Cg,node_set,M,D,xq,xd,xqp,xdp,Tq0p,Td0p,Rs,Tch,Rd,... 115 | gen_set,load_set,Sbase,w0,Genset,v0,theta0,pg0,qg0,pl0,ql0,pd0,qd0,pr0,qr0,GenSetMap,LoadSetMap,isPFok,delta0,E0,TM0,EFd0,Tr0,x0,u0,a0,V0,... 116 | Cr,Cl,ren_set,load_set_nonz,nR,nLN) 117 | 118 | obj.casename = casename; 119 | obj.nN = nN; 120 | obj.nG = nG; 121 | obj.nL = nL; 122 | obj.nM = nM; 123 | obj.Ybus = Ybus; 124 | obj.Gbus = Gbus; 125 | obj.Bbus = Bbus; 126 | obj.Yf = Yf; 127 | obj.Yt = Yt; 128 | obj.Yft = [Yf; Yt]; 129 | obj.Cg = Cg; 130 | obj.node_set = node_set; 131 | obj.gen_set = gen_set; 132 | obj.load_set = load_set; 133 | obj.Sbase = Sbase; 134 | obj.w0 = w0; 135 | obj.Genset = Genset; 136 | obj.v0 = v0; 137 | obj.theta0 = theta0; 138 | obj.pg0 = pg0; 139 | obj.qg0 = qg0; 140 | obj.pl0 = pl0; 141 | obj.ql0 = ql0; 142 | obj.pd0 = pd0; 143 | obj.qd0 = qd0; 144 | obj.pr0 = pr0; 145 | obj.qr0 = qr0; 146 | obj.GenSetMap = GenSetMap; 147 | obj.LoadSetMap = LoadSetMap; 148 | obj.isPFok = isPFok; 149 | obj.delta0 = delta0; 150 | obj.Ep0 = E0; 151 | obj.TM0 = TM0; 152 | obj.EFd0 = EFd0; 153 | obj.Tr0 = Tr0; 154 | obj.x0 = x0; 155 | obj.a0 = a0; 156 | obj.V0 = V0; 157 | obj.u0 = u0; 158 | obj.M = M; 159 | obj.D = D; 160 | obj.xq =xq; 161 | obj.xd = xd; 162 | obj.xqp = xqp; 163 | obj.xdp = xdp; 164 | obj.Tq0p =Tq0p; 165 | obj.Td0p = Td0p; 166 | obj.Rs = Rs; 167 | obj.Tch = Tch; 168 | obj.Rd = Rd; 169 | obj.Cr = Cr; 170 | obj.Cl = Cl; 171 | obj.ren_set = ren_set; 172 | obj.load_set_nonz = load_set_nonz; 173 | obj.nR = nR; 174 | obj.nLN = nLN; 175 | 176 | end 177 | 178 | %Update initial conditions after disturbance 179 | function obj = updateInitCondAfterDist(obj,delta0plus,E0plus,TM0plus,pd0plus,qd0plus,pl0plus,ql0plus,pr0plus,qr0plus,... 180 | pg0plus,qg0plus,v0plus,theta0plus) 181 | 182 | obj.pd0plus = pd0plus; 183 | obj.qd0plus = qd0plus; 184 | obj.delta0plus = delta0plus; 185 | obj.E0plus = E0plus; 186 | obj.TM0plus = TM0plus; 187 | obj.pl0plus = pl0plus; 188 | obj.ql0plus = ql0plus; 189 | obj.pr0plus = pr0plus; 190 | obj.qr0plus = qr0plus; 191 | obj.pg0plus = pg0plus; 192 | obj.qg0plus = qg0plus; 193 | obj.v0plus = v0plus; 194 | obj.theta0plus = theta0plus; 195 | obj.omega0plus = obj.w0*ones(obj.nG,1); 196 | end 197 | 198 | %Update controller gain matrix 199 | function obj = updateControlGain(obj,Kd,Kdfull,Klqr,Klqrfull,Kagc,Kagcfull) 200 | obj.Kd = Kd; 201 | obj.Kdfull = Kdfull; 202 | obj.Klqr = Klqr; 203 | obj.Klqrfull = Klqrfull; 204 | obj.Kagc = Kagc; 205 | obj.Kagcfull = Kagcfull; 206 | end 207 | 208 | %Update AGC constants 209 | function obj = updateAGCparameters(obj,Kg,Kpart) 210 | obj.Kg = Kg; 211 | obj.Kpart = Kpart; 212 | end 213 | 214 | %Generator nonlinear dynamics model (non state-space form) 215 | function [delta_dot,omega_dot,Ep_dot,Tm_dot] = f_gen_dyn(obj,delta,omega,Ep,Tm,Efd,Tr,Pg,v,theta) 216 | %Inputs: generator's states delta, omega, Ep, Tm, inputs Efd, Tr, active power Pg, bus voltage v, theta 217 | %Outputs: time derivative of the states 218 | 219 | if isnumeric(delta) && isnumeric(omega) && isnumeric(Ep) && isnumeric(Tm) 220 | delta_dot = zeros(obj.nG,1); omega_dot = zeros(obj.nG,1); Ep_dot = zeros(obj.nG,1); Tm_dot = zeros(obj.nG,1); 221 | else 222 | delta_dot = sym(zeros(obj.nG,1)); omega_dot = sym(zeros(obj.nG,1)); Ep_dot = sym(zeros(obj.nG,1)); Tm_dot = sym(zeros(obj.nG,1)); 223 | end 224 | 225 | %Generator's differential equations 226 | delta_dot(1:end) = omega - obj.w0*ones(obj.nG,1); 227 | omega_dot(1:end) = (diag(obj.M))\(Tm - Pg - diag(obj.D)*(omega - obj.w0*ones(obj.nG,1))); 228 | Ep_dot(1:end) = (diag(obj.Td0p))\(-diag((obj.xd./obj.xdp))*Ep + Efd ... 229 | + diag((obj.xd-obj.xdp)./obj.xdp)*(v(obj.gen_set).*cos(delta-theta(obj.gen_set)))); 230 | Tm_dot(1:end) = (diag(obj.Tch))\(-Tm + Tr - diag(1./obj.Rd)*(omega - obj.w0*ones(obj.nG,1))); 231 | end 232 | 233 | %Generator nonlinear dynamics model (non state-space COMPACT form) 234 | function [out] = f_gen_dyn_compact(obj,delta,omega,Ep,Tm,Efd,Tr,Pg,v,theta) 235 | [delta_dot,omega_dot,Ep_dot,Tm_dot] = obj.f_gen_dyn(delta,omega,Ep,Tm,Efd,Tr,Pg,v,theta); 236 | out = [delta_dot; omega_dot; Ep_dot; Tm_dot]; 237 | end 238 | 239 | %Generator's power equations (non state-space form) 240 | function [val1,val2] = f_gen_alg(obj,delta,Ep,Pg,Qg,v,theta) 241 | %Inputs: generator's states delta, Ep, active power Pg, reactive power Qg, bus voltage v, theta 242 | %Outputs: vectors of 2*nG which should be zero if satisfied 243 | 244 | if isnumeric(delta) && isnumeric(Ep) && isnumeric(Pg) && isnumeric(Qg) 245 | val1 = zeros(obj.nG,1); val2 = zeros(obj.nG,1); 246 | else 247 | val1 = sym(zeros(obj.nG,1)); val2 = sym(zeros(obj.nG,1)); 248 | end 249 | 250 | %Generator's algebraic equations 251 | val1(1:end) = -Pg + diag(1./obj.xdp)*(Ep.*v(obj.gen_set).*sin(delta-theta(obj.gen_set))) ... 252 | - diag((obj.xq-obj.xdp)./(2*(obj.xq.*obj.xdp)))*(v(obj.gen_set).^2.*sin(2*(delta-theta(obj.gen_set)))); 253 | val2(1:end) = -Qg + diag(1./obj.xdp)*(Ep.*v(obj.gen_set).*cos(delta-theta(obj.gen_set))) ... 254 | - diag((obj.xq+obj.xdp)./(2*(obj.xq.*obj.xdp)))*(v(obj.gen_set).^2) ... 255 | - diag((obj.xq-obj.xdp)./(2*(obj.xq.*obj.xdp)))*(v(obj.gen_set).^2.*cos(2*(delta-theta(obj.gen_set)))); 256 | end 257 | 258 | %Generator's power equations (non state-space COMPACT form) 259 | function [out] = f_gen_alg_compact(obj,delta,Ep,Pg,Qg,v,theta) 260 | [val1,val2] = obj.f_gen_alg(delta,Ep,Pg,Qg,v,theta); 261 | out = [val1; val2]; 262 | end 263 | 264 | %Network's power balance equations (non state-space form) without loads nor renewables 265 | function [valGp,valGq,valLp,valLq] = f_pf(obj,Pg,Qg,v,theta) 266 | %Inputs: active power Pg, reactive power Qg, bus voltage v, theta 267 | %Outputs: vectors of 2*nN which should be zero if satisfied 268 | 269 | if isnumeric(v) && isnumeric(theta) && isnumeric(Pg) && isnumeric(Qg) 270 | valGp = zeros(obj.nG,1); valGq = zeros(obj.nG,1); valLp = zeros(obj.nL,1); valLq = zeros(obj.nL,1); 271 | else 272 | valGp = sym(zeros(obj.nG,1)); valGq = sym(zeros(obj.nG,1)); valLp = sym(zeros(obj.nL,1)); valLq = sym(zeros(obj.nL,1)); 273 | end 274 | 275 | %Pre-compute variables for efficiency 276 | Dv = diag(v); 277 | Dctheta = diag(cos(theta)); 278 | Dstheta = diag(sin(theta)); 279 | vctheta = cos(theta); 280 | vstheta = sin(theta); 281 | 282 | %Power balance equations 283 | vala = -obj.Cg*Pg + Dv*Dctheta*obj.Gbus*Dv*vctheta + Dv*Dstheta*obj.Gbus*Dv*vstheta ... 284 | + Dv*Dstheta*obj.Bbus*Dv*vctheta - Dv*Dctheta*obj.Bbus*Dv*vstheta; 285 | valb = -obj.Cg*Qg + Dv*Dstheta*obj.Gbus*Dv*vctheta - Dv*Dctheta*obj.Gbus*Dv*vstheta ... 286 | - Dv*Dstheta*obj.Bbus*Dv*vstheta - Dv*Dctheta*obj.Bbus*Dv*vctheta; 287 | 288 | %Output 289 | valGp(1:end) = vala(obj.gen_set); 290 | valGq(1:end) = valb(obj.gen_set); 291 | valLp(1:end) = vala(obj.load_set); 292 | valLq(1:end) = valb(obj.load_set); 293 | end 294 | 295 | %Network's power balance equations (non state-space form) WITH loads or renewables 296 | function [valGp,valGq,valLp,valLq] = f_pf_loads(obj,Pg,Qg,v,theta,Pl,Ql) 297 | %Inputs: active power Pg, reactive power Qg, bus voltage v, 298 | %theta, loads power Pl, Ql 299 | %Outputs: vectors of 2*nN which should be zero if satisfied 300 | 301 | if isnumeric(v) && isnumeric(theta) && isnumeric(Pg) && isnumeric(Qg) 302 | valGp = zeros(obj.nG,1); valGq = zeros(obj.nG,1); valLp = zeros(obj.nL,1); valLq = zeros(obj.nL,1); 303 | else 304 | valGp = sym(zeros(obj.nG,1)); valGq = sym(zeros(obj.nG,1)); valLp = sym(zeros(obj.nL,1)); valLq = sym(zeros(obj.nL,1)); 305 | end 306 | 307 | %Pre-compute variables for efficiency 308 | Dv = diag(v); 309 | Dctheta = diag(cos(theta)); 310 | Dstheta = diag(sin(theta)); 311 | vctheta = cos(theta); 312 | vstheta = sin(theta); 313 | 314 | %Power balance equations 315 | vala = Pl - obj.Cg*Pg + Dv*Dctheta*obj.Gbus*Dv*vctheta + Dv*Dstheta*obj.Gbus*Dv*vstheta ... 316 | + Dv*Dstheta*obj.Bbus*Dv*vctheta - Dv*Dctheta*obj.Bbus*Dv*vstheta; 317 | valb = Ql - obj.Cg*Qg + Dv*Dstheta*obj.Gbus*Dv*vctheta - Dv*Dctheta*obj.Gbus*Dv*vstheta ... 318 | - Dv*Dstheta*obj.Bbus*Dv*vstheta - Dv*Dctheta*obj.Bbus*Dv*vctheta; 319 | 320 | %Output 321 | valGp(1:end) = vala(obj.gen_set); 322 | valGq(1:end) = valb(obj.gen_set); 323 | valLp(1:end) = vala(obj.load_set); 324 | valLq(1:end) = valb(obj.load_set); 325 | end 326 | 327 | %Network's power balance equations (non state-space COMPACT form) WITH loads or renewables 328 | function [out] = f_pf_loads_compact(obj,Pg,Qg,v,theta,Pl,Ql) 329 | [valGp,valGq,valLp,valLq] = obj.f_pf_loads(Pg,Qg,v,theta,Pl,Ql); 330 | out = [valGp; valGq; valLp; valLq]; 331 | end 332 | 333 | %Generator nonlinear dynamics model (non state-space form) with AGC 334 | %dynamics (after disturbance) 335 | function [out] = f_gen_dyn_AGC(obj,delta,omega,Ep,Tm,Efd,Tr,Pg,v,theta,y) 336 | [res] = obj.f_gen_dyn_compact(delta,omega,Ep,Tm,Efd,Tr,Pg,v,theta); 337 | y_dot = obj.Kg*(-y - (1/obj.nG)*((1./obj.Rd)+obj.D)'*(omega-obj.w0*ones(obj.nG,1)) ... 338 | + ones(1,obj.nG)*(Pg-obj.pg0)); 339 | out = [res; y_dot]; 340 | end 341 | 342 | %Compute steady state matrices 343 | %E_tildeD 344 | function E_tilD_m = E_tilD(obj) 345 | E_tilD_m = eye(4*obj.nG); 346 | end 347 | 348 | %A_tildeD 349 | function A_tilD_m = A_tilD(obj) 350 | A_tilD_m = zeros(4*obj.nG,4*obj.nG); 351 | A_tilD_m(1:obj.nG,obj.nG+1:2*obj.nG) = eye(obj.nG); 352 | A_tilD_m(obj.nG+1:2*obj.nG,obj.nG+1:2*obj.nG) = -diag(obj.D./obj.M); 353 | A_tilD_m(obj.nG+1:2*obj.nG,3*obj.nG+1:end) = diag(1./obj.M); 354 | A_tilD_m(2*obj.nG+1:3*obj.nG,2*obj.nG+1:3*obj.nG) = (diag(obj.Td0p))\(-diag((obj.xd./obj.xdp))); 355 | A_tilD_m(3*obj.nG+1:end,obj.nG+1:2*obj.nG) = -(diag(obj.Tch))\(diag(1./obj.Rd)); 356 | A_tilD_m(3*obj.nG+1:end,3*obj.nG+1:end) = -diag(1./obj.Tch); 357 | end 358 | 359 | %G_tildeD 360 | function G_tilD_m = G_tilD(obj) 361 | G_tilD_m = zeros(4*obj.nG,2*obj.nG); 362 | G_tilD_m(obj.nG+1:2*obj.nG,1:obj.nG) = -diag(1./obj.M); 363 | G_tilD_m(2*obj.nG+1:3*obj.nG,obj.nG+1:end) = (diag(obj.Td0p))\(diag((obj.xd-obj.xdp)./obj.xdp)); 364 | end 365 | 366 | %B_tildeU 367 | function B_tilU_m = B_tilU(obj) 368 | B_tilU_m = zeros(4*obj.nG,2*obj.nG); 369 | B_tilU_m(2*obj.nG+1:3*obj.nG,1:obj.nG) = diag(1./obj.Td0p); 370 | B_tilU_m(3*obj.nG+1:end,obj.nG+1:end) = diag(1./obj.Tch); 371 | end 372 | 373 | %F_tildeS 374 | function F_tilS_m = F_tilS(obj) 375 | F_tilS_m = [-ones(obj.nG,1); obj.D./obj.M; zeros(obj.nG,1); 1./(obj.Rd.*obj.Tch)]; 376 | end 377 | 378 | %G_tildeA 379 | function G_tilA_m = G_tilA(obj) 380 | G_tilA_m = zeros(2*obj.nG,5*obj.nG); 381 | G_tilA_m(1:obj.nG,1:obj.nG) = diag(1./obj.xdp); 382 | G_tilA_m(1:obj.nG,obj.nG+1:2*obj.nG) = -diag((obj.xq-obj.xdp)./(2*(obj.xq.*obj.xdp))); 383 | G_tilA_m(obj.nG+1:end,2*obj.nG+1:3*obj.nG) = diag(1./obj.xdp); 384 | G_tilA_m(obj.nG+1:end,3*obj.nG+1:4*obj.nG) = -diag((obj.xq+obj.xdp)./(2*(obj.xq.*obj.xdp))); 385 | G_tilA_m(obj.nG+1:end,4*obj.nG+1:end) = -diag((obj.xq-obj.xdp)./(2*(obj.xq.*obj.xdp))); 386 | end 387 | 388 | %A_tildeA 389 | function A_tilA_m = A_tilA(obj) 390 | A_tilA_m = zeros(2*obj.nG+2*obj.nN,2*obj.nG+2*obj.nN); 391 | A_tilA_m(1:2*obj.nG,1:2*obj.nG) = -eye(2*obj.nG); 392 | A_tilA_m(2*obj.nG+1:4*obj.nG,1:2*obj.nG) = -eye(2*obj.nG); 393 | end 394 | 395 | %B_tildeQ 396 | function B_tilQ_m = B_tilQ(obj) 397 | B_tilQ_m = []; 398 | 399 | %Pr and Qr 400 | B_tilQGP = zeros(obj.nG,length(obj.ren_set)); 401 | B_tilQGQ = zeros(obj.nG,length(obj.ren_set)); 402 | B_tilQLP = zeros(obj.nN,length(obj.ren_set)); 403 | B_tilQLQ = zeros(obj.nN,length(obj.ren_set)); 404 | for i = 1:length(obj.ren_set) 405 | if ismember(obj.ren_set(i),obj.gen_set) %Generator 406 | idxt = find(obj.gen_set == obj.ren_set(i)); 407 | B_tilQGP(idxt,i) = -1; 408 | B_tilQGQ(idxt,i) = -1; 409 | elseif ismember(obj.ren_set(i),obj.load_set) %Load 410 | idxt = find(obj.load_set == obj.ren_set(i)); 411 | B_tilQLP(idxt,i) = -1; 412 | B_tilQLQ(idxt,i) = -1; 413 | end 414 | end 415 | %Cut matrices for loads 416 | B_tilQLP(obj.gen_set,:) = []; 417 | B_tilQLQ(obj.gen_set,:) = []; 418 | 419 | %Pl and Ql 420 | B_tilQGP2 = zeros(obj.nG,length(obj.load_set_nonz)); 421 | B_tilQGQ2 = zeros(obj.nG,length(obj.load_set_nonz)); 422 | B_tilQLP2 = zeros(obj.nN,length(obj.load_set_nonz)); 423 | B_tilQLQ2 = zeros(obj.nN,length(obj.load_set_nonz)); 424 | for i = 1:length(obj.load_set_nonz) 425 | if ismember(obj.load_set_nonz(i),obj.gen_set) %Generator 426 | idxt = find(obj.gen_set == obj.load_set_nonz(i)); 427 | B_tilQGP2(idxt,i) = 1; 428 | B_tilQGQ2(idxt,i) = 1; 429 | elseif ismember(obj.load_set_nonz(i),obj.load_set) %Load 430 | idxt = find(obj.load_set == obj.load_set_nonz(i)); 431 | B_tilQLP2(idxt,i) = 1; 432 | B_tilQLQ2(idxt,i) = 1; 433 | end 434 | end 435 | %Cut matrices for loads 436 | B_tilQLP2(obj.gen_set,:) = []; 437 | B_tilQLQ2(obj.gen_set,:) = []; 438 | 439 | B_tilQ_m = [blkdiag(B_tilQGP,B_tilQGQ) blkdiag(B_tilQGP2,B_tilQGQ2); ... 440 | blkdiag(B_tilQLP,B_tilQLQ) blkdiag(B_tilQLP2,B_tilQLQ2)]; 441 | end 442 | 443 | end 444 | end -------------------------------------------------------------------------------- /plot_scenario1_case9.m: -------------------------------------------------------------------------------- 1 | %Plot figures for Scenario 1 - Case 9 2 | %Author: Sebastian A. Nugroho 3 | %Date: 3/14/2021 4 | 5 | clear 6 | close all 7 | 8 | %Load data from simulation 9 | load('Data_scenario1_Case9_dist_low.mat'); 10 | 11 | %Assign values 12 | t1_l = t2; %no control 13 | t2_l = t4; %NDAE 14 | t3_l = t5; %LQR 15 | t4_l = t6; %AGC 16 | x1_l = x_pre2; %no control 17 | x2_l = x_res2; %NDAE 18 | x3_l = x_res3; %LQR 19 | x4_l = x_res4; %AGC 20 | 21 | %Load data from simulation 22 | load('Data_scenario1_Case9_dist_moderate.mat'); 23 | 24 | %Assign values 25 | t1_m = t2; %no control 26 | t2_m = t4; %NDAE 27 | t3_m = t5; %LQR 28 | t4_m = t6; %AGC 29 | x1_m = x_pre2; %no control 30 | x2_m = x_res2; %NDAE 31 | x3_m = x_res3; %LQR 32 | x4_m = x_res4; %AGC 33 | 34 | %Load data from simulation 35 | load('Data_scenario1_Case9_dist_high.mat'); 36 | 37 | %Assign values 38 | t1_h = t2; %no control 39 | t2_h = t4; %NDAE 40 | t3_h = t5; %LQR 41 | t4_h = t6; %AGC 42 | x1_h = x_pre2; %no control 43 | x2_h = x_res2; %NDAE 44 | x3_h = x_res3; %LQR 45 | x4_h = x_res4; %AGC 46 | 47 | %Compute frequency deviation 48 | stateidx = 4; %Generator 1 frequency 49 | t_10 = 10; 50 | idx_10 = find(t4 == t_10); 51 | t_20 = 20; 52 | idx_20 = find(t4 == t_20); 53 | t_30 = 30; 54 | idx_30 = find(t4 == t_30); 55 | 56 | err_low_NDAE = norm(case_sys.w0*ones(1,case_sys.nG)-x2_l(idx_10,stateidx:stateidx+2),2); 57 | err_low_LQR = norm(case_sys.w0*ones(1,case_sys.nG)-x3_l(idx_10,stateidx:stateidx+2),2); 58 | err_low_AGC = norm(case_sys.w0*ones(1,case_sys.nG)-x4_l(idx_10,stateidx:stateidx+2),2); 59 | err_low_NDAE = [err_low_NDAE norm(case_sys.w0*ones(1,case_sys.nG)-x2_l(idx_20,stateidx:stateidx+2),2)]; 60 | err_low_LQR = [err_low_LQR norm(case_sys.w0*ones(1,case_sys.nG)-x3_l(idx_20,stateidx:stateidx+2),2)]; 61 | err_low_AGC = [err_low_AGC norm(case_sys.w0*ones(1,case_sys.nG)-x4_l(idx_20,stateidx:stateidx+2),2)]; 62 | err_low_NDAE = [err_low_NDAE norm(case_sys.w0*ones(1,case_sys.nG)-x2_l(idx_30,stateidx:stateidx+2),2)]; 63 | err_low_LQR = [err_low_LQR norm(case_sys.w0*ones(1,case_sys.nG)-x3_l(idx_30,stateidx:stateidx+2),2)]; 64 | err_low_AGC = [err_low_AGC norm(case_sys.w0*ones(1,case_sys.nG)-x4_l(idx_30,stateidx:stateidx+2),2)]; 65 | 66 | err_mod_NDAE = norm(case_sys.w0*ones(1,case_sys.nG)-x2_m(idx_10,stateidx:stateidx+2),2); 67 | err_mod_LQR = norm(case_sys.w0*ones(1,case_sys.nG)-x3_m(idx_10,stateidx:stateidx+2),2); 68 | err_mod_AGC = norm(case_sys.w0*ones(1,case_sys.nG)-x4_m(idx_10,stateidx:stateidx+2),2); 69 | err_mod_NDAE = [err_mod_NDAE norm(case_sys.w0*ones(1,case_sys.nG)-x2_m(idx_20,stateidx:stateidx+2),2)]; 70 | err_mod_LQR = [err_mod_LQR norm(case_sys.w0*ones(1,case_sys.nG)-x3_m(idx_20,stateidx:stateidx+2),2)]; 71 | %err_mod_AGC = [err_mod_AGC norm(case_sys.w0*ones(1,case_sys.nG)-x4_m(idx_20,stateidx:stateidx+2),2)]; 72 | err_mod_NDAE = [err_mod_NDAE norm(case_sys.w0*ones(1,case_sys.nG)-x2_m(idx_30,stateidx:stateidx+2),2)]; 73 | err_mod_LQR = [err_mod_LQR norm(case_sys.w0*ones(1,case_sys.nG)-x3_m(idx_30,stateidx:stateidx+2),2)]; 74 | %err_mod_AGC = [err_mod_AGC norm(case_sys.w0*ones(1,case_sys.nG)-x4_m(idx_30,stateidx:stateidx+2),2)]; 75 | 76 | err_high_NDAE = norm(case_sys.w0*ones(1,case_sys.nG)-x2_h(idx_10,stateidx:stateidx+2),2); 77 | %err_high_LQR = norm(case_sys.w0*ones(1,case_sys.nG)-x3_h(idx_10,stateidx:stateidx+2),2); 78 | %err_mod_AGC = norm(case_sys.w0*ones(1,case_sys.nG)-x4_h(idx_10,stateidx:stateidx+2),2); 79 | err_high_NDAE = [err_high_NDAE norm(case_sys.w0*ones(1,case_sys.nG)-x2_h(idx_20,stateidx:stateidx+2),2)]; 80 | %err_high_LQR = [err_high_LQR norm(case_sys.w0*ones(1,case_sys.nG)-x3_h(idx_20,stateidx:stateidx+2),2)]; 81 | %err_mod_AGC = [err_mod_AGC norm(case_sys.w0*ones(1,case_sys.nG)-x4_h(idx_20,stateidx:stateidx+2),2)]; 82 | err_high_NDAE = [err_high_NDAE norm(case_sys.w0*ones(1,case_sys.nG)-x2_h(idx_30,stateidx:stateidx+2),2)]; 83 | %err_high_LQR = [err_high_LQR norm(case_sys.w0*ones(1,case_sys.nG)-x3_h(idx_30,stateidx:stateidx+2),2)]; 84 | %err_mod_AGC = [err_mod_AGC norm(case_sys.w0*ones(1,case_sys.nG)-x4_h(idx_30,stateidx:stateidx+2),2)]; 85 | 86 | 87 | %Plot figures 88 | stateidx = 4; %Generator 1 frequency 89 | h(1) = figure; 90 | fs = 14; 91 | %Plot time index 92 | t_final = 55; 93 | idx_fin = find(t4 == t_final); 94 | hold on 95 | p(1) = plot(t2_l(1:idx_fin),x2_l(1:idx_fin,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.88 0.00 0.15]); 96 | p(2) = plot(t3_l(1:idx_fin),x3_l(1:idx_fin,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.47 0.67 0.19],'LineStyle','-.'); 97 | p(3) = plot(t4_l(1:idx_fin),x4_l(1:idx_fin,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.00 0.45 0.74],'LineStyle','--'); 98 | set(gcf,'color','w'); 99 | title('\rm 9-Bus Network','FontSize',fs-5) 100 | ax = gca; 101 | ax.TitleFontSizeMultiplier = 1; 102 | hold off 103 | grid on 104 | box on 105 | xlim([0 t2_l(idx_fin)]); 106 | ylim([59.9975 60.0001]); 107 | xlabel('$t$ [sec]', 'interpreter','latex','FontName','Times New Roman','FontSize',fs); 108 | ylabel('$\frac{1}{2\pi}\omega$ [Hz]', 'interpreter','latex','FontName','Times New Roman','FontSize',fs); 109 | set(gca,'fontsize',fs-3); 110 | leg1 = legend(p,{'NDAE-control','LQR-control','AGC'}); 111 | set(leg1,'Interpreter','latex'); 112 | set(leg1,'FontSize',fs-4); 113 | set(leg1,'Location','southeast'); 114 | %set(leg1,'Orientation','horizontal'); 115 | 116 | %Add text 117 | str = '$\rho_{\mathrm{L}} = 0.04$'; 118 | text(0.022727272727273,0.940238344579429,str,'Interpreter','latex','FontSize',fs-3,'Units','normalized'); 119 | 120 | set(h(1), 'Position', [300 100 390 240]) 121 | print(h(1), 'scenario1_case9_low.eps', '-depsc2','-r600') 122 | 123 | h(2) = figure; 124 | %Plot time index 125 | t_final = 55; 126 | idx_fin = find(t4 == t_final); 127 | hold on 128 | p(1) = plot(t2_m(1:idx_fin),x2_m(1:idx_fin,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.88 0.00 0.15]); 129 | p(2) = plot(t3_m(1:idx_fin),x3_m(1:idx_fin,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.47 0.67 0.19],'LineStyle','--'); 130 | p(3) = plot(t4_m,x4_m(:,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.00 0.45 0.74],'LineStyle','-.'); 131 | set(gcf,'color','w'); 132 | title('\rm 9-Bus Network','FontSize',fs-5) 133 | ax = gca; 134 | ax.TitleFontSizeMultiplier = 1; 135 | hold off 136 | grid on 137 | box on 138 | xlim([0 t2_m(idx_fin)]); 139 | ylim([59.994 60.0001]); 140 | xlabel('$t$ [sec]', 'interpreter','latex','FontName','Times New Roman','FontSize',fs); 141 | ylabel('$\frac{1}{2\pi}\omega$ [Hz]', 'interpreter','latex','FontName','Times New Roman','FontSize',fs); 142 | set(gca,'fontsize',fs-3); 143 | leg1 = legend(p,{'NDAE-control','LQR-control','AGC'}); 144 | set(leg1,'Interpreter','latex'); 145 | set(leg1,'FontSize',fs-4); 146 | set(leg1,'Location','southeast'); 147 | %set(leg1,'Orientation','horizontal'); 148 | 149 | %Add text 150 | str = '$\rho_{\mathrm{L}} = 0.08$'; 151 | text(0.022727272727273,0.940238344579429,str,'Interpreter','latex','FontSize',fs-3,'Units','normalized'); 152 | 153 | set(h(2), 'Position', [300 100 390 240]) 154 | print(h(2), 'scenario1_case9_mod.eps', '-depsc2','-r600') 155 | 156 | h(3) = figure; 157 | %Plot time index 158 | t_final = 55; 159 | idx_fin = find(t4 == t_final); 160 | hold on 161 | p(1) = plot(t2_h(1:idx_fin),x2_h(1:idx_fin,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.88 0.00 0.15]); 162 | p(2) = plot(t3_h,x3_h(:,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.47 0.67 0.19],'LineStyle','-.'); 163 | p(3) = plot(t4_h,x4_h(:,stateidx)/(2*pi),'LineWidth',1.2,'Color',[0.00 0.45 0.74],'LineStyle','--'); 164 | set(gcf,'color','w'); 165 | title('\rm 9-Bus Network','FontSize',fs-5) 166 | ax = gca; 167 | ax.TitleFontSizeMultiplier = 1; 168 | hold off 169 | grid on 170 | box on 171 | xlim([0 t2_h(idx_fin)]); 172 | ylim([59.993 60.0001]); 173 | xlabel('$t$ [sec]', 'interpreter','latex','FontName','Times New Roman','FontSize',fs); 174 | ylabel('$\frac{1}{2\pi}\omega$ [Hz]', 'interpreter','latex','FontName','Times New Roman','FontSize',fs); 175 | set(gca,'fontsize',fs-3); 176 | leg1 = legend(p,{'NDAE-control','LQR-control','AGC'}); 177 | set(leg1,'Interpreter','latex'); 178 | set(leg1,'FontSize',fs-4); 179 | set(leg1,'Location','southeast'); 180 | %set(leg1,'Orientation','horizontal'); 181 | 182 | %Add text 183 | str = '$\rho_{\mathrm{L}} = 0.12$'; 184 | text(0.022727272727273,0.940238344579429,str,'Interpreter','latex','FontSize',fs-3,'Units','normalized'); 185 | 186 | set(h(3), 'Position', [300 100 390 240]) 187 | print(h(3), 'scenario1_case9_high.eps', '-depsc2','-r600') -------------------------------------------------------------------------------- /powerNetwork_NDAE.m: -------------------------------------------------------------------------------- 1 | function [val] = powerNetwork_NDAE(t,state,input,Pl,Ql,case_sys) 2 | % Construct the overall power network's NDAE model 3 | % Author: Sebastian A. Nugroho 4 | % Date: 1/30/2021 5 | 6 | %Reshape vectors - state 7 | dels = state(1:case_sys.nG,1); 8 | oms = state(case_sys.nG+1:2*case_sys.nG,1); 9 | Eps = state(2*case_sys.nG+1:3*case_sys.nG,1); 10 | Tms = state(3*case_sys.nG+1:4*case_sys.nG,1); 11 | pgs = state(4*case_sys.nG+1:5*case_sys.nG,1); 12 | qgs = state(5*case_sys.nG+1:6*case_sys.nG,1); 13 | vs = state(6*case_sys.nG+1:6*case_sys.nG+case_sys.nN,1); 14 | thes = state(6*case_sys.nG+1+case_sys.nN:end,1); 15 | 16 | %Reshape vectors - input 17 | Efd = input(1:case_sys.nG,1); 18 | Tr = input(case_sys.nG+1:end,1); 19 | 20 | %Function #1: generator's differential equations 21 | [delta_dot,omega_dot,Ep_dot,Tm_dot] = case_sys.f_gen_dyn(dels,oms,Eps,Tms,Efd,Tr,pgs,vs,thes); 22 | 23 | %Function #2: generator's algebraic equations 24 | [val1,val2] = case_sys.f_gen_alg(dels,Eps,pgs,qgs,vs,thes); 25 | 26 | %Function #3: power flow equations 27 | [valGp,valGq,valLp,valLq] = case_sys.f_pf_loads(pgs,qgs,vs,thes,Pl,Ql); 28 | 29 | %Output 30 | val = [delta_dot; omega_dot; Ep_dot; Tm_dot; val1; val2; valGp; valGq; valLp; valLq]; 31 | 32 | end -------------------------------------------------------------------------------- /powerNetwork_NDAE_AGC.m: -------------------------------------------------------------------------------- 1 | function [val] = powerNetwork_NDAE_AGC(t,state,input,Pl,Ql,case_sys) 2 | % Construct the overall power network's NDAE model with AGC 3 | % Author: Sebastian A. Nugroho 4 | % Date: 2/22/2021 5 | 6 | %Reshape vectors - state 7 | dels = state(1:case_sys.nG,1); 8 | oms = state(case_sys.nG+1:2*case_sys.nG,1); 9 | Eps = state(2*case_sys.nG+1:3*case_sys.nG,1); 10 | Tms = state(3*case_sys.nG+1:4*case_sys.nG,1); 11 | ys = state(4*case_sys.nG+1,1); 12 | pgs = state(4*case_sys.nG+2:5*case_sys.nG+1,1); 13 | qgs = state(5*case_sys.nG+2:6*case_sys.nG+1,1); 14 | vs = state(6*case_sys.nG+2:6*case_sys.nG+case_sys.nN+1,1); 15 | thes = state(6*case_sys.nG+2+case_sys.nN:end,1); 16 | 17 | %Reshape vectors - input 18 | Efd = input(1:case_sys.nG,1); 19 | Tr = input(case_sys.nG+1:end,1); 20 | 21 | %Function #1: generator's differential equations 22 | [out] = case_sys.f_gen_dyn_AGC(dels,oms,Eps,Tms,Efd,Tr,pgs,vs,thes,ys); 23 | 24 | %Function #2: generator's algebraic equations 25 | [val1,val2] = case_sys.f_gen_alg(dels,Eps,pgs,qgs,vs,thes); 26 | 27 | %Function #3: power flow equations 28 | [valGp,valGq,valLp,valLq] = case_sys.f_pf_loads(pgs,qgs,vs,thes,Pl,Ql); 29 | 30 | %Output 31 | val = [out; val1; val2; valGp; valGq; valLp; valLq]; 32 | 33 | end -------------------------------------------------------------------------------- /psgen_class.m: -------------------------------------------------------------------------------- 1 | classdef psgen_class 2 | % Class for generator, 4-th order nonlinear DAE model 3 | % By: Sebastian A. Nugroho 4 | % Date: 7/22/2020 5 | properties 6 | %Generator's dynamic parameters 7 | w0; %Synchronous frequency 8 | M; %Rotor's inertia constant 9 | D; %Damping coefficient 10 | xq; %Direct axis synchronous reactance in q-axis 11 | xd; %Direct axis synchronous reactance in d-axis 12 | xqp; %Direct axis transient reactance in q-axis 13 | xdp; %Direct axis transient reactance in d-axis 14 | Tq0p; %Open circuit time constant in q-axis 15 | Td0p; %Open circuit time constant in d-axis 16 | Tch; %Chest valve time constants 17 | Rd; %Regulation constants 18 | 19 | %Generator's algebraic parameters 20 | Rs; %Stator resistance 21 | 22 | %Generator bus index 23 | bus_idx; 24 | 25 | %Generator index 26 | gen_idx; 27 | 28 | %Generator base MVA 29 | genSbase; 30 | end 31 | methods 32 | function obj = psgen_class(w0,M,D,xq,xd,xqp,xdp,Tq0p,Td0p,Tch,Rd,Rs,bus_idx,gen_idx,genSbase) 33 | % if nargin == 13 34 | obj.w0 = w0; 35 | obj.M = M; 36 | obj.D = D; 37 | obj.xq = xq; 38 | obj.xd = xd; 39 | obj.xqp = xqp; 40 | obj.xdp = xdp; 41 | obj.Tq0p = Tq0p; 42 | obj.Td0p = Td0p; 43 | obj.Tch = Tch; 44 | obj.Rd = Rd; 45 | obj.Rs = Rs; 46 | obj.bus_idx = bus_idx; 47 | obj.gen_idx = gen_idx; 48 | obj.genSbase = genSbase; 49 | % else 50 | % disp('Number of arguments does not match. Please check.') 51 | % end 52 | end 53 | end 54 | end -------------------------------------------------------------------------------- /shade.m: -------------------------------------------------------------------------------- 1 | function h = shade(varargin) 2 | %SHADE Filled area linear plot. 3 | % 4 | % SHADE should be called using the same syntax as the built-in PLOT. 5 | % 6 | % SHADE(X,Y) plots vector Y versus vector X, filling the area under the 7 | % curve. If either is a matrix, this function behaves like PLOT. 8 | % 9 | % SHADE(Y) plots the columns of Y versus their index. 10 | % 11 | % SHADE(X,Y,S) plots Y versus X using the line type, marker symbols and 12 | % colors as specified by S. For more information on the line specifier S, 13 | % see PLOT. 14 | % 15 | % SHADE(X1,Y1,X2,Y2,X3,Y3,...) combines the plots defined by 16 | % the (X,Y) pairs. 17 | % 18 | % SHADE(X1,Y1,S1,X2,Y2,S2,X3,Y3,S3,...) combines the plots defined by 19 | % the (X,Y,S) triples. 20 | % 21 | % SHADE(AX,...) plots into the axes with handle AX. 22 | % 23 | % H = SHADE(...) returns a column vector of handles to graphics objects. 24 | % 25 | % SHADE(...,Name,Value) specifies additional properties of the lines (see 26 | % help for PLOT) or the filled areas (see below). 27 | % 28 | % Three additional properties are provided to control the filling: 29 | % 30 | % - 'FillType' specifies the filling behaviour. The input should be a 31 | % matrix of size [N,2], such as [A1,B1;A2,B2;...;An,Bn], where Ai and 32 | % Bi indicate the upper and lower limits, respectively, of each of 33 | % the N areas to be filled. Each Ai and Bi is an index pointing to 34 | % one of the lines drawn by PLOT. If, for a particular combination of 35 | % inputs, PLOT draws M lines, then each Ai and Bi should be a number 36 | % between 1 and M. In addition, the special cases 0, -1 and -2 are 37 | % allowed, each representing the x-axis, the bottom of the active 38 | % axes and the top of the active axes, respectively. 'FillType' may 39 | % also be specified using a cell array of size [N,2], in which case 40 | % one may also use the keywords 'axis', 'bottom' and 'top' instead 41 | % of 0, -1 and -2. By default, the areas between each of the curves 42 | % and the x-axis are filled, which corresponds to the input matrix 43 | % [1,0;0,1;2,0;0,2;...;M,0;0,M]. 44 | % 45 | % - 'FillColor' specifies the color of the fillings. This should be a 46 | % matrix of size [N,3], where each row is the RGB triplet for the 47 | % corresponding area as specified above. 'FillColor' may also be 48 | % specified as a cell array of length N, in which case each entry may 49 | % be either an RGB triplet or any of the color names commonly used in 50 | % MATLAB. If only one RGB value or color name is provided, all areas 51 | % are treated equally. If this parameter is not specified, colors are 52 | % determined by the corresponding lines. 53 | % 54 | % - 'FillAlpha' specifies the transparency of the fillings. This should 55 | % be a vector of length N, where each entry specifies the alpha value 56 | % for each area. If only one alpha value is provided, all areas are 57 | % treated equally. If this parameter is not specified, an alpha value 58 | % of 0.3 is used for all areas. 59 | % 60 | % See also PLOT. 61 | % Copyright (c) 2018 Javier Montalt Tordera. 62 | % accepted params 63 | names = {'FillType','FillColor','FillAlpha'}; 64 | % init fill params 65 | fp = cell(1,3); 66 | % extract filling parameters, if present 67 | for n = 1:length(names) 68 | for i = 1:length(varargin) 69 | % if found 70 | if strcmpi(names{n},varargin{i}) 71 | if i+1 > nargin 72 | error(["Expected an input value after the name '" names{i} "'."]); 73 | end 74 | % save filling info 75 | fp{n} = varargin{i+1}; 76 | % delete from varargin array - otherwise plot will fail as it won't 77 | % understand the input 78 | varargin(i:i+1) = []; 79 | break; 80 | end 81 | end 82 | end 83 | % check if an axes object was specified 84 | if isscalar(varargin{1}) && ishandle(varargin{1}(1)) 85 | ax = varargin{1}; 86 | else 87 | ax = gca; 88 | end 89 | % initial hold state 90 | tf = ishold(ax); 91 | % plot lines 92 | ls = plot(varargin{:}); 93 | hold(ax,'on'); 94 | % provide default filling params 95 | fd = fp{1}; 96 | fc = fp{2}; 97 | fa = fp{3}; 98 | % validate fill type 99 | fd = validatetype(fd,ls); 100 | % number of fillings 101 | nf = size(fd,1); 102 | % validate fill color 103 | fc = validatecolor(fc,fd,ls,nf); 104 | % validate fill alpha 105 | fa = validatealpha(fa,nf); 106 | % array to hold patch objects 107 | ps = gobjects(nf,1); 108 | % for each filling 109 | for i = 1:nf 110 | 111 | x = cell(1,2); 112 | y = cell(1,2); 113 | 114 | % get data 115 | for j = 1:2 116 | switch fd(i,j) 117 | case {-2,-1} 118 | y{j} = ylim; 119 | y{j} = y{j}(abs(fd(i,j))); 120 | case 0 121 | y{j} = 0; 122 | otherwise 123 | x{j} = ls(fd(i,j)).XData; 124 | y{j} = ls(fd(i,j)).YData; 125 | end 126 | end 127 | 128 | if isequal(x{1},x{2}) 129 | x = x{1}; 130 | elseif isempty(x{1}) 131 | x = x{2}; 132 | y{1} = y{1} * ones(size(x)); 133 | elseif isempty(x{2}) 134 | x = x{1}; 135 | y{2} = y{2} * ones(size(x)); 136 | else 137 | x = sort([x{1} x{2}]); 138 | y{1} = interp1(x{1},y{1},x,'linear','extrap'); 139 | y{2} = interp1(x{2},y{2},x,'linear','extrap'); 140 | end 141 | 142 | % crossings 143 | x0 = [x(1) zcross(x,y{1} - y{2}) x(end)]; 144 | y0 = interp1(x,y{1},x0); 145 | 146 | % for each zero-crossing 147 | for j = 0:length(x0)-2 148 | % index 149 | idx = x >= x0(j+1) & x <= x0(j+2) & y{1} >= y{2}; 150 | if all(~idx), continue; end 151 | % polygon corners 152 | xv = [x0(j+1) x(idx) x0(j+2) fliplr(x(idx))]; 153 | yv = [y0(j+1) y{1}(idx) y0(j+2) fliplr(y{2}(idx))]; 154 | % fill polygon 155 | ps(i) = fill(ax,xv,yv,fc(i,:),'LineStyle','none','FaceAlpha',fa(i)); 156 | end 157 | end 158 | % release if the hold state was off when called 159 | if tf == 0 160 | hold(ax,'off'); 161 | end 162 | % set output argument if requested 163 | if nargout == 1 164 | h = [ls;ps]; 165 | end 166 | end 167 | function z = zcross(x,y) 168 | % find zero crossings of line Y versus X 169 | % logical index 170 | c = y > 0; 171 | % find point pairs where there is a sign change 172 | d = abs(diff(c)); 173 | p1 = find(d == 1); % before change 174 | p2 = p1 + 1; % after change 175 | % zero-crossing positions 176 | z = x(p1) + abs(y(p1)) ./ (abs(y(p1)) + abs(y(p2))) .* (x(p2) - x(p1)); 177 | end 178 | function fd = validatetype(fd,ls) 179 | % if no filling specified, fill to x-axis 180 | if isempty(fd) 181 | fd = [(1:length(ls))' zeros(size(ls))]; 182 | fd = [fd;fliplr(fd)]; 183 | fd = fd([1:2:length(fd) 2:2:length(fd)],:); 184 | end 185 | % if the filling was specified in cell form, convert to matrix 186 | if iscell(fd) 187 | tmp = zeros(size(fd)); 188 | for i = 1:numel(fd) 189 | if ischar(fd{i}) 190 | tmp(i) = str2ind(validatestring(fd{i},{'axis','bottom','top'},'shade','FillType')); 191 | else 192 | validateattributes(fd{i},{'numeric'},{'scalar'},'shade','FillType'); 193 | tmp(i) = fd{i}; 194 | end 195 | end 196 | fd = tmp; 197 | end 198 | validateattributes(fd,{'numeric'},{'integer','size',[nan 2],'>=',-2,'<=',length(ls)},'shade','FillType'); 199 | end 200 | function fc = validatecolor(fc,fd,ls,nf) 201 | % if no color specified, get it from plot lines 202 | if isempty(fc) 203 | fc = zeros(nf,3); 204 | for i = 1:nf 205 | if fd(i,1) <= 0 206 | fc(i,:) = ls(fd(i,2)).Color; 207 | elseif fd(i,2) <= 0 208 | fc(i,:) = ls(fd(i,1)).Color; 209 | else 210 | fc(i,:) = mean([ls(fd(i,1)).Color;ls(fd(i,2)).Color],1); 211 | end 212 | end 213 | end 214 | % if length 1, repeat 215 | if ischar(fc) 216 | fc = {fc}; 217 | fc = repmat(fc,nf,1); 218 | elseif (iscell(fc) && numel(fc) == 1) || (~iscell(fc) && size(fc,1) == 1) 219 | fc = repmat(fc,nf,1); 220 | end 221 | % if color is specified in cell form, convert to matrix 222 | if iscell(fc) 223 | validateattributes(fc,{'cell'},{'vector','numel',nf},'shade','FillColor'); 224 | tmp = zeros(numel(fc),3); 225 | for i = 1:numel(fc) 226 | if ischar(fc{i}) 227 | tmp(i,:) = str2rgb(validatestring(fc{i},{'y','m','c','r','g','b','w','k','yellow','magenta','cyan','red','green','blue','white','black'},'shade','FillColor')); 228 | else 229 | validateattributes(fc{i},{'numeric'},{'vector','numel',3},'shade','FillColor'); 230 | if iscolumn(fc{i}) 231 | fc{i} = fc{i}'; 232 | end 233 | tmp(i,:) = fc{i}; 234 | end 235 | end 236 | fc = tmp; 237 | end 238 | validateattributes(fc,{'numeric'},{'real','size',[nf 3],'>=',0,'<=',1},'shade','FillColor'); 239 | end 240 | function fa = validatealpha(fa,nf) 241 | % if no alpha specified, choose a value of 0.2 242 | if isempty(fa) 243 | fa = 0.3 * ones(nf,1); 244 | end 245 | % if length 1, repeat 246 | if length(fa) == 1 247 | fa = repmat(fa,nf,1); 248 | end 249 | if isrow(fa) 250 | fa = fa'; 251 | end 252 | validateattributes(fa,{'numeric'},{'real','vector','numel',nf,'>=',0,'<=',1},'shade','FillAlpha'); 253 | end 254 | function n = str2ind(s) 255 | % convert string to index 256 | switch s 257 | case 'axis' 258 | n = 0; 259 | case 'bottom' 260 | n = -1; 261 | case 'top' 262 | n = -2; 263 | end 264 | end 265 | function n = str2rgb(s) 266 | % convert string to RBG triplet 267 | switch s 268 | case {'y','yellow'} 269 | n = [1 1 0]; 270 | case {'m','magenta'} 271 | n = [1 0 1]; 272 | case {'c','cyan'} 273 | n = [0 1 1]; 274 | case {'r','red'} 275 | n = [1 0 0]; 276 | case {'g','green'} 277 | n = [0 1 0]; 278 | case {'b','blue'} 279 | n = [0 0 1]; 280 | case {'w','white'} 281 | n = [1 1 1]; 282 | case {'k','black'} 283 | n = [0 0 0]; 284 | end 285 | end 286 | --------------------------------------------------------------------------------