/*
  This file is a portion of the Dylp LP distribution.

        Copyright (C) 2004 -- 2007 Lou Hafer

        School of Computing Science
        Simon Fraser University
        Burnaby, B.C., V5A 1S6, Canada
        lou@cs.sfu.ca

  This code is licensed under the terms of the Eclipse Public License (EPL).
*/

/*
  There are two conditional compilation symbols in this file, BONSAIG and
  COIN_HAS_DYLP. BONSAIG should be defined only if you're building BonsaiG.
  COIN_HAS_DYLP should be defined only if you're trying to use this MPS
  reader instead of the COIN MPS i/o routines. Almost certainly, you don't
  want to define BONSAIG, and COIN_HAS_DYLP will be defined as appropriate in
  DylpConfig.h.
*/

/*
  This file contains C subroutines to parse MPS format problem files. The
  capabilities are compatible with the 'Free Format' MPS format.  But ...
  this parser won't handle some of the strange things that are legal in MPS
  `fixed' format (column names with spaces in them, for instance).

  The general format of an MPS file is

  NAME  name_of_model
  ROWS
	row data
  COLUMNS
	column data
  RHS
	right-hand-side data
  RANGES
	range data
  BOUNDS
	bounds data
  ENDATA

  where the RHS, RANGES, and BOUNDS sections are optional. Data lines consist
  of 6 fields. The number of fields actually used varies. The general format
  is:

  sense,  nam0,   nam1,    val1,    nam2,    val2
  (2-3)  (5-12)  (15-22)  (25-36)  (40-47)  (50-61)

  where sense is a character code, or blank; nam0, nam1, and nam2 are names,
  and val1 and val2 are numbers. The column numbers for fixed-format input
  are shown in ()'s below the field name. See the MPS documentation for more
  details.

  Fine points that're ignored here:
   * In the NAME section, nam1 should be FREE for free format input; mpsin
     doesn't require it. Just the opposite --- we always work in free format.
   * The standard interprets a '$' at the beginning of nam1 or nam2 to mean
     the rest of the line is a comment. mpsin will see the '$' anywhere in
     the line.

  Taking our lead from OSL, this code does not support Dx codes in the rows
  section or 'SCALE' markers.
  
  Just cause it's a pain and we don't need it, mpsin doesn't support multiple
  RHS, RANGE, or BOUNDS vectors. Nor does it support 'no constraint' rows,
  except for a single row which is taken as the objective function.

  Markers of the form:
                'MARKER'                 'INTORG'
                'MARKER'                 'INTEND'
  in the COLUMNS section are used to bracket blocks of integer variables.
  The code will recognise binary variables as integer variables with bounds
  of 0 and 1, or the BV code can be used in the BOUNDS section.

  SOS Type 3 variables (clique constraints) are also supported by mpsin.  The
  MPS 'MARKER' statement is used, with the keywords 'SOSORG' and 'SOSEND'. An
  equality constraint must be defined in the ROWS section of the MPS file;
  coefficients of 1 and a rhs of 1 will be generated by default. For example,
  if there are two SOS Type 3 sets, as follows:

		Variables		Constraints
                ---------		-----------
    SOS set 1	sos1var1 		sosname1,someeq1,someeq2,
		sos1var2		sosname1,someeq4

    SOS set 2	sos2var1		sosname2,someeq2,someeq3,someeq4
		sos2var2		sosname2,someeq1,someeq3

  Then the input would look like:

  ROWS
      ....
    E sosname1
    L someeq1
    G someeq2
      ....
    E sosname2
      ....
    E someeq3
    L someeq4
      ....

  COLUMNS

      sosname1  'MARKER'                 'SOSORG'
      sos1var1  someeq1          6.25    someeq2             245
      sos1var2  someeq4            15
                'MARKER'		 'SOSEND'
      sosname2  'MARKER'		 'SOSORG'
      sos2var1  someeq2		102.5	 someeq4	   5.388
      sos2var1  someeq3          24.8
      sos2var2  someeq1		 76.5    someeq3	   82.55
                'MARKER'		 'SOSEND'
*/



#include "dy_cmdint.h"
#include "dylp.h"
#include "dylib_hash.h"
#include "dylib_strrtns.h"
#include <string.h>
#include <errno.h>

#ifndef BONSAIG

/*
  We need to provide a definition for mipopts_struct in the OsiDylp
  configuration. Usually this comes in with bonsaiG's milp.h.
*/

typedef struct { int minmax ;
		 int objcon ;
		 double zinit ;
		 const char *objnme ; } mipopts_struct ;
#else
# include "milp.h"
#endif


static char sccsid[] UNUSED = "@(#)mpsio.c	4.4	11/06/04" ;
static char svnid[] UNUSED = "$Id: mpsio.c 407 2010-12-31 20:48:48Z lou $" ;

/*
  A bunch of definitions and declarations for mpsin and its slave routines.

  Separator characters -- these are used by mpsin and slave routines as the
  separator set for strtok. The exotics (\n, \r, \f, \v) aren't mentioned.
  They just shouldn't show up in a valid mps file.
*/

static const char *sepchars = " \t" ;

/*
  State codes -- these define where we're at in processing the mps input file.
*/

typedef enum { mpsinINV = 0, mpsinNAME,
	       mpsinROWS, mpsinCOLUMNS, mpsinRHS,
	       mpsinRANGES, mpsinBOUNDS, mpsinENDDATA } mpsinstate_enum ;

/*
  Hash tables -- used to translate row and column names to indices.
*/

static hel **conhash,**varhash ;
static int conhashsze,varhashsze ;

#define CONHASHSZE_DFLT 211
#define VARHASHSZE_DFLT 211

/*
  Linked list head for Type 3 Special Ordered Sets (the only kind that bonsai
  supports just now.
*/

static lnk_struct *sos3lst ;



static lex_struct *getmpsline (ioid mpschn)

/*
  This routine scans lines from the the file specified by mpschn. Blank lines
  and comments (lines starting with "*") are discarded. In-line comments (from
  the occurrence of "$" to the end of the line) are also stripped.

  Parameters:
    mpschn:	stream for the MPS input

  Returns: a pointer to a lex_struct; on normal return, the type will be
	   DY_LCQS; other possibilities are DY_LCEOF (end-of-file) and
	   DY_LCERR (i/o error)

  NOTE: The lex_struct returned by getmpsline is owned by dyio_scanstr. The
	string buffer it contains is allocated by scanstr and will be freed
	by scanstr on the next call unless lex.string is set to NULL. The mps
	input routines make copies of the pieces they need and allow scanstr
	to handle allocating and freeing the buffer.
*/

{ lex_struct *lex ;
  char *cptr ;
  bool empty ;

/*
  Fire up a loop to read lines. A line that's all white space will come back
  from dyio_scanstr as DY_LCNIL. If there's something in the line, check for
  comments.  If there's still a line left after peeling off the comment,
  we've got something.
*/
  empty = TRUE ;
  while (empty == TRUE)
  { lex = dyio_scanstr(mpschn,DY_LCQS,0,'\0','\n') ;
    if (lex->class == DY_LCEOF || lex->class == DY_LCERR) break ;
    if (lex->class == DY_LCNIL) continue ;
/*
  If the last character is ^M (carriage return), strip it. Likely we're looking
  at a file produced on a system where <cr><lf> ends a line.
*/
    cptr = &lex->string[strlen(lex->string)-1] ;
    if (*cptr == '\r') *cptr = '\0' ;
/*
  Check for full line or partial line comments. 
*/
    if (*lex->string == '*') continue ;
    cptr = strchr(lex->string,'$') ;
    if (cptr != NULL) *cptr = '\0' ;
    if ((int) strlen(lex->string) > 0) empty = FALSE ; }
  
  return (lex) ; }



static mpsinstate_enum mpsin_name (ioid mpschn, consys_struct **consys,
				   double infinity)

/*
  This routine handles the NAME section of the MPS file. It should have the
  form
    name <model name> FREE
  and it should be the first line of the file (blank lines & comments
  excepted, of course).

  Parameters:
    mpschn:	input stream
    consys:	(o) the newly created constraint system
    infinity:	the value to be used for infinity

  Returns: mpsinROWS unless there's a problem, in which case it returns
	   mpsinINV
*/

