Пример #1
0
def _simulate(system, timespan, x0, dx0=None, dt=0.1, control_vars=None):
    """Simulate the system dynamics.

    This method integrates the dynamics of the system over the specified
    interval of time, starting at the specified initial state.

    The solver used is a differential-algebraic integrator which respects
    conservation laws and algebraic constraints. It is expected that the
    initial state satisfies the systems inherent algebraic constrains;
    inconsistent initial conditions will raise exceptions.

    The initial values of derivatives can be specified and the solver will
    ensure they are consistent with the initial state, or change them if they
    are not.

    todo: detial control variables.

    Args:
        system :obj:`BondGraph`: The system to simulate
        timespan: A pair (`list` or `tuple`) containing the start and end
                  points of the simulation.
        x0: The initial conditions of the system.
        dx0 (Optional): The initial rates of change of the system. The default
                        value (`None`) indicates that the system should be
                        initialised from the state variable initial conditions.
        dt: The time step between reported (not integrated) values.
        control_vars: A `dict`, `list` or `tuple` specifing the values of the
                      control variables.
    Returns:
        t: numpy array of timesteps
        x: numpy array of state values

    Raises:
        ModelException, SolverException
    """

    if system.ports:
        raise ModelException("Cannot Simulate %s: unconnected ports %s",
                             system, system.ports)

    if system.control_vars and not control_vars:
        raise ModelException("Control variable not specified")

    samples = int((timespan[1] - timespan[0]) / dt) + 1
    t = np.linspace(*timespan, samples)

    res, X = _bondgraph_to_residuals(system, control_vars)

    X0, DX0 = _fetch_ic(x0, dx0, system, res, t[0])

    solver_name = 'ida'
    dae_solver = dae(solver_name, res)
    sol = dae_solver.solve(t, X0, DX0)

    return t.reshape((samples, 1)), np.transpose(sol.values.y).T
