%%*****************************************************************
%% sqlp: main solver
%%*****************************************************************
%% SDPT3: version 4.0
%% Copyright (c) 1997 by
%% Kim-Chuan Toh, Michael J. Todd, Reha H. Tutuncu
%% Last Modified: 16 Sep 2004
%%*****************************************************************

  function [obj,X,y,Z,info,runhist] = sqlpmain(blk,At,C,b,par,parbarrier,X0,y0,Z0);

   global spdensity smallblkdim  printlevel msg
   global solve_ok  use_LU  exist_analytic_term numpertdiagschur
   global schurfun  schurfun_par
%%
   matlabversion = par.matlabversion;
   vers          = par.vers;
   predcorr      = par.predcorr;
   gam           = par.gam;
   expon         = par.expon;
   gaptol        = par.gaptol;
   inftol        = par.inftol;
   steptol       = par.steptol;
   maxit         = par.maxit;
   printlevel    = par.printlevel;
   stoplevel     = par.stoplevel;
   scale_data    = par.scale_data;
   spdensity     = par.spdensity;
   rmdepconstr   = par.rmdepconstr;
   smallblkdim   = par.smallblkdim;
   schurfun      = par.schurfun;
   schurfun_par  = par.schurfun_par;
   ublksize      = par.ublksize;
%%
   tstart = clock;
   X = X0; y = y0; Z = Z0;
   for p = 1:size(blk,1)
      if strcmp(blk{p,1},'u'); Z{p} = zeros(blk{p,2},1); end
   end