{ lex_struct *lex ;
  char *tok ;

  const char *rtnnme = "mpsin_name",
	     *mysection = "name" ;

# ifndef NDEBUG
/*
  Paranoia
*/
  if (consys == NULL)
  { errmsg(2,rtnnme,"consys") ;
    return (mpsinINV) ; }
# endif
/*
  Find the "name" keyword.
*/
  lex = getmpsline(mpschn) ;
  if (lex->class == DY_LCERR)
  { errmsg(168,rtnnme,mysection) ;
    return (mpsinINV) ; }
  if (lex->class == DY_LCEOF)
  { errmsg(169,rtnnme,mysection) ;
    return (mpsinINV) ; }
  tok = strtok(lex->string,sepchars) ;
  if (tok == NULL || cistrcmp(tok,"name") != 0)
  { errmsg(150,rtnnme,mysection,(tok == NULL)?"":tok) ;
    return (mpsinINV) ; }
/*
  And now the name itself. If we get a name, create the constraint system.
*/
  tok = strtok(NULL,sepchars) ;
  if (tok == NULL)
  { errmsg(151,rtnnme,mysection,"indicator") ;
    return (mpsinINV) ; }
  *consys = consys_create(tok,0,CONSYS_WRNATT,10,10,infinity) ;
  if (*consys == NULL)
  { errmsg(152,rtnnme,tok) ;
    return (mpsinINV) ; }
# ifndef NDEBUG
  dyio_outfmt(dy_logchn,dy_gtxecho,"\n\treading model %s.\n",(*consys)->nme) ;
# endif
/*
  Check for the keyword "free", which should follow the name, and issue a
  warning if it isn't there. This is such a common error that it's not worth
  complaining about unless we're paranoid.

  [May 28, 2001] Warning about the absence of the keyword "free" becomes
  annoying in the testing of osi-bonsai. We disable the warning permanently.
*/
  tok = strtok(NULL,sepchars) ;
# if 0
  if (tok == NULL || cistrcmp(tok,"free") != 0)
    warn(150,rtnnme,"free",(tok == NULL)?"":tok) ;
# endif
/*
  Now check for the "rows" keyword that marks the start of the rows section.
  It's an error to see anything else.
*/
  lex = getmpsline(mpschn) ;
  if (lex->class == DY_LCERR)
  { errmsg(168,rtnnme,mysection) ;
    return (mpsinINV) ; }
  if (lex->class == DY_LCEOF)
  { errmsg(169,rtnnme,mysection) ;
    return (mpsinINV) ; }
  tok = strtok(lex->string,sepchars) ;
  if (tok == NULL || cistrcmp(tok,"rows") != 0)
  { errmsg(150,rtnnme,"rows",(tok == NULL)?"":tok) ;
    return (mpsinINV) ; }

  return (mpsinROWS) ; }



static mpsinstate_enum mpsin_rows (ioid mpschn, consys_struct *consys)

/*
  This routine is responsible for processing the "rows" section, which
  provides the names and types for all constraints (the constraint stub,
  so to speak).

  If a name is passed in consys->objnme, the routine will look for this row
  and make it the objective.  If consys->objnme is null, the routine will
  choose the first non-binding constraint to be the objective, placing the
  name in consys->objnme. In either case, the index is returned in
  consys->objndx.

  Non-binding constraints are discarded, with a warning to the user (with
  the exception that the first one may be chosen as the objective).

  Parameters:
    mpschn:	input stream
    consys:	(i) newly minted constraint system
		(o) constraint system with constraint stubs and constraint
		    type array.

  Returns: mpsinCOLUMNS if all goes well, mpsinINV otherwise.
*/

{ lex_struct *lex ;
  char typelett,*tok ;
  contyp_enum typecode ;
  bool seen_cols,keep_row ;
  pkvec_struct *pkrow ;

  /*
    Useful to suppress compiler warnings re. integer <-> pointer conversion
  */
  ptrdiff_t intermediary ;

  const char *rtnnme = "mpsin_rows",
	     *mysection = "rows" ;

/*
  Set up to process the rows. Create a hash table for the row names,
  associate a constraint type vector with the system, and make a trivial
  vector to use as a parameter to consys_addrow_pk.
*/
  conhashsze = CONHASHSZE_DFLT ;
  conhash = (hel **) CALLOC(conhashsze,sizeof(hel *)) ;
  pkrow = pkvec_new(0) ;
  if (consys_attach(consys,CONSYS_CTYP,
		    sizeof(contyp_enum),(void **) &consys->ctyp) == FALSE)
  { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_CTYP)) ;
    return (mpsinINV) ; }
  setflg(consys->parts,CONSYS_CTYP) ;
/*
  Now start up a loop and process the row entries. Each entry is of the form
  <constraint type> <row name>, where the legal constraint types are n,e,l, or
  g. The various dx codes are not recognized. The correct escape from the
  loop is spotting the "columns" indicator.
*/
  seen_cols = FALSE ;
  consys->objndx = -1 ;
  for (lex = getmpsline(mpschn) ;
       lex->class == DY_LCQS ;
       lex = getmpsline(mpschn))
  { tok = strtok(lex->string,sepchars) ;
    if (cistrcmp(tok,"columns") == 0)
    { seen_cols = TRUE ;
      break ; }
/*
  Keep the type letter and get the name of the constraint. Check that each is
  valid. Skip a non-binding constraint unless it'll be used as the objective,
  and warn the user that we're skipping it. (But we'll need to put the name
  in the hash table, so we'll be able to identify and skip coefficients of
  the constraint when we process the COLUMNS section.)
*/
    typelett = *tok ;
    tok = strtok(NULL,sepchars) ;
    if (tok == NULL)
    { errmsg(153,rtnnme,"constraint",mysection) ;
      return (mpsinINV) ; }
    keep_row = TRUE ;
    switch (typelett)
    { case 'N':
      case 'n':
      { typecode = contypNB ;
	if (!(consys->objndx <= 0 &&
	      (consys->objnme == NULL || strcmp(tok,consys->objnme) == 0)))
	{ warn(172,rtnnme,consys->nme,tok) ;
	  keep_row = FALSE ; }
	break ; }
      case 'E':
      case 'e':
      { typecode = contypEQ ;
	break ; }
      case 'L':
      case 'l':
      { typecode = contypLE ;
	break ; }
      case 'G':
      case 'g':
      { typecode = contypGE ;
	break ; }
      case 'D':
      case 'd':
      { errmsg(171,rtnnme,tok) ;
	return (mpsinINV) ; }
      default:
      { errmsg(154,rtnnme,consys->nme,(unsigned) typelett,typelett,tok) ;
	return (mpsinINV) ; } }
/*
  Install the constraint (unless it's a superfluous contypNB). The objective
  goes in like any other constraint, and will be postprocessed into the
  objective vector (but remember that we've found it).
*/
    pkrow->nme = tok ;
    if (keep_row == TRUE)
    { if (consys_addrow_pk(consys,
			   'a',typecode,pkrow,0.0,0.0,NULL,NULL) == FALSE)
      { errmsg(156,rtnnme,"constraint",consys->nme,pkrow->nme) ;
	return (mpsinINV) ; }
      if (typecode == contypNB)
      { consys->objndx = pkrow->ndx ;
	if (consys->objnme == NULL) consys->objnme = STRALLOC(pkrow->nme) ; } }
    else
    { pkrow->ndx = -1 ; }
    intermediary = pkrow->ndx ;
    if ((void *) intermediary !=
	dyhash_enter(STRALLOC(pkrow->nme),conhash,conhashsze,
		     (void *) intermediary))
    { errmsg(155,rtnnme,"constraint",
	     consys_nme(consys,'c',pkrow->ndx,TRUE,NULL)) ;
      return (mpsinINV) ; }
/*
  Check for garbage at the end of the line, and complain if there is any.
*/
    tok = strtok(NULL,sepchars) ;
    if (tok != NULL) warn(157,rtnnme,tok,"constraint",pkrow->nme) ; }
/*
  This is the end of the loop which parses the rows section.  Check that we
  got here because we saw the "columns" indicator, and fail if things are
  otherwise. Also make sure we've found an objective function.
*/
  pkrow->nme = NULL ;
  pkvec_free(pkrow) ;
# ifndef NDEBUG
  dyio_outfmt(dy_logchn,dy_gtxecho,
  	      "\n\t(%s) read %d constraints from the MPS file.",
	      rtnnme,consys->archccnt) ;