Пример #2
0
def run(SV_0, an, sep, ca, algvars, params):
    """ 
    Run the simulation
    """

    # Store the location of all algebraic variables.
    params['algvars'] = algvars

    # Initialize the vector of state vector time derivative (dSV/dt):
    SVdot_0 = np.zeros_like(SV_0)

    # This function checks to see if certain limits are exceeded which will
    # terminate the simulation:
    def terminate_check(t, SV, SVdot, return_val, inputs):
        #TODO #35
        #TODO #34
        return_val[0] = 1.0
        return_val[1] = 1.0

    # Set up the differential algebraic equation (dae) solver:
    options = {
        'user_data': (an, sep, ca, params),
        'rtol': 1e-8,
        'atol': 1e-11,
        'algebraic_vars_idx': algvars,
        'first_step_size': 1e-18,
        'rootfn': terminate_check,
        'nr_rootfns': 2,
        'compute_initcond': 'yp0'
    }
    solver = dae('ida', residual, **options)

    # Initialize the step counter:
    i_step = 0
    """ Equilibration """
    # If requested by the user, begin with a hold at zero current, to
    # equilibrate the system:
    if params['simulation']['equilibrate']['enable']:

        params['boundary'] = 'current'
        params['i_ext'] = 0.0

        print('\n Equilibrating at i_ext = 0.0 A/cm2.\n')

        # Set the OCV hold time:
        t_equil = params['simulation']['equilibrate']['time']
        t_out = np.linspace(0, t_equil, 10000)

        # Run the solver
        solution = solver.solve(t_out, SV_0, SVdot_0)

        # Array of current densities (all equal to zero), one for each time
        # step taken:
        i_ext = np.zeros_like(solution.values.t)

        # Stack the times, current densities, and state vectors into a single
        # data stack:
        data_out = np.vstack((solution.values.t, i_ext, solution.values.y.T))

        # Use solution at the end of simulation as the new initial condition:
        SV_0 = solution.values.y[-1, :]

        # Increment the step counter:
        i_step += 1

        # Save the potential as the (possible) initial cell potential (it may
        # be overwritten, below):
        params['simulation']['ocv'] = \
            SV_0[ca.SV_offset+int(ca.SVptr['phi_ed'][-1])]

    # Specify the boundary condition as potentiostatic:
    params['boundary'] = 'potential'
    """ Initial Potentiostatic Hold """
    # If requested by the user, begin, with a hold at a specified potential
    if params['simulation']['initial-hold']['enable']:

        # Set the potential for the hold:
        params['potentials'] = \
            np.array((params['simulation']['initial-potential'],
            params['simulation']['initial-potential']))

        # Read out the time for the hold
        t_hold = params['simulation']['initial-hold']['time']

        # Set the times in the input parameters, used to read out the potential
        # as a function of time:
        params['times'] = np.array((0, t_hold))

        # Set the solver time:
        t_out = np.linspace(0, t_hold, 10000)

        # Print out the conditions:
        print('Potentiostatic hold at', round(params['potentials'][0], 3),
              ' V \n')

        # Run the solver
        solution = solver.solve(t_out, SV_0, SVdot_0)

        # Calculate the external current at each time step:
        i_ext = calc_current(solution, sep, an, params)

        # Stack times, current densities, and state vectors into the data array
        # for output/saving. Save the final state as the new initial state:
        if i_step:  # data_out already exists:
            data_out, SV_0 = data_prepare(i_step, solution, i_ext, data_out)
        else:  # data_out does not yet exist:
            data_out, SV_0 = data_prepare(i_step, solution, i_ext)
    """ Run the CV experiment:"""
    # Set up the array of times and potentials for the CV:
    potentials, times = setup_cycles(params['simulation'])

    # Save the array of potentials and times to the 'params' input:
    params['potentials'] = potentials
    params['times'] = times

    # Tell the solver the range of times to simulate:
    t_out = np.linspace(0, times[-1], 1000)

    # Run the solver
    solution = solver.solve(t_out, SV_0, SVdot_0)

    # Calculate the external current at each time step:
    i_ext = calc_current(solution, sep, an, params)

    # Append the current data array to any preexisting data, for output.
    # If this is the first step, create the output data array.
    if i_step:  # data_out already exists:
        data_out, SV_0 = data_prepare(i_step, solution, i_ext, data_out)
    else:  # data_out does not yet exist:
        data_out, SV_0 = data_prepare(i_step, solution, i_ext)

    return data_out
Пример #3
0
def simulate(system, timespan, x0, dx0=None, dt=0.1, control_vars=None):
    """Simulate the system dynamics.

    This method integrates the dynamics of the system over the specified
    interval of time, starting at the specified initial state.

    The solver used is a differential-algebraic integrator which respects
    conservation laws and algebraic constraints. It is expected that the
    initial state satisfies the systems inherent algebraic constrains;
    inconsistent initial conditions will raise exceptions.

    The initial values of derivatives can be specified and the solver will
    ensure they are consistent with the initial state, or change them if they
    are not.

    Currently, control variables can take the form of numbers or a strings
    and are assigned via a dictionary or list.

    Permissible strings:

        * numerical constants such as `1.0`, `pi`
        * time `t`
        * state variables; for example `x_0`
        * arithmetic operators such as `+`,`-`, `*`, `/`, as well as `^`
          (power operator), `%` (remainder)
        * elementary math functions such as `sin`, `exp`, `log`
        * ternary if; for example `t < 0 ? 0 : 1` which implements the Heaviside

    Args:
        system :obj:`BondGraph`: The system to simulate
        timespan: A pair (`list` or `tuple`) containing the start and end points
                  of the simulation.
        x0: The initial conditions of the system.
        dx0 (Optional): The initial rates of change of the system. The default
                        value (`None`) indicates that the system should be
                        initialised from the state variable initial conditions.
        dt: The time step between reported (not integrated) values.
        control_vars: A `dict`, `list` or `tuple` specifing the values of the
                      control variables.
    Returns:
        t: numpy array of timesteps
        x: numpy array of state values

    Raises:
        ModelException, SolverException
    """

    if system.ports:
        raise ModelException("Cannot Simulate %s: unconnected ports %s",
                             system, system.ports)

    if system.control_vars and not control_vars:
        raise ModelException("Control variable not specified")

    samples = int((timespan[1] - timespan[0]) / dt) + 1
    t = np.linspace(*timespan, samples)

    res, X = _bondgraph_to_residuals(system, control_vars)

    X0, DX0 = _fetch_ic(x0, dx0, system, res, t[0])

    solver_name = 'ida'
    dae_solver = dae(solver_name, res)
    sol = dae_solver.solve(t, X0, DX0)

    return t.reshape((samples, 1)), np.transpose(sol.values.y).T
