#include "MCP_Interface.h"
#include "ALG.h"
#include "ALGOptions.h"
#include "Macros.h"
#include "Output.h"
#include "Output_Interface.h"
#include "Options.h"

#undef Char
#include "getstub.h"

#define CRIPPLE_SIZE 200
#define CRIPPLE_NNZ 10000

extern char xxxvers[];
static int debug, functimes, quitnow, wantfuncs;
static int sideineq = 1, sqwarn = 1;
#ifndef LOGFILE
#define LOGFILE 0
#endif
#ifndef STATUSFILE
#define STATUSFILE 0
#endif
#ifndef OPTFILE
#define OPTFILE 0
#endif
static char *logfile = LOGFILE, *statusfile = STATUSFILE, *optfile = OPTFILE;
#ifdef MDEBUG /* debugging malloc */
extern int zapvalue;
#endif

 typedef struct
FuncInfo {
        real    *c_scratch;
        char    *ctype;
        char    *vtype;
        int     *vb;    /* variables with bounds */
        int     *sba;   /* constraints tweaked by jadj */
        int     *sbae;  /* end of sba */
        int     *cb;    /* constraints involving inequalities */
        int     *cbe;   /* end of cb */
        int     n;      /* adjusted problem size */
        int     ineq;   /* number of (simple) side inequalities */
        int     jadj;   /* whether to convert side inequalities from */
                        /* expr >= 0 complements newvar >= 0  to */
                        /* expr + newvar >= 0 complements newvar >= 0 */
        int     nvb;    /* number of bounded variables */
        int     nvr;    /* number of ranged variables */
        int     nnz;    /* adjusted number of Jacobian nonzeros */
        int     ncsq;   /* number of constraints added to make square */
        int     nvsq;   /* number of variables added to make square */
        } FuncInfo;

 static FuncInfo FI;

 static int numJacEvals;
 static Options_Interface *opt;
 static void install_interface(MCP *m);

 static fint
mkey(char *phrase, ftnlen len)
{
        Not_Used(len);

        Options_Set(opt, phrase);
        return 0;
        }

 static char *
Optfile(Option_Info *oi, keyword *kw, char *value)
{
        char *rv = C_val(oi, kw, value);
        if (optfile)
                Options_Read(opt, optfile);
        return rv;
        }

 static char
debug_desc[] = "debug level (default 0): sum of\n\
                1 = show initial z and bounds\n\
                2 = show z and f(z)\n\
                4 = show z and J = f'(z)\n\
                8 = show sparsity of J";

 static char
sideineq_text[] = "handling of side inequalities:\n\
                0 = no warning\n\
                1 = warn (default)\n\
                2 = quit, permitting AMPL loops to test solve_result\n\
                3 = quit, terminating AMPL loops and scripts\n\
                4 = do not make Jacobian nonsingular (avoid nonuniqueness)\n\
                5 = warn and do not make Jacobian nonsingular";

 static char
sqwarn_text[] = "whether to warn of nonsquare systems:\n\
                0 = no\n\
                1 = yes (default)\n\
                2 = quit, permitting AMPL loops to test solve_result\n\
                3 = quit, terminating AMPL loops and scripts";

 static keyword
keywds[] = {
        KW("debug", I_val, &debug, debug_desc),
#ifndef NEOS
        KW("logfile", C_val, &logfile, "name of log file"),
#endif
        KW("maxfwd",  IA_val, voffset_of(ASL, p.maxfwd_), "max vars in fwd AD of common exprs (default 5)"),
#ifndef NEOS
        KW("optfile", Optfile, &optfile, "name of options file"),
#endif
        KW("sideineq", I_val, &sideineq, sideineq_text),
        KW("sqwarn", I_val, &sqwarn, sqwarn_text),
#ifndef NEOS
        KW("statusfile", C_val, &statusfile, "name of status file"),
#endif
        KW("version", Ver_val, 0, "report version"),
        KW("wantsol", WS_val, 0, WS_desc_ASL + 5)
#ifdef MDEBUG
        ,KW("zapvalue", I_val, &zapvalue, "zapvalue (in debugging malloc)")
#endif
        };

 static