# endif
  if (seen_cols == FALSE)
  { if (lex->class == DY_LCERR)
      errmsg(168,rtnnme,mysection) ;
    else
    if (lex->class == DY_LCEOF)
      errmsg(169,rtnnme,mysection) ;
    else
      errmsg(1,rtnnme,__LINE__) ;
    return (mpsinINV) ; }
  
  if (consys->objndx < 0)
  { errmsg(177,rtnnme,consys->nme,
	   (consys->objnme == NULL)?"<n/a>":consys->objnme) ;
    return (mpsinINV) ; }

  return (mpsinCOLUMNS) ; }



static mpsinstate_enum mpsin_columns (ioid mpschn, consys_struct *consys)

/*
  This routine is responsible for processing the columns section, which
  provides names and types for variables and the coefficients of the constraint
  matrix, in column-major order. It also deals with the various markers that
  can appear in the columns section.

  Parameters:
    mpschn:	input stream
    consys:	(i) constraint system with constraint stubs and constraint
		    type array
		(o) constraint system with coefficient matrix, constraint and
		    variable type arrays
  
  Returns: successful returns will be one of mpsinRHS, mpsinBOUNDS,
	   mpsinRANGES, or mpsinENDDATA, depending on the presence (absence)
	   of optional sections; in the event of an error, mpsinINV
*/

{ bool marker, sosset, intblk ;
  double aij ;
  int rowndx,sosndx ;
  mpsinstate_enum nxtstate ;
  vartyp_enum vartype ;
  lex_struct *lex ;
  lnk_struct *sos3 ;
  pkvec_struct *pkcol ;
  char *rownam,*tok,*chkptr ;
  char colnam[50] ;		/* grossly oversized, but safe */

  /*
    Useful to suppress compiler warnings re. integer <-> pointer conversion
  */
  ptrdiff_t intermediary ;

  const char *rtnnme = "mpsin_columns",
	     *mysection = "columns" ;

/*
  Set up to process the columns. Create a hash table for the variable names,
  a vector to hold the column coefficients, and associate a variable type
  vector with the constraint system.

  Note the use of colnam as the buffer for pkcol->nme.  pkcol->nme is `const
  char *' so we'll always write to colnam, but we'll use pkcol->nme in other
  places.
*/
  varhashsze = VARHASHSZE_DFLT ;
  varhash = (hel **) CALLOC(varhashsze,sizeof(hel *)) ;
  pkcol = pkvec_new(maxx(10,((int) .2*consys->concnt))) ;
  pkcol->nme = colnam ;
  colnam[0] = '\0' ;
  if (consys_attach(consys,CONSYS_VTYP,
		    sizeof(vartyp_enum),(void **) &consys->vtyp) == FALSE)
  { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_VTYP)) ;
    return (mpsinINV) ; }
  setflg(consys->parts,CONSYS_VTYP) ;
  sos3lst = NULL ;
  nxtstate = mpsinINV ;
/*
  Now try to parse the coefficients of the constraint matrix. The general
  idea is to collect a column, then install it. Note that MPS requires all
  elements of a column to be given together. Each line is of the form
      <column name> <row name> <coeff> <row name 2> <coeff 2>
  where the second row name and coeff are optional.  The name of the column
  being processed is held in pkcol->nme. We pretend we've just processed a
  marker to force the start of a new column without installing the (non-
  existent) previous column.
*/
  marker = TRUE ;
  sosset = FALSE ;
  sosndx = -1 ;
  intblk = FALSE ;
  pkcol->cnt = 0 ;
  vartype = vartypCON ;

  for (lex = getmpsline(mpschn) ;
       lex->class == DY_LCQS ;
       lex = getmpsline(mpschn))
  { 
/*
  Scan the first token and see if it matches the current column name. If it
  doesn't, there are two possibilities:
  * We've just finished collecting the coefficients of a column (marker ==
    FALSE) and are about to move to something new.
  * We've just finished processing a marker (marker == TRUE) and we're about
    to move to something new.
  Markers require no residual action. But in the first case, we have to install
  the column in the constraint system and reset the vector to 0 length.
*/
    tok = strtok(lex->string,sepchars) ;
    if (cistrcmp(pkcol->nme,tok) != 0)
    { if (marker == FALSE)
      { if (consys_addcol_pk(consys,vartype,pkcol,0.0,0.0,0.0) == FALSE)
	{ errmsg(156,rtnnme,"column",consys->nme,pkcol->nme) ;
	  return (mpsinINV) ; }
	intermediary = pkcol->ndx ;
	if ((void *) intermediary !=
	    dyhash_enter(STRALLOC(pkcol->nme),varhash,varhashsze,
	    	  (void *) intermediary))
	{ errmsg(155,rtnnme,"variable",
		 consys_nme(consys,'v',pkcol->ndx,TRUE,NULL)) ;
	  return (mpsinINV) ; }
	pkcol->cnt = 0 ;
	pkcol->nme = colnam ;
	colnam[0] = '\0' ; }
/*
  Just what is 'something new'? First check for any of the section keywords:
  rhs, ranges, bounds, or enddata.  Any of them are valid escapes out of the
  columns section. Change the state and exit the loop.
*/
      if (cistrcmp(tok,"rhs") == 0 || cistrcmp(tok,"rhs'") == 0)
	nxtstate = mpsinRHS ;
      else
      if (cistrcmp(tok,"ranges") == 0) nxtstate = mpsinRANGES ;
      else
      if (cistrcmp(tok,"bounds") == 0) nxtstate = mpsinBOUNDS ;
      else
      if (cistrcmp(tok,"enddata") == 0) nxtstate = mpsinENDDATA ;
      if (nxtstate != mpsinINV) break ;
/*
  Perhaps 'something new' is a marker. Check for one of the markers that we
  recognise. The token we've already parsed becomes the new 'column name'.
  The next token should be 'marker' (quotes included), and the one after that
  will tell us what sort of marker. Possibilities are sosorg, sosend, intorg,
  intend. At the end of if (marker), we'll continue to force the next iteration
  of the loop.
*/
      strcpy(colnam,tok) ;
      tok = strtok(NULL,sepchars) ;
      if (tok == NULL)
      { errmsg(158,rtnnme,consys->nme,pkcol->nme) ;
	return (mpsinINV) ; }
      if (cistrcmp(tok,"'marker'") == 0)
      { marker = TRUE ;
	tok = strtok(NULL,sepchars) ;
	if (tok == NULL)
	{ errmsg(159,rtnnme,consys->nme,pkcol->nme) ;
	  return (mpsinINV) ; }
/*
  sosorg/sosend bracket groups of variables which are SOS3.  There is no
  limit on the number of groups.  For a SOS3 group, the coefficients of the
  constraint (and, later, the rhs) are generated automatically.  The 'column
  name' for the line is the name of the SOS constraint. The name with the
  sosend marker is not important.
*/
	if (cistrcmp(tok,"'sosorg'") == 0)
	{ if (sosset == TRUE)
	  { errmsg(161,rtnnme,"sosorg",pkcol->nme,"sosend") ;
	    return (mpsinINV) ; }
	  intermediary =
		(ptrdiff_t) dyhash_lookup(pkcol->nme,conhash,conhashsze) ;
	  sosndx = (int) intermediary ;
	  if (sosndx == 0)
	  { errmsg(162,rtnnme,"SOS constraint",pkcol->nme,"marker","sosorg") ;
	    return (mpsinINV) ; }
	  sosset = TRUE ;
	  vartype = vartypBIN ;
	  pkcol->coeffs[0].val = 1.0 ;
	  pkcol->coeffs[0].ndx = sosndx ;
	  pkcol->cnt++ ;
	  sos3 = (lnk_struct *) MALLOC(sizeof(lnk_struct)) ;
	  sos3->llnxt = sos3lst ;
	  sos3lst = sos3 ;
	  intermediary = sosndx ;
	  lnk_in(sos3,intermediary) ; }
	else
	if (cistrcmp(tok,"'sosend'") == 0)
	{ if (sosset != TRUE)
	  { errmsg(161,rtnnme,"sosend",pkcol->nme,"sosorg") ;
	    return (mpsinINV) ; }
	  sosset = FALSE ;
	  vartype = vartypCON ; }
/*
  intorg/intend bracket a group of integer variables.  (Note that variables
  can also be specified as binary using the BV code in the bounds section).
  Nothing is done with the name assigned to intorg and intend markers.
*/
	else
	if (cistrcmp(tok,"'intorg'") == 0)
	{ if (intblk == TRUE)
	  { errmsg(161,rtnnme,"intorg",pkcol->nme,"intend") ;
	    return (mpsinINV) ; }
	  intblk = TRUE ;
	  vartype = vartypINT ; }
	else
	if (cistrcmp(tok,"'intend'") == 0)
	{ if (intblk != TRUE)
	  { errmsg(161,rtnnme,"intend",pkcol->nme,"intorg") ;
	    return (mpsinINV) ; }
	  intblk = FALSE ;
	  vartype = vartypCON ; }
	else
	{ errmsg(163,rtnnme,tok,pkcol->nme) ;
	  return (mpsinINV) ; }
	continue ; }
/*
  'Something new' wasn't a keyword or marker; it must be the start of a new
  column. Reset the marker flag and drop through to the code that collects
  coefficients for a column.
*/
      else
      { marker = FALSE ; } }
    else
    { tok = strtok(NULL,sepchars) ; }

/*
  To get here, either the first token on this line matched, and we're in the
  middle of collecting a column, or 'something new' turned out to be a new
  column and we've fallen through from the previous code.  The line contains
  one or two (row,coefficient) pairs. Parse them and stash them in the vector
  we're collecting.  Note that the coefficients of the SOS equality for a
  SOS3 set are supplied by default, so suppress an explicit one if we see it.
  Also suppress explicit 0's, and any coefficients that belong to superfluous
  contypNB constraints.
*/
    for ( ; tok != NULL ; tok = strtok(NULL,sepchars))
    { rownam = tok ;
      tok = strtok(NULL,sepchars) ;
      if (tok == NULL)
      { errmsg(164,rtnnme,consys->nme,pkcol->nme,rownam) ;
	return (mpsinINV) ; }
      aij = strtod(tok,&chkptr) ;
      if (chkptr == tok || errno == ERANGE)
      { errmsg(165,rtnnme,tok,consys->nme,pkcol->nme,rownam) ;
	return (mpsinINV) ; }
      intermediary = (ptrdiff_t) dyhash_lookup(rownam,conhash,conhashsze) ;
      rowndx = (int) intermediary ;
      if (rowndx == 0)
      { errmsg(166,rtnnme,"constraint",rownam,"column",consys->nme,pkcol->nme) ;
	return (mpsinINV) ; }
      if (rowndx != -1)
      { if (aij != 0.0 && !(sosset == TRUE && rowndx == sosndx))
	{ if (pkcol->cnt >= pkcol->sze)
	    if (pkvec_resize(pkcol,0) == FALSE)
	    { errmsg(174,rtnnme,consys->nme,pkcol->nme) ;
	      return (mpsinINV) ; }
	  pkcol->coeffs[pkcol->cnt].val = aij ;
	  pkcol->coeffs[pkcol->cnt].ndx = rowndx ;
	  pkcol->cnt++ ; } } } }
