//NEWTONPF  Solves power flow using full Newton's method (power/polar)
//   [V, CONVERGED, I] = NEWTONPF(YBUS, SBUS, V0, REF, PV, PQ, MPOPT)
//
//   Solves for bus voltages using a full Newton-Raphson method, using nodal
//   power balance equations and polar coordinate representation of
//   voltages, given the following inputs:
//       YBUS  - full system admittance matrix (for all buses)
//       SBUS  - handle to function that returns the complex bus power
//               injection vector (for all buses), given the bus voltage
//               magnitude vector (for all buses)
//       V0    - initial vector of complex bus voltages
//       REF   - bus index of reference bus (voltage ang reference & gen slack)
//       PV    - vector of bus indices for PV buses
//       PQ    - vector of bus indices for PQ buses
//       MPOPT - (optional) MATPOWER option struct, used to set the
//               termination tolerance, maximum number of iterations, and
//               output options (see MPOPTION for details).
//
//   The bus voltage vector contains the set point for generator
//   (including ref bus) buses, and the reference angle of the swing
//   bus, as well as an initial guess for remaining magnitudes and
//   angles.
//
//   Returns the final complex voltages, a flag which indicates whether it
//   converged or not, and the number of iterations performed.
//
//   See also RUNPF, NEWTONPF_S_CART, NEWTONPF_I_POLAR, NEWTONPF_I_CART.

//   MATPOWER
//   Copyright (c) 1996-2019, Power Systems Engineering Research Center (PSERC)
//   by 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.

fn newtonpf(Ybus, Sbus, V0, ref, pv, pq, mpopt) {
    // options
    tol         = get(mpopt, 0);
    max_it      = get(mpopt, 1);

    // initialize
    converged = 0;
    i = 0;
    V = V0;
    Va = angle(V);
    Vm = abs(V);
    nb = length(V);
    npv = length(pv);
    npq = length(pq);
    j1 = 0;         j2 = npv;       // j1:j2 - V angle of pv buses
    j3 = j2;    j4 = j2 + npq;      // j3:j4 - V angle of pq buses
    j5 = j4;    j6 = j4 + npq;      // j5:j6 - V mag of pq buses
    pv_pq_i = horzcat(pv, pq);
    // evaluate F(x0)
    mis = V .* conj(Ybus * V) - Sbus;
    F = vertcat(real(get_multi(mis, pv_pq_i))', imag(get_multi(mis, pq))');
    // check tolerance
    normF = norm_max(F);
    // do Newton iterations
    while ~~converged && i < max_it {
        // update iteration counter
        i = i + 1;
        // evaluate Jacobian
        jac = dSbus_dV(Ybus, V, 0);
        dSbus_dVa = slice(jac, [0], [0,nb]);
        dSbus_dVm = slice(jac, [0], [nb,2*nb]);

        j11 = real(select(dSbus_dVa, pv_pq_i, pv_pq_i));
        j12 = real(select(dSbus_dVm, pv_pq_i, pq));
        j21 = imag(select(dSbus_dVa, pq, pv_pq_i));
        j22 = imag(select(dSbus_dVm, pq, pq));
        J = vertcat(
            horzcat(j11, j12),
            horzcat(j21, j22)
        );
        // compute update step
        // dx = linsolve(J, -F);
        dx = J \ -F;
        // update voltage vector
        if npv {
            Va = set2(Va, pv, slice(dx, [j1,j2], [0]));
        }
        if npq {
            Va = set2(Va, pq, slice(dx, [j3,j4], [0]));
            Vm = set2(Vm, pq, slice(dx, [j5,j6], [0]));
        }
        V = Vm .* exp(c(0,1) * Va);
        Vm = abs(V);            // update Vm and Va again in case
        Va = angle(V);          // we wrapped around with a negative Vm

        mis = V .* conj(Ybus * V) - Sbus;
        F = vertcat(real(get_multi(mis, pv_pq_i))', imag(get_multi(mis, pq))');
        // check for convergence
        normF = norm_max(F);
        if normF < tol {
            converged = 1;
        }
    }
    return V;
}