%%
%%-----------------------------------------
%% convert unrestricted blk to linear blk.
%%-----------------------------------------
%%
   convertlen = 0;
   [blk,At,C,X,Z,u2lblk,ublkidx] = sqlpu2lblk(blk,At,C,X,Z,par,convertlen);
   for p = 1:size(blk,1)
      pblk = blk(p,:);
      if (u2lblk(p) == 1)
         n = 2*blk{p,2};
         blk{p,1} = 'l';  blk{p,2} = n;
         parbarrier{p} = zeros(1,n);
         At{p} = [At{p}; -At{p}];
         tau = max(1,norm(C{p}));
         C{p} = [C{p}; -C{p}];
         msg = 'convert ublk to lblk';
         if (printlevel); fprintf(' *** %s',msg); end
         b2 = 1 + abs(b');
         normCtmp = 1+norm(C{p});
         normAtmp = 1+sqrt(sum(At{p}.*At{p}));
         if (n > 1000)
            const = sqrt(n);
         else
	    const = n;
         end
         if (par.startpoint == 1)
            X{p} = const* max([1,b2./normAtmp]) *ones(n,1);
            Z{p} = const* max([1,normAtmp/sqrt(n),normCtmp/sqrt(n)]) *ones(n,1);
            X{p} = X{p}.*(1+1e-10*randmat(n,1,0,'u'));
            Z{p} = Z{p}.*(1+1e-10*randmat(n,1,0,'u'));
	 else
            const = max(abs(X{p})) + 100;
            X{p} = [X{p}+const; const*ones(n/2,1)];
            %%old: const = 100; Z{p} = [const*ones(n/2,1); const*ones(n/2,1)];
            Z{p} = [abs(Z0{p}); abs(Z0{p})] + 1e-4;
         end
      end
   end
%%-----------------------------------------
%% check whether {A1,...,Am} is
%% linearly independent.
%%-----------------------------------------
%%
   m0 = length(b);
   [At,b,y,indeprows,par.depconstr,feasible,par.AAt] = ...
    checkdepconstr(blk,At,b,y,rmdepconstr);
   if (~feasible)
      obj = []; X = cell(size(blk,1),1); y = []; Z = cell(size(blk,1),1);
      runhist = [];
      msg = 'SQLP is not feasible';
      if (printlevel); fprintf('\n %s \n',msg); end
      return;
   end
   par.normAAt = norm(par.AAt,'fro');
%%
%%-----------------------------------------
%% scale SQLP data. Note: must be done only
%% after checkdepconstr
%%-----------------------------------------
%%
   normA2 = 1+ops(At,'norm');
   normb2 = 1+norm(b);
   normC2 = 1+ops(C,'norm');
   normX0 = 1+ops(X0,'norm');
   normZ0 = 1+ops(Z0,'norm');
   if (scale_data)
      [At,C,b,normA,normC,normb,X,y,Z] = scaling(blk,At,C,b,X,y,Z);
   else
      normA = 1; normC = 1; normb = 1;
   end
%%
%%-----------------------------------------
%% find the combined list of non-zero
%% elements of Aj, j = 1:k, for each k.
%% IMPORTANT NOTE: Ak, C are permuted.
%%-----------------------------------------
%%
   par.numcolAt = length(b);
   [At,C,X,Z,par.permA,par.permZ] = sortA(blk,At,C,b,X,Z);
   [par.isspA,par.nzlistA,par.nzlistAsum,par.isspAy,par.nzlistAy] = nzlist(blk,At,par);
%%
%%-----------------------------------------
%% create an artifical non-negative block
%% for a purely log-barrier problem
%%-----------------------------------------
%%
   numblkold = size(blk,1);
   nn = 0;
   for p = 1:size(blk,1);
      pblk = blk(p,:);
      idx = find(parbarrier{p}==0);
      if ~isempty(idx);
         if strcmp(pblk{1},'l')
            nn = nn + length(idx);
         elseif strcmp(pblk{1},'q')
            nn = nn + sum(pblk{2}(idx));
         elseif strcmp(pblk{1},'s')
            nn = nn + sum(pblk{2}(idx));
         end
      end
   end
   if (nn==0)
      analytic_prob = 1;
      numblk = size(blk,1)+1;
      blk{numblk,1} = 'l'; blk{numblk,2} = 1;
      At{numblk,1} = sparse(1,length(b));
      C{numblk,1} = 1;
      X{numblk,1} = 1e3;
      Z{numblk,1} = 1e3;
      parbarrier{numblk,1} = 0;
      u2lblk(numblk,1) = 0;
      nn = nn + 1;
   else
      analytic_prob = 0;
   end
%%
   exist_analytic_term = 0;
   for p = 1:size(blk,1);
      idx = find(parbarrier{p} > 0);
      if ~isempty(idx);
         exist_analytic_term = 1;
      end
   end
%%-----------------------------------------
%% initialization
%%-----------------------------------------
%%
   EE = ops(blk,'identity');
   normE2 = ops(EE,'norm'); Zpertold = 1;
   for p = 1:size(blk,1)
      normCC(p) = 1+ops(C(p),'norm');
      normEE(p) = 1+ops(EE(p),'norm');
   end
   [Xchol,indef(1)] = blkcholfun(blk,X);
   [Zchol,indef(2)] = blkcholfun(blk,Z);
   if any(indef)
      msg = 'stop: X or Z not positive definite';
      if (printlevel); fprintf('\n  %s\n',msg); end
      info.termcode = -3;
      info.msg1 = msg;
      obj = []; X = cell(size(blk,1),1); y = []; Z = cell(size(blk,1),1);
      runhist = [];
      return;
   end
   AX = AXfun(blk,At,par.permA,X);
   rp = b-AX;
   ZpATy = ops(Z,'+',Atyfun(blk,At,par.permA,par.isspAy,y));
   ZpATynorm = ops(ZpATy,'norm');
   Rd = ops(C,'-',ZpATy);
   objadd0 = 0;
   if (scale_data)
      for p = 1:size(blk,1)
         pblk = blk(p,:);
         objadd0 = objadd0 + sum(parbarrier{p}.*pblk{2})*log(normA{p});
      end
   end
   objadd = blkbarrier(blk,X,Z,Xchol,Zchol,parbarrier) + objadd0;
   obj = (normb*normC)*[blktrace(blk,C,X), b'*y] + objadd;
   gap = (normb*normC)*blktrace(blk,X,Z) - diff(objadd);
   relgap = gap/(1+sum(abs(obj)));
   prim_infeas = norm(rp)/normb2;
   dual_infeas = ops(Rd,'norm')/normC2;
   infeas = max(prim_infeas,dual_infeas);
   if (scale_data)
      infeas_org(1) = prim_infeas*normb;
      infeas_org(2) = dual_infeas*normC;
   else
      infeas_org = [0,0];
   end
   trXZ = blktrace(blk,X,Z,parbarrier);
   if (nn > 0); mu  = trXZ/nn; else; mu = gap/ops(X,'getM'); end
   normX = ops(X,'norm');
%%
   termcode = 0; restart = 0;
   pstep = 1; dstep = 1; pred_convg_rate = 1; corr_convg_rate = 1;
   prim_infeas_min  = prim_infeas;
   dual_infeas_min  = dual_infeas;
   prim_infeas_best = prim_infeas;
   dual_infeas_best = dual_infeas;
   infeas_best = infeas;
   relgap_best = relgap;
   homRd = inf; homrp = inf; dy = zeros(length(b),1);
   msg = []; msg2 = []; msg3 = [];
   runhist.pobj    = obj(1);
   runhist.dobj    = obj(2);
   runhist.gap     = gap;
   runhist.relgap  = relgap;
   runhist.pinfeas = prim_infeas;
   runhist.dinfeas = dual_infeas;
   runhist.infeas  = infeas;
   runhist.pstep   = 0;
   runhist.dstep   = 0;
   runhist.step    = 0;
   runhist.normX   = normX;
   runhist.cputime = etime(clock,tstart);
   ttime.preproc   = runhist.cputime;
   ttime.pred = 0; ttime.pred_pstep = 0; ttime.pred_dstep = 0;
   ttime.corr = 0; ttime.corr_pstep = 0; ttime.corr_dstep = 0;
   ttime.pchol = 0; ttime.dchol = 0; ttime.misc = 0;
%%
%%-----------------------------------------
%% display parameters and initial info
%%-----------------------------------------
%%
   if (printlevel >= 2)
      fprintf('\n********************************************');
      fprintf('***********************\n');
      fprintf('   SDPT3: Infeasible path-following algorithms');
      fprintf('\n********************************************');
      fprintf('***********************\n');
      [hh,mm,ss] = mytime(ttime.preproc);
      if (printlevel>=3)
         fprintf(' version  predcorr  gam  expon  scale_data\n');
         if (vers == 1); fprintf('   HKM '); elseif (vers == 2); fprintf('    NT '); end
         fprintf('     %1.0f      %4.3f',predcorr,gam);
         fprintf('   %1.0f        %1.0f    %1.0f\n',expon,scale_data);
         fprintf('\nit pstep dstep pinfeas dinfeas  gap')
         fprintf('      prim-obj      dual-obj    cputime\n');
         fprintf('------------------------------------------------');
         fprintf('-------------------\n');
         fprintf('%2.0f|%4.3f|%4.3f|%2.1e|%2.1e|',0,0,0,prim_infeas,dual_infeas);
         fprintf('%2.1e|%- 7.6e %- 7.6e| %s:%s:%s|',gap,obj(1),obj(2),hh,mm,ss);
      end
   end
%%
%%---------------------------------------------------------------
%% start main loop
%%---------------------------------------------------------------
%%
   param.termcode    = termcode;
   param.iter        = 0;
   param.obj         = obj;
   param.relgap      = relgap;
   param.prim_infeas = prim_infeas;   param.dual_infeas = dual_infeas;
   param.homRd       = homRd;         param.homrp       = homrp;
   param.AX          = AX;            param.ZpATynorm   = ZpATynorm;
   param.normA       = normA;
   param.normb       = normb;         param.normC       = normC;
   param.normX0      = normX0;        param.normZ0      = normZ0;
   param.m0          = m0;            param.indeprows   = indeprows;
   param.prim_infeas_bad = 0;
   param.dual_infeas_bad = 0;
   param.prim_infeas_min = prim_infeas;
   param.dual_infeas_min = dual_infeas;
   param.gaptol      = gaptol;
   param.inftol      = inftol;
   param.maxit       = maxit;
   param.scale_data  = scale_data;
   param.printlevel  = printlevel;
   param.ublksize    = ublksize;
   Xbest = X; ybest = y; Zbest = Z;
%%
   for iter = 1:maxit;
      tstart  = clock;
      timeold = tstart;
      update_iter = 0; breakyes = 0;
      pred_slow = 0; corr_slow = 0; step_short = 0;
      par.parbarrier = parbarrier;
      par.iter    = iter;
      par.obj     = obj;
      par.relgap  = relgap;
      par.pinfeas = prim_infeas;
      par.dinfeas = dual_infeas;
      par.rp      = rp;
      par.y       = y;
      par.dy      = dy;
      par.normX   = normX;
      par.ZpATynorm = ZpATynorm;
      %%if (printlevel > 2); fprintf(' %2.1e',par.normX); end
      if (iter == 1 | restart); Cpert = min(1,normC2/ops(EE,'norm')); end
      if (runhist.dinfeas(1) > 1e-3) & (~exist_analytic_term) ...
         & (relgap > 1e-4)
         if (par.normX > 5e3 & iter < 20)
            Cpert = Cpert*0.5;
         elseif (par.normX > 5e2 & iter < 20);
            Cpert = Cpert*0.3;
         else;
            Cpert = Cpert*0.1;
         end
         Rd = ops(Rd,'+',EE,Cpert);
         %%if (printlevel > 2); fprintf('|%2.1e',Cpert); end
      end
%%---------------------------------------------------------------
%% predictor step.
%%---------------------------------------------------------------
%%
      if (predcorr)
         sigma = 0;
      else
         sigma = 1-0.9*min(pstep,dstep);
         if (iter == 1); sigma = 0.5; end;
      end
      sigmu = cell(size(blk,1),1);
      for p = 1:size(blk,1)
         sigmu{p} = max(sigma*mu, parbarrier{p}');
      end
      invXchol = cell(size(blk,1),1);
      invZchol = ops(Zchol,'inv');
      if (vers == 1);
         [par,dX,dy,dZ,coeff,L,hRd] = ...
          HKMpred(blk,At,par,rp,Rd,sigmu,X,Z,invZchol);
      elseif (vers == 2);
         [par,dX,dy,dZ,coeff,L,hRd] = ...
          NTpred(blk,At,par,rp,Rd,sigmu,X,Z,Zchol,invZchol);
      end
      if (solve_ok <= 0)
         msg = 'stop: difficulty in computing predictor directions';
         if (printlevel); fprintf('\n  %s',msg); end
         runhist.pinfeas(iter+1) = runhist.pinfeas(iter);
         runhist.dinfeas(iter+1) = runhist.dinfeas(iter);
         runhist.relgap(iter+1)  = runhist.relgap(iter);
         runhist.cputime(iter+1) = etime(clock,tstart);
         termcode = -4;
         break; %% do not ues breakyes = 1
      end
      timenew = clock;
      ttime.pred = ttime.pred + etime(timenew,timeold); timeold = timenew;
%%
%%-----------------------------------------
%% step-lengths for predictor step
%%-----------------------------------------
%%
      if (gam == 0)
         gamused = 0.9 + 0.09*min(pstep,dstep);
      else
         gamused = gam;
      end
      [Xstep,invXchol] = steplength(blk,X,dX,Xchol,invXchol);
      pstep = min(1,gamused*full(Xstep));
      timenew = clock;
      ttime.pred_pstep = ttime.pred_pstep + etime(timenew,timeold); timeold = timenew;
      Zstep = steplength(blk,Z,dZ,Zchol,invZchol);
      dstep = min(1,gamused*full(Zstep));
      trXZnew = trXZ + pstep*blktrace(blk,dX,Z,parbarrier) ...
                 + dstep*blktrace(blk,X,dZ,parbarrier) ...
                 + pstep*dstep*blktrace(blk,dX,dZ,parbarrier);
      if (nn > 0); mupred  = trXZnew/nn; else; mupred = 1e-16; end
      mupredhist(iter) = mupred;
      timenew = clock;
      ttime.pred_dstep = ttime.pred_dstep + etime(timenew,timeold); timeold = timenew;
%%
%%-----------------------------------------
%%  stopping criteria for predictor step.
%%-----------------------------------------
%%
      if (min(pstep,dstep) < steptol) & (stoplevel) & (iter > 10)
         msg = 'stop: steps in predictor too short';
         if (printlevel)
            fprintf('\n  %s',msg);
            fprintf(': pstep = %3.2e,  dstep = %3.2e\n',pstep,dstep);
         end
         runhist.cputime(iter+1) = etime(clock,tstart);
         termcode = -2;
         breakyes = 1;
      end
      if (~predcorr)
         if (iter >= 2)
            idx = [max(2,iter-2) : iter];
            pred_slow = all(mupredhist(idx)./mupredhist(idx-1) > 0.4);
            idx = [max(2,iter-5) : iter];
            pred_convg_rate = mean(mupredhist(idx)./mupredhist(idx-1));
            pred_slow = pred_slow + (mupred/mu > 5*pred_convg_rate);
         end
         if (max(mu,infeas) < 1e-6) & (pred_slow) & (stoplevel)
            msg = 'stop: lack of progress in predictor';
            if (printlevel)
               fprintf('\n  %s',msg);
               fprintf(': mupred/mu = %3.2f, pred_convg_rate = %3.2f.',...
               mupred/mu,pred_convg_rate);
            end
            runhist.cputime(iter+1) = etime(clock,tstart);
            termcode = -2;
            breakyes = 1;
         else
            update_iter = 1;
         end
      end
%%---------------------------------------------------------------
%% corrector step.
%%---------------------------------------------------------------
%%
      if (predcorr) & (~breakyes)
         step_pred = min(pstep,dstep);
         if (mu > 1e-6)
            if (step_pred < 1/sqrt(3));
               expon_used = 1;
            else
               expon_used = max(expon,3*step_pred^2);
            end
         else
            expon_used = max(1,min(expon,3*step_pred^2));
         end
         if (nn==0)
             sigma = 0.2;
         elseif (mupred < 0)
             sigma = 0.8;
         else
            sigma = min(1, (mupred/mu)^expon_used);
         end
         sigmu = cell(size(blk,1),1);
         for p = 1:size(blk,1)
            sigmu{p} = max(sigma*mu, parbarrier{p}');
         end
         if (vers == 1)
            [dX,dy,dZ] = HKMcorr(blk,At,par,rp,Rd,sigmu,hRd,...
             dX,dZ,coeff,L,X,Z);
         elseif (vers == 2)
            [dX,dy,dZ] = NTcorr(blk,At,par,rp,Rd,sigmu,hRd,...
             dX,dZ,coeff,L,X,Z);
         end
         if (solve_ok <= 0)
            msg = 'stop: difficulty in computing corrector directions';
            if (printlevel); fprintf('\n  %s',msg); end
            runhist.pinfeas(iter+1) = runhist.pinfeas(iter);
            runhist.dinfeas(iter+1) = runhist.dinfeas(iter);
            runhist.relgap(iter+1)  = runhist.relgap(iter);
            runhist.cputime(iter+1) = etime(clock,tstart);
            termcode = -4;
            break; %% do not ues breakyes = 1
         end
         timenew = clock;
         ttime.corr = ttime.corr + etime(timenew,timeold); timeold = timenew;
%%
%%-----------------------------------
%% step-lengths for corrector step
%%-----------------------------------
%%
         if (gam == 0)
            gamused = 0.9 + 0.09*min(pstep,dstep);
         else
            gamused = gam;
         end
         Xstep = steplength(blk,X,dX,Xchol,invXchol);
         pstep = min(1,gamused*full(Xstep));
         timenew = clock;
         ttime.corr_pstep = ttime.corr_pstep+etime(timenew,timeold); timeold = timenew;
         Zstep = steplength(blk,Z,dZ,Zchol,invZchol);
         dstep = min(1,gamused*full(Zstep));
         trXZnew = trXZ + pstep*blktrace(blk,dX,Z,parbarrier) ...
                    + dstep*blktrace(blk,X,dZ,parbarrier)...
                    + pstep*dstep*blktrace(blk,dX,dZ,parbarrier);
         if (nn > 0); mucorr  = trXZnew/nn; else; mucorr = 1e-16; end
         timenew = clock;
         ttime.corr_dstep = ttime.corr_dstep+etime(timenew,timeold); timeold = timenew;
%%
%%-----------------------------------------
%%  stopping criteria for corrector step
%%-----------------------------------------
         if (iter >= 2)
            idx = [max(2,iter-2) : iter];
            corr_slow = all(runhist.gap(idx)./runhist.gap(idx-1) > 0.8);
            idx = [max(2,iter-5) : iter];
            corr_convg_rate = mean(runhist.gap(idx)./runhist.gap(idx-1));
            corr_slow = corr_slow + (mucorr/mu > max(min(1,5*corr_convg_rate),0.8));
         end
	 if (max(relgap,infeas) < 1e-6) & (iter > 20) ...
            & (corr_slow > 1) & (stoplevel)
            msg = 'stop: lack of progress in corrector';
   	    if (printlevel)
               fprintf('\n  %s',msg);
               fprintf(': mucorr/mu = %3.2f, corr_convg_rate = %3.2f',...
               mucorr/mu,corr_convg_rate);
            end
            runhist.cputime(iter+1) = etime(clock,tstart);
            termcode = -2;
            breakyes = 1;
         else
            update_iter = 1;
         end
      end
%%---------------------------------------------------------------
%% udpate iterate
%%---------------------------------------------------------------
      indef = [1,1];
      if (update_iter)
         for t = 1:5
            [Xchol,indef(1)] = blkcholfun(blk,ops(X,'+',dX,pstep));
            timenew = clock;
            ttime.pchol = ttime.pchol + etime(timenew,timeold); timeold = timenew;
            if (indef(1)); pstep = 0.8*pstep; else; break; end
         end
	 if (t > 1); pstep = gamused*pstep; end
	 for t = 1:5
            [Zchol,indef(2)] = blkcholfun(blk,ops(Z,'+',dZ,dstep));
            timenew = clock;
            ttime.dchol = ttime.dchol + etime(timenew,timeold); timeold = timenew;
            if (indef(2)); dstep = 0.8*dstep; else; break; end
         end
	 if (t > 1); dstep = gamused*dstep; end
         %%-------------------------------------------
         AXtmp = AX + pstep*AXfun(blk,At,par.permA,dX);
         prim_infeasnew = norm(b-AXtmp)/normb2;
         if (relgap < 5*infeas); alpha = 1e2; else; alpha = 1e3; end
         if any(indef)
            if indef(1); msg = 'stop: X not positive definite'; end
            if indef(2); msg = 'stop: Z not positive definite'; end
            if (printlevel); fprintf('\n  %s',msg); end
            termcode = -3;
            breakyes = 1;
         elseif (prim_infeasnew > max([1e-8,relgap,20*prim_infeas]) & iter > 10) ...
            | (prim_infeasnew > max([1e-7,1e3*prim_infeas,0.1*relgap]) & relgap < 1e-2) ...
            | (prim_infeasnew > alpha*max([1e-9,param.prim_infeas_min]) ...
               & (prim_infeasnew > max([3*prim_infeas,0.1*relgap])) ...
               & (iter > 25) & (dual_infeas < 1e-6) & (relgap < 0.1)) ...
            | ((prim_infeasnew > 1e3*prim_infeas & prim_infeasnew > 1e-12) ...
               & (max(relgap,dual_infeas) < 1e-8))
            if (stoplevel)
               msg = 'stop: primal infeas has deteriorated too much';
               if (printlevel); fprintf('\n  %s, %2.1e',msg,prim_infeasnew); end
               termcode = -7;
               breakyes = 1;
            end
         elseif (trXZnew > 1.05*runhist.gap(iter)) & (~exist_analytic_term) ...
	    & ((infeas < 1e-5) & (relgap < 1e-4) & (iter > 20) ...
	       | (max(infeas,relgap) < 1e-7) & (iter > 10))
            if (stoplevel)
               msg = 'stop: progress in duality gap has deteriorated';
               if (printlevel); fprintf('\n  %s, %2.1e',msg,trXZnew); end
               termcode = -8;
               breakyes = 1;
            end
         else
            X = ops(X,'+',dX,pstep);
            y = y + dstep*dy;
            Z = ops(Z,'+',dZ,dstep);
         end
      end
%%---------------------------------------------------------------
%% adjust linear blk arising from unrestricted blk
%%---------------------------------------------------------------
      if (~breakyes)
         for p = 1:size(blk,1)
            if (u2lblk(p) == 1)
               len = blk{p,2}/2;
               xtmp = min(X{p}([1:len]),X{p}(len+[1:len]));
               alpha = 0.8;
               X{p}([1:len])     = X{p}([1:len]) - alpha*xtmp;
               X{p}(len+[1:len]) = X{p}(len+[1:len]) - alpha*xtmp;
               if (mu < 1e-4) %% old: (mu < 1e-7)
                  Z{p} = 0.5*mu./max(1,X{p}); %% good to keep this step
               else
                  ztmp = min(1,max(Z{p}([1:len]),Z{p}(len+[1:len])));
                  if (dual_infeas > 1e-4 & dstep < 0.2)
                     beta = 0.3;
                  else
                     beta = 0.0;
                  end
                  %% important to set beta = 0 at later stage.
                  Z{p}([1:len])     = Z{p}([1:len]) + beta*ztmp;
                  Z{p}(len+[1:len]) = Z{p}(len+[1:len]) + beta*ztmp;
               end
            end
         end
      end
%%--------------------------------------------------
%% perturb Z: do this step before checking for break
%%--------------------------------------------------
      if (~breakyes) & (~exist_analytic_term)
         trXZtmp = blktrace(blk,X,Z);
         trXE  = blktrace(blk,X,EE);
         Zpert = max(1e-12,0.2*min(relgap,prim_infeas)).*normC2./normE2;
         Zpert = min(Zpert,0.1*trXZtmp./trXE);
         Zpert = min([1,Zpert,1.5*Zpertold]);
         if (infeas < 0.1)
            Z = ops(Z,'+',EE,Zpert);
            [Zchol,indef(2)] = blkcholfun(blk,Z);
            if any(indef(2))
               msg = 'stop: Z not positive definite';
               if (printlevel); fprintf('\n  %s',msg); end
               termcode = -3;
               breakyes = 1;
            end
            %%if (printlevel > 2); fprintf(' %2.1e',Zpert); end
         end
         Zpertold = Zpert;
      end
%%---------------------------------------------------------------
%% compute rp, Rd, infeasibities, etc
%%---------------------------------------------------------------
%%
      AX  = AXfun(blk,At,par.permA,X);
      rp  = b-AX;
      ZpATy = ops(Z,'+',Atyfun(blk,At,par.permA,par.isspAy,y));
      ZpATynorm = ops(ZpATy,'norm');
      Rd  = ops(C,'-',ZpATy);
      objadd = blkbarrier(blk,X,Z,Xchol,Zchol,parbarrier) + objadd0;
      obj = (normb*normC)*[blktrace(blk,C,X), b'*y] + objadd;
      gap = (normb*normC)*blktrace(blk,X,Z) - diff(objadd);
      relgap = gap/(1+sum(abs(obj)));
      prim_infeas = norm(rp)/normb2;
      dual_infeas = ops(Rd,'norm')/normC2;
      infeas = max(prim_infeas,dual_infeas);
      if (scale_data)
         infeas_org(1) = prim_infeas*normb;
         infeas_org(2) = dual_infeas*normC;
      end
      homRd = inf; homrp = inf;
      if (ops(parbarrier,'norm') == 0)
         if (obj(2) > 0); homRd = ZpATynorm/(obj(2)); end
         if (obj(1) < 0); homrp = norm(AX)/(-obj(1))/(normC); end
      end
      trXZ = blktrace(blk,X,Z,parbarrier);
      if (nn > 0); mu = trXZ/nn; else; mu = gap/ops(X,'getM'); end
      normX = ops(X,'norm');
%%
      runhist.pobj(iter+1)  = obj(1);
      runhist.dobj(iter+1)  = obj(2);
      runhist.gap(iter+1)   = gap;
      runhist.relgap(iter+1)  = relgap;
      runhist.pinfeas(iter+1) = prim_infeas;
      runhist.dinfeas(iter+1) = dual_infeas;
      runhist.infeas(iter+1)  = infeas;
      runhist.pstep(iter+1)   = pstep;
      runhist.dstep(iter+1)   = dstep;
      runhist.step(iter+1)    = min(pstep,dstep);
      runhist.normX(iter+1)   = normX;
      runhist.cputime(iter+1) = etime(clock,tstart);
      timenew = clock;
      ttime.misc = ttime.misc + etime(timenew,timeold); timeold = timenew;
      [hh,mm,ss] = mytime(sum(runhist.cputime));
      if (printlevel>=3)
         fprintf('\n%2.0f|%4.3f|%4.3f',iter,pstep,dstep);
         fprintf('|%2.1e|%2.1e|%2.1e|',prim_infeas,dual_infeas,gap);
         fprintf('%- 7.6e %- 7.6e| %s:%s:%s|',obj(1),obj(2),hh,mm,ss);
      end
%%--------------------------------------------------
%% check convergence
%%--------------------------------------------------
      param.use_LU      = use_LU;
      param.stoplevel   = stoplevel;
      param.termcode    = termcode;
      param.iter        = iter;
      param.obj         = obj;
      param.gap         = gap;
      param.relgap      = relgap;
      param.prim_infeas = prim_infeas;
      param.dual_infeas = dual_infeas;
      param.mu        = mu;
      param.homRd     = homRd;
      param.homrp     = homrp;
      param.AX        = AX;
      param.ZpATynorm = ZpATynorm;
      param.normX     = ops(X,'norm');
      param.normZ     = ops(Z,'norm');
      param.numpertdiagschur = numpertdiagschur;
      if (~breakyes)
         [param,breakyes,restart,msg2] = sqlpcheckconvg(param,runhist);
      end
      if (restart)
         [X,y,Z] = infeaspt(blk,At,C,b,2,1e5);
         rp  = b-AXfun(blk,At,par.permA,X);
         ZpATy = ops(Z,'+',Atyfun(blk,At,par.permA,par.isspAy,y));
         Rd  = ops(C,'-',ZpATy);
         trXZ = blktrace(blk,X,Z,parbarrier);
         mu   = trXZ/nn;
         gap  =  (normb*normC)*blktrace(blk,X,Z) - diff(objadd);
         prim_infeas = norm(rp)/normb2;
         dual_infeas = ops(Rd,'norm')/normC2;
         infeas = max(prim_infeas,dual_infeas);
         [Xchol,indef(1)] = blkcholfun(blk,X);
         [Zchol,indef(2)] = blkcholfun(blk,Z);
         stoplevel = 3;
      end
%%--------------------------------------------------
%% check for break
%%--------------------------------------------------
      if ((prim_infeas < 1.5*prim_infeas_best) ...
         | (max(relgap,infeas) < 0.8*max(relgap_best,infeas_best))) ...
         & (max(relgap,dual_infeas) < 0.8*max(relgap_best,dual_infeas_best))
         Xbest = X; ybest = y; Zbest = Z;
         prim_infeas_best = prim_infeas;
         dual_infeas_best = dual_infeas;
         relgap_best = relgap; infeas_best = infeas;
         update_best(iter+1) = 1;
         %%fprintf('#')
      else
         update_best(iter+1) = 0;
      end
      if (max(relgap_best,infeas_best) < 1e-4 ...
          & norm(update_best(max(1,iter-1):iter+1)) == 0)
         msg = 'lack of progress in infeas';
         if (printlevel); fprintf('\n  %s',msg); end
         termcode = -9;
         breakyes = 1;
      end
      if (breakyes); break; end
   end
%%---------------------------------------------------------------
%% end of main loop
%%---------------------------------------------------------------
%%
   use_bestiter = 1;
   if (use_bestiter) & (param.termcode <= 0)
      X = Xbest; y = ybest; Z = Zbest;
      Xchol = blkcholfun(blk,X);
      Zchol = blkcholfun(blk,Z);
      AX = AXfun(blk,At,par.permA,X);
      rp = b-AX;
      ZpATy = ops(Z,'+',Atyfun(blk,At,par.permA,par.isspAy,y));
      Rd = ops(C,'-',ZpATy);
      objadd = blkbarrier(blk,X,Z,Xchol,Zchol,parbarrier) + objadd0;
      obj = (normb*normC)*[blktrace(blk,C,X), b'*y] + objadd;
      gap = (normb*normC)*blktrace(blk,X,Z) - diff(objadd);
      relgap = gap/(1+sum(abs(obj)));
      prim_infeas = norm(rp)/normb2;
      dual_infeas = ops(Rd,'norm')/normC2;
      infeas = max(prim_infeas,dual_infeas);
      runhist.pobj(iter+1)  = obj(1);
      runhist.dobj(iter+1)  = obj(2);
      runhist.gap(iter+1)   = gap;
      runhist.relgap(iter+1)  = relgap;
      runhist.pinfeas(iter+1) = prim_infeas;
      runhist.dinfeas(iter+1) = dual_infeas;
      runhist.infeas(iter+1)  = infeas;
   end
%%---------------------------------------------------------------
%% unscale and produce infeasibility certificates if appropriate
%%---------------------------------------------------------------
   if (iter >= 1)
      [X,y,Z,termcode,resid,reldist,msg3] = ...
      sqlpmisc(blk,At,C,b,X,y,Z,par.permZ,param);
   end
%%---------------------------------------------------------------
%% recover unrestricted blk from linear blk
%%---------------------------------------------------------------
%%
   for p = 1:size(blk,1)
      if (u2lblk(p) == 1)
         n = blk{p,2}/2;
         X{p} = X{p}(1:n)-X{p}(n+[1:n]);
         Z{p} = Z{p}(1:n);
      end
   end
   for p = 1:size(ublkidx,1)
      if ~isempty(ublkidx{p,2})
         n0 = ublkidx{p,1}; idxB = setdiff([1:n0]',ublkidx{p,2});
         tmp = zeros(n0,1); tmp(idxB) = X{p}; X{p} = tmp;
         tmp = zeros(n0,1); tmp(idxB) = Z{p}; Z{p} = tmp;
      end
   end
   if (analytic_prob)
      X = X(1:numblkold); Z = Z(1:numblkold);
   end
%%---------------------------------------------------------------
%% print summary
%%---------------------------------------------------------------
%%
   maxC = 1+ops(ops(C,'abs'),'max');
   maxb = 1+max(abs(b));
   if (scale_data)
      dimacs = [infeas_org(1)*normb2/maxb; 0; infeas_org(2)*normC2/maxC; 0];
   else
      dimacs = [prim_infeas*normb2/maxb; 0; dual_infeas*normC2/maxC; 0];
   end
   dimacs = [dimacs; [-diff(obj); gap]/(1+sum(abs(obj)))];
   info.dimacs   = dimacs;
   info.termcode = termcode;
   info.iter     = iter;
   info.obj      = obj;
   info.gap      = gap;
   info.relgap   = relgap;
   info.pinfeas  = prim_infeas;
   info.dinfeas  = dual_infeas;
   info.cputime  = sum(runhist.cputime);
   info.time     = ttime;
   info.resid    = resid;
   info.reldist  = reldist;
   info.normX    = ops(X,'norm');
   info.normy    = norm(y);
   info.normZ    = ops(Z,'norm');
   info.normb    = normb2; info.maxb = maxb;
   info.normC    = normC2; info.maxC = maxC;
   info.normA    = normA2;
   info.msg1     = msg;
   info.msg2     = msg2;
   info.msg3     = msg3;
   sqlpsummary(info,ttime,infeas_org,printlevel);
%%*****************************************************************************


function [data, warns] = psse_parse(records, sections, verbose, rev)
%PSSE_PARSE  Parses the data from a PSS/E RAW data file.
%   DATA = PSSE_PARSE(RECORDS, SECTIONS)
%   DATA = PSSE_PARSE(RECORDS, SECTIONS, VERBOSE)
%   DATA = PSSE_PARSE(RECORDS, SECTIONS, VERBOSE, REV)
%   [DATA, WARNINGS] = PSSE_PARSE(RECORDS, SECTIONS, ...)
%
%   Parses the data from a PSS/E RAW data file (as read by PSSE_READ)
%   into a struct.
%
%   Inputs:
%       RECORDS : cell array of strings, corresponding to the lines
%                 in the RAW file
%       SECTIONS : struct array with indices marking the beginning
%                  and end of each section, and the name of the
%                  section, fields are:
%           first   : index into RECORDS of first line of section
%           last    : index into RECORDS of last line of section
%           name    : name of the section, as extracted from the
%                     END OF ... DATA comments
%       VERBOSE      :  1 (default) to display progress info, 0 otherwise
%       REV          :  (optional) assume the input file is of this
%                       PSS/E revision number, attempts to determine
%                       REV from the file by default
%
%   Output(s):
%       DATA :  a struct with the following fields, each with two
%               sub-fields, 'num' and 'txt' containing the numeric and
%               text data read from the file for the corresponding section
%           id
%           bus
%           load
%           gen
%           shunt
%           branch
%           trans2
%           trans3
%           area
%           twodc
%           swshunt
%       WARNINGS :  cell array of strings containing accumulated
%                   warning messages
%
%   See also PSSE2MPC, PSSE_READ, PSSE_PARSE_SECTION, PSSE_PARSE_LINE

%   MATPOWER
%   Copyright (c) 2014-2016, Power Systems Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%   Based on mpreadraw.m, written by: Yujia Zhu, Jan 2014, yzhu54@asu.edu.
%
%   This file is part of MATPOWER.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://matpower.org for more info.

%% default args
if nargin < 4
    rev = 0;
    if nargin < 3
        verbose = 0;
    end
end
defaultrev = 23;

%% inititialize section counter
s = 1;
warns = {};

%%-----  case identification data  -----
%% In some files, SBASE is followed by a comment with the REV number such as:
%%  / PSS/E-29.0
%%  / PSS(tm)E-30 RAW
if verbose
    if rev
        fprintf('Forcing interpretation as PSS/E revision %d\n', rev);
    else
        fprintf('Attempting to determine PSS/E revision from content.\n');
    end
    fprintf('Parsing case identification data ...');
end
if rev
    warns{end+1} = sprintf('Conversion explicitly using PSS/E revision %d', rev);
end
[d, c] = psse_parse_line(records{1}, 'dfdfff');
nn = length(d);
data.id.IC = d{1};
if isempty(d{2}) || d{2} <= 0
    error('ERROR: Probable corrupt file, unable to read a valid SBASE value from the first line.');
else
    data.id.SBASE = d{2};
end
if ~isempty(d{3})
    data.id.REV = d{3};
else    %% attempt to extract revision from comment
    tmp = regexp(c, 'PSS(/|\(tm\))E-(?<rev>\d+)', 'tokens');
    if ~isempty(tmp) && size(tmp{1}, 2) == 2
        data.id.REV = str2num(tmp{1}{2});
    else
        data.id.REV = 0;
    end
end
if ~isempty(d{4})
    data.id.XFRRAT = d{4};
else
    data.id.XFRRAT = 0;
end
if ~isempty(d{5})
    data.id.NXFRAT = d{5};
else
    data.id.NXFRAT = 0;
end
if ~isempty(d{6})
    data.id.BASFRQ = d{6};
else
    data.id.BASFRQ = 0;
end
data.id.comment0 = c;
data.id.comment1 = records{2};
data.id.comment2 = records{3};
if verbose
    if rev
        if data.id.REV
            fprintf('.. override detected rev %2d w/%2d ... done.\n', data.id.REV, rev);
        else
            fprintf('...... unknown rev, using rev %2d ... done.\n', rev);
        end
    else
        if data.id.REV
            fprintf('......... rev %2d format detected ... done.\n', data.id.REV);
        else
            fprintf('...... unknown rev, using rev %2d ... done.\n', defaultrev);
        end
    end
end
if ~rev
    if data.id.REV
        rev = data.id.REV;      %% use detected value
    else
        rev = defaultrev;       %% none detected, use default value
        data.id.REV = defaultrev;
        warns{end+1} = sprintf('Unknown REV, using REV %2d format.', defaultrev);
    end
else
    data.id.REV = rev;          %% use override value
end
if isempty(data.id.IC) || data.id.IC ~= 0
    warns{end+1} = sprintf('IC = %d indicates that this may be a change case, rather than base case\n         PSSE2MPC is NOT designed to handle change cases.', data.id.IC);
    if verbose
        fprintf('WARNING : %s\n', warns{end});
    end
end
s = s + 1;

%%-----  bus data  -----
if rev < 24         %% includes load data
    [data.bus, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'bus', 'ddffffdffsfd');
elseif rev < 31     %% includes fixed shunt data, load separate
    [data.bus, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'bus', 'dsfdffddffd');
else                %% fixed shunt and load data separate
    [data.bus, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'bus', 'dsfddddffff..');
%       'bus', 'dsfddddffffff');
end
s = s + 1;

%%-----  load data  -----
if rev >= 24
    [data.load, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'load', 'd.d..ffffff...');
%       'load', 'dsdddffffffddd');
    s = s + 1;
end

%%-----  fixed shunt data  -----
if rev > 30     %% fixed shunt data is included in bus data for rev <= 30
    [data.shunt, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'fixed shunt', 'd.dff');
%       'fixed shunt', 'dsdff');
    s = s + 1;
end

%%-----  generator data  -----
[data.gen, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
    'generator', 'd.fffff.f.....d.ff...........');
%   'generator', 'dsfffffdffffffdfffdfdfdfdfsdf');
s = s + 1;

%%-----  branch data  -----
if rev <= 27   %% includes transformer ratio, angle
    [data.branch, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'branch', 'dd.ffffffffffffd');
%       'branch', 'dddffffffffffffd');
elseif rev < 31
    [data.branch, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'branch', 'dd.ffffffffffd');
%       'branch', 'ddsffffffffffdfdfdfdfdf');
else
    [data.branch, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
        'branch', 'dd.ffffffffffd');
%       'branch', 'ddsffffffffffddfdfdfdfdf');
end
s = s + 1;

