def api_func(exe, idx, model_ws=None):
    success = False

    name = ex[idx].upper()
    if model_ws is None:
        model_ws = "."

    try:
        mf6 = ModflowApi(exe, working_directory=model_ws)
    except Exception as e:
        print("Failed to load " + exe)
        print("with message: " + str(e))
        return api_return(success, model_ws)

    # initialize the model
    try:
        mf6.initialize()
    except:
        return api_return(success, model_ws)

    # test the interface models
    check_interface_models(mf6)

    # time loop
    current_time = mf6.get_current_time()
    end_time = mf6.get_end_time()
    while current_time < end_time:
        try:
            mf6.update()
        except:
            return api_return(success, model_ws)
        current_time = mf6.get_current_time()

    # finish
    try:
        mf6.finalize()
        success = True
    except:
        return api_return(success, model_ws)

    # cleanup and return
    return api_return(success, model_ws)
def api_func(exe, idx, model_ws=None):
    success = False

    name = ex[idx].upper()
    if model_ws is None:
        model_ws = "."

    try:
        mf6 = ModflowApi(exe, working_directory=model_ws)
    except Exception as e:
        print("Failed to load " + exe)
        print("with message: " + str(e))
        return api_return(success, model_ws)

    # initialize the model
    try:
        mf6.initialize()
    except:
        return api_return(success, model_ws)

    # time loop
    current_time = mf6.get_current_time()
    end_time = mf6.get_end_time()

    # reset sy with bmi set_value
    sy_tag = mf6.get_var_address("SY", name, "STO")
    new_sy = mf6.get_value(sy_tag)
    new_sy.fill(sy_val)

    mf6.set_value(sy_tag, new_sy)

    # model time loop
    idx = 0
    while current_time < end_time:

        # run the time step
        try:
            mf6.update()
        except:
            return api_return(success, model_ws)

        # update time
        current_time = mf6.get_current_time()

        # increment counter
        idx += 1

    # cleanup
    try:
        mf6.finalize()
        success = True
    except:
        return api_return(success, model_ws)

    # cleanup and return
    return api_return(success, model_ws)
def api_func(exe, idx, model_ws=None):
    success = False

    name = ex[idx].upper()
    if model_ws is None:
        model_ws = "."

    try:
        mf6 = ModflowApi(exe, working_directory=model_ws)
    except Exception as e:
        print("Failed to load " + exe)
        print("with message: " + str(e))
        return api_return(success, model_ws)

    # initialize the model
    try:
        mf6.initialize()
    except:
        return api_return(success, model_ws)

    # time loop
    current_time = mf6.get_current_time()
    end_time = mf6.get_end_time()

    # get copy of (multi-dim) array with river parameters
    riv_tag = mf6.get_var_address("BOUND", name, riv_packname)
    new_spd = mf6.get_value(riv_tag)

    # model time loop
    idx = 0
    while current_time < end_time:

        # get dt
        dt = mf6.get_time_step()

        # prepare... and reads the RIV data from file!
        mf6.prepare_time_step(dt)

        # set the RIV data through the BMI
        if current_time < 5:
            # set columns of BOUND data (we're setting entire columns of the
            # 2D array for convenience, setting only the value for the active
            # stress period should work too)
            new_spd[:] = [riv_stage, riv_cond, riv_bot]
            mf6.set_value(riv_tag, new_spd)
        else:
            # change only stage data
            new_spd[:] = [riv_stage2, riv_cond, riv_bot]
            mf6.set_value(riv_tag, new_spd)

        kiter = 0
        mf6.prepare_solve()

        while kiter < nouter:
            has_converged = mf6.solve()
            kiter += 1

            if has_converged:
                msg = ("Component {}".format(1) +
                       " converged in {}".format(kiter) + " outer iterations")
                print(msg)
                break

        if not has_converged:
            return api_return(success, model_ws)

        # finalize time step
        mf6.finalize_solve()

        # finalize time step and update time
        mf6.finalize_time_step()
        current_time = mf6.get_current_time()

        # increment counter
        idx += 1

    # cleanup
    try:
        mf6.finalize()
        success = True
    except:
        return api_return(success, model_ws)

    # cleanup and return
    return api_return(success, model_ws)