Пример #4
0
def main():

    net = get_net()
    print(net)
    check_unsupported(net)

    pp.runpp(net)
    ybus_og = np.array(net._ppc["internal"]["Ybus"].todense())
    ybus_og += get_load_admittances(np.zeros_like(ybus_og), net)

    opt = {'t_sim': 2.0, 'fn': 60}
    # Map from pp_bus to machine.
    all_mach_params = {
        1: {
            'xdp': 0.0608,
            'h': 23.64
        },
        2: {
            'xdp': 0.1198,
            'h': 6.01
        },
        3: {
            'xdp': 0.1813,
            'h': 3.01
        },
    }

    machs = [
        Machine(
            pp_bus=pp_bus,
            bus=net._pd2ppc_lookups["bus"][pp_bus],
            fn=opt['fn'],
            vt0=get_v_at_bus(net, pp_bus),
            s0=get_gen_s_at_bus(net, pp_bus),
            xdp=mach_params['xdp'],
            h=mach_params['h'],
        ) for pp_bus, mach_params in all_mach_params.items()
    ]

    if [mach.params['bus'] for mach in machs] != [0, 1, 2]:
        raise ValueError

    # Need to properly understand current injection equations.
    for mach in machs:
        bus = mach.params['bus']
        ybus_og[bus, bus] += 1 / (1j * mach.params['xdp'])

    ybus_1 = ybus_og.copy()

    ybus_2 = np.zeros_like(ybus_og)
    ybus_2[7, 7] += 1e4 - 1j * 1e4

    ybus_3 = ybus_2 * -1

    ybus_states = [(1.0, ybus_2), (1.083, ybus_3)]

    resid_t = []
    resid_vals = []

    # Define function here so it has access to outer scope variables.
    def residual_wrapper(t, x, xdot, result):
        return residual(t, x, xdot, result, machs, ybus_og, ybus_states)

    init_x = np.concatenate([mach.init_state_vector for mach in machs])
    init_x = np.concatenate([
        init_x,
        np.squeeze(np.array(net._ppc["internal"]["V"])).real,
        np.squeeze(np.array(net._ppc["internal"]["V"])).imag
    ])
    init_xdot = np.zeros_like(init_x)

    a = np.zeros_like(init_x)
    residual_wrapper(0, init_x, init_xdot, a)
    print(a, '\n', np.sum(np.abs(a)))  # should be about zero.

    solver = dae(
        'ida',
        residual_wrapper,
        # compute_initcond='yp0',
        first_step_size=1e-18,
        atol=1e-6,
        rtol=1e-6,
        algebraic_vars_idx=list(range(6, 6 + 9 * 2)),
        old_api=False,
        max_steps=500000,
        max_step_size=1e-3,
    )

    solution = solver.solve(np.linspace(0, 2, 1000), init_x, init_xdot)

    gen1_vt = abs(solution.values.y[:, 6 + 0] +
                  1j * solution.values.y[:, 6 + 9 + 0])
    gen2_vt = abs(solution.values.y[:, 6 + 1] +
                  1j * solution.values.y[:, 6 + 9 + 1])
    gen3_vt = abs(solution.values.y[:, 6 + 2] +
                  1j * solution.values.y[:, 6 + 9 + 2])
    # plt.plot(solution.values.t[-30:], gen1_vt[-30:])
    plt.plot(solution.values.t, gen1_vt)
    plt.plot(solution.values.t, gen2_vt)
    plt.plot(solution.values.t, gen3_vt)

    plt.figure()
    plt.plot(solution.values.t, solution.values.y[:, 0])

    plt.figure()
    plt.plot(solution.values.t, solution.values.y[:, 1])

    plt.show()