keyword cloptions[] = {
        KW("f", IK1_val, &wantfuncs, "list available user-defined functions"),
        KW("q", IK1_val, &quitnow,   "quit now"),
        KW("t", IK1_val, &functimes, "time function evaluations")
        };

 static
Option_Info Oinfo = {
        "LNAME", 0, "LNAME_options", keywds, nkeywds, 3, 0,
        0, mkey, 0, cloptions, sizeof(cloptions) / sizeof(keyword)
        };

 static int
cvarcomp(const void *a, const void *b, void *v)
{
        return ((int*)v)[*(int*)a] - ((int*)v)[*(int*)b];
        }

enum {
        C_COMPL = 0,
        C_LB = 1,
        C_UB = 2,
        C_RANGE = 3,
        C_EQN = 4,
        C_FREE = 5,
        C_ZERO = 6
        };

 static int
mcp_adj(ASL *asl)
{
        int *Cvar, *z, *zc;
        int ct, i, j, k, *ka, L, nc, ncs, nv, nvb, nvr, nvs;
        real *lu;
        cgrad *cg;
        size_t st, st1;

        /* Match variables to equations and inequalities, adding one
         * new variable for each simple inequality, including simple
         * variable bounds, and two new variables for ranges:
         * range constraints, -Infinity < L <= f(x) <= U < Infinity,
         * with L < U, are represented by
         *      f(x) = y complements v
         *      v        complements L <= y <= U
         * in which v and y are new variables.  Ranged variables are
         * handled similarly, with f(x) := x[i] for variable i if it
         * has distinct finite lower and upper bounds.   This adds
         * a new nonzero to column i of the Jacobian matrix.
         */

        nc = n_con;
        ncs = nc + FI.ncsq;
        nv = n_var;
        nvs = nv + FI.nvsq;
        if (!(Cvar = cvar) || FI.ncsq) {
                Cvar = (int * )M1zapalloc(ncs * sizeof(int));
                if (cvar)
                        memcpy(Cvar, cvar, nc*sizeof(int));
                cvar = Cvar;
                }
        st = ncs * sizeof(real);
        st1 = (ncs + nvs) * sizeof(int);
        if (st < st1)
                st = st1;
        FI.c_scratch = (real *)M1zapalloc(st);
        z = (int *)FI.c_scratch;
        zc = z + nvs;
        for (i = 0; i < nc; )
                if ((j = Cvar[i++]))
                        z[j-1] = i;

        lu = LUv;

        /* nvb := number of variables with one or two finite bounds. */
        /* nvr := number of ranged variables: two finite bounds */

        for (i = nvb = nvr = 0; i < nv; i++, lu += 2)
                if (!z[i]) {
                        if (lu[0] > negInfinity) {
                                nvb++;
                                if (lu[1] < Infinity)
                                        nvr++;
                                }
                        else if (lu[1] < Infinity)
                                nvb++;
                        }
        FI.nvb = nvb;
        FI.nvr = nvr;
        FI.vb = (int *)M1zapalloc((sizeof(int)+1)*nvb + ncs);
        FI.vtype = (char * )(FI.vb + nvb);
        FI.ctype = FI.vtype + nvb;
        if (nvb) {
                lu = LUv;
                for (i = k = 0; i < nv; i++, lu += 2)
                        if (!z[i]) {
                                ct = 0;
                                if (lu[0] > negInfinity)
                                        ct = lu[1] < Infinity ? C_RANGE : C_LB;
                                else if (lu[1] < Infinity)
                                        ct = C_UB;
                                else
                                        continue;
                                FI.vtype[k] = ct;
                                FI.vb[k++] = i;
                                }
                }

        /* match equations */

        j = nvs;
        for (i = k = 0; i < nc; i++) {
                if (!Cvar[i]) {
                        lu = LUrhs + (i << 1);
                        if (lu[0] < lu[1]) {
                                Cvar[i] = ++j;

                                /* For range constraints, */
                                /* increment j twice... */

                                if (lu[0] > negInfinity)
                                        FI.ctype[i] = lu[1] < Infinity
                                             ? (j++,C_RANGE) : C_LB;
                                else
                                        FI.ctype[i] = lu[1] < Infinity
                                             ? C_UB : C_FREE;
                                }
                        else {
                                FI.ctype[i] = C_EQN;
                                while(z[k])
                                        k++;
                                z[k++] = i + 1;
                                Cvar[i] = k;
                                }
                        }
                }
        while(i < ncs) {
                FI.ctype[i] = C_ZERO;
                while(z[k])
                        k++;
                Cvar[i++] = ++k;
                }

        /* Prepare zc = permutation of constraints. */

        for (i = 0; i < ncs; i++)
                --Cvar[zc[i] = i];
        if (nc > 1)
                qsortv(zc, nc, sizeof(int), cvarcomp, Cvar);

        /* Adjust A_colstarts to insert new singleton Jacobian rows */
        /* for ranged variables. */

        ka = A_colstarts + 1;
        j = k = 0;
        L = FI.nvb ? FI.vb[0] : nv;
        for (i = 0; i < nv; i++) {
                z[i] = ka[i] + j - 1;
                if (i == L) {
                        j++;
                        L = ++k < FI.nvb ? FI.vb[k] : nv;
                        }
                ka[i] += j;
                }

        /* Adjust cg->goff for use in Jacobian computations */
        /* -- see sparseJ() below. */

        for (i = nc; i-- > 0; ) {
                j = zc[i];
                for (cg = Cgrad[j]; cg; cg = cg->next)
                        cg->goff = z[cg->varno]--;
                }

        FI.nnz = nzc + 2*(nranges + nvr) + nvb;
        return ncs + nranges + nvb + nvr;
        }

 static void