/*
  We've broken out of the loop -- if it isn't i/o error, clean up and return
  the next state code.
*/
  if (nxtstate == mpsinINV)
  { if (lex->class == DY_LCERR)
      errmsg(168,rtnnme,mysection) ;
    else
    if (lex->class == DY_LCEOF)
      errmsg(169,rtnnme,mysection) ;
    else
      errmsg(1,rtnnme,__LINE__) ;
    return (mpsinINV) ; }

  pkcol->nme = NULL ;
  pkvec_free(pkcol) ;

  return (nxtstate) ; }



static mpsinstate_enum mpsin_rhs (ioid mpschn, consys_struct *consys)

/*
  This routine is responsible for processing the rhs section, which provides
  values for the right-hand-sides of the the constraints.  Data lines in the
  rhs section are of the form:
	<rhs_vec_name> <row_name_1> <coeff1> <row_name_2> <coeff_2>
  Basically, we're specifying a column vector of right-hand-side values,
  named rhs_vec_name, in the same way we specified column vectors for the
  coefficients. The <row_name_2> <coeff_2> pair is optional. Note that this
  code does not support multiple rhs vectors. Unfortunately, it seems we do
  need to entertain the possibility that <rhs_vec_name> is null.

  Parameters:
    mpschn:	input stream
    consys:	(i) constraint system with coefficient matrix, constraint and
		    variable type arrays.
		(o) constraint system with coefficient matrix, rhs vector,
		    constraint and variable type  arrays
  
  Returns: successful returns will be one of mpsinRANGES, mpsinBOUNDS, or
	   mpsinENDDATA, depending on the presence (absence) of optional
	   sections; in the event of an error, mpsinINV
*/

{ int rowndx ;
  double *rhs,aij ;
  bool named_vec ;
  mpsinstate_enum nxtstate ;
  lex_struct *lex ;
  char *tok,*rownme,*chkptr ;
  const char *rhsnme ;
  /*
    Useful to suppress compiler warnings re. integer <-> pointer conversion
  */
  ptrdiff_t intermediary ;

  const char *rtnnme = "mpsin_rhs",
	     *mysection = "rhs" ;

/*
  Associate a rhs vector with the constraint system.
*/
  if (consys_attach(consys,CONSYS_RHS,
			   sizeof(double),(void **) &consys->rhs) == FALSE)
  { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_RHS)) ;
    return (mpsinINV) ; }
  setflg(consys->parts,CONSYS_RHS) ;
  rhs = consys->rhs ;
/*
  Check the first line to decide if this is a named vector or not. Given that
  the string scanner will insist on removing leading whitespace, the only way
  to tell is to count the number of tokens. An odd number indicates a named
  vector. An even number indicates the name is null.
*/
  lex = getmpsline(mpschn) ;
  if (lex->class == DY_LCQS)
  { rowndx = 1 ;
    for (tok = strpbrk(lex->string,sepchars) ;
	 tok != NULL ;
	 tok = strpbrk(tok,sepchars))
    { tok += strspn(tok,sepchars) ;
      if (*tok != '\0') rowndx++ ; }
    if (rowndx%2 != 0)
    { named_vec = TRUE ;
      rhsnme = NULL ; }
    else
    { named_vec = FALSE ;
      rhsnme = "<nil>" ; } }
  else
  { named_vec = FALSE ;
    rhsnme = "<nil>" ; }
/*
  Now start to parse the rhs values.  There are three possible escapes from
  this section: a ranges indicator, a bounds indicator, or an endata
  indicator. Ranges and bounds are optional sections, so we look for all
  three.
*/
  nxtstate = mpsinINV ;
  for ( ; lex->class == DY_LCQS ; lex = getmpsline(mpschn))
  { tok = strtok(lex->string,sepchars) ;
    if (cistrcmp(tok,"bounds") == 0) nxtstate = mpsinBOUNDS ;
    else
    if (cistrcmp(tok,"ranges") == 0) nxtstate = mpsinRANGES ;
    else
    if (cistrcmp(tok,"endata") == 0) nxtstate = mpsinENDDATA ;
    if (nxtstate != mpsinINV) break ;
/*
  If this is the vector name, save it and parse the first row name.
*/
    if (named_vec == TRUE)
    { rhsnme = tok ;
      tok = strtok(NULL,sepchars) ; }
/*
  Start a loop to parse off the <row name> <coeff> pairs. Hash the row name
  to get the row index, then try for the coefficient. If there are no errors,
  install the coefficient in the rhs vector.
*/
    for ( ; tok != NULL ; tok = strtok(NULL,sepchars))
    { intermediary = (ptrdiff_t) dyhash_lookup(tok,conhash,conhashsze) ;
      rowndx = (int) intermediary ;
      if (rowndx == 0)
      { errmsg(166,rtnnme,"constraint",tok,consys_assocnme(NULL,CONSYS_RHS),
	       consys->nme,rhsnme) ;
	return (mpsinINV) ; }
      rownme = tok ;
      tok = strtok(NULL,sepchars) ;
      if (tok == NULL)
      { errmsg(164,rtnnme,consys->nme,rhsnme,
	       consys_nme(consys,'c',rowndx,TRUE,NULL)) ;
	return (mpsinINV) ; }
      aij = strtod(tok,&chkptr) ;
      if (chkptr == tok || errno == ERANGE)
      { errmsg(165,rtnnme,tok,consys->nme,rhsnme,rownme) ;
	return (mpsinINV) ; }
       rhs[rowndx] = aij ; } }