Пример #5
0
def run(SV_0, an, sep, ca, algvars, params):
    """ 
    Run the simulation
    """

    # Store the location of all algebraic variables.
    params['algvars'] = algvars

    # Initialize the vector of state vector time derivative (dSV/dt):
    SVdot_0 = np.zeros_like(SV_0)

    # This function checks to see if certain limits are exceeded which will
    # terminate the simulation:
    def terminate_check(t, SV, SVdot, return_val, inputs):
        #TODO #36
        return_val[0] = 1.0
        return_val[1] = 1.0

    # Set up the differential algebraic equation (dae) solver:
    options = {
        'user_data': (an, sep, ca, params),
        'rtol': 1e-8,
        'atol': 1e-11,
        'algebraic_vars_idx': algvars,
        'first_step_size': 1e-18,
        'rootfn': terminate_check,
        'nr_rootfns': 2,
        'compute_initcond': 'yp0'
    }
    solver = dae('ida', residual, **options)
    """ Equilibration: Optional """
    # If requested by the user, begin with a hold at zero current, to
    # equilibrate the system:
    if params['simulation']['equilibrate']['enable']:

        # Set the boundary condition to galvanostatic and the external current
        # to 0.0:
        params['boundary'] = 'current'
        params['i_ext'] = 0.0

        # Print out conditions:
        print('\nStep 1: Equilibrating...\n')
        print('    i_ext = 0.0 A/cm2.\n')

        # Read out and set the OCV hold time:
        t_equil = params['simulation']['equilibrate']['time']
        t_out = np.linspace(0, t_equil, 10000)

        # Run the solver
        solution = solver.solve(t_out, SV_0, SVdot_0)

        # Array of current densities, one for each time step taken:
        i_ext = np.zeros_like(solution.values.t)

        # Stack the times, current densities, and state vectors:
        data_out = np.vstack((solution.values.t, i_ext, solution.values.y.T))

        # Use solution at the end of simulation as the new initial condition:
        SV_0 = solution.values.y[-1, :]

        # Initialize the step counter to 1:
        i_step = 1

    else:
        # Initialize the step counter:
        i_step = 0

    # Specify the boundary condition as potentiostatic:
    params['boundary'] = 'potential'
    """ Run the potential hold(s) """
    for step in params['simulation']['steps']:

        # Store the potential. We do this as an array of potential vs. time,
        # which the residual function interpolates.  For a constant potnetial,
        # we just need the initial and final potentials, which are the same:
        params['potentials'] = np.array((step['potential'], step['potential']))
        params['times'] = np.array((0, step['time']))

        # Print out the conditions:
        print('Step {:0.0f}: Potentiostatic hold...\n'.format(i_step + 1))
        print('    Potential = ', round(params['potentials'][0], 3), ' V \n')

        # Determine the range of times to simulate:
        t_out = np.linspace(0, step['time'], 10000)

        # Run the solver
        solution = solver.solve(t_out, SV_0, SVdot_0)

        # Calculate the external current at each time step:
        i_ext = calc_current(solution, sep, an, params)

        # Append the current data array to any preexisting data, for output.
        # If this is the first step, create the output data array.
        if i_step:  # Not the first step. 'data_out' already exists:
            # Stack the times, the current at each time step, and the solution
            # vector at each time step into a single data array.

            # For the times, add the final time from the previous step to all
            # values:
            SV = np.vstack((solution.values.t + data_out[0, -1], i_ext,
                            solution.values.y.T))
            data_out = np.hstack((data_out, SV))

            # Use SV at the end of the simulation as the new initial condition:
            SV_0 = solution.values.y[-1, :]
        else:  # First step. 'data_out' does not yet exist:
            # Stack the times, the current at each time step, and the solution
            # vector at each time step into a single data array.
            SV = np.vstack((solution.values.t, i_ext, solution.values.y.T))
            data_out = SV

            # Use SV at the end of the simulation as the new initial condition:
            SV_0 = solution.values.y[-1, :]

        # Increment the step number:
        i_step += 1

    return data_out