%%-----  skip transformer adjustment data  -----
if rev <= 27
    [s, warns] = psse_skip_section(warns, sections, s, verbose, 'transformer adjustment');
end

%%-----  transformer data  -----
if rev > 27
    %% PSS/E stores two winding and three winding transformer data in the same
    %% section in RAW file. We read in 2 passes, first pass determines the type of
    %% each, second pass reads the data.
    label = 'transformer';
    if ~isempty(sections(s).name) && ~strcmpi(label, sections(s).name)
        if verbose > 1
            fprintf('-----  WARNING:  Expected section labeled: ''%s''\n', upper(label));
            fprintf('-----            Found section labeled:    ''%s''\n', sections(s).name);
        end
    end

    %% Step 1 : Count and collect transformer types
    if verbose
        fprintf('Analyzing transformer types ...');
    end

    %% estimate max number of transformers by number of lines in section
    nt2 = round((sections(s).last - sections(s).first + 1) / 4);
    nt3 = round((sections(s).last - sections(s).first + 1) / 5);

    %% initialize indices for transformer types
    idx2 = zeros(nt2, 1);
    idx3 = zeros(nt3, 1);

    %% set up counters
    i = sections(s).first;      %% initialize record index
    i2 = 0;
    i3 = 0;

    while i <= sections(s).last
        %% determine transformer type
        pat = '[^''",\s/]+\s*(,|\s)\s*[^''",\s/]+\s*(,|\s)\s*([^''",\s/]+)';
        m = regexp(records{i}, pat, 'tokens', 'once');
        if length(m) ~= 3
            disp(m);
            error('m should be length 3');
        end
        if length(m{3}) == 1 && m{3}(1) == '0'  %% two-winding
            i2 = i2 + 1;
            idx2(i2) = i;
            i = i + 4;
        else                                    %% three-winding
            i3 = i3 + 1;
            idx3(i3) = i;
            i = i + 5;
        end
    end
    nt2 = i2;
    nt3 = i3;

    if verbose
        str = sprintf(' %d(%d) two(three)-winding.', nt2, nt3);
        spacers = repmat('.', 1, 36-length(str));
        fprintf('%s %s ... done.\n', spacers, str);
    end

    %% trim index vectors down to size
    idx2 = idx2(1:nt2);
    idx3 = idx3(1:nt3);

    %% parse record 1 (cols 1-20)
    [t2_1, warns] = psse_parse_section(warns, records(idx2), verbose, ...
        '2-winding transformers (1)', 'dd..ddd....d........');
