/* BEGIN LICENSE BLOCK
 * Version: CMPL 1.1
 *
 * The contents of this file are subject to the Cisco-style Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file except
 * in compliance with the License.  You may obtain a copy of the License
 * at www.eclipseclp.org/license.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
 * the License for the specific language governing rights and limitations
 * under the License. 
 * 
 * The Original Code is  The ECLiPSe/HiGHS Interface
 * The Initial Developer of the Original Code is Joachim Schimpf
 * Portions created by the Initial Developer are
 * Copyright (C) 2024 Joachim Schimpf
 * 
 * Contributor(s): Joachim Schimpf, Coninfer Ltd
 * 
 * END LICENSE BLOCK */


/*
 * ECLiPSe/HiGHS interface (for inclusion in eplex.c)
 *
 * Notes on HiGHS
 *
 * HiGHS parameters are identified with string names. The interface
 * is good enough for us not to need an explicit parameter table.
 *
 * HiGHS uses HighsInt almost everywhere, which may not be the same
 * size as int (depending on its build configuration).
 * This is a problem mainly when integer arrays are passed.
 * We currently require (and assert) that the size matches!
 * 
 * In HiGHS, all rows are ranged, so we map
 *      LHS LE RHS     -INF =< LHS =< RHS
 *      LHS GE RHS      RHS =< LHS =< +INF
 *      LHS EQ RHS      RHS =< LHS =< RHS
 *
 * TODO:
 *      - activate QP, IPM, PDLP
 *      - clean up message printing (output_flag, log_to_console, log_file)
 *      - change all integer arrays being passed to a configurable type
 *        and remove restriction sizeof(HighsInt) == sizeof(int)
 *      - make column type codes configurable
 *      - make basis type codes configurable
 *      - number of integers should be counted in eplex.c (get rid of getnumint)
 *
 * Wish list for HiGHS C interface:
 *      - functions to set upper and lower bounds separately
 *          Highs_changeColUpper(highs, i, ub)
 *          Highs_changeColLower(highs, i, lb)
 *          (also for Rows?)
 *      - function to get upper and lower bounds only
 *           Highs_getColBounds(highs, j, lb, ub)
 *           or Highs_getColBoundsByRange(highs, j1, j2, lbs, ubs)
 *      - allow NULL for unwanted solution arrays in Highs_getSolution()
 *      - print hooks for error/warning/log
 * Wish list for HiGHS:
 *      - treat empty problem as optimal
 *
 * HiGHS functions mostly return kHighsStatus (-1 error, 0 ok, 1 warning).
 *
 * The functions here return 0 for success, 1 for error, -1 for unimplemented.
 */


/* -------------------- Initialization and setup -------------------- */


static void
_error(char *msg)
{
    ec_outfs(solver_streams[ErrType], msg);
    ec_newline(solver_streams[ErrType]);
}


static void
_warn(char *msg)
{
    ec_outfs(solver_streams[WrnType], msg);
    ec_newline(solver_streams[WrnType]);
}


static int
cpx_init_env(CPXENVptr *penv, char *licloc, int serialnum, char *subdir)
{
    /* required when using Highs functions with HighsInt output arrays */
    assert(sizeof(HighsInt) == sizeof(int));

    /* Use a global dummy problem as "environment", for holding default settings */
    CPXENVptr env = Highs_create();

    Highs_setBoolOptionValue(env, "log_to_console", 0);

    *penv = env;
    return 0;
}


static void
cpx_exit(CPXENVptr *penv)
{
    Highs_destroy(*penv);
    *penv = 0;
}


/* -------------------- Set/change problem data -------------------- */

static inline int
cpx_setbds(void* highs, int j, double lb, double ub)
{
    return Highs_changeColBounds(highs, j, lb, ub) ? 1 : 0;
}


static inline int
cpx_setlb(void* highs, int j, double lb)
{
    double ub[1];
    double da[1];
    HighsInt dummy;
    int res = Highs_getColsByRange(highs, j, j, &dummy, da, da, ub, &dummy, NULL, NULL, NULL);
    return res || Highs_changeColBounds(highs, j, lb, ub[0]) ? 1 : 0;
}


static inline int
cpx_setub(void* highs, int j, double ub)
{
    double lb[1];
    double da[1];
    HighsInt dummy;
    int res = Highs_getColsByRange(highs, j, j, &dummy, da, lb, da, &dummy, NULL, NULL, NULL);
    return res || Highs_changeColBounds(highs, j, lb[0], ub) ? 1 : 0;
}


static int
cpx_chgrhs(void *highs, int sz, int *row, double *rhs)
{
    int i;
    HighsInt num_rows, num_nz;
    double lower[sz], upper[sz];
    if (Highs_getRowsBySet(highs, sz, row, 
        &num_rows, lower, upper, &num_nz, NULL, NULL, NULL))
    {
        return 1;
    }
    for (i=0; i<sz; ++i) {
        if (lower[i] == upper[i]) {
            lower[i] = upper[i] = rhs[i];
        } else if (lower[i] <= -Highs_getInfinity(highs) ) {
            upper[i] = rhs[i];
        } else if (Highs_getInfinity(highs) <= upper[i]) {
            lower[i] = rhs[i];
        } else {
            _error("HiGHS interface: no support for ranged rows");
            return 1;
        }
    }
    return Highs_changeRowsBoundsBySet(highs, sz, row, lower, upper) ? 1 : 0;
}


static int
cpx_chgobj(void* highs, int sz, int *row, double *obj)
{
    assert(sizeof(HighsInt) == sizeof(int));
    return Highs_changeColsCostBySet(highs, sz, row, obj) ? 1 : 0;
}