def main():

    net = get_net()
    print(net)
    check_unsupported(net)

    pp.runpp(net)
    ybus_og = np.array(net._ppc["internal"]["Ybus"].todense())
    ybus_og += get_load_admittances(np.zeros_like(ybus_og), net)

    opt = {'t_sim': 5.0, 'fn': 60}
    # Map from pp_bus to machine.
    all_mach_params = {
        1: {
            'ra': 0.01,
            'xa': 0.0,
            'xd': 0.36,
            'xq': 0.23,
            'xdp': 0.15,
            'xqp': 0.15,
            'xdpp': 0.1,
            'xqpp': 0.1,
            'td0p': 8.952,
            'tq0p': 5.76,
            'td0pp': 0.075,
            'tq0pp': 0.075,
            'h': 8
        },
        2: {
            'ra': 0.0,
            'xa': 0.0,
            'xd': 1.72,
            'xq': 1.66,
            'xdp': 0.378,
            'xqp': 0.378,
            'xdpp': 0.2,
            'xqpp': 0.2,
            'td0p': 5.982609,
            'tq0p': 4.5269841,
            'td0pp': 0.0575,
            'tq0pp': 0.0575,
            'h': 4
        },
        3: {
            'ra': 0.0,
            'xa': 0.0,
            'xd': 1.68,
            'xq': 1.61,
            'xdp': 0.32,
            'xqp': 0.32,
            'xdpp': 0.2,
            'xqpp': 0.2,
            'td0p': 5.5,
            'tq0p': 4.60375,
            'td0pp': 0.0575,
            'tq0pp': 0.0575,
            'h': 2
        },
    }

    machs = []
    for pp_bus, mach_params in all_mach_params.items():

        mach_params = {
            **mach_params,
            'pp_bus': pp_bus,
            'bus': net._pd2ppc_lookups["bus"][pp_bus],
            'fn': opt['fn'],
        }

        vt0 = get_v_at_bus(net, pp_bus)
        s0 = get_gen_s_at_bus(net, pp_bus)
        mach = Machine(vt0, s0, mach_params)
        machs.append(mach)

    # Need to properly understand current injection equations.
    for mach in machs:
        bus = mach.params['bus']
        ybus_og[bus, bus] += mach.yg

    ybus_2 = np.zeros_like(ybus_og)
    ybus_2[6, 6] += 1e4 - 1j * 1e4
    ybus_3 = ybus_2 * -1
    ybus_states = [(1.0, ybus_2), (1.1, ybus_3)]

    # Define function here so it has access to outer scope variables.
    def residual_wrapper(t, x, xdot, result):
        return residual(t, x, xdot, result, machs, ybus_og, ybus_states)

    init_x = np.concatenate([mach.init_state_vector for mach in machs])
    init_x = np.concatenate([
        init_x,
        np.squeeze(np.array(net._ppc["internal"]["V"])).real,
        np.squeeze(np.array(net._ppc["internal"]["V"])).imag
    ])
    init_xdot = np.zeros_like(init_x)

    a = np.zeros_like(init_x)
    residual_wrapper(0, init_x, init_xdot, a)
    print(a, '\n', np.sum(np.abs(a)))  # should be about zero.

    solver = dae(
        'ida',
        residual_wrapper,
        # compute_initcond='yp0',
        first_step_size=1e-18,
        atol=1e-6,
        rtol=1e-6,
        old_api=False,
        max_steps=500000,
        max_step_size=1e-3,
    )

    solution = solver.solve(np.linspace(0, 5, 5000), init_x, init_xdot)

    gen1_vt = solution.values.y[:, -18 +
                                0] + 1j * solution.values.y[:, -18 + 9 + 0]
    gen1_delta = solution.values.y[:, 1]

    gen1_vd = np.abs(gen1_vt) * np.sin(gen1_delta - np.angle(gen1_vt))
    gen1_vq = np.abs(gen1_vt) * np.cos(gen1_delta - np.angle(gen1_vt))

    # Data saved from pypower dynamics.
    # Data saved from pypower dynamics.
    df = pd.read_csv('./pypower_dynamic_output.csv')

    print()

    plt.figure()
    plt.plot(df['time'], df['GEN1:Vd'], '-', label='GEN1:Vd pydyn')
    plt.plot(solution.values.t, gen1_vd, '-.', label='Gen1 Vd')

    plt.plot(df['time'], df['GEN1:Vq'], '-', label='GEN1:Vq pydyn')
    plt.plot(solution.values.t, gen1_vq, '-.', label='Gen1 Vq')
    plt.legend()

    plt.show()