prepare_jadj(ASL *asl)
{
        int *Cvar, i, j, n, ncb, nvb, *x, *y;

/* Adjust single side inequalities to make their contribution
 * to the Jacobian matrix nonsingular -- suggested by
 * Todd Munson (May 2000):  change, e.g., from
 *
 *      expr >= 0 complements newvar >= 0
 * to
 *      expr - newvar >= 0 complements newvar >= 0
 *
 * which makes newvar's Jacobian column nonzero but introduces
 * nonuniqueness (for expr > 0, we can have either newvar = 0
 * or newvar = expr).  Preliminary testing suggests that this
 * change (now the default) often saves iterations and takes
 * fewer pivots than the alternative of treating sinqle
 * inequalities the same as double inequalities, i.e.,
 * converting expr >= 0 to
 *
 *      newvar1 - expr  complements  -Infinity <= newvar2 <= Infinity
 *      newvar2         complements  0 <= newvar1 <= Infinity
 *
 * which also avoids adding zero columns to the Jacobian matrix
 * and contributes only the new nonuniqueness that newvar2 can
 * assume any nonnegative value.
 */

        ncb = FI.ineq - nranges;
        nvb = FI.nvb - FI.nvr;
        FI.nnz += ncb + nvb;
        n = FI.ineq + nvb + ncb;
        x = FI.sba = (int*)M1alloc(n*sizeof(int));
        y = FI.cb = FI.sbae = x + nvb + ncb;
        FI.cbe = y + FI.ineq;
        if (FI.ineq) {
                n = n_con;
                Cvar = cvar;
                for(i = 0; i < n; i++)
                        switch(FI.ctype[i]) {
                        case C_LB:
                        case C_UB:
                        case C_FREE:
                                *x++ = Cvar[i];
                                /* no break */
                        case C_RANGE:
                                *y++ = i;
                        }
                }
        if ((n = FI.nvb)) {
                j = FI.n - (n + FI.nvr);
                for(i = 0; i < n; i++, j++)
                        switch(FI.vtype[i]) {
                        case C_RANGE:
                                j++;
                                break;
                        case C_LB:
                        case C_UB:
                                *x++ = j;
                                break;
                        }
                }
        }

 static FILE *