static inline void
cpx_chgobjsen(void* highs, int sense)
{
    Highs_changeObjectiveSense(highs, sense==SENSE_MIN ? kHighsObjSenseMinimize : kHighsObjSenseMaximize);
}


static int
cpx_chgctype(void* highs, int sz, int *col, char *ctypes)
{
    int i;
    int res = 0;
    for (i=0; i<sz; ++i) {
        res |= Highs_changeColIntegrality(highs, col[i],
                ctypes[i] == 'B' ? kHighsVarTypeInteger :
                ctypes[i] == 'I' ? kHighsVarTypeInteger :
                kHighsVarTypeContinuous);
    }
    return res ? 1 : 0;
}


static int
cpx_addcols(void* highs,
        int nc, int nnz, double *obj,
        int *matbeg, int *matind, double *matval,
        double *lb, double *ub)
{
    return Highs_addCols(highs, nc, obj, lb, ub, nnz, matbeg, matind, matval) ? 1 : 0;
}


static int
cpx_addrows(void* highs,
        int nr, int nnz, double *rhs, char *sense,
        int *matbeg, int *matind, double *matval)
{
    const double pinf = Highs_getInfinity(highs);
    const double minf = -pinf;
    double row_lower[nr];
    double row_upper[nr];
    int i;

    /* convert to ranged rows */
    for(i=0; i<nr; ++i) {
        switch(sense[i]) {
        case SOLVER_SENSE_EQ:
            row_lower[i] = row_upper[i] = rhs[i];
            break;
        case SOLVER_SENSE_LE:
            row_lower[i] = minf; row_upper[i] = rhs[i];
            break;
        case SOLVER_SENSE_GE:
            row_lower[i] = rhs[i]; row_upper[i] = pinf;
            break;
        default:
           return 1;
        }
    }
    return Highs_addRows(highs, nr, row_lower, row_upper, nnz, matbeg, matind, matval) ? 1 : 0;
}


static inline int
cpx_delrangeofrows(lp_desc *lpd, int from, int to)
{
    return Highs_deleteRowsByRange(lpd->lp, from, to-1) ? 1 : 0;
}


static inline int
cpx_delrangeofcols(lp_desc *lpd, int from, int to)
{
    return Highs_deleteColsByRange(lpd->lp, from, to-1) ? 1 : 0;
}


static int
cpx_chgname(void* highs, int which, int i, const char *name, int length)
{
    switch(which)
    {
    case 'c':
	return Highs_passColName(highs, i, name) ? 1 : 0;
    case 'r':
	return Highs_passRowName(highs, i, name) ? 1 : 0;
    default:
    	return -1;
    }
}


static inline int
cpx_addsos(void* highs, int nsos, int nsosnz, sostype_t *sostype, int *sosbeg, int *sosind, double *sosref)
{
    return -1;
}


static int
cpx_delsos(lp_desc *lpd, int from, int to)
{
    return -1;
}


static int
cpx_loadbasis(void* highs, int nc, int nr, int* cbase, int* rbase)
{
    assert(sizeof(HighsInt) == sizeof(int));
    assert(Highs_getNumCol(highs) == nc && Highs_getNumRow(highs) == nr);
    return Highs_setBasis(highs, (HighsInt*)cbase, (HighsInt*)rbase) ? 1 : 0;
}


static inline int
cpx_chgprobtype(void* highs, int type)
{
    switch(type)
    {
        case PROBLEM_LP:
        case PROBLEM_MIP:
        case PROBLEM_RELAXEDL:
        case PROBLEM_FIXEDL:
#ifdef HAS_QUADRATIC
        case PROBLEM_QP:
# ifdef HAS_MIQP
        case PROBLEM_MIQP:
        case PROBLEM_RELAXEDQ:
# endif
#endif
            return 0;
    }
    return -1;          /* unimplemented */
}


static inline int
cpx_loadorder(void* highs, int i, int *idx, int *prio, int *bdir)
{
    return -1;
}


static inline int
cpx_chgqpcoef(void* highs, int i, int j, double val)
{
    return -1;
}


/* -------------------- Problem creation -------------------- */


static int _copy_options(void*, void*);

static int
cpx_loadprob(lp_desc *lpd)
{
    int i,res;

    lpd->lp = Highs_create();
    lpd->lpcopy = NULL;
    if (!lpd->lp)
        return 1;
    Highs_passModelName(lpd->lp, "eclipse");
    _copy_options(cpx_env, lpd->lp);

    /* convert to ranged rows */
    const double pinf = Highs_getInfinity(lpd->lp);
    const double minf = -pinf;
    double row_lower[lpd->mar];
    double row_upper[lpd->mar];
    for(i=0; i<lpd->mar; ++i) {
        switch(lpd->senx[i]) {
        case SOLVER_SENSE_EQ:
            row_lower[i] = row_upper[i] = lpd->rhsx[i];
            break;
        case SOLVER_SENSE_LE:
            row_lower[i] = minf; row_upper[i] = lpd->rhsx[i];
            break;
        case SOLVER_SENSE_GE:
            row_lower[i] = lpd->rhsx[i]; row_upper[i] = pinf;
            break;
        default:
           return 1;
        }
    }

    int highs_ctype[lpd->mac];
    int has_integers = 0;
    for(i=0; i<lpd->mac; ++i) {
        switch(lpd->ctype[i]) {
            case 'I':
            case 'B':
                highs_ctype[i] = kHighsVarTypeInteger;
                has_integers = 1;
                break;
            default:
                highs_ctype[i] = kHighsVarTypeContinuous;
                break;
        }
    }
    assert(has_integers ? IsMIPProb(lpd->prob_type) : !IsMIPProb(lpd->prob_type));

    res = Highs_passModel(lpd->lp,
            lpd->mac, lpd->mar, lpd->matnz, 0,
            kHighsMatrixFormatColwise, kHighsHessianFormatTriangular,
            lpd->sense==SENSE_MIN ? kHighsObjSenseMinimize : kHighsObjSenseMaximize,
            0.0,
            lpd->objx, lpd->bdl, lpd->bdu,
            row_lower, row_upper,
            lpd->matbeg, lpd->matind, lpd->matval,
            NULL, NULL, NULL,   /* QP matrix */
            has_integers ? highs_ctype : NULL);
    return res ? 1 : 0;
}