def api_func(exe, idx, model_ws=None):
    success = False

    name = ex[idx].upper()
    if model_ws is None:
        model_ws = "."

    try:
        mf6 = ModflowApi(exe, working_directory=model_ws)
    except Exception as e:
        print("Failed to load " + exe)
        print("with message: " + str(e))
        return api_return(success, model_ws)

    # initialize the model
    try:
        mf6.initialize()
    except:
        return api_return(success, model_ws)

    # time loop
    current_time = mf6.get_current_time()
    end_time = mf6.get_end_time()

    # maximum outer iterations
    max_iter = mf6.get_value(mf6.get_var_address("MXITER", "SLN_1"))

    # get pointer to simulated heads
    head_tag = mf6.get_var_address("X", name.upper())
    head = mf6.get_value_ptr(head_tag)

    # get pointers to API data
    nbound_tag = mf6.get_var_address("NBOUND", name.upper(), ghb_packname)
    nbound = mf6.get_value_ptr(nbound_tag)
    nodelist_tag = mf6.get_var_address("NODELIST", name.upper(), ghb_packname)
    nodelist = mf6.get_value_ptr(nodelist_tag)
    hcof_tag = mf6.get_var_address("HCOF", name.upper(), ghb_packname)
    hcof = mf6.get_value_ptr(hcof_tag)
    rhs_tag = mf6.get_var_address("RHS", name.upper(), ghb_packname)
    rhs = mf6.get_value_ptr(rhs_tag)

    # set nbound and nodelist
    nbound[0] = 1
    nodelist[0] = ncol

    # model time loop
    while current_time < end_time:

        # get dt
        dt = mf6.get_time_step()

        # prepare... and reads the RIV data from file!
        mf6.prepare_time_step(dt)

        # convergence loop
        kiter = 0
        mf6.prepare_solve()

        while kiter < max_iter:
            # update api package
            hcof[:], rhs[:] = api_ghb_pak(
                hcof,
                rhs,
            )

            # solve with updated api data
            has_converged = mf6.solve()
            kiter += 1

            if has_converged:
                msg = "Converged in {}".format(kiter) + " outer iterations"
                print(msg)
                break

        # finalize time step
        mf6.finalize_solve()

        # finalize time step and update time
        mf6.finalize_time_step()
        current_time = mf6.get_current_time()

        # terminate if model did not converge
        if not has_converged:
            print("model did not converge")
            break

    # cleanup
    try:
        mf6.finalize()
        success = True
    except:
        return api_return(success, model_ws)

    # cleanup and return
    return api_return(success, model_ws)
def api_func(exe, idx, model_ws=None):
    print("\nBMI implementation test:")
    success = False

    name = ex[idx].upper()
    init_wd = os.path.abspath(os.getcwd())
    if model_ws is not None:
        os.chdir(model_ws)

    # get the observations from the standard run
    fpth = os.path.join("..", "{}.head.obs.csv".format(ex[idx]))
    hobs = np.genfromtxt(fpth, delimiter=",", names=True)["H1_6_6"]

    try:
        mf6 = ModflowApi(exe)
    except Exception as e:
        print("Failed to load " + exe)
        print("with message: " + str(e))
        return api_return(success, model_ws)

    # initialize the model
    try:
        mf6.initialize()
    except:
        return api_return(success, model_ws)

    # time loop
    current_time = mf6.get_current_time()
    end_time = mf6.get_end_time()

    # get pointer to simulated heads
    head_tag = mf6.get_var_address("X", name)
    head = mf6.get_value_ptr(head_tag)

    # maximum outer iterations
    mxit_tag = mf6.get_var_address("MXITER", "SLN_1")
    max_iter = mf6.get_value(mxit_tag)

    # get copy of recharge array
    rch_tag = mf6.get_var_address("BOUND", name, rch_pname)
    new_recharge = mf6.get_value(rch_tag).copy()

    # determine initial recharge value
    np.random.seed(0)
    rch = np.random.normal(1) * avg_rch

    # model time loop
    idx = 0
    while current_time < end_time:

        # target head
        htarget = hobs[idx]

        # get dt and prepare for non-linear iterations
        dt = mf6.get_time_step()
        mf6.prepare_time_step(dt)

        est_iter = 0
        while est_iter < 100:
            # base simulation loop
            has_converged = run_perturbation(
                mf6, max_iter, new_recharge, rch_tag, rch
            )
            if not has_converged:
                return api_return(success, model_ws)
            h0 = head.reshape((nrow, ncol))[5, 5]
            r0 = h0 - htarget

            # perturbation simulation loop
            has_converged = run_perturbation(
                mf6, max_iter, new_recharge, rch_tag, rch + drch
            )
            if not has_converged:
                return api_return(success, model_ws)
            h1 = head.reshape((nrow, ncol))[5, 5]
            r1 = h1 - htarget

            # calculate update terms
            dqdr = drch / (r0 - r1)
            dr = r1 * dqdr

            # evaluate if the estimation iterations need to continue
            if abs(r0) < 1e-5:
                msg = (
                    "Estimation for time {:5.1f}".format(current_time)
                    + " converged in {:3d}".format(est_iter)
                    + " iterations"
                    + " -- final recharge={:10.5f}".format(rch)
                    + " residual={:10.2g}".format(rch - rch_rates[idx])
                )
                print(msg)
                break
            else:
                est_iter += 1
                rch += dr

        # solution with final estimated recharge for the timestep
        has_converged = run_perturbation(
            mf6, max_iter, new_recharge, rch_tag, rch
        )
        if not has_converged:
            return api_return(success, model_ws)

        # finalize time step
        mf6.finalize_solve()

        # finalize time step and update time
        mf6.finalize_time_step()
        current_time = mf6.get_current_time()

        # increment counter
        idx += 1
    # cleanup
    try:
        mf6.finalize()
        success = True
    except:
        return api_return(success, model_ws)

    if model_ws is not None:
        os.chdir(init_wd)

    # cleanup and return
    return api_return(success, model_ws)