/*
  See if we're out of the loop due to an i/o error.
*/
  if (nxtstate == mpsinINV)
  { if (lex->class == DY_LCERR)
      errmsg(168,rtnnme,mysection) ;
    else
    if (lex->class == DY_LCEOF)
      errmsg(169,rtnnme,mysection) ;
    else
      errmsg(1,rtnnme,__LINE__) ;
    return (mpsinINV) ; }

  return (nxtstate) ; }



static mpsinstate_enum mpsin_ranges (ioid mpschn, consys_struct *consys)

/*
  This routine is responsible for processing the ranges section, which
  provides a rhs range to establish constraints of the form blow < ax < b.
  The rules for incorporating range information are as follows:

    Constraint Type	Range sign	blow		b
    ---------------	----------	----------	-------
      contypGE		irrelevant	b		b+|range|
      contypLE		irrelevant	b-|range|	b
      contypEQ		+		b		b+|range|
			-		b-|range|	b
  
  In all cases, the constraint type becomes contypRNG.

  Data lines in the ranges section are identical in form and interpretation
  to rhs lines. Note that this code does not support multiple range vectors.

  Parameters:
    mpschn:	input stream
    consys:	(i) constraint system with coefficient matrix, rhs vector,
		    constraint and variable type  arrays
    		(o) constraint system with coefficient matrix, rhs vector,
		    constraint and variable type  arrays
  
  Returns: successful returns will be one of mpsinBOUNDS or mpsinENDDATA,
	   depending on the presence (absence) of optional sections; in the
	   event of an error, mpsinINV
*/

{ int rowndx ;
  double aij ;
  double *rhs,*rhslow ;
  bool named_vec ;
  contyp_enum *contyp ;
  mpsinstate_enum nxtstate ;
  lex_struct *lex ;
  const char *rngnme ;
  char *rownme,*tok,*chkptr ;
  /*
    Useful to suppress compiler warnings re. integer <-> pointer conversion
  */
  ptrdiff_t intermediary ;

  const char *rtnnme = "mpsin_ranges",
	     *mysection = "ranges" ;

/*
  Associate a rhslow vector with the constraint system. Pick up the rhs and
  constraint type vectors.
*/
  if (consys_attach(consys,CONSYS_RHSLOW,
		    sizeof(double),(void **) &consys->rhslow) == FALSE)
  { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_RHSLOW)) ;
    return (mpsinINV) ; }
  setflg(consys->parts,CONSYS_RHSLOW) ;
  rhslow = consys->rhslow ;
  rhs = consys->rhs ;
  contyp = consys->ctyp ;
# ifndef NDEBUG
  if (rhs == NULL)
  { errmsg(160,rtnnme,mysection,consys_assocnme(consys,CONSYS_RHS)) ;
    return (mpsinINV) ; }
  if (contyp == NULL)
  { errmsg(160,rtnnme,mysection,consys_assocnme(consys,CONSYS_CTYP)) ;
    return (mpsinINV) ; }
# endif
/*
  Check the first line to decide if this is a named vector or not. Given that
  the string scanner will insist on removing leading whitespace, the only way
  to tell is to count the number of tokens. An odd number indicates a named
  vector. An even number indicates the name is null.
*/
  lex = getmpsline(mpschn) ;
  if (lex->class == DY_LCQS)
  { rowndx = 1 ;
    for (tok = strpbrk(lex->string,sepchars) ;
	 tok != NULL ;
	 tok = strpbrk(tok,sepchars))
    { tok += strspn(tok,sepchars) ;
      if (*tok != '\0') rowndx++ ; }
    if (rowndx%2 != 0)
    { named_vec = TRUE ;
      rngnme = NULL ; }
    else
    { named_vec = FALSE ;
      rngnme = "<nil>" ; } }
  else
  { named_vec = FALSE ;
    rngnme = "<nil>" ; }
/*
  Now start to parse the range values.  There are two possible escapes from
  this section: a bounds indicator or an endata indicator. Bounds is an
  optional section, so we look for both.
*/
  nxtstate = mpsinINV ;
  for ( ; lex->class == DY_LCQS ; lex = getmpsline(mpschn))
  { tok = strtok(lex->string,sepchars) ;
    if (cistrcmp(tok,"bounds") == 0) nxtstate = mpsinBOUNDS ;
    else
    if (cistrcmp(tok,"endata") == 0) nxtstate = mpsinENDDATA ;
    if (nxtstate != mpsinINV) break ;
/*
  If this is the vector name, discard it and parse the first row name.
*/
    if (named_vec == TRUE)
    { rngnme = tok ;
      tok = strtok(NULL,sepchars) ; }
/*
  Start a loop to parse off the <row name> <coeff> pairs. Hash the row name
  to get the row index, then try for the coefficient.
*/
    for ( ; tok != NULL ; tok = strtok(NULL,sepchars))
    { intermediary = (ptrdiff_t) dyhash_lookup(tok,conhash,conhashsze) ;
      rowndx = (int) intermediary ;
      if (rowndx == 0)
      { errmsg(166,rtnnme,"constraint",tok,"range vector",consys->nme,rngnme) ;
	return (mpsinINV) ; }
      rownme = tok ;
      tok = strtok(NULL,sepchars) ;
      if (tok == NULL)
      { errmsg(164,rtnnme,consys->nme,rngnme,
	       consys_nme(consys,'c',rowndx,TRUE,NULL)) ;
	return (mpsinINV) ; }
      aij = strtod(tok,&chkptr) ;
      if (chkptr == tok || errno == ERANGE)
      { errmsg(165,rtnnme,tok,consys->nme,rngnme,rownme) ;
	return (mpsinINV) ; }
/*
  We've got the row index and coefficient, so install the range. rhs, rhslow,
  and contyp may have to be modified, as described in the header comments.
*/
      switch (contyp[rowndx])
      { case contypEQ:
	{ if (aij < 0)
	  { rhslow[rowndx] = rhs[rowndx]+aij ; }
	  else
	  { rhslow[rowndx] = rhs[rowndx] ;
	    rhs[rowndx] += aij ; }
	  break ; }
	case contypGE:
	{ rhslow[rowndx] = rhs[rowndx] ;
	  rhs[rowndx] += fabs(aij) ;
	  break ; }
	case contypLE:
	{ rhslow[rowndx] = rhs[rowndx]-fabs(aij) ;
	  break ; }
	case contypNB:
	{ errmsg(175,rtnnme,consys_nme(consys,'c',rowndx,TRUE,NULL),rowndx) ;
	  return (mpsinINV) ; }
	default:
	{ errmsg(1,rtnnme,__LINE__) ;
	  break ; } }
      contyp[rowndx] = contypRNG ; } }
/*
  See if we're out of the loop due to an i/o error.
*/
  if (nxtstate == mpsinINV)
  { if (lex->class == DY_LCERR)
      errmsg(168,rtnnme,mysection) ;
    else
    if (lex->class == DY_LCEOF)
      errmsg(169,rtnnme,mysection) ;
    else
      errmsg(1,rtnnme,__LINE__) ;
    return (mpsinINV) ; }
  
  return (nxtstate) ; }



static mpsinstate_enum mpsin_bounds (ioid mpschn, consys_struct *consys)

/*
  This routine is responsible for processing the bounds section, which
  specifies upper and lower bounds for variables. All variables start from the
  following default values:
    continous variables		0 -- +inf
    integer variables		0 -- 1		(i.e., assumed binary)
  
  These bounds are modified as follows by the bound type codes:

  LO	sets the lower bound to the given value
  UP	sets the upper bound to the given value
  UI	sets the upper bound to the floor of the given value
  MI	sets the lower bound to -inf
  PL	sets the upper bound to +inf
  FR	sets the lower bound to -inf and the upper bound to +inf (free)
  FX	sets the lower and upper bounds to the given value (fixed)
  BV	sets the lower bound to 0 and the upper bound to 1

  Note that UI and BV have the side effect of converting the variable type to
  general integer and binary, respectively.

  Data lines in the bounds section have the format:
      <bound_type> <bound_vec_name> <var_name> <coeff>
  The coeff is not needed for types MI, PL, FR, and BV. Note that this code
  does not support multiple bounds vectors.

  For this routine, the test for a null vector name is based on the MPS fixed
  field specification. We take the attitude that the only way we can reliably
  detect a blank name is if the user is obeying the strict fixed-field format
  rules and leaves all of nam0 blank.

  Parameters:
    mpschn:	input stream
    consys:	(i) constraint system with coefficient matrix, rhs vector,
		    constraint and variable type arrays; rhslow optional.
    		(o) constraint system with coefficient matrix, rhs vector,
		    variable upper and lower bound vectors, constraint and
		    variable type arrays; rhslow optional.
  
  Returns: mpsinENDDATA if all goes well; mpsinINV in the event of an error
*/