static inline void
cpx_freeprob(lp_desc *lpd)
{
    if (lpd->lp)
        Highs_destroy(lpd->lp);
}


/* -------------------- Retrieve problem data -------------------- */

static int
cpx_getrhs(void* highs, double *rhs, int i)
{
    HighsInt num_rows, num_nz;
    double lower[1], upper[1];
    if (Highs_getRowsByRange(highs, i, i,
            &num_rows, lower, upper, &num_nz, NULL, NULL, NULL))
    {
        return 1;
    }
    if (lower[0] == upper[0]) {
        *rhs = lower[0];
    } else if (lower[0] <= -Highs_getInfinity(highs) ) {
        *rhs = upper[0];
    } else if (Highs_getInfinity(highs) <= upper[0]) {
        *rhs = lower[0];
    } else {
        _error("HiGHS interface: no support for ranged rows");
        return 1;
    }
    return 0;
}


static inline int
cpx_getsense(void* highs, char *sense, int i)
{
    HighsInt num_rows, num_nz;
    double lower[1], upper[1];
    if (Highs_getRowsByRange(highs, i, i,
        &num_rows, lower, upper, &num_nz, NULL, NULL, NULL))
    {
        return 1;
    }
    if (lower[0] == upper[0]) {
        *sense = SOLVER_SENSE_EQ;
    } else if (lower[0] <= -Highs_getInfinity(highs) ) {
        *sense = SOLVER_SENSE_LE;
    } else if (Highs_getInfinity(highs) <= upper[0]) {
        *sense = SOLVER_SENSE_GE;
    } else {
        _error("HiGHS interface: no support for ranged rows");
        return 1;
    }
    return 0;
}


static inline int
cpx_getlb(void* highs, double *lb, int j)
{
    double da[1];
    HighsInt dummy;
    return Highs_getColsByRange(highs, j, j, &dummy, da, lb, da, &dummy, NULL, NULL, NULL) ? 1 : 0;
}


static inline int
cpx_getub(void* highs, double *ub, int j)
{
    double da[1];
    HighsInt dummy;
    return Highs_getColsByRange(highs, j, j, &dummy, da, da, ub, &dummy, NULL, NULL, NULL) ? 1 : 0;
}


static inline int
cpx_getbds(void* highs, double *lb, double *ub, int j)
{
    /*TODO: this seems heavy*/
    double da[1];
    HighsInt dummy;
    return Highs_getColsByRange(highs, j, j, &dummy, da, lb, ub, &dummy, NULL, NULL, NULL) ? 1 : 0;
}


static inline int
cpx_getctype(void* highs, char *kind, int j)
{
    HighsInt integrality;
    if (Highs_getColIntegrality(highs, j, &integrality))
        return 1;
    if (integrality == kHighsVarTypeContinuous)
        *kind = 'C';
    else if (integrality == kHighsVarTypeInteger)
        *kind = 'I';
    else if (integrality == kHighsVarTypeImplicitInteger)
        *kind = 'I';
    else {
        _error("HiGHS: unsupported column type");
        return 1;
    }
    return 0;
}


static inline int
cpx_get_obj_coef(void* highs, double *coef, int j)
{
    double da[1];
    HighsInt dummy;
    return Highs_getColsByRange(highs, j, j, &dummy, coef, da, da, &dummy, NULL, NULL, NULL) ? 1 : 0;
}


static inline int
cpx_getrow(void* highs, int *nnz, int *matind, double *matval, int nnz_sz, int i)
{
    HighsInt num_rows;
    double lower[1], upper[1];
    HighsInt start[1];
    assert(sizeof(HighsInt) == sizeof(int));
    if (Highs_getRowsByRange(highs, i, i,
        &num_rows, lower, upper, nnz, start, matind, matval))
    {
        return 1;
    }
    assert(start[0] == 0 && *nnz <= nnz_sz);
    return 0;
}


static inline int
cpx_getnumnz(void* highs)
{
    return Highs_getNumNz(highs);
}


static inline int
cpx_getnumint(void* highs)
{
    int i, nint = 0;
    /* we should maintain a count in lp_desc */
    for (i=0; i<Highs_getNumCol(highs); ++i) {
        HighsInt ctype;
        Highs_getColIntegrality(highs, i, &ctype);
        if (ctype != kHighsVarTypeContinuous)
            ++nint;
    }
    return nint;
}


static inline int
cpx_getnumbin(void* highs)
{
    return 0;
}


static inline int
cpx_getnumqpnz(void* highs)
{
    return 0;
}


/* -------------------- Solving -------------------- */

static int
solver_has_method(int m) {
    switch (m) {
    case METHOD_AUTO:
    case METHOD_BAR:
    case METHOD_DEFAULT:
    case METHOD_PRIMAL:
    case METHOD_DUAL:
	return 1;
    default:
	return 0;
    }
}

static int
solver_has_node_method(int m) {
    switch (m) {
    case METHOD_DEFAULT:
	return 1;
    default:
	return 0;
    }
}