Fopen(char *name, char *kind)
{
        FILE *f;
        if (!(f = fopen(name, "w"))) {
                fprintf(Stderr, "Cannot open %s file \"%s\".\n", kind, name);
                exit(2);
                }
        return f;
        }

 int
main(int argc, char **argv)
{
        ASL *asl;
        FILE *nl, *logfl = NULL, *statusfl = NULL;
        Information info;
        MCP *mcp;
        char buf[1024];
        char verBuf[16];
        char *stub;
        int i, n, tc;
        typedef struct { char *msg; int LNAMEcode, code; } Sol_info;
        Sol_info *si, *sie;
        static Sol_info solinfo[] = {
                { "Solution found", MCP_Solved, 0 },
                { "Not enough progress", MCP_NoProgress, 510 },
                { "Major iteration limit", MCP_MajorIterationLimit, 400 },
                { "Cumulative iteration limit", MCP_MinorIterationLimit, 401 },
                { "Time limit", MCP_TimeLimit, 402 },
                { "User interrupt", MCP_UserInterrupt, 403 },
                { "Inconsistent bounds", MCP_BoundError, 200 },
                { "Domain error", MCP_DomainError, 503 },
                { "Infeasible", MCP_Infeasible, 201 },
                { "Solver error", MCP_Error, 501 }
                };

        Not_Used(argc);

        asl = ASL_alloc(ASL_read_fg);

        /* Get version of UNAME */
        strncpy(verBuf, ALG_Version(), sizeof(verBuf));
        verBuf[sizeof(verBuf)-1] = '\0';
        Oinfo.bsname = stub = verBuf;
#ifdef Student_Edition
        Oinfo.version = (char*)M1alloc(strlen(xxxvers+6) + strlen(stub) + 8);
        strcpy(Oinfo.version, "Student ");
        sprintf(Oinfo.version+8, xxxvers+6, stub);
#else
        Oinfo.version = (char*)M1alloc(strlen(xxxvers+6) + strlen(stub));
        sprintf(Oinfo.version, xxxvers+6, stub);
#endif

        asl->i.congrd_mode = 2; /* sparse Jacobians */
        want_xpi0 = 1;  /* want X0 */
        stub = getstub(&argv, &Oinfo);
        if (wantfuncs)
                show_funcs();
        if (quitnow | wantfuncs)
                exit(0);
        if (!stub)
                usage_ASL(&Oinfo, 1);

        nl = jac0dim(stub, (fint)strlen(stub));

        opt = Options_Create();
        ALG_AddOptions(opt);
        Options_Default(opt);
        Options_SetDouble(opt, "infinity", Infinity);

        if (getopts(argv, &Oinfo)) {
                Options_Destroy(opt);
                exit(2);
                }

        FI.nvsq = FI.ncsq = 0;
        if ((n = n_eqn + n_cc) != n_var) {
                if (sqwarn & 3) {
                        sprintf(buf, "%s %s %d x %d.\n", Oinfo.bsname,
                                "requires a square system; this one is",
                                n, n_var);
                        if (sqwarn & 2) {
                                if (sqwarn & 1) {
                                        fprintf(Stderr, "%s", buf);
                                        Options_Destroy(opt);
                                        exit(1);
                                        }
                                solve_result_num = 504;
                                goto quit_now;
                                }
                        }
                if ((n -= n_var) > 0)
                        FI.nvsq = n;
                else
                        FI.ncsq = -n;
                }

        Output_SetLog(stdout);
        if (statusfile)
                Output_SetStatus(statusfl = Fopen(statusfile, "statusfile"));
        if (logfile)
                Output_SetLog(logfl = Fopen(logfile, "logfile"));
        Options_Display(opt);

        fg_read(nl, ASL_no_linear_cc_rhs_adjust);
        FI.n = mcp_adj(asl);

        FI.ineq = n_con - n_eqn - n_cc;
        if (sideineq & 3 && (n = FI.ineq + FI.nvb) > 0) {
                i = sideineq & 2 ? sprintf(buf, "%s: ", Oinfo.bsname) : 0;
                i += sprintf(buf+i,"%d%s%d%s%d%s%d%s%d%s",
                        n + FI.nvr + nranges, " side inequalities:\n\t",
                        FI.nvb - FI.nvr, " simple bounds,\n\t",
                        FI.nvr, " range bounds,\n\t", FI.ineq - nranges,
                        " single inequality constraints, and\n\t",
                        nranges, " range constraints.");
                if (sideineq & 2) {
                        if (sideineq & 1) {
                                fprintf(Stderr, "%s\n", buf);
                                Options_Destroy(opt);
                                exit(1);
                                }
                        solve_result_num = 505;
 quit_now:
                        write_sol(buf, 0, 0, &Oinfo);
                        goto bailout;
                        }
                printf("%s\n", buf);
                need_nl = 0;
                }
        if ((FI.ineq + FI.nvb - FI.nvr - nranges) > 0
         && (FI.jadj = !(sideineq & 4)))
                prepare_jadj(asl);

        info.generate_output = Output_Log | Output_Status | Output_Listing;
        info.use_start = True;
        info.use_basics = True;

        mcp = MCP_Create(FI.n, FI.nnz);
        MCP_Jacobian_Structure_Constant(mcp, 1);
        MCP_Jacobian_Data_Contiguous(mcp, 1);
        install_interface(mcp);

        tc = ALG_Solve(mcp, &info);

        i = sprintf(buf, "%s: ", Oinfo.bsname);
        sie = solinfo + sizeof(solinfo) / sizeof(Sol_info);
        for(si = solinfo;; si++) {
                if (si >= sie) {
                        i += sprintf(buf + i, "Unexpected return code %d", tc);
                        solve_result_num = 500;
                        break;
                        }
                if (si->LNAMEcode == tc) {
                        i += sprintf(buf + i, si->msg);
                        solve_result_num = si->code;
                        break;
                        }
                }
        i += sprintf(buf + i, ".\n%d iterations (%d for crash); %d pivots",
                info.major_iterations + info.crash_iterations,
                info.crash_iterations, info.minor_iterations);
        i += sprintf(buf + i, ".\n%d function, %d gradient evaluations.",
                info.function_evaluations, info.jacobian_evaluations);
        write_sol(buf, MCP_GetX(mcp), 0, &Oinfo);
        MCP_Destroy(mcp);
 bailout:
        Options_Destroy(opt);
        if (logfl != NULL)
                fclose(logfl);
        if (statusfl != NULL)
                fclose(statusfl);
        return 0;
        }

 static void