%       '2-winding transformers (1)', 'dddsdddffdsddfdfdfdf');
    [t3_1, warns] = psse_parse_section(warns, records(idx3), verbose, ...
        '3-winding transformers (1)', 'ddd.ddd....d........');
%       '3-winding transformers (1)', 'dddsdddffdsddfdfdfdf');

    %% two-winding
    %% parse record 2 (cols 21-23)
    [t2_2, warns] = psse_parse_section(warns, records(idx2+1), verbose, ...
        '2-winding transformers (2)', 'fff');

    %% parse record 3 (cols 24-39)
    %% parse up to CX1, should warn if CNXA1 is present and non-zero
    [t2_3, warns] = psse_parse_section(warns, records(idx2+2), verbose, ...
        '2-winding transformers (3)', 'ffffff..........');
%       '2-winding transformers (3)', 'ffffffddffffddff');

    %% parse record 4 (cols 40-41)
    [t2_4, warns] = psse_parse_section(warns, records(idx2+3), verbose, ...
        '2-winding transformers (4)', 'ff');

    %% three-winding
    %% parse record 2 (cols 21-31)
    [t3_2, warns] = psse_parse_section(warns, records(idx3+1), verbose, ...
        '3-winding transformers (2)', 'fffffffffff');
%       '3-winding transformers (2)', 'fffffffffff');

    %% parse record 3 (cols 32-47)
    %% parse up to CX1, should warn if CNXA1 is present and non-zero
    [t3_3, warns] = psse_parse_section(warns, records(idx3+2), verbose, ...
        '3-winding transformers (3)', 'ffffff..........');
%       '3-winding transformers (3)', 'ffffffddffffddff');

    %% parse record 4 (cols 48-63)
    %% parse up to CX2
    [t3_4, warns] = psse_parse_section(warns, records(idx3+3), verbose, ...
        '3-winding transformers (4)', 'ffffff..........');
%       '3-winding transformers (4)', 'ffffffddffffddff');

    %% parse record 5 (cols 64-79)
    %% parse up to CX3
    [t3_5, warns] = psse_parse_section(warns, records(idx3+4), verbose, ...
        '3-winding transformers (5)', 'ffffff..........');
%       '3-winding transformers (5)', 'ffffffddffffddff');

    %% assemble two-winding transformer records
    data.trans2.num = [t2_1.num(:, 1:20) t2_2.num(:, 1:3) t2_3.num(:, 1:16) t2_4.num(:, 1:2)];
    data.trans2.txt = [t2_1.txt(:, 1:20) t2_2.txt(:, 1:3) t2_3.txt(:, 1:16) t2_4.txt(:, 1:2)];

    %% assemble three-winding transformer records
    data.trans3.num = [t3_1.num(:, 1:20) t3_2.num(:, 1:11) t3_3.num(:, 1:16) t3_4.num(:, 1:16) t3_5.num(:, 1:16)];
    data.trans3.txt = [t3_1.txt(:, 1:20) t3_2.txt(:, 1:11) t3_3.txt(:, 1:16) t3_4.txt(:, 1:16) t3_5.txt(:, 1:16)];

    % if verbose
    %     fprintf('%s\n', upper(label));
    %     fprintf('%s\n', sections(s).name);
    % end
    s = s + 1;
end

%%-----  area interchange data  -----
[data.area, warns] = psse_parse_section(warns, records, sections, s, verbose, 'area', 'ddffs');
s = s + 1;

%%-----  two-terminal DC transmission line data  -----
label = 'two-terminal DC';
if ~isempty(sections(s).name) && ~strcmpi(label, sections(s).name)
    if verbose > 1
        fprintf('-----  WARNING:  Expected section labeled: ''%s''\n', upper(label));
        fprintf('-----            Found section labeled:    ''%s''\n', sections(s).name);
    end
end
idx = sections(s).first:3:sections(s).last;
if rev < 31
    [dc1, warns] = psse_parse_section(warns, records(idx), verbose, ...
        'two-terminal DC (1)', '.d.ff.......');
%       'two-terminal DC (1)', 'ddffffffsfdf');
    [dc2, warns] = psse_parse_section(warns, records(idx+1), verbose, ...
        'two-terminal DC (2)', 'd.ff.............');
%       'two-terminal DC (2)', 'ddffffffffffdddsf');
    [dc3, warns] = psse_parse_section(warns, records(idx+2), verbose, ...
        'two-terminal DC (3)', 'd.ff.............');
%       'two-terminal DC (3)', 'ddffffffffffdddsf');
else
    [dc1, warns] = psse_parse_section(warns, records(idx), verbose, ...
        'two-terminal DC (1)', '.d.ff.......');
%       'two-terminal DC (1)', 'sdffffffsfdf');
    [dc2, warns] = psse_parse_section(warns, records(idx+1), verbose, ...
        'two-terminal DC (2)', 'd.ff.............');
%       'two-terminal DC (2)', 'ddffffffffffdddDf');
    [dc3, warns] = psse_parse_section(warns, records(idx+2), verbose, ...
        'two-terminal DC (3)', 'd.ff.............');
%       'two-terminal DC (3)', 'ddffffffffffdddDf');
end

%% assemble two-terminal DC transmission line
data.twodc.num = [dc1.num dc2.num dc3.num];
data.twodc.txt = [dc1.txt dc2.txt dc3.txt];
% if verbose
%     fprintf('%s\n', upper(label));
%     fprintf('%s\n', sections(s).name);
% end
s = s + 1;

%%-----  skip voltage source converter data  -----
if rev > 28
    [s, warns] = psse_skip_section(warns, sections, s, verbose, 'voltage source converter');
end

%%-----  switched shunt data  -----
if rev < 31
    %% parse up to B1
    if rev <= 27
        [data.swshunt, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
            'switched shunt', 'd....f');
%           'switched shunt', 'ddffdfdfdfdfdfdfdfdfdf');
    elseif rev <= 29
        [data.swshunt, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
            'switched shunt', 'd.....f');
%           'switched shunt', 'ddffdsfdfdfdfdfdfdfdfdf');
    else    %%  rev == 30
        [data.swshunt, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
            'switched shunt', 'd......f');
%           'switched shunt', 'ddffdfsfdfdfdfdfdfdfdfdf');
    end
    s = s + 1;
end

%%-----  skip impedance correction data  -----
[s, warns] = psse_skip_section(warns, sections, s, verbose, 'impedance correction');

%%-----  skip multi-terminal DC data  -----
[s, warns] = psse_skip_section(warns, sections, s, verbose, 'multi-terminal DC');

%%-----  skip multi-section line data  -----
[s, warns] = psse_skip_section(warns, sections, s, verbose, 'multi-section line');

%%-----  skip zone data  -----
[s, warns] = psse_skip_section(warns, sections, s, verbose, 'zone');

%%-----  skip inter-area transfer data  -----
[s, warns] = psse_skip_section(warns, sections, s, verbose, 'inter-area transfer');

%%-----  skip owner data  -----
if rev > 24
    [s, warns] = psse_skip_section(warns, sections, s, verbose, 'owner');
end

%%-----  skip FACTS control device data  -----
if rev > 25
    [s, warns] = psse_skip_section(warns, sections, s, verbose, 'FACTS control device');
end