static int
cpx_prepare_solve(lp_desc* lpd, struct lp_meth *meth, double timeout)
{
    int res = 0;
    int timeout_ms = timeout>0.0 ? (int)(timeout*1000) : INT_MAX;

    if (timeout <= 0.0)
    	timeout = HUGE_VAL;
    if (Highs_setDoubleOptionValue(lpd->lp, "time_limit", timeout))
        return 1;

    switch(meth->meth)
    {
        case METHOD_AUTO:
            res = Highs_setStringOptionValue(lpd->lp, "solver", "choose");
            break;
        case METHOD_PRIMAL:
            res = Highs_setStringOptionValue(lpd->lp, "solver", "simplex");
            res = res || Highs_setIntOptionValue(lpd->lp, "simplex_strategy", 4);
            break;
        case METHOD_DUAL:
            res = Highs_setStringOptionValue(lpd->lp, "solver", "simplex");
            res = res || Highs_setIntOptionValue(lpd->lp, "simplex_strategy", 1);
            break;
        case METHOD_BAR:
            res = Highs_setStringOptionValue(lpd->lp, "solver", "ipm");
            break;
        case METHOD_PDLP:
            res = Highs_setStringOptionValue(lpd->lp, "solver", "pdlp");
            break;
        default:
            _warn("Unsupported solver method, using default.");
        case METHOD_DEFAULT:
            res = 0;
            break;
    }
    if (res)
        return 1;

    switch(meth->node_meth)     /* MIP node method */
    {
        default:
            _warn("Unsupported node_method, using default.");
        case METHOD_DEFAULT:
            break;
    }
    return 0;
}