Пример #7
0
def run(SV_0, an, sep, ca, algvars, params, sim):
    """ 
    Run the simulation
    """
    # Determine the current to run at, and the time to fully charge/discharge.
    # 'calc_current' is defined below.
    current, t_final = calc_current(sim, an, ca)

    # Store the location of all algebraic variables.
    params['algvars'] = algvars

    # Specify the boundary condition as galvanostatic:
    params['boundary'] = 'current'

    # Figure out which steps and at what currents to run the model. This
    # returns a tuple of 'charge' and 'discharge' steps, and a tuple with a
    # current for each step. 'equil' is a flag to indicate whether there is an
    # equilibration step.
    steps, currents, times, equil = setup_cycles(sim, current, t_final)
    n_steps = len(steps)

    # This function checks to see if certain limits are exceeded which will
    # terminate the simulation:
    def terminate_check(t, SV, SVdot, return_val, inputs):
        return_val[0] = ca.voltage_lim(SV, sim['phi-cutoff-lower'])
        return_val[1] = ca.voltage_lim(SV, sim['phi-cutoff-upper'])

    # Set up the differential algebraic equation (dae) solver:
    options = {
        'user_data': (an, sep, ca, params),
        'rtol': 1e-3,
        'atol': 1e-6,
        'algebraic_vars_idx': algvars,
        'first_step_size': 1e-18,
        'rootfn': terminate_check,
        'nr_rootfns': 2,
        'compute_initcond': 'yp0'
    }
    solver = dae('ida', residual, **options)

    # Go through the current steps and integrate for each current:
    for i, step in enumerate(steps):
        print('Step ', int(i + 1), '(out of', n_steps, '): ', step, '...\n')

        # Set the external current density (A/m2)
        params['i_ext'] = currents[i]
        print('    Current = ', round(currents[i], 3), 'A/m^2 \n')

        t_out = np.linspace(0, times[i], 10000)

        # Create an initial array of time derivatives and runs the integrator:
        SVdot_0 = np.zeros_like(SV_0)
        solution = solver.solve(t_out, SV_0, SVdot_0)

        # Create an array of currents, one for each time step:
        i_data = currents[i] * np.ones_like(solution.values.t)
        cycle_number = int(i + 1 - equil) * np.ones_like(solution.values.t)
        cycle_capacity = 1000 * solution.values.t * abs(i_data) / 3600

        # Append the current data array to any preexisting data, for output.
        # If this is the first step, create the output data array.
        if i:  # Not the first step. 'data_out' already exists:
            # Stack the times, the current at each time step, and the solution
            # vector at each time step into a single data array.
            SV = np.vstack((solution.values.t + data_out[0, -1], cycle_number,
                            i_data, cycle_capacity, solution.values.y.T))
            data_out = np.hstack((data_out, SV))

            # Use SV at the end of the simualtion as the new initial condition:
            SV_0 = solution.values.y[-1, :]
        else:  # First step. 'data_out' does not yet exist:
            # Stack the times, the current at each time step, and the solution
            # vector at each time step into a single data array.
            SV = np.vstack((solution.values.t, cycle_number, i_data,
                            cycle_capacity, solution.values.y.T))
            data_out = SV

            # Use SV at the end of the simualtion as the new initial condition:
            SV_0 = solution.values.y[-1, :]

    return data_out