%%-----  switched shunt data  -----
if rev > 30
    %% parse up to B1
    if rev < 32
        [data.swshunt, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
            'switched shunt', 'd......f');
%           'switched shunt', 'ddffdfsfdfdfdfdfdfdfdfdf');
    else
        [data.swshunt, warns] = psse_parse_section(warns, records, sections, s, verbose, ...
            'switched shunt', 'd........f');
%           'switched shunt', 'ddddffdfsfdfdfdfdfdfdfdfdf');
    end
    s = s + 1;
end

%%-----  skip GNE device data  -----
if rev > 31
    [s, warns] = psse_skip_section(warns, sections, s, verbose, 'GNE device');
end

%%-----  skip induction machine data  -----
if rev > 32
    [s, warns] = psse_skip_section(warns, sections, s, verbose, 'induction machine');
end

%%-----  check for extra sections  -----
if s <= length(sections)
    warns{end+1} = sprintf('Found %d additional section(s)', length(sections)-s+1);
    if verbose > 1
        fprintf('-----  WARNING:   Found %d additional section(s):\n', length(sections)-s+1);
    end
end
while s <= length(sections)
    n = sections(s).last - sections(s).first + 1;
    if n
        str = sprintf('with %d line(s)', n);
    else
        str = sprintf('(empty)');
    end
    if isempty(sections(s).name)
        warns{end+1} = sprintf('  unlabeled section %s', str);
        if verbose > 1
            fprintf('-----            unlabeled section %s\n', str);
        end
    else
        warns{end+1} = sprintf('  ''%s DATA'' %s', sections(s).name, str);
        if verbose > 1
            fprintf('-----            ''%s DATA'' %s\n', sections(s).name, str);
        end
    end
    s = s + 1;
end

%%---------------------------------------------------------------------
function [s, warns] = psse_skip_section(warns, sections, s, verbose, label)
%PSSE_SKIP_SECTION  Skips over a section without extracting any data.
%   [SIDX, WARNINGS] = PSSE_SKIP_SECTION(WARNINGS, SECTIONS, SIDX, VERBOSE, LABEL)

if s > length(sections)
    if verbose
        spacers = repmat('.', 1, 58-length(label));
        fprintf('No %s data read %s done.\n', label, spacers);
    end
else
    nr = sections(s).last - sections(s).first + 1;
    if nr > 1
        ss = 'lines';
    else
        ss = 'line';
    end
    if nr
        warns{end+1} = sprintf('Skipped %d %s of %s data.', nr, ss, label);
    end
    if ~isempty(sections(s).name) && ~strcmp(upper(label), sections(s).name)
        warns{end+1} = sprintf('Section label mismatch, found ''%s'', expected ''%s''', ...
            sections(s).name, upper(label));
        if verbose
            fprintf('-----  WARNING:  Found section labeled:    ''%s''\n', sections(s).name);
            fprintf('-----            Expected section labeled: ''%s''\n', upper(label));
        end
    end
    if verbose && nr
        spacers = repmat('.', 1, 47-length(ss)-length(label));
        fprintf('Skipping%6d %s of %s data %s done.\n', nr, ss, label, spacers);
    end
    s = s + 1;
end


function [ output_meta ] = meta2sicd_nisar( HDF5_fid )
%META2SICD_NISAR Converts NISAR SLC HDF5 into a SICD-style metadata structure
%
% Takes as input a file identifier to an open HDF5 file-- as is returned by
% H5F.open
%
% Note that MATLAB's h5disp is generally a good tool for manually browsing
% through HDF5 metadata like that found in NISAR format.
%
% Known issues with simulated NISAR data (from UAVSAR) to date:
%
% 1) ISCEVersion not populated
% 2) Doppler rate provided is positive.  JPL says will it fix in future
% update.
% 3) processedAzimuthBandwidth is acknowledged by JPL to be wrong and will
% be fixed in a future update.
% 4) Weighting description is currently a placeholder, not an accurate
% description of UAVSAR data used for simulated data foramt.
%
% Written by: Wade Schwartzkopf, NGA/Research
%
% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////

SECONDS_IN_A_DAY = 24*60*60;

% Query for frequencies and polarization (each of which would create its
% own SICD)
freqs_id = H5D.open(HDF5_fid,'/science/LSAR/identification/listOfFrequencies');
freqs = H5D.read(freqs_id);
freqs = freqs(1,:);
H5D.close(freqs_id);
for i=1:numel(freqs)
    pol_id = H5D.open(HDF5_fid,['/science/LSAR/SLC/swaths/frequency' freqs(i) '/listOfPolarizations']);
    pols{i} = H5D.read(pol_id)';
    H5D.close(pol_id);
end