static int
_solve_and_get_status(lp_desc* lpd, struct lp_meth *meth, double* bestbound, double* worstbound)
{

    HighsInt res;
    HighsInt primal_solution_status;
    HighsInt dual_solution_status;
    HighsInt basis_validity;

    res = Highs_run(lpd->lp);
    if (res == kHighsStatusWarning) {
        _warn("Highs_run returned warning.");
    } else if (res == kHighsStatusError) {
        _error("Highs_run returned error.");
        return 1;
    }

    /*
    From HighsInfo.h:
    struct HighsInfoStruct {
      bool valid;
      int64_t mip_node_count;
      HighsInt simplex_iteration_count;
      HighsInt ipm_iteration_count;
      HighsInt crossover_iteration_count;
      HighsInt pdlp_iteration_count;
      HighsInt qp_iteration_count;
      HighsInt primal_solution_status;
      HighsInt dual_solution_status;
      HighsInt basis_validity;
      double objective_function_value;
      double mip_dual_bound;
      double mip_gap;
      double max_integrality_violation;
      HighsInt num_primal_infeasibilities;
      double max_primal_infeasibility;
      double sum_primal_infeasibilities;
      HighsInt num_dual_infeasibilities;
      double max_dual_infeasibility;
      double sum_dual_infeasibilities;
      double max_complementarity_violation;
      double sum_complementarity_violations;
      double primal_dual_integral;
    };
    */

    lpd->sol_state = Highs_getModelStatus(lpd->lp);
    switch (lpd->prob_type)
    {
        case PROBLEM_RELAXEDL:
            res = Highs_getIntInfoValue(lpd->lp, "simplex_iteration_count", &lpd->sol_itcnt);
            break;

        case PROBLEM_LP:
            res = Highs_getIntInfoValue(lpd->lp, "simplex_iteration_count", &lpd->sol_itcnt);
            break;

        case PROBLEM_MIP:
        case PROBLEM_MIQP:
            res = Highs_getIntInfoValue(lpd->lp, "mip_node_count", &lpd->sol_nodnum);
            res = Highs_getIntInfoValue(lpd->lp, "simplex_iteration_count", &lpd->sol_itcnt);
            break;

        case PROBLEM_QP:
            res = Highs_getIntInfoValue(lpd->lp, "qp_iteration_count", &lpd->sol_itcnt);
            break;

        /* others:
            Highs_getIntInfoValue(lpd->lp, "ipm_iteration_count", ...);
            Highs_getIntInfoValue(lpd->lp, "crossover_iteration_count", ...);
            Highs_getIntInfoValue(lpd->lp, "pdlp_iteration_count", ...);
            Highs_getIntInfoValue(lpd->lp, "qp_iteration_count", ...);
        */
    }
    res = Highs_getIntInfoValue(lpd->lp, "primal_solution_status", &primal_solution_status);
    res = Highs_getIntInfoValue(lpd->lp, "dual_solution_status", &dual_solution_status);
    res = Highs_getIntInfoValue(lpd->lp, "basis_validity", &basis_validity);

    if (lpd->sol_state == kHighsModelStatusOptimal	/* SuccessState */
     || lpd->sol_state == kHighsModelStatusObjectiveBound
     || lpd->sol_state == kHighsModelStatusObjectiveTarget)
    {
        assert(primal_solution_status == kHighsSolutionStatusFeasible);
        if (IsMIPProb(lpd->prob_type) || lpd->prob_type == PROBLEM_FIXEDL) {
            assert(dual_solution_status == kHighsSolutionStatusNone);
            assert(basis_validity == kHighsBasisValidityInvalid);
        } else {
            assert(dual_solution_status == kHighsSolutionStatusFeasible);
            assert(basis_validity == kHighsBasisValidityValid);
        }
        lpd->descr_state = DESCR_SOLVED_SOL;
        lpd->optimum_ctr++;
        lpd->objval = Highs_getObjectiveValue(lpd->lp);
        *worstbound = *bestbound = lpd->objval;
    }
    else if (lpd->sol_state == kHighsModelStatusModelEmpty) /* SuccessState */
    {
        lpd->descr_state = DESCR_SOLVED_SOL;
        lpd->optimum_ctr++;
        lpd->objval = 0.0;
        *worstbound = *bestbound = 0.0;
    }
    else if (lpd->sol_state == kHighsModelStatusInfeasible) /* FailState */
    {
        lpd->descr_state = DESCR_SOLVED_NOSOL;
        lpd->infeas_ctr++;
        lpd->objval = 0.0;
        *worstbound = (lpd->sense == SENSE_MIN ? -HUGE_VAL :  HUGE_VAL);
        *bestbound = (lpd->sense ==  SENSE_MIN ?  HUGE_VAL : -HUGE_VAL);
    }
    else if (lpd->sol_state == kHighsModelStatusUnboundedOrInfeasible)	/* MaybeFailState */
    {
        lpd->descr_state = DESCR_UNKNOWN_NOSOL;
        lpd->infeas_ctr++;
        lpd->objval = 0.0;
        *worstbound = (lpd->sense == SENSE_MIN ?  HUGE_VAL : -HUGE_VAL);
        *bestbound = (lpd->sense ==  SENSE_MIN ? -HUGE_VAL :  HUGE_VAL);
    }
    else if (lpd->sol_state == kHighsModelStatusUnbounded)	/* UnboundedState */
    {
        /* a feasible solution MAY be available, or is unproven */
        lpd->descr_state = DESCR_UNBOUNDED_NOSOL;
        *bestbound = (lpd->sense == SENSE_MIN ? -HUGE_VAL : HUGE_VAL);
        if (primal_solution_status == kHighsSolutionStatusFeasible) {
            /* is primary feasible, i.e. has feasible solution and is unbounded */
            lpd->descr_state = DESCR_UNBOUNDED_NOSOL;
            *bestbound = *worstbound = (lpd->sense == SENSE_MIN ? -HUGE_VAL : HUGE_VAL);
        } else {
            /* not proven to be primary feasible */
            lpd->descr_state = DESCR_UNKNOWN_NOSOL;
            lpd->abort_ctr++;
            /* no information on bounds */
            *worstbound = (lpd->sense == SENSE_MIN ?  HUGE_VAL : -HUGE_VAL);
            *bestbound = (lpd->sense ==  SENSE_MIN ? -HUGE_VAL :  HUGE_VAL);
            lpd->objval = *worstbound;
        }
    }
    else if (lpd->sol_state == kHighsModelStatusNotset
          || lpd->sol_state == kHighsModelStatusLoadError
          || lpd->sol_state == kHighsModelStatusModelError
          || lpd->sol_state == kHighsModelStatusPresolveError
          || lpd->sol_state == kHighsModelStatusSolveError
          || lpd->sol_state == kHighsModelStatusPostsolveError
          || lpd->sol_state == kHighsModelStatusUnknown)
    {
        lpd->abort_ctr++;
        lpd->descr_state = DESCR_UNKNOWN_NOSOL;
        *worstbound = (lpd->sense == SENSE_MIN ? -HUGE_VAL :  HUGE_VAL);
        *bestbound = (lpd->sense ==  SENSE_MIN ?  HUGE_VAL : -HUGE_VAL);
    }
    else if (lpd->sol_state == kHighsModelStatusTimeLimit
          || lpd->sol_state == kHighsModelStatusIterationLimit
          || lpd->sol_state == kHighsModelStatusInterrupt)
    {
        lpd->abort_ctr++;
        if (primal_solution_status == kHighsSolutionStatusFeasible) {
            assert(dual_solution_status != kHighsSolutionStatusInfeasible);
            lpd->descr_state = DESCR_ABORTED_SOL;
        } else {
            lpd->descr_state = DESCR_ABORTED_NOSOL;
        }
    }
    else if (lpd->sol_state == kHighsModelStatusSolutionLimit)
    {
        lpd->abort_ctr++;
        assert(primal_solution_status == kHighsSolutionStatusFeasible
            && dual_solution_status != kHighsSolutionStatusInfeasible);
        lpd->descr_state = DESCR_ABORTED_SOL;
    }
    else
    {
        _error("HiGHS: unrecognised solver status");
        return 1;
    }
    return 0;
}


static int
cpx_solve(lp_desc* lpd, struct lp_meth *meth, double* bestbound, double* worstbound)
{

    HighsInt res, i;

    /* wrappers for different problem types */
    switch (lpd->prob_type)
    {
        case PROBLEM_LP:
        case PROBLEM_MIP:
            return _solve_and_get_status(lpd, meth, bestbound, worstbound);

        case PROBLEM_RELAXEDL:
        {
            /* Remember all integralities and clear them temporarily */
            HighsInt integrality_mask[lpd->mac];
            HighsInt integrality_type[lpd->mac];
            for (i=0; i<lpd->mac; i++) {
                if (Highs_getColIntegrality(lpd->lp, i, &integrality_type[i]))
                    return 1;
                integrality_mask[i] = (integrality_type[i] == kHighsVarTypeContinuous) ? 0 : 1;
            }
            if (Highs_clearIntegrality(lpd->lp))
                return 1;

            if (_solve_and_get_status(lpd, meth, bestbound, worstbound))
                return 1;

            /* Restore the integralities */
            if (Highs_changeColsIntegralityByMask(lpd->lp, integrality_mask, integrality_type))
                return 1;
            break;
        }

        case PROBLEM_FIXEDL:
        {
            /* Retrieve all bounds and solutions */
            double lb[lpd->mac >0 ? lpd->mac : 1];
            double ub[lpd->mac >0 ? lpd->mac : 1];
            double sol[lpd->mac >0 ? lpd->mac : 1];
            double col_dummy[lpd->mac > 0 ? lpd->mac : 1];
            double row_dummy[lpd->mar > 0 ? lpd->mar : 1];
            HighsInt dummy;
            if (Highs_getColsByRange(lpd->lp, 0, lpd->mac-1, &dummy, col_dummy, lb, ub, &dummy, NULL, NULL, NULL))
                return 1;
            if (Highs_getSolution(lpd->lp, sol, col_dummy, row_dummy, row_dummy))
                return 1;
            /* Fix integer variables to their solution */
            for (i=0; i<lpd->mac; i++) {
                HighsInt coltype;
                if (Highs_getColIntegrality(lpd->lp, i, &coltype))
                    return 1;
                if (coltype != kHighsVarTypeContinuous) {
                    if (Highs_changeColBounds(lpd->lp, i, sol[i], sol[i]))
                        return 1;
                }
            }

            if (_solve_and_get_status(lpd, meth, bestbound, worstbound))
                return 1;

            /* Restore the bounds */
            if (Highs_changeColsBoundsByRange(lpd->lp, 0, lpd->mac-1, lb, ub))
                return 1;
            break;
        }

        default:
            return -1;
    }
    return 0;
}