bad_n(char *who, char *what, int n)
{
        fprintf(Stderr,
            "Bad dimension argument '%s' to %s: got %d, expected %d\nexiting . . .\n",
            what, who, n, FI.n);
        exit(9);
        }

 static void
bad_nnz(char *who, char *what, int n)
{
        fprintf(Stderr,
            "Bad dimension argument '%s' to %s: got %d, expected %d\nexiting . . .\n",
            what, who, n, FI.nnz);
        exit(9);
        }

 static void
zshow(char *what, real *z, int n)
{
        int i;

        printf("\n%s:\n", what);
        for (i = 0; i < n; i++)
                printf("%d\t%.g\n", i, z[i]);
        }

 static CB_FUNC(int)
function_evaluation(void *v, int n, real *z, real *f)
{
        ASL *asl = cur_ASL;
        int *Cvar, i, j, nc, nr, *sb, *sbe;
        fint nd = 0;
        real *c, *lu;
        Not_Used(v);

        if (n != FI.n)
                bad_n("F", "n", n);
        nc = n_con + FI.ncsq;
        lu = LUrhs;

        conval(z, c = FI.c_scratch, &nd);
        if (nd) return 1;

        Cvar = cvar;
        for (i = 0; i < nc; i++, lu += 2) {
                j = Cvar[i];
                switch (FI.ctype[i]) {
                case C_COMPL:
                        f[j] = c[i];
                        break;
                case C_LB:
                case C_EQN:
                        f[j] = c[i] - lu[0];
                        break;
                case C_UB:
                        f[j] = c[i] - lu[1];
                        break;
                case C_RANGE:
                        f[j] = c[i] - z[j+1];
                        f[j+1] = z[j];
                        break;
                case C_FREE:
                case C_ZERO:
                        f[j] = 0;
                        }
                }
        nr = n - (FI.nvb + FI.nvr);
        for (i = 0; i < FI.nvb; i++, nr++) {
                j = FI.vb[i];
                lu = LUv + 2*j;
                switch(FI.vtype[i]) {
                  case C_LB:
                        f[nr] = z[j] - lu[0];
                        break;
                  case C_UB:
                        f[nr] = z[j] - lu[1];
                        break;
                  case C_RANGE:
                        f[nr] = z[j] - z[nr+1];
                        ++nr;
                        f[nr] = z[nr-1];
                        }
                }
        if ((sb = FI.sba))
                for(sbe = FI.sbae; sb < sbe; ) {
                        j = *sb++;
                        f[j] -= z[j];
                        }
        if (debug & 2) {
                printf("\nf = F(z):\ni\tz[i]\t\tf[i]\n");
                for (i = 0; i < n; i++)
                        printf("%d\t%.g\t%.g\n", i, z[i], f[i]);
                }
        return 0;
        }

 static void