%% CollectionInfo
% CollectorName could also be deblank(get_hdf_data(HDF5_fid,'/science/LSAR/identification','missionId')')
output_meta.CollectionInfo.CollectorName=get_hdf_attribute(HDF5_fid,'/','mission_name');
% JPL suggested this as best way to form unique string for each collection
output_meta.CollectionInfo.CoreName=[...
    num2str(get_hdf_data(HDF5_fid,'/science/LSAR/identification','absoluteOrbitNumber'),'%07u') ...
    get_hdf_data(HDF5_fid,'/science/LSAR/identification','trackNumber')'];
output_meta.CollectionInfo.CollectType='MONOSTATIC';
output_meta.CollectionInfo.RadarMode.ModeType='STRIPMAP';
output_meta.CollectionInfo.Classification='UNCLASSIFIED';

%% ImageCreation
try
    % TODO: UNTESTED
    % Sample data did not have valid values for this
    output_meta.ImageCreation.Application=['ISCE ' ...
        deblank(get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/processingInformation/algorithms','ISCEVersion')')];
end
output_meta.ImageCreation.Profile='Prototype';

%% ImageData
output_meta.ImageData=struct(); % Just a placeholder
% Most subfields added below in "per band" section
% Used for computing SCP later

%% GeoData
output_meta.GeoData.EarthModel='WGS_84';
% Initially, we just seed this with a very rough value.  Later we will
% put in something more precise.
poly_str = get_hdf_data(HDF5_fid,'/science/LSAR/identification','boundingPolygon')';
poly_str(isletter(poly_str)|poly_str=='('|poly_str==')')='';
poly_str(poly_str==',')=';';
bound_poly=str2num(poly_str);
bound_poly=bound_poly(1:(end-1),:);
rough_center = mean(bound_poly);
output_meta.GeoData.SCP.LLH.Lat=rough_center(2);
output_meta.GeoData.SCP.LLH.Lon=rough_center(1);
output_meta.GeoData.SCP.LLH.HAE=...
    mean(get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/processingInformation/parameters/','referenceTerrainHeight'));
% NISAR stores this as single, which is sufficient for storage, but we need
% double precision for some of the computations later on, to include
% geopositioning of exact SCP.
output_meta.GeoData.SCP.LLH.HAE=double(output_meta.GeoData.SCP.LLH.HAE);
ecf=geodetic_to_ecf([output_meta.GeoData.SCP.LLH.Lat output_meta.GeoData.SCP.LLH.Lon output_meta.GeoData.SCP.LLH.HAE]);
output_meta.GeoData.SCP.ECF.X=ecf(1);
output_meta.GeoData.SCP.ECF.Y=ecf(2);
output_meta.GeoData.SCP.ECF.Z=ecf(3);
% Calling derived_sicd_fields at the end will populate these fields
% with the sensor model, so we don't need to do it here.
% latlon=get_hdf_attribute(dset_id(i),'Top Left Geodetic Coordinates');
% output_meta.GeoData.ImageCorners.ICP.FRFC.Lat=bound_poly(1,1);
% output_meta.GeoData.ImageCorners.ICP.FRFC.Lon=bound_poly(1,2);
% latlon=get_hdf_attribute(dset_id(i),'Bottom Left Geodetic Coordinates');
% output_meta.GeoData.ImageCorners.ICP.FRLC.Lat=bound_poly(2,1);
% output_meta.GeoData.ImageCorners.ICP.FRLC.Lon=bound_poly(2,2);
% latlon=get_hdf_attribute(dset_id(i),'Bottom Right Geodetic Coordinates');
% output_meta.GeoData.ImageCorners.ICP.LRLC.Lat=bound_poly(3,1);
% output_meta.GeoData.ImageCorners.ICP.LRLC.Lon=bound_poly(3,2);
% latlon=get_hdf_attribute(dset_id(i),'Top Right Geodetic Coordinates');
% output_meta.GeoData.ImageCorners.ICP.LRFC.Lat=bound_poly(4,1);
% output_meta.GeoData.ImageCorners.ICP.LRFC.Lon=bound_poly(4,2);

%% Grid
output_meta.Grid.ImagePlane='SLANT';
output_meta.Grid.Type='RGZERO';
output_meta.Grid.Row.Sgn=-1; % Always true for NISAR
output_meta.Grid.Col.Sgn=-1; % Always true for NISAR
output_meta.Grid.Col.KCtr=0;
output_meta.Grid.Row.DeltaKCOAPoly=0;
% TODO: JPL states that uniform weighting in data simulated from UAVSAR is
% placeholder, not an accurate description of the data.  At this point, it
% is not clear what the final weighting description for NISAR will be.
output_meta.Grid.Row.WgtFunct = get_hdf_data(HDF5_fid,...
    '/science/LSAR/SLC/metadata/processingInformation/parameters','rangeChirpWeighting');
if all(output_meta.Grid.Row.WgtFunct(1)==output_meta.Grid.Row.WgtFunct)
    output_meta.Grid.Row.WgtType.WindowName = 'UNIFORM'; % In the sample datasets
else
    output_meta.Grid.Row.WgtType.WindowName = 'UNKNOWN';
end
output_meta.Grid.Col.WgtFunct = get_hdf_data(HDF5_fid,...
    '/science/LSAR/SLC/metadata/processingInformation/parameters','azimuthChirpWeighting');
if all(output_meta.Grid.Col.WgtFunct(1)==output_meta.Grid.Col.WgtFunct)
    output_meta.Grid.Col.WgtType.WindowName = 'UNIFORM'; % In the sample datasets
else
    output_meta.Grid.Col.WgtType.WindowName = 'UNKNOWN';
end
% More subfields added below in "per band" section

%% Timeline
% Using zero Doppler times.  Pulse transmission likely started before this
[collectStart, collectStartFrac]=datenum_w_frac(...
    deblank(get_hdf_data(HDF5_fid, '/science/LSAR/identification', ...
    'zeroDopplerStartTime')'));
[collectEnd, collectEndFrac]=datenum_w_frac(...
    deblank(get_hdf_data(HDF5_fid, '/science/LSAR/identification', ...
    'zeroDopplerEndTime')'));
output_meta.Timeline.CollectStart=collectStart + (collectStartFrac/SECONDS_IN_A_DAY);
output_meta.Timeline.CollectDuration=...
    round((collectEnd-collectStart)*SECONDS_IN_A_DAY) + ... % Convert days to seconds
    (collectEndFrac-collectStartFrac); % Handle fractional seconds
output_meta.Timeline.IPP.Set.TStart=0;
output_meta.Timeline.IPP.Set.TEnd=0; % Apply real value later.  Just a placeholder.
output_meta.Timeline.IPP.Set.IPPStart=uint32(0);
% More subfields added below in "per band" section

%% Position
% Compute polynomial from state vectors
% Get reference time
ref_time = get_ref_time(HDF5_fid,'/science/LSAR/SLC/metadata/orbit/time');
% Times in SICD are with respect to time from start of collect.
ref_time_offset = round((ref_time-collectStart)*SECONDS_IN_A_DAY) + ... % Convert from days to secs
    collectStartFrac; % Handle fractional seconds
state_vector_T  = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/orbit','time')';
state_vector_T = state_vector_T + ref_time_offset; % Make with respect to Timeline.CollectStart
state_vector_pos  = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/orbit','position');
% sv2poly.m shows ways to determine best polynomial order, but we just use 6th here
polyorder=min(6, numel(state_vector_T) - 1);
old_state = warning('off','MATLAB:polyfit:RepeatedPointsOrRescale');
P_x  = polyfit(state_vector_T, state_vector_pos(1,:), polyorder);
P_y  = polyfit(state_vector_T, state_vector_pos(2,:), polyorder);
P_z  = polyfit(state_vector_T, state_vector_pos(3,:), polyorder);
% We don't use these since they are derivable from the position polynomial
% state_vector_vel = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/orbit','velocity');
% state_vector_acc = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/orbit','acceleration');
% P_vx = polyfit(state_vector_T, state_vector_vel(1,:), polyorder);
% P_vy = polyfit(state_vector_T, state_vector_vel(2,:), polyorder);
% P_vz = polyfit(state_vector_T, state_vector_vel(3,:), polyorder);
% P_ax = polyfit(state_vector_T, state_vector_acc(1,:), polyorder);
% P_ay = polyfit(state_vector_T, state_vector_acc(2,:), polyorder);
% P_az = polyfit(state_vector_T, state_vector_acc(3,:), polyorder);
warning(old_state);
% Store position polynomial
output_meta.Position.ARPPoly.X  = P_x(end:-1:1).';
output_meta.Position.ARPPoly.Y  = P_y(end:-1:1).';
output_meta.Position.ARPPoly.Z  = P_z(end:-1:1).';

%% SCPCOA
% This should almost always be left looking for NISAR.
output_meta.SCPCOA.SideOfTrack=get_hdf_data(HDF5_fid,'/science/LSAR/identification','lookDirection');
output_meta.SCPCOA.SideOfTrack=upper(output_meta.SCPCOA.SideOfTrack(1));
% Most subfields added below in "per band" section.

%% ImageFormation
output_meta.ImageFormation.RcvChanProc=struct('NumChanProc',1,'PRFScaleFactor',1);
output_meta.ImageFormation.ImageFormAlgo='RMA';
output_meta.ImageFormation.TStartProc=0;
output_meta.ImageFormation.TEndProc=output_meta.Timeline.CollectDuration;
output_meta.ImageFormation.STBeamComp='NO';
output_meta.ImageFormation.ImageBeamComp='SV';
output_meta.ImageFormation.AzAutofocus='NO';
output_meta.ImageFormation.RgAutofocus='NO';
% More subfields added below in "per band" section
output_meta.RMA.RMAlgoType='OMEGA_K';
output_meta.RMA.ImageType='INCA';
output_meta.RMA.INCA.DopCentroidCOA=true;
% Get reference time
ref_time = get_ref_time(HDF5_fid,'/science/LSAR/SLC/swaths/zeroDopplerTime');
% Times in SICD are with respect to time from start of collect.
ref_time_offset = round((ref_time-collectStart)*SECONDS_IN_A_DAY) + ... % Convert from days to secs
    collectStartFrac; % Handle fractional seconds
zd_t = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/swaths','zeroDopplerTime') + ref_time_offset;
ss_az_s = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/swaths','zeroDopplerTimeSpacing');
if output_meta.SCPCOA.SideOfTrack(1)=='L'
    zd_t = zd_t(end:-1:1);
    ss_az_s = -ss_az_s;
end
grid_r = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/processingInformation/parameters','slantRange');
% Get reference time
ref_time = get_ref_time(HDF5_fid,'/science/LSAR/SLC/metadata/processingInformation/parameters/zeroDopplerTime');
% Times in SICD are with respect to time from start of collect.
ref_time_offset = round((ref_time-collectStart)*SECONDS_IN_A_DAY) + ... % Convert from days to secs
    collectStartFrac; % Handle fractional seconds
grid_zd = get_hdf_data(HDF5_fid,'/science/LSAR/SLC/metadata/processingInformation/parameters','zeroDopplerTime') + ref_time_offset;

%% Radiometric
cal_fields = {'beta0','gamma0','sigma0'};
sicd_cal_fields = {'BetaZeroSFPoly','GammaZeroSFPoly','SigmaZeroSFPoly'};
cal_data = cell(3,1);  % beta0, gamma0, sigma0
for i = 1:numel(cal_fields)
    cal_data{i} = get_hdf_data(HDF5_fid, '/science/LSAR/SLC/metadata/calibrationInformation/geometry',cal_fields{i}).';
    % Get fill value
    gid = H5G.open(HDF5_fid, '/science/LSAR/SLC/metadata/calibrationInformation/geometry/');
    data = H5D.open(gid, cal_fields{i});
    H5G.close(gid);
    attribute = H5A.open(data, '_FillValue');
    H5D.close(data);
    fill = H5A.read(attribute,H5A.get_type(attribute));
    H5A.close(attribute);
    fill = cast(fill,class(cal_data{i}));
    cal_data{i}(cal_data{i}==fill) = NaN;
    if any(diff(cal_data{i}(isfinite(cal_data{i}))))
        % We expect NISAR to be constant beta, although simulated data from
        % UAVSAR is constant sigma
        if strcmp(cal_fields{i},'beta0')
            warning('META2SICD_NISAR:unexpectedCalibrationValues',...
                'Beta-0 values expected to be constant.');
        end
    else
        output_meta.Radiometric.(sicd_cal_fields{i}) = mean(cal_data{i}(:),'omitnan');
    end
    % We will derive non-constant Radiometric fields from the constant one
    % (likely beta).
end
output_meta.Radiometric.NoiseLevel.NoiseLevelType = 'ABSOLUTE';

%% Process fields specific to each polarimetric band
band_independent_meta=output_meta; % Values that are consistent across all bands
grouped_meta=cell(sum(cellfun(@(x) size(x,1),pols)),1);
meta_i=0;
for i=1:numel(freqs)
    freq_meta = band_independent_meta;

    freq_id=H5G.open(HDF5_fid,['/science/LSAR/SLC/swaths/frequency' freqs(i)]);

    %% Grid
    row_ss_id=H5D.open(freq_id,'slantRangeSpacing');
    freq_meta.Grid.Row.SS=H5D.read(row_ss_id);
    H5D.close(row_ss_id);
    % Col.SS is derived after DRateSFPoly below, rather than used from this
    % given field, so that SICD metadata can be internally consistent:
    % col_ss_id=H5D.open(freq_id,'sceneCenterAlongTrackSpacing');
    % output_meta.Grid.Col.SS=H5D.read(col_ss_id);
    % H5D.close(col_ss_id);
    proc_bw_id=H5D.open(freq_id,'processedRangeBandwidth');
    freq_meta.Grid.Row.ImpRespBW=2*H5D.read(proc_bw_id)/SPEED_OF_LIGHT;
    H5D.close(proc_bw_id);
    freq_meta.Grid.Row.DeltaK1=-freq_meta.Grid.Row.ImpRespBW/2;
    freq_meta.Grid.Row.DeltaK2=-freq_meta.Grid.Row.DeltaK1;

    %% Timeline
    prf_id=H5D.open(freq_id,'nominalAcquisitionPRF');
    prf=H5D.read(prf_id);
    H5D.close(prf_id);
    freq_meta.Timeline.IPP.Set.IPPEnd=uint32(floor(prf*band_independent_meta.Timeline.CollectDuration));
    freq_meta.Timeline.IPP.Set.IPPPoly=[0; prf];
    freq_meta.Timeline.IPP.Set.TEnd=freq_meta.Timeline.CollectDuration;

    %% RadarCollection
    fc_id=H5D.open(freq_id,'acquiredCenterFrequency');
    fc=H5D.read(fc_id);
    H5D.close(fc_id);
    bw_id=H5D.open(freq_id,'acquiredRangeBandwidth');
    bw=H5D.read(bw_id);
    H5D.close(bw_id);
    freq_meta.RadarCollection.TxFrequency.Min=fc-(bw/2);
    freq_meta.RadarCollection.TxFrequency.Max=fc+(bw/2);
    % No waveform parameters provided
    for j=1:size(pols{i},1)
        freq_meta.RadarCollection.RcvChannels.ChanParameters(j).TxRcvPolarization=[pols{i}(j,1) ':' pols{i}(j,2)];
    end
    tx_pol = unique(pols{i}(:,1));
    if numel(tx_pol)==1
        freq_meta.RadarCollection.TxPolarization = tx_pol;
    else
        freq_meta.RadarCollection.TxPolarization = 'SEQUENCE';
        for j = 1:numel(tx_pol)
            freq_meta.RadarCollection.TxSequence.TxStep(j).WFIndex = j;
            freq_meta.RadarCollection.TxSequence.TxStep(j).TxPolarization = tx_pol(j);
        end
    end

    %% ImageFormation
    fc_id=H5D.open(freq_id,'processedCenterFrequency');
    fc=H5D.read(fc_id);
    H5D.close(fc_id);
    bw_id=H5D.open(freq_id,'processedRangeBandwidth');
    bw=H5D.read(bw_id);
    H5D.close(bw_id);
    freq_meta.ImageFormation.TxFrequencyProc.MinProc=fc-(bw/2);
    freq_meta.ImageFormation.TxFrequencyProc.MaxProc=fc+(bw/2);
    range_id=H5D.open(freq_id,'slantRange');
    r_ca_sampled=H5D.read(range_id);
    H5D.close(range_id);
    % TODO: processedAzimuthBandwidth acknowledged by JPL to be wrong in
    % simulated datasets.
    dop_bw_id=H5D.open(freq_id,'processedAzimuthBandwidth');
    dop_bw=H5D.read(dop_bw_id);
    H5D.close(dop_bw_id);
    H5G.close(freq_id);
    dopcentroid_sampled = get_hdf_data(HDF5_fid,[...
        '/science/LSAR/SLC/metadata/processingInformation/parameters/frequency' freqs(i)], ...
        'dopplerCentroid').';
    doprate_sampled = get_hdf_data(HDF5_fid,[...
        '/science/LSAR/SLC/metadata/processingInformation/parameters/frequency' freqs(i)], ...
        'azimuthFMRate').';

    for j=1:size(pols{i},1)
        pol_meta=freq_meta;

        %% ImageData
        dset_id=H5D.open(HDF5_fid,['/science/LSAR/SLC/swaths/frequency' freqs(i) '/' pols{i}(j,:)]);
        dspace_id=H5D.get_space(dset_id);
        [~, datasize] = H5S.get_simple_extent_dims(dspace_id);
        pol_meta.ImageData.NumCols=uint32(datasize(1));
        pol_meta.ImageData.NumRows=uint32(datasize(2));
        pol_meta.ImageData.FullImage=pol_meta.ImageData;
        if (H5T.get_class(H5D.get_type(dset_id)) == H5ML.get_constant_value('H5T_COMPOUND'))
            if (H5T.get_member_class(H5D.get_type(dset_id),0) == H5ML.get_constant_value('H5T_FLOAT'))
                % Could be either 16- or 32-bit float.  Either way it will
                % cast to this SICD type.
                pol_meta.ImageData.PixelType='RE32F_IM32F';
            elseif (H5T.get_member_class(H5D.get_type(dset_id),0) == H5ML.get_constant_value('H5T_INTEGER')) && ...
                    H5T.get_size(H5D.get_type(dset_id)) == 4
                pol_meta.ImageData.PixelType='RE16I_IM16I';
            else
                error('META2SICD_NISAR:unrecognizedDataType','Must be 16- or 32-bit complex float.');
            end
        else
            error('META2SICD_NISAR:unrecognizedDataType','Must be 16- or 32-bit complex float.');
        end
        H5S.close(dspace_id);
        H5D.close(dset_id);
        pol_meta.ImageData.FirstRow=uint32(0); pol_meta.ImageData.FirstCol=uint32(0);
        % SCP pick is arbitrary.  Just pick something near center.
        pol_meta.ImageData.SCPPixel.Col = floor(datasize(1)/2);
        pol_meta.ImageData.SCPPixel.Row = floor(datasize(2)/2);

        %% ImageFormation
        pol_meta.ImageFormation.RcvChanProc.ChanIndex=meta_i;
        pol_meta.ImageFormation.TxRcvPolarizationProc=...
            freq_meta.RadarCollection.RcvChannels.ChanParameters(j).TxRcvPolarization;

        %% RMA
        pol_meta.RMA.INCA.R_CA_SCP = r_ca_sampled(pol_meta.ImageData.SCPPixel.Row+1);
        scp_ca_time = zd_t(pol_meta.ImageData.SCPPixel.Col+1);
        % Compute DRateSFPoly
        % For the purposes of the DRateSFPoly computation, we ignore any
        % changes in velocity or doppler rate over the azimuth dimension.
        pos_coefs = [P_x(:) P_y(:) P_z(:)];
        % Velocity is derivate of position.
        vel_coefs=pos_coefs(1:end-1,:).*repmat(((size(pos_coefs,1)-1):-1:1)',[1 3]);
        vel_x = polyval(vel_coefs(:,1), scp_ca_time);
        vel_y = polyval(vel_coefs(:,2), scp_ca_time);
        vel_z = polyval(vel_coefs(:,3), scp_ca_time);
        vm_ca_sq = vel_x.^2 + vel_y.^2 + vel_z.^2; % Magnitude of the velocity squared
        r_ca_poly = [pol_meta.RMA.INCA.R_CA_SCP; 1]; % Polynomial representing range as a function of range distance from SCP
        [~, min_ind] = min(abs(grid_zd - scp_ca_time));  % Closest Doppler rate polynomial to SCP
        coords_rg_m = grid_r - pol_meta.RMA.INCA.R_CA_SCP;
        % TODO: Is it the NISAR convention to give the absolute value of
        % Doppler rate? (It should be negative.) Sample datasets appear
        % this way.
        old_warning_state=warning('off','MATLAB:polyfit:RepeatedPointsOrRescale');
        dop_rate_poly=polyfit(coords_rg_m, -doprate_sampled(min_ind,:).', 4);
        warning(old_warning_state);
        % figure; plot(r,dop_rate(:,min_ind),r,polyval(dop_rate_poly,r));  % Show closeness of fit
        dop_rate_poly = dop_rate_poly(end:-1:1);
        pol_meta.RMA.INCA.DRateSFPoly = - conv(dop_rate_poly,r_ca_poly) * ... % Multiplication of two polynomials is just a convolution of their coefficients
            SPEED_OF_LIGHT / (2 * fc * vm_ca_sq(1)); % Assumes a SGN of -1
        % Fields dependent on Doppler rate
        pol_meta.Grid.Col.SS = sqrt(vm_ca_sq(1)) * abs(ss_az_s) * pol_meta.RMA.INCA.DRateSFPoly(1,1);
        % Should be close to this:
        % colss_id=H5D.open(freq_id,'sceneCenterAlongTrackSpacing');
        % pol_meta.Grid.Col.SS = H5D.read(colss_id)
        % H5D.close(colss_id);
        pol_meta.Grid.Col.ImpRespBW = ... % Convert to azimuth spatial bandwidth (cycles per meter)
            min(dop_bw*abs(ss_az_s),1)/pol_meta.Grid.Col.SS; % Can't have more bandwidth in data than sample spacing
        pol_meta.RMA.INCA.TimeCAPoly = [scp_ca_time; ... % With respect to start of collect
            ss_az_s/pol_meta.Grid.Col.SS]; % Convert zero doppler spacing from sec/pixels to sec/meters
        % TimeCOAPoly/DopCentroidPoly/DeltaKCOAPoly
        POLY_ORDER = 3; % Order of polynomial which we want to compute
        coords_az_m = (grid_zd - scp_ca_time) * pol_meta.Grid.Col.SS / ss_az_s;
        timeca_sampled = repmat(grid_zd,[1 numel(grid_r)]);
        % TimeCOAPoly=TimeCA+(DopCentroid/dop_rate)
        timecoa_sampled = timeca_sampled+(dopcentroid_sampled./doprate_sampled);
        pol_meta.RMA.INCA.DopCentroidPoly=polyfit2d(dopcentroid_sampled, coords_az_m, coords_rg_m, POLY_ORDER);
        pol_meta.Grid.Col.DeltaKCOAPoly=...
            pol_meta.RMA.INCA.DopCentroidPoly*ss_az_s/pol_meta.Grid.Col.SS;
        pol_meta.Grid.TimeCOAPoly=polyfit2d(timecoa_sampled, coords_az_m, coords_rg_m, POLY_ORDER);

        %% Radiometric
        % In the simulated data (from UAVSAR), we expect these noise values
        % to be only valid for the same incidence angle as NISAR (small
        % part of swath).  We also expect the values to be in dB, not
        % linear.
        nesz = get_hdf_data(HDF5_fid,['/science/LSAR/SLC/metadata/calibrationInformation/frequency' freqs(i) '/' pols{i}(j,:)], 'nes0').';
        sigma0sf = cal_data{3};
        noise_samples = nesz - (10*log10(sigma0sf));
        pol_meta.Radiometric.NoiseLevel.NoisePoly = ...
            polyfit2d(noise_samples, coords_az_m, coords_rg_m, POLY_ORDER);
        % The conversion between radiometric fields in simulated data don't
        % seem to be valid, so we will skip using the non-constant terms
        % for now.  But this is how one would do it:
        % Fit polynomial only to valid data in image
        % az_bounds1 = [-double(pol_meta.ImageData.SCPPixel.Col) ...
        %     double(pol_meta.ImageData.NumCols - pol_meta.ImageData.SCPPixel.Col - 1)] *...
        %     pol_meta.Grid.Col.SS;
        % rg_bounds1 = [-double(pol_meta.ImageData.SCPPixel.Row) ...
        %     double(pol_meta.ImageData.NumRows - pol_meta.ImageData.SCPPixel.Row - 1)] *...
        %     pol_meta.Grid.Row.SS;
        % for k = 1:numel(cal_fields)
        %     % Only fit valid values
        %     rg_val = all(isfinite(cal_data{k}),1)&coords_rg_m'>rg_bounds1(1)&coords_rg_m'<rg_bounds1(2);
        %     if ~any(diff(cal_data{k}(isfinite(cal_data{k}))))  % Constant across image
        %         pol_meta.Radiometric.(sicd_cal_fields{k}) = mean(cal_data{k}(:),'omitnan');
        %     elseif all(all(diff(cal_data{k},1,1)==0) | all(~isfinite(cal_data{k})))% Only varies in range
        %         rad_poly = polyfit(coords_rg_m(rg_val), cal_data{k}(1,rg_val), 7);
        %         pol_meta.Radiometric.(sicd_cal_fields{k}) = rad_poly(end:-1:1).';
        %     else  % Varies 2-dimensionally.  Not tested.  POLY_ORDER may not be reasonable.
        %         pol_meta.Radiometric.(sicd_cal_fields{k}) = ...
        %             polyfit2d(cal_data{k}(:,rg_val), coords_az_m, coords_rg_m(rg_val), POLY_ORDER);
        %     end
        % end

        %% GeoData
        % Now that sensor model fields have been populated, we can populate
        % GeoData.SCP more precisely.
        ecf = point_image_to_ground([pol_meta.ImageData.SCPPixel.Row;pol_meta.ImageData.SCPPixel.Col],pol_meta);
        pol_meta.GeoData.SCP.ECF.X=ecf(1);
        pol_meta.GeoData.SCP.ECF.Y=ecf(2);
        pol_meta.GeoData.SCP.ECF.Z=ecf(3);
        llh=ecf_to_geodetic([pol_meta.GeoData.SCP.ECF.X pol_meta.GeoData.SCP.ECF.Y pol_meta.GeoData.SCP.ECF.Z]);
        pol_meta.GeoData.SCP.LLH.Lat=llh(1);
        pol_meta.GeoData.SCP.LLH.Lon=llh(2);
        pol_meta.GeoData.SCP.LLH.HAE=llh(3);

        pol_meta = derived_sicd_fields(pol_meta);
        meta_i=meta_i+1;
        grouped_meta{meta_i}=pol_meta;
    end
end
output_meta = grouped_meta;

end

% Reads HDF5 attribute
function value = get_hdf_attribute(hid_t,path,attribute_name)
gid = H5G.open(hid_t,path);
attribute = H5A.open(gid,attribute_name);
value = H5A.read(attribute,H5A.get_type(attribute));
% The behavior of whether HDF5 attributes are returned in cells seems to
% have changed in 2020a. The behavior also changed in h5readatt.
if iscell(value)
    value = value{1};
end
H5G.close(gid);
H5A.close(attribute);
end

% Reads HDF5 data
function value = get_hdf_data(hid_t,path,data_name)
gid = H5G.open(hid_t,path);
data = H5D.open(gid,data_name);
value = H5D.read(data);
H5G.close(gid);
H5D.close(data);
end

function ref_time = get_ref_time(hid_t,data_path)
    data = H5D.open(hid_t,data_path);
    attribute = H5A.open(data,'units');
    ref_time_string = H5A.read(attribute,H5A.get_type(attribute))';
    H5A.close(attribute);
    H5D.close(data);
    [ref_time_string,~] = regexp(ref_time_string,'seconds since (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s*$','tokens','match');
    if numel(ref_time_string)==1  % Should always be the case according to JPL
        ref_time = datenum(ref_time_string{1}{1},'yyyy-mm-dd HH:MM:SS');
    else
        error('META2SICD_NISAR:unrecognizedTimeUnit','Time units must be seconds since a reference UTC time.');
    end
end

% MATLAB's datenum function won't handle precise times down under a
% millisecond, because 1) It won't accept a format with more than 3 .FFF in
% the string description of the date format and 2) the resulting serial
% date number is stored in days from 00-JAN-0000 and just doesn't have
% enough bits to handle fractional seconds to the level we want.  Here we
% handle the fractional seconds separately so we can read date with the
% precision we need.
function [datenum_s, datenum_frac] = datenum_w_frac(datestring)
    datenum_s = datenum(datestring,'yyyy-mm-ddTHH:MM:SS');
    datenum_frac = str2double(regexp(datestring,'\.\d*','match'));
    if any(isnan(datenum_frac))||isempty(datenum_frac), datenum_frac = 0; end
end

function p = polyfit2d(samples, dim1, dim2, poly_order)
    % Least squares fit for 2D polynomial
    % A*x = b
    [dim1_2d, dim2_2d] = ndgrid(dim1, dim2);
    a = zeros(numel(samples), (poly_order+1)^2);
    for k = 0:poly_order
        for m = 0:poly_order
            a(:,k*(poly_order+1)+m+1) = (dim2_2d(:).^m).*(dim1_2d(:).^k);
        end
    end
    b = zeros((poly_order+1)^2,1);
    for k=1:((poly_order+1)^2)
        b(k)=sum(samples(:).*a(:,k)); % center of aperture
    end
    A=zeros((poly_order+1)^2);
    for k=1:((poly_order+1)^2)
        for m=1:((poly_order+1)^2)
            A(k,m)=sum(a(:,k).*a(:,m));
        end
    end
    old_warning_state=warning('off','MATLAB:nearlySingularMatrix');
    x_coa=A\b; % MATLAB often flags this as badly scaled, but results still appear valid
    warning(old_warning_state);
    p=reshape(x_coa, poly_order+1, poly_order+1);
end

% //////////////////////////////////////////
% /// CLASSIFICATION: UNCLASSIFIED       ///
% //////////////////////////////////////////


function mpc = apply_changes(label, mpc, chgtab)
%APPLY_CHANGES  Applies a set of changes to a MATPOWER case
%   mpc_modified = apply_changes(label, mpc_original, chgtab)
%
%   Applies the set of changes identified by LABEL to the case in MPC, where
%   the change sets are specified in CHGTAB.
%
%   LABEL is an integer which identifies the set of changes of interest
%
%   MPC is a MATPOWER case struct with at least fields bus, gen and branch
%
%   CHGTAB is the table of changes that lists the individual changes required
%   by each change set, one "change" per row (multiple rows or changes
%   allowed for each change set). Type "help idx_ct" for more complete
%   information about the format.
%   1st column: change set label, integer > 0
%   2nd column: change set probability
%   3rd column: table to be modified (1:bus, 2:gen, 3:branch) or
%               4: bus area changes, apply to all gens/buses/branches in
%               a given area; 5: gen table area changes apply to all
%               generators in a given area; or 6: branch area changes apply
%               to all branches connected to buses in a given area.
%   4th column: row of table to be modified (if 3rd col is 1-3),
%               or area number for overall changes (if 3rd column is 4-6).
%   5th column: column of table to be modified. It is best to use the
%               named column index defined in the corresponding idx_bus,
%               idx_gen, idx_branch or idx_cost M-files in MATPOWER.
%   6th column: type of change: 1: absolute (replace)
%                               2: relative (multiply by factor)
%                               3: additive (add to value)
%   7th column: new value or multiplicative or additive factor
%
%   Examples:
%
%   chgtab = [ ...
%       1   0.1   CT_TGEN       2  GEN_STATUS     CT_REP  0;
%       2   0.05  CT_TGEN       3  PMAX           CT_REP  100;
%       3   0.2   CT_TBRCH      2  BR_STATUS      CT_REP  0;
%       4   0.1   CT_TAREALOAD  2  CT_LOAD_ALL_P  CT_REL  1.1;
%    ];
%
%   Description of each change set:
%   1. Turn off generator 2, 10% probability.
%   2. Set generator 3's max output to 100 MW, 5% probability.
%   3. Take branch 2 out of service, 20% probability.
%   4. Scale all loads in area 2 (real & reactive, fixed and dispatchable)
%      by a factor of 1.1, 10% probability.
%
%   See IDX_CT.

%   To do:
%       - check for valid row number

%   MATPOWER
%   Copyright (c) 2000-2016, Power Systems Engineering Research Center (PSERC)
%   by Carlos E. Murillo-Sanchez, PSERC Cornell & Universidad Nacional de Colombia
%   and Ray Zimmerman, PSERC Cornell
%
%   This file is part of MATPOWER.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://matpower.org for more info.

%% define named indices into data matrices
[PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ...
    VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus;
[GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ...
    MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ...
    QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen;
[F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ...
    TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ...
    ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch;
[PW_LINEAR, POLYNOMIAL, MODEL, STARTUP, SHUTDOWN, NCOST, COST] = idx_cost;
[CT_LABEL, CT_PROB, CT_TABLE, CT_TBUS, CT_TGEN, CT_TBRCH, CT_TAREABUS, ...
    CT_TAREAGEN, CT_TAREABRCH, CT_ROW, CT_COL, CT_CHGTYPE, CT_REP, ...
    CT_REL, CT_ADD, CT_NEWVAL, CT_TLOAD, CT_TAREALOAD, CT_LOAD_ALL_PQ, ...
    CT_LOAD_FIX_PQ, CT_LOAD_DIS_PQ, CT_LOAD_ALL_P, CT_LOAD_FIX_P, ...
    CT_LOAD_DIS_P, CT_TGENCOST, CT_TAREAGENCOST, CT_MODCOST_F, ...
    CT_MODCOST_X] = idx_ct;

%% find the change set to be applied
if numel(label) > 1
    error('apply_changes: LABEL must be a scalar');
end
kk = find(label == chgtab(:, CT_LABEL));
if isempty(kk)
    error('apply_changes: LABEL %d not found in CHGTAB', label);
end

%% create map of external bus numbers to bus indices
i2e = mpc.bus(:, BUS_I);
e2i = sparse(max(i2e), 1);
e2i(i2e) = (1:size(mpc.bus, 1))';

for c = 1:length(kk)
    %% extract data for individual change to be applied
    %% (c-th change in specified change set)
    change = num2cell(chgtab(kk(c), :));
    [tbl, row, col, typ, val] = ...
        deal(change{[CT_TABLE, CT_ROW, CT_COL, CT_CHGTYPE, CT_NEWVAL]});

    %% apply the change
    if tbl == CT_TBUS                               %% modify bus table
      if ~any(col == [PD QD GS BS VMAX VMIN])
        error('apply_changes: modification to column %d of bus table not supported', col);
      end
      if row == 0                                       %% modify all rows
        if typ == CT_REP                                    %% replace
          mpc.bus(:, col) = val * ones(size(mpc.bus, 1), 1);
        elseif typ == CT_REL                                %% scale
          mpc.bus(:, col) = val * mpc.bus(:, col);
        elseif typ == CT_ADD                                %% shift
          mpc.bus(:, col) = val + mpc.bus(:, col);
        else
          error('apply_changes: unsupported modification type %d for bus table', typ);
        end
      else                                              %% modify single row
        if typ == CT_REP                                    %% replace
          mpc.bus(row, col) = val;
        elseif typ == CT_REL                                %% scale
          mpc.bus(row, col) = val * mpc.bus(row, col);
        elseif typ == CT_ADD                                %% shift
          mpc.bus(row, col) = val + mpc.bus(row, col);
        else
          error('apply_changes: unsupported modification type %d for bus table', typ);
        end
      end
    elseif tbl == CT_TBRCH                          %% modify branch table
      if ~any(col == ...
            [BR_R BR_X BR_B RATE_A RATE_B RATE_C TAP SHIFT BR_STATUS ANGMIN ANGMAX])
        error('apply_changes: modification to column %d of branch table not supported', col);
      end
      if row == 0                                       %% modify all rows
        if typ == CT_REP                                    %% replace
          mpc.branch(:, col) = val * ones(size(mpc.branch, 1), 1);
        elseif typ == CT_REL                                %% scale
          mpc.branch(:, col) = val * mpc.branch(:, col);
        elseif typ == CT_ADD                                %% shift
          mpc.branch(:, col) = val + mpc.branch(:, col);
        else
          error('apply_changes: unsupported modification type %d for branch table', typ);
        end
      else                                              %% modify single row
        if typ == CT_REP                                    %% replace
          mpc.branch(row, col) = val;
        elseif typ == CT_REL                                %% scale
          mpc.branch(row, col) = val * mpc.branch(row, col);
        elseif typ == CT_ADD                                %% shift
          mpc.branch(row, col) = val + mpc.branch(row, col);
        else
          error('apply_changes: unsupported modification type %d for branch table', typ);
        end
      end
    elseif tbl == CT_TGEN                           %% modify gen table
      if ~any(col == ...
            [QMAX QMIN GEN_STATUS PMAX PMIN PC1 PC2 QC1MIN QC1MAX QC2MIN  QC2MAX ...
            RAMP_AGC RAMP_10 RAMP_30 RAMP_Q APF])
        error('apply_changes: modification to column %d of gen table not supported', col);
      end
      if row == 0                                       %% modify all rows
        if typ == CT_REP                                    %% replace
          mpc.gen(:, col) = val * ones(size(mpc.gen, 1), 1);
        elseif typ == CT_REL                                %% scale
          mpc.gen(:, col) = val * mpc.gen(:, col);
        elseif typ == CT_ADD                                %% shift
          mpc.gen(:, col) = val + mpc.gen(:, col);
        else
          error('apply_changes: unsupported modification type %d for gen table', typ);
        end
      else                                              %% modify single row
        if typ == CT_REP                                    %% replace
          mpc.gen(row, col) = val;
        elseif typ == CT_REL                                %% scale
          mpc.gen(row, col) = val * mpc.gen(row, col);
        elseif typ == CT_ADD                                %% shift
          mpc.gen(row, col) = val + mpc.gen(row, col);
        else
          error('apply_changes: unsupported modification type %d for gen table', typ);
        end
      end
    elseif tbl == CT_TGENCOST                       %% modify gencost table
      if col == CT_MODCOST_F || col == CT_MODCOST_X     %% use modcost to scale/shift cost
        if typ == CT_REL
          if col == CT_MODCOST_F
            modcost_type = 'SCALE_F';
          else  %% col == CT_MODCOST_X
            modcost_type = 'SCALE_X';
          end
        elseif typ == CT_ADD
          if col == CT_MODCOST_F
            modcost_type = 'SHIFT_F';
          else  %% col == CT_MODCOST_X
            modcost_type = 'SHIFT_X';
          end
        else
            error('apply_changes: unsupported modification type %d for gencost table CT_MODCOST_F/X modification', typ);
        end
        if row == 0
          mpc.gencost = modcost(mpc.gencost, val, modcost_type);
        else
          mpc.gencost(row, :) = modcost(mpc.gencost(row, :), val, modcost_type);
        end
      else                                              %% normal individual column mod
        if col < 1 || fix(col) ~= col   %% needs to be positive integer
          error('apply_changes: modification to column %d of gencost table not supported', col);
        end
        if row == 0                                       %% modify all rows
          if typ == CT_REP                                    %% replace
            mpc.gencost(:, col) = val * ones(size(mpc.gencost, 1), 1);
          elseif typ == CT_REL                                %% scale
            mpc.gencost(:, col) = val * mpc.gencost(:, col);
          elseif typ == CT_ADD                                %% shift
            mpc.gencost(:, col) = val + mpc.gencost(:, col);
          else
            error('apply_changes: unsupported modification type %d for gencost table', typ);
          end
        else                                              %% modify single row
          if typ == CT_REP                                    %% replace
            mpc.gencost(row, col) = val;
          elseif typ == CT_REL                                %% scale
            mpc.gencost(row, col) = val * mpc.gencost(row, col);
          elseif typ == CT_ADD                                %% shift
            mpc.gencost(row, col) = val + mpc.gencost(row, col);
          else
            error('apply_changes: unsupported modification type %d for gencost table', typ);
          end
        end
      end
    elseif tbl == CT_TAREABUS                   %% area-wide mod to bus table
      if ~any(col == [PD QD GS BS VMAX VMIN])
        error('apply_changes: area-wide modification to column %d of bus table not supported', col);
      end
      jj = find(mpc.bus(:, BUS_AREA) == row);
      if typ == CT_REP                                      %% replace
        mpc.bus(jj, col) = val * ones(size(jj));
      elseif typ == CT_REL                                  %% scale
        mpc.bus(jj, col) = val * mpc.bus(jj, col);
      elseif typ == CT_ADD                                  %% shift
        mpc.bus(jj, col) = val + mpc.bus(jj, col);
      else
        error('apply_changes: unsupported area-wide modification type %d for bus table', typ);
      end
    elseif tbl == CT_TAREABRCH                  %% area-wide mod to branch table
      if ~any(col == ...
            [BR_R BR_X BR_B RATE_A RATE_B RATE_C TAP SHIFT BR_STATUS ANGMIN ANGMAX])
        error('apply_changes: area-wide modification to column %d of branch table not supported', col);
      end
      jj = find((row == mpc.bus(e2i(mpc.branch(:, F_BUS)), BUS_AREA)) | ...
                (row == mpc.bus(e2i(mpc.branch(:, T_BUS)), BUS_AREA)) );
      if typ == CT_REP                                      %% replace
        mpc.branch(jj, col) = val * ones(size(jj));
      elseif typ == CT_REL                                  %% scale
        mpc.branch(jj, col) = val * mpc.branch(jj, col);
      elseif typ == CT_ADD                                  %% shift
        mpc.branch(jj, col) = val + mpc.branch(jj, col);
      else
        error('apply_changes: unsupported area-wide modification type %d for branch table', typ);
      end
    elseif tbl == CT_TAREAGEN                   %% area-wide mod to gen table
      if ~any(col == ...
            [QMAX QMIN GEN_STATUS PMAX PMIN PC1 PC2 QC1MIN QC1MAX QC2MIN  QC2MAX ...
            RAMP_AGC RAMP_10 RAMP_30 RAMP_Q APF])
        error('apply_changes: area-wide modification to column %d of gen table not supported', col);
      end
      jj = find(row == mpc.bus(e2i(mpc.gen(:, GEN_BUS)), BUS_AREA));
      if typ == CT_REP                                      %% replace
        mpc.gen(jj, col) = val * ones(size(jj));
      elseif typ == CT_REL                                  %% scale
        mpc.gen(jj, col) = val * mpc.gen(jj, col);
      elseif typ == CT_ADD                                  %% shift
        mpc.gen(jj, col) = val + mpc.gen(jj, col);
      else
        error('apply_changes: unsupported area-wide modification type %d for gen table', typ);
      end
    elseif tbl == CT_TAREAGENCOST               %% area-wide mod to gencost table
      jj = find(row == mpc.bus(e2i(mpc.gen(:, GEN_BUS)), BUS_AREA));
      if col == CT_MODCOST_F || col == CT_MODCOST_X
        if typ == CT_REL
          if col == CT_MODCOST_F
            modcost_type = 'SCALE_F';
          else  %% col == CT_MODCOST_X
            modcost_type = 'SCALE_X';
          end
        elseif typ == CT_ADD
          if col == CT_MODCOST_F
            modcost_type = 'SHIFT_F';
          else  %% col == CT_MODCOST_X
            modcost_type = 'SHIFT_X';
          end
        else
            error('apply_changes: unsupported area-wide modification type %d for gencost table CT_MODCOST_F/X modification', typ);
        end
        mpc.gencost(jj, :) = modcost(mpc.gencost(jj, :), val, modcost_type);
      else
        if col < 1 || fix(col) ~= col   %% needs to be positive integer
          error('apply_changes: area-wide modification to column %d of gencost table not supported', col);
        end
        if typ == CT_REP                                      %% replace
          mpc.gencost(jj, col) = val * ones(size(jj));
        elseif typ == CT_REL                                  %% scale
          mpc.gencost(jj, col) = val * mpc.gencost(jj, col);
        elseif typ == CT_ADD                                  %% shift
          mpc.gencost(jj, col) = val + mpc.gencost(jj, col);
        else
          error('apply_changes: unsupported area-wide modification type %d for gencost table', typ);
        end
      end
    elseif tbl == CT_TLOAD || tbl == CT_TAREALOAD           %% modify loads
      if ~any(abs(col) == (CT_LOAD_ALL_PQ:CT_LOAD_DIS_P))
        error('apply_changes: column=%d for load modifications is not supported', col);
      end
      switch abs(col)
        case {CT_LOAD_ALL_PQ, CT_LOAD_FIX_PQ, CT_LOAD_DIS_PQ}
            opt.pq = 'PQ';
        case {CT_LOAD_ALL_P, CT_LOAD_FIX_P, CT_LOAD_DIS_P}
            opt.pq = 'P';
      end
      switch abs(col)
        case {CT_LOAD_ALL_PQ, CT_LOAD_ALL_P}
            opt.which = 'BOTH';
        case {CT_LOAD_FIX_PQ, CT_LOAD_FIX_P}
            opt.which = 'FIXED';
        case {CT_LOAD_DIS_PQ, CT_LOAD_DIS_P}
            opt.which = 'DISPATCHABLE';
      end

      %% define load_zone
      if tbl == CT_TLOAD
        nb = size(mpc.bus, 1);
        if row == 0                                     %% modify load at all buses
          load_zone = ones(nb, 1);
        else                                            %% modify load at single bus
          load_zone = zeros(nb, 1);
          load_zone(row) = 1;
        end
      elseif tbl == CT_TAREALOAD
        load_zone = double(mpc.bus(:, BUS_AREA) == row);  %% modify load in single area
      end

      switch typ
        case CT_REP                                     %% replace
            opt.scale = 'QUANTITY';
            dmd = val;
        case CT_REL                                     %% scale
            opt.scale = 'FACTOR';
            dmd = val;
        case CT_ADD                                     %% shift
            opt.scale = 'QUANTITY';
            old_val = total_load(mpc, load_zone);
            dmd = old_val + val;
        otherwise
          error('apply_changes: unsupported modification type %d for loads', typ);
      end
      %% scale the loads ...
      if col < 0    %% ... including dispatchable load costs
          [mpc.bus, mpc.gen, mpc.gencost] = scale_load(dmd, mpc.bus, mpc.gen, load_zone, opt, mpc.gencost);
      else          %% ... not including dispatchable load costs
          [mpc.bus, mpc.gen] = scale_load(dmd, mpc.bus, mpc.gen, load_zone, opt);
      end
    else
      error('apply_changes: CHGTAB attempts to modify unsupported table type')
    end
end