static int
cpx_get_soln_state(lp_desc* lpd)
{
    double col_dummy[lpd->mac > 0 ? lpd->mac : 1];
    double row_dummy[lpd->mar > 0 ? lpd->mar : 1];
    struct lp_sol *sol = &lpd->sol;
    if (Highs_getSolution(lpd->lp, sol->sols,
            sol->djs ? sol->djs : col_dummy,
            sol->slacks ? sol->slacks : row_dummy, /* gets primal row values */
            sol->pis ? sol->pis : row_dummy))      /* gets dual row values */
        return 1;

    /* Compute slacks from the primal row values */
    if (sol->slacks && lpd->mar > 0) {
        int i;
        HighsInt num_rows, num_nz;
        double lower[lpd->mar], upper[lpd->mar];
        if (Highs_getRowsByRange(lpd->lp, 0, lpd->mar-1, &num_rows, lower, upper, &num_nz, NULL, NULL, NULL))
            return 1;
        for(i=0; i<lpd->mar; ++i) {
            double l = sol->slacks[i] - lower[i];
            double r = upper[i] - sol->slacks[i];
            sol->slacks[i] = l < r ? l : r;
        }
    }

    if (sol->cbase && sol->rbase) {
        if (Highs_getBasis(lpd->lp, sol->cbase, sol->rbase))
            return 1;
    } else {
        assert(!sol->cbase && !sol->rbase);
    }
   return 0;
}


/* -------------------- Read/Write -------------------- */

static int
cpx_read(lp_desc *lpd, char *file, char *fmt)
{
    char buf[PATH_MAX];
    HighsInt sense;
    void *highs;
    int i;

    if (strcmp(fmt, "mps") && strcmp(fmt, "lp"))
    {
        strcat(strcat(strcpy(buf, "HiGHS: Unknown format \""), fmt), "\"\n");
        _error(buf);
        return 1;
    }

    /* Highs_readModel() requires a .mps or .lp suffix */
    if (access(file, R_OK))
    {
        /* plain file doesn't exist, append .fmt suffix */
        int fmax = PATH_MAX-1-strlen(fmt)-1;
        if (fmax<0) fmax=0;
        buf[0] = 0;
        file = strcat(strcat(strncat(buf,file,fmax),"."),fmt);
    } else {
        /* file exists, make sure it has a .fmt suffix */
        const char* dot = strrchr(file, '.');
        if (!dot || strcmp(dot+1, fmt)) {
            strcat(strcat(strcpy(buf, "HiGHS: problem file needs .\""), fmt), "\" suffix\n");
            _error(buf);
            return 1;
        }
    }

    highs = Highs_create();
    if (!highs)
        return 1;

    _copy_options(cpx_env, highs);       /* init parameters */

    if (Highs_readModel(highs, file)) {
        Highs_destroy(highs);
        return 1;
    }

    /* initialize non-zero fields in lp_desc */
    lpd->lpcopy = NULL;
    Highs_getObjectiveSense(highs, &sense);
    lpd->sense = sense==kHighsObjSenseMinimize ? SENSE_MIN : SENSE_MAX;
    lpd->mac = Highs_getNumCol(highs);
    lpd->mar = Highs_getNumRow(highs);

    /* find out whether this is a MIP (or rather non-LP) */
    lpd->prob_type = PROBLEM_LP;
    for(i=0; i<lpd->mac; ++i) {
        HighsInt coltype;
        Highs_getColIntegrality(highs, i, &coltype);
        if (coltype != kHighsVarTypeContinuous) {
            lpd->prob_type = PROBLEM_MIP;
            break;
        }
    }
    lpd->lp = highs;

    return 0;
}


static int
cpx_write(lp_desc *lpd, char *file, char *fmt)
{
    char buf[PATH_MAX];
    if (strcmp(fmt, "mps") && strcmp(fmt, "lp"))
    {
        strcat(strcat(strcpy(buf, "HiGHS: Unknown format \""), fmt), "\"\n");
        _error(buf);
        return 1;
    }

    const char* dot = strrchr(file, '.');
    if (!dot || strcmp(dot+1, fmt)) {
        /* append .fmt suffix */
        int fmax = PATH_MAX-1-strlen(fmt)-1;
        if (fmax<0) fmax=0;
        buf[0] = 0;
        file = strcat(strcat(strncat(buf,file,fmax),"."),fmt);
    }

    if (Highs_writeModel(lpd->lp, file)) {
#if 1
        /* Workaround: HiGHS 1.11 sometimes returns an error even when
         * the file was written correctly - ignore if file exists.
         */
        if (!access(file, R_OK))
            return 0;
#endif
        return 1;
    }
    return 0;
}