{ int colndx ;
  double *vlb,*vub,aij,infinity ;
  vartyp_enum *vartyp ;
  bool seen_end,named_vec ;
  lex_struct *lex ;
  const char *bndnme ;
  char *varnme,*bndcode,*tok,*chkptr ;
  /*
    Useful to suppress compiler warnings re. integer <-> pointer conversion
  */
  ptrdiff_t intermediary ;

  const char *rtnnme = "mpsin_bounds",
	     *mysection = "bounds" ;

/*
  Associate upper and lower bound vectors with the constraint system. These
  will be initialised to 0 and +inf, respectively, as they are allocated and
  attached. Pick up the variable type vector.
*/
  if (consys_attach(consys,CONSYS_VUB,
		    sizeof(double),(void **) &consys->vub) == FALSE)
  { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_VUB)) ;
    return (mpsinINV) ; }
  setflg(consys->parts,CONSYS_VUB) ;
  vub = consys->vub ;
  infinity = consys->inf ;
  if (consys_attach(consys,CONSYS_VLB,
		    sizeof(double),(void **) &consys->vlb) == FALSE)
  { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_VLB)) ;
    return (mpsinINV) ; }
  setflg(consys->parts,CONSYS_VLB) ;
  vlb = consys->vlb ;
/*
  Pick up the variable type vector. Walk the variables and reset the upper
  bound on any integer variables to 1.
*/
  vartyp = consys->vtyp ;
# ifndef NDEBUG
  if (vartyp == NULL)
  { errmsg(160,rtnnme,mysection,consys_assocnme(consys,CONSYS_VTYP)) ;
    return (mpsinINV) ; }
# endif
  for (colndx = 1 ; colndx <= consys->archvcnt ; colndx++)
  { if (vartyp[colndx] == vartypINT || vartyp[colndx] == vartypBIN)
      vub[colndx] = 1 ; }
/*
  Grab the first line and decide if this is a named vector or not. The only
  way we can have a null name is if the user is using fixed format. In this
  case, the bound code will occupy columns 2-3, the vector name columns 5-12,
  and the first variable name columns 15- 22. If the vector name is blank,
  we'll see 10 characters worth of space.
*/
  lex = getmpsline(mpschn) ;
  if (lex->class == DY_LCQS)
  { tok = (lex->string)+3 ;
    if (strspn(tok,sepchars) == 10)
    { named_vec = FALSE ;
      bndnme = "<nil>" ; }
    else
    { named_vec = TRUE ; } }
  else
  { named_vec = FALSE ;
    bndnme = "<nil>" ; }
/*
  Now start to parse the bound values. The only correct way out of the loop
  is to see the enddata indicator line. Guess that this is a named vector,
  and correct it below if we're wrong.
*/
  seen_end = FALSE ;
  for ( ; lex->class == DY_LCQS ; lex = getmpsline(mpschn))
  { tok = strtok(lex->string,sepchars) ;
    if (cistrcmp(tok,"endata") == 0)
    { seen_end = TRUE ;
      break ; }
/*
  The first token on the line is the bound code. Then parse off the bound
  vector name, the variable name, and (if we need it) the bound value.
*/
    bndcode = tok ;
    if (named_vec == TRUE)
    { bndnme = strtok(NULL,sepchars) ;
      if (bndnme == NULL)
      { errmsg(153,rtnnme,"bound vector name",mysection) ;
	return (mpsinINV) ; } }
    varnme = strtok(NULL,sepchars) ;
    if (varnme == NULL)
    { errmsg(153,rtnnme,"variable",mysection) ;
      return (mpsinINV) ; }
    intermediary = (ptrdiff_t) dyhash_lookup(varnme,varhash,varhashsze) ;
    colndx = (int) intermediary ;
    if (colndx == 0)
    { errmsg(162,rtnnme,"variable",varnme,consys->nme,mysection) ;
      return (mpsinINV) ; }
    aij = quiet_nan(0) ;
    if (cistrcmp(bndcode,"lo") == 0 || cistrcmp(bndcode,"up") == 0 ||
	cistrcmp(bndcode,"fx") == 0 || cistrcmp(bndcode,"ui") == 0)
    { tok = strtok(NULL,sepchars) ;
      if (tok == NULL)
      { errmsg(164,rtnnme,consys->nme,varnme,mysection) ;
	return (mpsinINV) ; }
      aij = strtod(tok,&chkptr) ;
      if (chkptr == tok || errno == ERANGE)
      { errmsg(165,rtnnme,tok,consys->nme,varnme,mysection) ;
	return (mpsinINV) ; } }
/*
  Begin a series of if statements, to see if we recognise the bound type.
  Adjust the bound(s) according to the bound specification.  Various bound
  types require a few additional actions.

  For garden-variety upper & lower bounds, we just enter the bound in the
  appropriate vector.
*/
    if (cistrcmp(bndcode,"lo") == 0)
      vlb[colndx] = aij ;
    else
    if (cistrcmp(bndcode,"up") == 0)
    { vub[colndx] = aij ; }
/*
  For the ui code, we have to round aij down to the nearest integer. Force
  an integral variable type as well.
*/
    else
    if (cistrcmp(bndcode,"ui") == 0)
    { vub[colndx] = floor(aij) ;
      if (vartyp[colndx] == vartypCON)
      { vartyp[colndx] = vartypINT ;
	consys->intvcnt++ ; } }
/*
  For binary variables, we need to make sure the integer and binary counts
  are correct.
*/
    else
    if (cistrcmp(bndcode,"bv") == 0)
    { if (vartyp[colndx] == vartypINT) consys->intvcnt-- ;
      vartyp[colndx] = vartypBIN ;
      consys->binvcnt++ ;
      vub[colndx] = 1.0 ;
      vlb[colndx] = 0.0 ; }
/*
  The rest of these are straightforward.
*/
    else
    if (cistrcmp(bndcode,"fx") == 0)
    { vub[colndx] = vlb[colndx] = aij ; }
    else
    if (cistrcmp(bndcode,"fr") == 0)
    { vub[colndx] = infinity ;
      vlb[colndx] = -infinity ; }
    else
    if (cistrcmp(bndcode,"pl") == 0)
    { vub[colndx] = infinity ; }
    else
    if (cistrcmp(bndcode,"mi") == 0)
    { vlb[colndx] = -infinity ; }
/*
  Lastly, the error case, for a bound type we don't recognise.
*/
    else
    { errmsg(167,rtnnme,bndcode,consys_nme(consys,'v',colndx,TRUE,NULL),
	     colndx) ;
      return (mpsinINV) ; } }
/*
  Check to see if we escaped the loop due to i/o error.
*/
  if (seen_end == FALSE)
  { if (lex->class == DY_LCERR)
      errmsg(168,rtnnme,mysection) ;
    else
    if (lex->class == DY_LCEOF)
      errmsg(169,rtnnme,mysection) ;
    else
      errmsg(1,rtnnme,__LINE__) ;
    return (mpsinINV) ; }
/*
  Otherwise, scan the bounds vectors and see if there are any integer variables
  that could be converted to binary, then return.
*/
  for (colndx = 1 ; colndx <= consys->archvcnt ; colndx++)
    if (vartyp[colndx] == vartypINT &&
	vub[colndx] == 1.0 && vlb[colndx] == 0.0)
    { vartyp[colndx] = vartypBIN ;
      consys->intvcnt-- ;
      consys->binvcnt++ ; }

  return (mpsinENDDATA) ; }



static mpsinstate_enum mpsin_enddata (ioid mpschn, consys_struct *consys,
				      mipopts_struct *mipopts)