sparse_struct(int n, int maxnnz, int *row, int *col, int *len)
{
        ASL *asl = cur_ASL;
        int *Cvar, i, j, ja, k, *ka, nc, nv, *vb, *vbe;
        cgrad *cg;
        char *vt;

        if (n != FI.n)
                bad_n("sparse_struct", "n", n);
        if (maxnnz < FI.nnz)
                bad_nnz("sparse_struct", "nnz", maxnnz);
        nv = n_var;
        ka = A_colstarts;
        for (i = 0; i < nv; i++) {
                col[i] = ka[i] + 1;
                len[i] = ka[i+1] - ka[i];
                }
        j = ka[nv] + 1;
        for(nv += FI.nvsq; i < nv; i++) {
                col[i] = j;
                len[i] = 0;
                }
        nc = n_con;
        Cvar = cvar;
        for (i = 0; i < nc; ) {
                j = Cvar[i] + 1;
                for (cg = Cgrad[i++]; cg; cg = cg->next)
                        row[cg->goff] = j;
                }
        j = nzc + FI.nvb;
        nc += FI.ncsq;
        ja = FI.jadj;
        for (i = 0; i < nc; i++)
                switch (FI.ctype[i]) {
                case C_LB:
                case C_UB:
                case C_FREE:
                        col[nv] = j+1;
                        if ((len[nv++] = ja))
                                row[j++] = Cvar[i] + 1;
                        break;

                case C_RANGE:
                        row[j++] = k = Cvar[i] + 2;
                        col[nv] = j;
                        len[nv++] = 1;

                        row[j++] = k - 1;
                        col[nv] = j;
                        len[nv++] = 1;
                }
        if (FI.nvb) {
                k = nc + nranges;
                vb = FI.vb;
                vbe = vb + FI.nvb;
                vt = FI.vtype;
                ++ka;
                do {
                        i = *vb++;
                        if (*vt++ == C_RANGE) {
                                row[j++] = k += 2;
                                col[nv] = j;
                                len[nv++] = 1;

                                row[ka[i]-1] = row[j++] = k-1;
                                col[nv] = j;
                                len[nv++] = 1;
                                }
                        else {
                                col[nv] = j+1;
                                row[ka[i]-1] = ++k;
                                if ((len[nv++] = ja))
                                        row[j++] = k;
                                }
                        } while (vb < vbe);
                }
        if (debug & 8) {
                printf("\nJacobian nonzeros:\ni\tcol[i]\trow[i]\n");
                for(i = j = 0; j < n; j++)
                        for(k = i + len[j]; i < k; i++)
                                printf("%d\t%d\t%d\n", i, j, row[i]);
                }
        }

 static int