/* -------------------- Parameter handling -------------------- */

/*
 * Parameter Table
 *
 * For HiGHS we don't need a table because parameters are identified
 * by strings and their type can be queried.
 * We only have a table mapping alias names to HiGHS names.
 */

/*
From version 1.8.1 HighsOptions.h, just for reference:

struct HighsOptionsStruct {
  // Run-time options read from the command line
  std::string presolve;
  std::string solver;
  std::string parallel;
  std::string run_crossover;
  double time_limit;
  std::string solution_file;
  std::string write_model_file;
  HighsInt random_seed;
  std::string ranging;

  // Options read from the file
  double infinite_cost;
  double infinite_bound;
  double small_matrix_value;
  double large_matrix_value;
  double primal_feasibility_tolerance;
  double dual_feasibility_tolerance;
  double ipm_optimality_tolerance;
  double objective_bound;
  double objective_target;
  HighsInt threads;
  HighsInt user_bound_scale;
  HighsInt user_cost_scale;
  HighsInt highs_debug_level;
  HighsInt highs_analysis_level;
  HighsInt simplex_strategy;
  HighsInt simplex_scale_strategy;
  HighsInt simplex_crash_strategy;
  HighsInt simplex_dual_edge_weight_strategy;
  HighsInt simplex_primal_edge_weight_strategy;
  HighsInt simplex_iteration_limit;
  HighsInt simplex_update_limit;
  HighsInt simplex_min_concurrency;
  HighsInt simplex_max_concurrency;

  std::string log_file;
  bool write_model_to_file;
  bool write_presolved_model_to_file;
  bool write_solution_to_file;
  HighsInt write_solution_style;
  HighsInt glpsol_cost_row_location;
  std::string write_presolved_model_file;

  // Control of HiGHS log
  bool output_flag;
  bool log_to_console;

  // Options for IPM solver
  HighsInt ipm_iteration_limit;

  // Options for PDLP solver
  bool pdlp_native_termination;
  bool pdlp_scaling;
  HighsInt pdlp_iteration_limit;
  HighsInt pdlp_e_restart_method;
  double pdlp_d_gap_tol;

  // Options for QP solver
  HighsInt qp_iteration_limit;
  HighsInt qp_nullspace_limit;

  // Options for IIS calculation
  HighsInt iis_strategy;

  // Advanced options
  HighsInt log_dev_level;
  bool log_githash;
  bool solve_relaxation;
  bool allow_unbounded_or_infeasible;
  bool use_implied_bounds_from_presolve;
  bool lp_presolve_requires_basis_postsolve;
  bool mps_parser_type_free;
  HighsInt keep_n_rows;
  HighsInt cost_scale_factor;
  HighsInt allowed_matrix_scale_factor;
  HighsInt allowed_cost_scale_factor;
  HighsInt ipx_dualize_strategy;
  HighsInt simplex_dualize_strategy;
  HighsInt simplex_permute_strategy;
  HighsInt max_dual_simplex_cleanup_level;
  HighsInt max_dual_simplex_phase1_cleanup_level;
  HighsInt simplex_price_strategy;
  HighsInt simplex_unscaled_solution_strategy;
  HighsInt presolve_reduction_limit;
  HighsInt restart_presolve_reduction_limit;
  HighsInt presolve_substitution_maxfillin;
  HighsInt presolve_rule_off;
  bool presolve_rule_logging;
  bool presolve_remove_slacks;
  bool simplex_initial_condition_check;
  bool no_unnecessary_rebuild_refactor;
  double simplex_initial_condition_tolerance;
  double rebuild_refactor_solution_error_tolerance;
  double dual_steepest_edge_weight_error_tolerance;
  double dual_steepest_edge_weight_log_error_threshold;
  double dual_simplex_cost_perturbation_multiplier;
  double primal_simplex_bound_perturbation_multiplier;
  double dual_simplex_pivot_growth_tolerance;
  double presolve_pivot_threshold;
  double factor_pivot_threshold;
  double factor_pivot_tolerance;
  double start_crossover_tolerance;
  bool less_infeasible_DSE_check;
  bool less_infeasible_DSE_choose_row;
  bool use_original_HFactor_logic;
  bool run_centring;
  double primal_residual_tolerance;
  double dual_residual_tolerance;
  HighsInt max_centring_steps;
  double centring_ratio_tolerance;

  // Options for iCrash
  bool icrash;
  bool icrash_dualize;
  std::string icrash_strategy;
  double icrash_starting_weight;
  HighsInt icrash_iterations;
  HighsInt icrash_approx_iter;
  bool icrash_exact;
  bool icrash_breakpoints;

  // Options for MIP solver
  bool mip_detect_symmetry;
  bool mip_allow_restart;
  HighsInt mip_max_nodes;
  HighsInt mip_max_stall_nodes;
  HighsInt mip_max_start_nodes;
  HighsInt mip_max_leaves;
  HighsInt mip_max_improving_sols;
  HighsInt mip_lp_age_limit;
  HighsInt mip_pool_age_limit;
  HighsInt mip_pool_soft_limit;
  HighsInt mip_pscost_minreliable;
  HighsInt mip_min_cliquetable_entries_for_parallelism;
  HighsInt mip_report_level;
  double mip_feasibility_tolerance;
  double mip_rel_gap;
  double mip_abs_gap;
  double mip_heuristic_effort;
  double mip_min_logging_interval;
#ifdef HIGHS_DEBUGSOL
  std::string mip_debug_solution_file;
#endif
  bool mip_improving_solution_save;
  bool mip_improving_solution_report_sparse;
  std::string mip_improving_solution_file;
*/