def api_func(exe, idx, model_ws=None):
    success = False

    name = ex[idx].upper()
    if model_ws is None:
        model_ws = "."

    try:
        mf6 = ModflowApi(exe, working_directory=model_ws)
    except Exception as e:
        print("Failed to load " + exe)
        print("with message: " + str(e))
        return api_return(success, model_ws)

    # initialize the model
    try:
        mf6.initialize()
    except:
        return api_return(success, model_ws)

    # time loop
    current_time = mf6.get_current_time()
    end_time = mf6.get_end_time()

    # get pointer to simulated heads
    head_tag = mf6.get_var_address("X", "LIBGWF_EVT01")
    head = mf6.get_value_ptr(head_tag)

    # maximum outer iterations
    mxit_tag = mf6.get_var_address("MXITER", "SLN_1")
    max_iter = mf6.get_value(mxit_tag)

    # get copy of well data
    well_tag = mf6.get_var_address("BOUND", name, "WEL_0")
    well = mf6.get_value(well_tag)

    twell = np.zeros(ncol, dtype=np.float64)

    # model time loop
    idx = 0
    while current_time < end_time:

        # get dt and prepare for non-linear iterations
        dt = mf6.get_time_step()
        mf6.prepare_time_step(dt)

        # convergence loop
        kiter = 0
        mf6.prepare_solve()

        while kiter < max_iter:

            # update well rate
            twell[:] = head2et_wellrate(head[0])
            well[:, 0] = twell[:]
            mf6.set_value(well_tag, well)

            # solve with updated well rate
            has_converged = mf6.solve()
            kiter += 1

            if has_converged:
                msg = (
                    "Component {}".format(1)
                    + " converged in {}".format(kiter)
                    + " outer iterations"
                )
                print(msg)
                break

        if not has_converged:
            return api_return(success, model_ws)

        # finalize time step
        mf6.finalize_solve()

        # finalize time step and update time
        mf6.finalize_time_step()
        current_time = mf6.get_current_time()

        # increment counter
        idx += 1
    # cleanup
    try:
        mf6.finalize()
        success = True
    except:
        return api_return(success, model_ws)

    # cleanup and return
    return api_return(success, model_ws)
def api_func(exe, idx, model_ws=None):
    success = False

    name = ex[idx].upper()
    if model_ws is None:
        model_ws = "."

    try:
        mf6 = ModflowApi(exe, working_directory=model_ws)
    except Exception as e:
        print("Failed to load " + exe)
        print("with message: " + str(e))
        return api_return(success, model_ws)

    # initialize the model
    try:
        mf6.initialize()
    except:
        return api_return(success, model_ws)

    # time loop
    current_time = mf6.get_current_time()
    end_time = mf6.get_end_time()

    # maximum outer iterations
    mxit_tag = mf6.get_var_address("MXITER", "SLN_1")
    max_iter = mf6.get_value(mxit_tag)

    # get copy of recharge array
    rch_tag = mf6.get_var_address("BOUND", name, rch_pname)
    new_recharge = mf6.get_value(rch_tag)

    # model time loop
    idx = 0
    while current_time < end_time:

        # get dt and prepare for non-linear iterations
        dt = mf6.get_time_step()
        mf6.prepare_time_step(dt)

        # convergence loop
        kiter = 0
        mf6.prepare_solve()

        # update recharge
        new_recharge[:, 0] = rch_spd[idx] * area
        mf6.set_value(rch_tag, new_recharge)

        while kiter < max_iter:
            has_converged = mf6.solve()
            kiter += 1

            if has_converged:
                msg = (
                    "Component {}".format(1)
                    + " converged in {}".format(kiter)
                    + " outer iterations"
                )
                print(msg)
                break

        if not has_converged:
            return api_return(success, model_ws)

        # finalize time step
        mf6.finalize_solve()

        # finalize time step and update time
        mf6.finalize_time_step()
        current_time = mf6.get_current_time()

        # increment counter
        idx += 1

    # cleanup
    try:
        mf6.finalize()
        success = True
    except:
        return api_return(success, model_ws)

    # cleanup and return
    return api_return(success, model_ws)