/*
  This routine does the cleanup. We copy the objective function from the
  constraint matrix to the objective vector, and delete the row.  We go
  through the list of SOS3 sets and set the rhs value to 1 for each
  constraint. We convert all >= constraints to <= constraints (this results
  in considerable simplification at various places further along in the
  code).  Finally, we free the hash tables and the string buffer used by
  dyio_scanstr.

  Parameters:
    consys:	(i) complete constraint system
    		(o) complete constraint system, with the objective function
		    installed, and generally tidied up a bit.
  
  Returns: mpsinENDDATA if all goes well; mpsinINV in the event of an error
	   (currently no errors are possible unless NDEBUG is undefined).
*/

{ int ndx ;
  lnk_struct *sos3 ;
  pkvec_struct *pkvec ;
  hel *entry ;
  lex_struct *lex ;
  /*
    Useful to suppress compiler warnings re. integer <-> pointer conversion
  */
  ptrdiff_t intermediary ;

  const char *rtnnme = "mpsin_enddata" ;

#ifdef BONSAIG

  /* bonsaiG uses this; it's not present in the OsiDylp configuration. */

  /* tourclass_utils.c */

  extern bool tourclass_init(consys_struct *consys,
			     hel **hashtab, int hashsize) ;
#endif
  

# ifdef PARANOIA
  if (consys->ctyp == NULL)
  { errmsg(101,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_CTYP)) ;
    return (mpsinINV) ; }
  if (consys->rhs == NULL)
  { errmsg(101,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_RHS)) ;
    return (mpsinINV) ; }
  if (consys->objndx <= 0 || consys->objndx > consys->archccnt)
  { errmsg(102,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_OBJ),
	   1,consys->archccnt) ;
    return (mpsinINV) ; }
# endif
/*
  Copy the objective function from the constraint matrix into the objective
  vector.  If the user has specified a maximisation problem, we deal with it
  here by multiplying through by -1, so that the rest of the code always sees
  minimisation.

  If the objective function is to be used as a constraint, the form will be
  cx - x<z> = 0, where x<z> is installed as a free or upper-bounded
  variable.  The initial value for the lower bound is -inf (later tightened
  to the value of the objective of the root LP relaxation). The initial value
  for the upper bound is mipopts.zinit, which defaults to +inf unless the
  user supplies a different value. The bound is tightened each time the
  incumbent solution is replaced.
*/
  consys->obj = NULL ;
  if (consys_attach(consys,CONSYS_OBJ,
		    sizeof(double),(void **) &consys->obj) == FALSE)
  { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_OBJ)) ;
    return (mpsinINV) ; }
  setflg(consys->parts,CONSYS_OBJ) ;
  if (mipopts->minmax == -1)
  { if (consys_mulrow(consys,consys->objndx,-1.0) == FALSE)
    { errmsg(173,rtnnme,consys->nme,
	     consys_nme(consys,'c',consys->objndx,FALSE,NULL),consys->objndx) ;
      return (mpsinINV) ; } }
  if (consys_getrow_ex(consys,consys->objndx,&consys->obj) == FALSE)
  { errmsg(112,rtnnme,"retrieve","row",
	   consys_nme(consys,'c',consys->objndx,FALSE,NULL),consys->objndx) ;
    return (mpsinINV) ; }
/*
  The OSI test suite doesn't take kindly to reordering the constraint system.
  consys_delrow_stable performs O(m) deletion, shifting all constraints down
  to fill a hole.
*/
  if (mipopts->objcon == FALSE)
#ifdef COIN_HAS_DYLP
  { if (consys_delrow_stable(consys,consys->objndx) == FALSE)
#else
  { if (consys_delrow(consys,consys->objndx) == FALSE)
#endif
    { errmsg(112,rtnnme,consys->nme,"delete","row",
	     consys_nme(consys,'c',consys->objndx,FALSE,NULL),consys->objndx) ;
      return (mpsinINV) ; } }
  else
  { consys->ctyp[consys->objndx] = contypEQ ;
    pkvec = pkvec_new(1) ;
    pkvec->nme = STRALLOC("x<z>") ;
    pkvec->dim = consys->concnt ;
    pkvec->dflt = 0 ;
    pkvec->cnt = 1 ;
    pkvec->coeffs[0].ndx = consys->objndx ;
    pkvec->coeffs[0].val = -1.0 ;
    if (consys_addcol_pk(consys,vartypCON,
			 pkvec,0.0,-consys->inf,mipopts->zinit) == FALSE)
    { errmsg(156,rtnnme,"column",consys->nme,pkvec->nme) ;
      return (mpsinINV) ; }
    consys->xzndx = pkvec->ndx ;
    (void) STRFREE(pkvec->nme) ;
    pkvec_free(pkvec) ;
    pkvec = NULL ; }
/*
  Walk the list of SOS3 sets, get the row index, and set the rhs entry to 1.
  Do some integrity checks while we're at it, provided debugging is enabled.
*/
  for (sos3 = sos3lst ; sos3 != NULL ; sos3 = sos3->llnxt)
  { intermediary = lnk_out(sos3,ptrdiff_t) ;
    ndx = (int) intermediary ;
#   ifndef NDEBUG
    if (ndx < 1 || ndx > consys->archccnt)
    { errmsg(102,rtnnme,consys->nme,"constraint",ndx,1,consys->archccnt) ;
      return (mpsinINV) ; }
    if (consys->ctyp[ndx] != contypEQ)
    { errmsg(176,rtnnme,consys_nme(consys,'c',ndx,TRUE,NULL),ndx,
	     consys_prtcontyp(consys->ctyp[ndx])) ;
      return (mpsinINV) ; }
#   endif
    consys->rhs[ndx] = 1.0 ; }

#ifndef COIN_HAS_DYLP
/*
  Step through the constraints and replace ax >= b constraints with
  (-a)x <= -b constraints. consys_mulrow will take care of the necessary
  modifications.

  While we're at it, look for and delete rows with no coefficients. (Yes, it
  happens.)

  The OSI test suite does not like re-arrangement of constraints, or the
  removal of constraints, so disable this for OSI. We can do (and undo) the
  contypGE => contypLE conversion around the call to dylp. Fortunately, dylp
  can tolerate empty constraints.
*/
  for (ndx = consys->archccnt ; ndx > 0 ; ndx--)
  { if (consys_infnormrow(consys,ndx) == 0)
    { warn(179,rtnnme,consys->nme,consys_nme(consys,'c',ndx,FALSE,NULL),ndx) ;
      if (consys_delrow(consys,ndx) == FALSE)
      { errmsg(112,rtnnme,consys->nme,"delete","row",
	       consys_nme(consys,'c',ndx,FALSE,NULL),ndx) ;
	return (mpsinINV) ; }
    continue ; }
      
    if (consys->ctyp[ndx] == contypGE)
    { if (consys_mulrow(consys,ndx,-1.0) == FALSE)
      { errmsg(112,rtnnme,consys->nme,"scalar multiply","row",
	       consys_nme(consys,'c',ndx,FALSE,NULL),ndx) ;
	return (mpsinINV) ; } } }
#endif /* !COIN_HAS_DYLP */
#ifdef BONSAIG
/*
  Convert the temporary data structure from the parse of tour class
  specifications into the permanent run-time structure.
*/
  if (tourclass_init(consys,varhash,varhashsze) == FALSE)
  { errmsg(761,rtnnme) ;
    return (mpsinINV) ; }
#endif
/*
  Free the hash tables!
*/
  for (ndx = 0 ; ndx < conhashsze ; ndx++)
    for (entry = conhash[ndx] ; entry != NULL ; entry = conhash[ndx])
    { conhash[ndx] = entry->next ;
      STRFREE(entry->key) ;
      (void) FREE((char *) entry) ; }
  (void) FREE((char *) conhash) ;
  for (ndx = 0 ; ndx < varhashsze ; ndx++)
    for (entry = varhash[ndx] ; entry != NULL ; entry = varhash[ndx])
    { varhash[ndx] = entry->next ;
      STRFREE(entry->key) ;
      (void) FREE((char *) entry) ; }
  (void) FREE((char *) varhash) ;
/*
  Clean up after dyio_scanstr.
*/
  lex = dyio_scanstr(mpschn,DY_LCQS,0,'\0','\0') ;
  if (lex->class == DY_LCQS && lex->string != NULL)
  { FREE(lex->string) ;
    lex->string = NULL ; }
  

# ifndef NDEBUG
  dyio_outfmt(dy_logchn,dy_gtxecho,"\n\t(%s) read %d variables",
	      rtnnme,consys->archvcnt) ;
  if (consys->intvcnt > 0 || consys->binvcnt > 0)
    dyio_outfmt(dy_logchn,dy_gtxecho,
    		" (%d continuous, %d integer, %d binary)",
	        consys->archvcnt-(consys->intvcnt+consys->binvcnt),
	        consys->intvcnt,consys->binvcnt) ;
  dyio_outfmt(dy_logchn,dy_gtxecho,
	      ".\n\t\tread %d non-zero coefficients from the MPS file.",
	      consys->mtx.coeffcnt) ;
  if (consys->maxcolndx > 0)
    dyio_outfmt(dy_logchn,dy_gtxecho,
		"\n\t\tthe longest column is %s, with %d entries.",
		consys_nme(consys,'v',consys->maxcolndx,FALSE,NULL),
		consys->maxcollen) ;
  if (consys->maxrowndx > 0)
    dyio_outfmt(dy_logchn,dy_gtxecho,
		"\n\t\tthe longest row is %s, with %d entries.\n",
		consys_nme(consys,'c',consys->maxrowndx,FALSE,NULL),
		consys->maxrowlen) ;
# endif

  return (mpsinENDDATA) ; }