/*
 * Table of solver- and version-independent aliases
 * {ALIAS, HIGHS_NAME, _unused_}
 */

#define NUMALIASES 7

static struct param_desc params[NUMALIASES] = {

{"feasibility_tol", "primal_feasibility_tolerance", -1},
{"mipgap", "mip_rel_gap", -1},
{"absmipgap", "mip_abs_gap", -1},
{"integrality", "mip_feasibility_tolerance", 1},
{"iteration_limit", "simplex_iteration_limit", -1},
{"solution_limit", "mip_max_improving_sols", -1},
{"node_limit", "mip_max_nodes", -1},
};


/* Copy all options from one instance to another */
static int
_copy_options(void* orig_highs, void* dest_highs)
{
    HighsInt i, res, highs_partype;
    char *name;

    for (i=0; i<Highs_getNumOptions(cpx_env); ++i) {
        res = Highs_getOptionName(cpx_env, i, &name);
        assert(res == kHighsStatusOk);
        res = Highs_getOptionType(cpx_env, name, &highs_partype);
        assert(res == kHighsStatusOk);
        if (highs_partype == kHighsOptionTypeBool) {
            HighsInt ival;
            Highs_getBoolOptionValue(orig_highs, name, &ival);
            res = Highs_setBoolOptionValue(dest_highs, name, ival);
            assert(res == kHighsStatusOk);
        } else if (highs_partype == kHighsOptionTypeInt) {
            HighsInt ival;
            Highs_getIntOptionValue(orig_highs, name, &ival);
            res = Highs_setIntOptionValue(dest_highs, name, ival);
            assert(res == kHighsStatusOk);
        } else if (highs_partype == kHighsOptionTypeDouble) {
            double dval;
            Highs_getDoubleOptionValue(orig_highs, name, &dval);
            res = Highs_setDoubleOptionValue(dest_highs, name, dval);
            assert(res == kHighsStatusOk);
        } else {
            char sval[kHighsMaximumStringLength];
            assert(highs_partype == kHighsOptionTypeString);
            Highs_getStringOptionValue(orig_highs, name, sval);
            res = Highs_setStringOptionValue(dest_highs, name, sval);
            assert(res == kHighsStatusOk);
        }
    }
}


static int
cpx_get_par_info(char* name, param_id_t* pparnum, int* ppartype)
{
    int i;
    HighsInt highs_partype;
    if (Highs_getOptionType(cpx_env, name, &highs_partype)) {
        /* name not valid, try an alias */
        for (i=0; i<NUMALIASES; ++i) {
            if (strcmp(params[i].name, name) == 0) {
                name = params[i].num;
                break;
            }
        }
        if (i >= NUMALIASES || Highs_getOptionType(cpx_env, name, &highs_partype))
            return 1;
    }
    *pparnum = name;
    if (highs_partype == kHighsOptionTypeBool)        *ppartype = PARTYPE_BOOL;
    else if (highs_partype == kHighsOptionTypeInt)    *ppartype = PARTYPE_INT;
    else if (highs_partype == kHighsOptionTypeDouble) *ppartype = PARTYPE_DBL;
    else if (highs_partype == kHighsOptionTypeString) *ppartype = PARTYPE_STR;
    else return 1;
    return 0;
}

static inline int
cpx_set_int_param(lp_desc *lpd, param_id_t parname, int val)
{
    return Highs_setIntOptionValue(lpd?lpd->lp:cpx_env, parname, val) ? 1 : 0;
}

static inline int
cpx_set_bool_param(lp_desc *lpd, param_id_t parname, int val)
{
    return Highs_setBoolOptionValue(lpd?lpd->lp:cpx_env, parname, val) ? 1 : 0;
}

static inline int
cpx_set_dbl_param(lp_desc *lpd, param_id_t parname, double val)
{
    return Highs_setDoubleOptionValue(lpd?lpd->lp:cpx_env, parname, val) ? 1 : 0;
}

static inline int
cpx_set_str_param(lp_desc *lpd, param_id_t parname, const char *val)
{
    return Highs_setStringOptionValue(lpd?lpd->lp:cpx_env, parname, val) ? 1 : 0;
}

static inline int
cpx_get_int_param(lp_desc *lpd, param_id_t parname, int *pval)
{
    HighsInt ival;
    if (Highs_getIntOptionValue(lpd?lpd->lp:cpx_env, parname, &ival))
        return 1;
    *pval = ival;
    return 0;
}

static inline int
cpx_get_bool_param(lp_desc *lpd, param_id_t parname, int *pval)
{
    HighsInt ival;
    if (Highs_getIntOptionValue(lpd?lpd->lp:cpx_env, parname, &ival))
        return 1;
    *pval = ival;
    return 0;
}

static inline int
cpx_get_dbl_param(lp_desc *lpd, param_id_t parname, double *pval)
{
    return Highs_getDoubleOptionValue(lpd?lpd->lp:cpx_env, parname, pval) ? 1 : 0;
}

static inline int
cpx_get_str_param(lp_desc *lpd, param_id_t parname, char *pval)
{
    return Highs_getStringOptionValue(lpd?lpd->lp:cpx_env, parname, pval) ? 1 : 0;
}

static inline double
cpx_feasibility_tol(lp_desc *lpd)
{
    double tol;
    Highs_getDoubleOptionValue(lpd->lp, "primal_feasibility_tolerance", &tol);
    return tol;
}