sparseJ(int n, int maxnnz, double *z, double *J)
{
        ASL *asl = cur_ASL;
        int *cb, *cbe, i, j, *ka;
        fint nd = 0;

        if (n != FI.n)
                bad_n("sparseJ", "n", n);
        if (maxnnz < FI.nnz)
                bad_nnz("sparseJ", "nnz", maxnnz);
        if (debug & 4)
                zshow("z in sparseJ", z, n);

        jacval(z, J, &nd);
        if (nd) return 1;

        j = nzc + FI.nvb;
        if (FI.sba) {
                cb = FI.cb;
                cbe = FI.cbe;
                while(cb < cbe)
                        switch(FI.ctype[*cb++]) {
                        case C_LB:
                        case C_UB:
                        case C_FREE:
                                J[j++] = -1;
                                break;
                        case C_RANGE:
                                J[j++] = 1;
                                J[j++] = -1;
                        }
                for(i = 0; i < FI.nvb; i++)
                        switch(FI.vtype[i]) {
                        case C_LB:
                        case C_UB:
                        case C_FREE:
                                J[j++] = -1;
                                break;
                        case C_RANGE:
                                J[j++] = 1;
                                J[j++] = -1;
                        }
                }
        else
                for(i = FI.nvr + nranges; i > 0; --i) {
                        J[j++] = 1;
                        J[j++] = -1;
                        }
        ka = A_colstarts + 1;
        --J;
        for(i = 0; i < FI.nvb; i++)
                J[ka[FI.vb[i]]] = 1;
        if (debug & 4)
                zshow("J in sparseJ", J+1, FI.nnz);
        return 0;
        }

 static CB_FUNC(void)
problem_size(void *v, int *n, int *nnz)
{
#ifdef Student_Edition
        ASL *asl = cur_ASL;
        Not_Used(v);

        if (FI.n  > CRIPPLE_SIZE || FI.nnz > CRIPPLE_NNZ) {
                fflush(stdout);
                fprintf(Stderr,
"\nSorry, the student edition is limited to\n\
%d variables and %d Jacobian nonzeros.\n\
%sou have %d variables and %d nonzeros.\n",
                        CRIPPLE_SIZE, CRIPPLE_NNZ,
                        n_var != FI.n || nzc != FI.nnz
                        ? "After problem adjustments,\ny" : "Y",
                        FI.n, FI.nnz);
                exit(1);
                }
#else
        Not_Used(v);
#endif
        *n = FI.n;
        *nnz = FI.nnz;
        }

 static CB_FUNC(void)