Пример #8
0
def run(SV_0, an, sep, ca, algvars, params):
    # Determine the current to run at. 'calc_current' is defined below.
    current, t_final = calc_current(params['simulation'], an, ca)

    # Figure out which steps and at what currents to run the model. This
    # returns a tuple of 'charge' and 'discharge' steps, and a tuple with a
    # current for each step.
    steps, currents = setup_steps(params['simulation'], current)

    # Specify conditions at which the simulation terminates:
    def root_fn(t, SV, SVdot, return_root, inputs):
        return_root[0] = ca.voltage_lim(
            SV, ca, params['simulation']['phi-cutoff-lower'])
        return_root[1] = ca.voltage_lim(
            SV, ca, params['simulation']['phi-cutoff-upper'])

    # Set up the solver:
    options = {
        'user_data': (an, sep, ca, params),
        'rtol': 1e-8,
        'atol': 1e-11,
        'algebraic_vars_idx': algvars,
        'first_step_size': 1e-18,
        'rootfn': root_fn,
        'nr_rootfns': 2
    }
    solver = dae('ida', residual, **options)

    for i, step in enumerate(steps):
        print(step, '...\n')

        # Set the external current density (A/m2)
        # Note that you might rather set a voltage.
        params['i_ext'] = currents[i]
        print('    Current = ', round(currents[i], 3), '\n')
        t_out = np.linspace(0, t_final, 10000)

        # Make the initial solution consistent with the algebraic constraints:
        SV_0 = an.make_alg_consistent(SV_0, an, sep, ca, params)
        SV_0 = sep.make_alg_consistent(SV_0, an, sep, ca, params)

        # This runs the integrator.
        SVdot_0 = np.zeros_like(SV_0)
        solution = solver.solve(t_out, SV_0, SVdot_0)

        # Create an array of currents, one for each time step:
        i_data = currents[i] * np.ones_like(solution.values.t)

        # Append the current data array to any preexisting data, for output.
        # If this is the first step, create the output data array.
        if i:
            # Stack the times, the current at each time step, and the solution
            # vector at each time step into a single data array.
            SV = np.vstack((solution.values.t + data_out[0, -1], i_data,
                            solution.values.y.T))
            data_out = np.hstack((data_out, SV))

            # Use SV at the end of the simualtion as the new initial condition:
            SV_0 = solution.values.y[-1, :]
        else:
            # Stack the times, the current at each time step, and the solution
            # vector at each time step into a single data array.
            SV = np.vstack((solution.values.t, i_data, solution.values.y.T))
            data_out = SV

            # Use SV at the end of the simualtion as the new initial condition:
            SV_0 = solution.values.y[-1, :]

    return data_out