static mpsinstate_enum mpsin_force (consys_struct *consys,
				    mpsinstate_enum fromstate,
				    mpsinstate_enum tostate)

/*
  This routine installs default vectors when optional sections of the mps
  file are missing. It handles the rhs, range (rhslow), and variable bounds
  (vlb and vub). The operation of the routine is based on the requirement
  that optional sections of the mps file must occur in a fixed order: rhs,
  ranges, bounds. For example, a call with fromstate == mpsinRHS and tostate
  == mpsinRANGES will fill in a default for the rhs vector only. A call with
  fromstate == mpsinRANGES and tostate == mpsinENDDATA will fill in defaults
  for rhslow (ranges), and vlb and vub (bounds).

  Note that the default for rhslow is nothing -- if there are no range
  constraints in the mps file, we'll never need this vector.

  Parameters:
    consys:	constraint system
    fromstate:	the first section missing from the mps file.
    tostate:	the next section actually present in the mps file.

  Returns: tostate, or mpsinINV if something goes wrong.
*/

{ const char *rtnnme = "mpsin_force" ;

/*
  The strategy here is that fromstate takes us to the appropriate starting
  state in the switch, and then we fall through the cases until we break out
  based on tostate. If we fall through to the final default case, we're in
  deep trouble.
*/
  switch (fromstate)
  { case mpsinRHS:
    { if (consys_attach(consys,CONSYS_RHS,
			sizeof(double),(void **) &consys->rhs) == FALSE)
      { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_RHS)) ;
	return (mpsinINV) ; }
      setflg(consys->parts,CONSYS_RHS) ;
      if (tostate == mpsinRANGES) break ; }
    case mpsinRANGES:
    { if (tostate == mpsinBOUNDS) break ; }
    case mpsinBOUNDS:
    { if (consys_attach(consys,CONSYS_VUB,
			sizeof(double),(void **) &consys->vub) == FALSE)
      { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_VUB)) ;
	return (mpsinINV) ; }
      setflg(consys->parts,CONSYS_VUB) ;
      if (consys_attach(consys,CONSYS_VLB,
			sizeof(double),(void **) &consys->vlb) == FALSE)
      { errmsg(100,rtnnme,consys->nme,consys_assocnme(NULL,CONSYS_VLB)) ;
	return (mpsinINV) ; }
      setflg(consys->parts,CONSYS_VLB) ;
      if (tostate == mpsinENDDATA) break ; }
    default:
    { errmsg(1,rtnnme,__LINE__) ;
      return (mpsinINV) ; } }

  return (tostate) ; }



bool mpsin (const char *mpspath, consys_struct **consys,
	    mipopts_struct *mipopts, double infinity)

/*
  This routine reads an MPS format input file and creates a constraint system
  structure to hold it. See the comments at the beginning of the file for
  info about the MPS standard.

  The overall organisation is a state machine, with one state for each section
  of the MPS file, plus a start state.

  If objnme is NULL, the default action defined by the MPS standard is to use
  the first non-binding constraint in the file as the objective function.

  Parameters:
    mpspath:	file name of the MPS input file
    consys:	(o) the constraint system
    objnme:	the name of the objective function, or NULL
    infinity:	the value to be used for infinity

  Returns: TRUE if the MPS file is successfully read into the internal data
	   structures, FALSE otherwise.
*/

{ ioid mpschn ;
  mpsinstate_enum nxtstate ;

  const char *rtnnme = "mpsin" ;

/*
  Paranoia & initialisation.
*/
# ifndef NDEBUG
  if (consys == NULL)
  { errmsg(2,rtnnme,"consys") ;
    return (FALSE) ; }
  if (mpspath == NULL)
  { errmsg(2,rtnnme,"mpspath") ;
    return (FALSE) ; }
  if (mipopts == NULL)
  { errmsg(2,rtnnme,"mipopts") ;
    return (FALSE) ; }
# endif
/*
  Open the MPS input file. 
*/
  mpschn = dyio_openfile(mpspath,"r") ;
  if (mpschn == IOID_INV) return (FALSE) ;
/*
  The main state machine for working through the file. Legal transitions are
  listed beside each case.
*/
  nxtstate = mpsinNAME ;
  while (nxtstate != mpsinINV)
    switch (nxtstate)
    { case mpsinNAME:	/* mpsinROWS */
      { nxtstate = mpsin_name(mpschn,consys,infinity) ;
	if (nxtstate != mpsinINV)
	{ if (mipopts->objnme == NULL)
	    (*consys)->objnme = NULL ;
	  else
	    (*consys)->objnme = STRALLOC(mipopts->objnme) ; }
	break ; }
      case mpsinROWS:	/* mpsinCOLUMNS */
      { nxtstate = mpsin_rows(mpschn,*consys) ;
	break ; }
      case mpsinCOLUMNS: /* mpsinRHS, mpsinRANGES, mpsinBOUNDS, mpsinENDDATA */
      { nxtstate = mpsin_columns(mpschn,*consys) ;
	if (nxtstate != mpsinRHS && nxtstate != mpsinINV)
	  nxtstate = mpsin_force(*consys,mpsinRHS,nxtstate) ;
	break ; }
      case mpsinRHS: /* mpsinRANGES, mpsinBOUNDS, mpsinENDDATA */
      { nxtstate = mpsin_rhs(mpschn,*consys) ;
	if (nxtstate != mpsinRANGES && nxtstate != mpsinINV)
	  nxtstate = mpsin_force(*consys,mpsinRANGES,nxtstate) ;
	break ; }
      case mpsinRANGES: /* mpsinBOUNDS, mpsinENDDATA */
      { nxtstate = mpsin_ranges(mpschn,*consys) ;
	if (nxtstate != mpsinBOUNDS && nxtstate != mpsinINV)
	  nxtstate = mpsin_force(*consys,mpsinBOUNDS,nxtstate) ;
	break ; }
      case mpsinBOUNDS: /* mpsinENDDATA */
      { nxtstate = mpsin_bounds(mpschn,*consys) ;
	break ; }
      case mpsinENDDATA:
      { nxtstate = mpsin_enddata(mpschn,*consys,mipopts) ;
	(void) dyio_closefile(mpschn) ;
	if (nxtstate == mpsinENDDATA)
	{ if (mipopts->objnme == NULL)
	    mipopts->objnme = STRALLOC((*consys)->objnme) ;
	  return (TRUE) ; }
	break ; }
      default:
      { errmsg(1,rtnnme,__LINE__) ;
	nxtstate = mpsinINV ;
	break ; } }
/*
  Something's screwed up if we reach here.
*/
  errmsg(170,rtnnme,mpspath) ;
  return (FALSE) ; }

#ifndef BONSAIG

bool dy_mpsin (const char *mpspath, consys_struct **consys, double infinity)
/*
  A wrapper routine to get from OsiDylp to mpsin. No need to bother OsiDylp
  with the bits of information that need to be present in mipopts.
*/
{ bool retval ;

  mipopts_struct mipopts ;
  mipopts.minmax = 1 ;
  mipopts.objcon = FALSE ;
  mipopts.objnme = NULL ;
  retval = mpsin(mpspath,consys,&mipopts,infinity) ;
  if (mipopts.objnme != NULL) STRFREE(mipopts.objnme) ;

  return (retval) ; }

#endif