bounds(void *v, int n, real *z, real *lower, real *upper)
{
        ASL *asl = cur_ASL;
        int i, j, k, nc, nv;
        real Lb = 0, Ub = 0, *lu;
        Not_Used(v);

        if (n != FI.n)
                bad_n("bounds", "n", n);
        nv = n_var;
        lu = LUv;
        if (X0)
                memcpy(z, X0, nv * sizeof(real));
        else
                memset(z, 0, nv * sizeof(real));
        for (i = 0; i < nv; i++, lu += 2) {
                lower[i] = lu[0];
                upper[i] = lu[1];
                }
        k = nc = n_con + FI.ncsq;
        for (i = 0; i < nc; i++)
                if ((j = cvar[i]) >= nv) {
                        switch (FI.ctype[i]) {
                        case C_LB:
                                Lb = 0;
                                Ub = Infinity;
                                break;
                        case C_UB:
                                Lb = negInfinity;
                                Ub = 0;
                                break;
                        case C_RANGE:
                                lu = LUrhs + (i << 1);
                                k = j + 1;
                                z[k] = lu[0] + 0.5 * (lu[1] - lu[0]);
                                lower[k] = lu[0];
                                upper[k] = lu[1];
                                /* no break; */
                        case C_EQN:
                        case C_ZERO:
                                Lb = negInfinity;
                                Ub = Infinity;
                                break;
                        case C_FREE:
                                Lb = 0;
                                Ub = 0;
                                break;
                        default:
                                fprintf(Stderr,
                                    "bounds: FI.ctype[%d] = %d\n",
                                    i, FI.ctype[i]);
                                exit(1);
                                }
                        z[j] = 0;
                        lower[j] = Lb;
                        upper[j] = Ub;
                        }
        for(i = 0, k = n - FI.nvb - FI.nvr; k < n; i++, k++) {
                z[k] = 0;
                switch(FI.vtype[i]) {
                  case C_LB:
                        lower[k] = 0;
                        upper[k] = Infinity;
                        break;
                  case C_UB:
                        lower[k] = negInfinity;
                        upper[k] = 0;
                        break;
                  case C_RANGE:
                        lower[k] = negInfinity;
                        upper[k] = Infinity;
                        z[++k] = 0;
                        j = FI.vb[i];
                        lu = LUv + 2*j;
                        lower[k] = lu[0];
                        upper[k] = lu[1];
                        break;
                  default:
                        fprintf(Stderr, "bounds: FI.vtype[%d] = %d\n",
                                i, FI.vtype[i]);
                        exit(1);
                  }
                }
        for(i = FI.nvb; i-- > 0; ) {
                j = FI.vb[i];
                lower[j] = negInfinity;
                upper[j] = Infinity;
                }
        if (debug & 1) {
                printf("i\tz[i]\tlower[i]\tupper[i]\n");
                for(i = 0; i < n; i++)
                        printf("%d\t%.g\t%.g\t%.g\n", i, z[i],
                                lower[i], upper[i]);
                }
        return;
        }

 static CB_FUNC(void)
variable_name(void *v, int variable, char *buffer, int buffer_size)
{
        ASL * asl = cur_ASL;
        Not_Used(v);

        strncpy(buffer, var_name(variable - 1), buffer_size - 1);
        buffer[buffer_size-1] = '\0';
        return;
        }

 static CB_FUNC(void)
constraint_name(void *v, int constraint, char *buffer, int buffer_size)
{
        ASL * asl = cur_ASL;
        Not_Used(v);

        strncpy(buffer, con_name(constraint - 1), buffer_size - 1);
        buffer[buffer_size-1] = '\0';
        return;
        }

 static CB_FUNC(int)
jacobian_evaluation(void *v, int n, double *z, int  wantf, double *f,
        int *nnz, int *col_start, int *col_len, int *row, double *data)
{
        int     err;

        if (wantf)
                err = function_evaluation(v, n, z, f);
        else
                err = 0;

        if (numJacEvals == 0)
                sparse_struct(n, *nnz, row, col_start, col_len);
        numJacEvals++;
        err += sparseJ(n, *nnz, z, data);

        *nnz = col_start[n - 1] + col_len[n - 1] - 1;
        return err;
        }

 static CB_FUNC(void)
jac_typ(void *v, int nnz, int *typ)
{
        ASL *asl = cur_ASL;
        cgrad *cg;
        int i, n;
        Not_Used(v);

        for (i = 0, n = nnz; i < n; i++)
                typ[i] = PRESOLVE_LINEAR;

        for (i = 0, n = nlc; i < n; i++)
                for (cg = Cgrad[i]; cg; cg = cg->next)
                        if (cg->varno < nlvc)
                                typ[cg->goff] = PRESOLVE_NONLINEAR;
        }

 static MCP_Interface
m_interface = {
        NULL,
        problem_size, bounds,
        function_evaluation, jacobian_evaluation,
        NULL, /* Hessian evaluation */
        NULL, NULL,
        variable_name, constraint_name,
        NULL
        };

 static Presolve_Interface
p_interface = {
        NULL,
        NULL, NULL,
        NULL, NULL,
        jac_typ,
        NULL
        };

 static void
install_interface(MCP *m)
{
        MCP_SetInterface(m, &m_interface);
        MCP_SetPresolveInterface(m, &p_interface);
        }