def main():

    net = get_net()
    print(net)
    check_unsupported(net)

    pp.runpp(net)
    ybus_og = np.array(net._ppc["internal"]["Ybus"].todense())
    ybus_og += get_load_admittances(np.zeros_like(ybus_og), net)

    opt = {'t_sim': 5.0, 'fn': 60}
    # Map from pp_bus to machine.
    all_mach_params = {
        1: {
            'mach_type': 'ext_grid',
            'ra': 0,
            'xdp': 0.1,
            'h': 99999
        },
        2: {
            'mach_type': 'anderson_fouad_six',
            'ra': 0.0,
            'xd': 2.29,
            'xq': 2.18,
            'xdp': 0.25,
            'xqp': 0.25,
            'xdpp': 0.18,
            'xqpp': 0.18,
            'td0p': 13.1979,
            'tq0p': 3.2423,
            'td0pp': 0.0394,
            'tq0pp': 0.1157,
            'h': 5.8
        },
    }

    machs = []
    for pp_bus, mach_params in all_mach_params.items():

        mach_params = {
            **mach_params,
            'pp_bus': pp_bus,
            'bus': net._pd2ppc_lookups["bus"][pp_bus],
            'fn': opt['fn'],
        }

        vt0 = get_v_at_bus(net, pp_bus)
        s0 = get_gen_s_at_bus(net, pp_bus)
        if mach_params['mach_type'] == 'ext_grid':
            mach = ExtGrid(vt0, s0, mach_params)
        elif mach_params['mach_type'] == 'sauer_pai_six':
            mach = SauerPaiOrderSix(vt0, s0, mach_params)
        elif mach_params['mach_type'] == 'anderson_fouad_six':
            mach = AndersonFouadOrderSix(vt0, s0, mach_params)
        else:
            raise ValueError(
                f'Unknown machine type: {mach_params["mach_type"]}')
        machs.append(mach)

    # Need to properly understand current injection equations.
    for mach in machs:
        bus = mach.params['bus']
        ybus_og[bus, bus] += mach.yg

    ybus_2 = np.zeros_like(ybus_og)
    ybus_2[1, 1] += 1e4 - 1j * 1e4
    ybus_3 = ybus_2 * -1
    ybus_states = [(1.0, ybus_2), (1.2, ybus_3)]

    # Define function here so it has access to outer scope variables.
    def residual_wrapper(t, x, xdot, result):
        return residual(t, x, xdot, result, machs, ybus_og, ybus_states)

    init_x = np.concatenate([mach.init_state_vector for mach in machs])
    init_x = np.concatenate([
        init_x,
        np.squeeze(np.array(net._ppc["internal"]["V"])).real,
        np.squeeze(np.array(net._ppc["internal"]["V"])).imag
    ])
    init_xdot = np.zeros_like(init_x)

    a = np.zeros_like(init_x)
    residual_wrapper(0, init_x, init_xdot, a)
    print(a, '\n', np.sum(np.abs(a)))  # should be about zero.

    solver = dae(
        'ida',
        residual_wrapper,
        # compute_initcond='yp0',
        first_step_size=1e-18,
        atol=1e-6,
        rtol=1e-6,
        old_api=False,
        max_steps=500000,
        max_step_size=1e-3,
    )

    solution = solver.solve(np.linspace(0, 8, 5000), init_x, init_xdot)

    # The "Grid"
    grid_vt = solution.values.y[:,
                                -4 + 0] + 1j * solution.values.y[:, -4 + 2 + 0]
    grid_delta = solution.values.y[:, 1]

    # The machine we're interested in.
    gen1_vt = solution.values.y[:,
                                -4 + 1] + 1j * solution.values.y[:, -4 + 2 + 1]

    # Data saved from pypower dynamics.
    df = pd.read_csv('./smib_fault_no_avr_pydn.csv')

    plt.figure()
    plt.plot(df['time'], df['GEN1:Vt'], '-', label='GEN1:Vt pydyn')
    plt.plot(solution.values.t, abs(gen1_vt), '-.', label='Gen Bus 2 Vt')
    plt.legend()

    plt.show()