/* glpmpl03.c */

/***********************************************************************
*  This code is part of GLPK (GNU Linear Programming Kit).
*
*  Copyright (C) 2000, 01, 02, 03, 04, 05, 06, 07, 08 Andrew Makhorin,
*  Department for Applied Informatics, Moscow Aviation Institute,
*  Moscow, Russia. All rights reserved. E-mail: <mao@mai2.rcnet.ru>.
*
*  GLPK is free software: you can redistribute it and/or modify it
*  under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  GLPK is distributed in the hope that it will be useful, but WITHOUT
*  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
*  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
*  License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with GLPK. If not, see <http://www.gnu.org/licenses/>.
***********************************************************************/

#define _GLPSTD_ERRNO
#define _GLPSTD_STDIO
#include "glplib.h"
#include "glpmpl.h"

/**********************************************************************/
/* * *                   FLOATING-POINT NUMBERS                   * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- fp_add - floating-point addition.
--
-- This routine computes the sum x + y. */

double fp_add(MPL *mpl, double x, double y)
{     if (x > 0.0 && y > 0.0 && x > + 0.999 * DBL_MAX - y ||
          x < 0.0 && y < 0.0 && x < - 0.999 * DBL_MAX - y)
         mpl_error(mpl, "%.*g + %.*g; floating-point overflow",
            DBL_DIG, x, DBL_DIG, y);
      return x + y;
}

/*----------------------------------------------------------------------
-- fp_sub - floating-point subtraction.
--
-- This routine computes the difference x - y. */

double fp_sub(MPL *mpl, double x, double y)
{     if (x > 0.0 && y < 0.0 && x > + 0.999 * DBL_MAX + y ||
          x < 0.0 && y > 0.0 && x < - 0.999 * DBL_MAX + y)
         mpl_error(mpl, "%.*g - %.*g; floating-point overflow",
            DBL_DIG, x, DBL_DIG, y);
      return x - y;
}

/*----------------------------------------------------------------------
-- fp_less - floating-point non-negative subtraction.
--
-- This routine computes the non-negative difference max(0, x - y). */

double fp_less(MPL *mpl, double x, double y)
{     if (x < y) return 0.0;
      if (x > 0.0 && y < 0.0 && x > + 0.999 * DBL_MAX + y)
         mpl_error(mpl, "%.*g less %.*g; floating-point overflow",
            DBL_DIG, x, DBL_DIG, y);
      return x - y;
}

/*----------------------------------------------------------------------
-- fp_mul - floating-point multiplication.
--
-- This routine computes the product x * y. */

double fp_mul(MPL *mpl, double x, double y)
{     if (fabs(y) > 1.0 && fabs(x) > (0.999 * DBL_MAX) / fabs(y))
         mpl_error(mpl, "%.*g * %.*g; floating-point overflow",
            DBL_DIG, x, DBL_DIG, y);
      return x * y;
}

/*----------------------------------------------------------------------
-- fp_div - floating-point division.
--
-- This routine computes the quotient x / y. */

double fp_div(MPL *mpl, double x, double y)
{     if (fabs(y) < DBL_MIN)
         mpl_error(mpl, "%.*g / %.*g; floating-point zero divide",
            DBL_DIG, x, DBL_DIG, y);
      if (fabs(y) < 1.0 && fabs(x) > (0.999 * DBL_MAX) * fabs(y))
         mpl_error(mpl, "%.*g / %.*g; floating-point overflow",
            DBL_DIG, x, DBL_DIG, y);
      return x / y;
}

/*----------------------------------------------------------------------
-- fp_idiv - floating-point quotient of exact division.
--
-- This routine computes the quotient of exact division x div y. */

double fp_idiv(MPL *mpl, double x, double y)
{     if (fabs(y) < DBL_MIN)
         mpl_error(mpl, "%.*g div %.*g; floating-point zero divide",
            DBL_DIG, x, DBL_DIG, y);
      if (fabs(y) < 1.0 && fabs(x) > (0.999 * DBL_MAX) * fabs(y))
         mpl_error(mpl, "%.*g div %.*g; floating-point overflow",
            DBL_DIG, x, DBL_DIG, y);
      x /= y;
      return x > 0.0 ? floor(x) : x < 0.0 ? ceil(x) : 0.0;
}

/*----------------------------------------------------------------------
-- fp_mod - floating-point remainder of exact division.
--
-- This routine computes the remainder of exact division x mod y.
--
-- NOTE: By definition x mod y = x - y * floor(x / y). */

double fp_mod(MPL *mpl, double x, double y)
{     double r;
      xassert(mpl == mpl);
      if (x == 0.0)
         r = 0.0;
      else if (y == 0.0)
         r = x;
      else
      {  r = fmod(fabs(x), fabs(y));
         if (r != 0.0)
         {  if (x < 0.0) r = - r;
            if (x > 0.0 && y < 0.0 || x < 0.0 && y > 0.0) r += y;
         }
      }
      return r;
}

/*----------------------------------------------------------------------
-- fp_power - floating-point exponentiation (raise to power).
--
-- This routine computes the exponentiation x ** y. */

double fp_power(MPL *mpl, double x, double y)
{     double r;
      if (x == 0.0 && y <= 0.0 || x < 0.0 && y != floor(y))
         mpl_error(mpl, "%.*g ** %.*g; result undefined",
            DBL_DIG, x, DBL_DIG, y);
      if (x == 0.0) goto eval;
      if (fabs(x) > 1.0 && y > +1.0 &&
            +log(fabs(x)) > (0.999 * log(DBL_MAX)) / y ||
          fabs(x) < 1.0 && y < -1.0 &&
            +log(fabs(x)) < (0.999 * log(DBL_MAX)) / y)
         mpl_error(mpl, "%.*g ** %.*g; floating-point overflow",
            DBL_DIG, x, DBL_DIG, y);
      if (fabs(x) > 1.0 && y < -1.0 &&
            -log(fabs(x)) < (0.999 * log(DBL_MAX)) / y ||
          fabs(x) < 1.0 && y > +1.0 &&
            -log(fabs(x)) > (0.999 * log(DBL_MAX)) / y)
         r = 0.0;
      else
eval:    r = pow(x, y);
      return r;
}

/*----------------------------------------------------------------------
-- fp_exp - floating-point base-e exponential.
--
-- This routine computes the base-e exponential e ** x. */

double fp_exp(MPL *mpl, double x)
{     if (x > 0.999 * log(DBL_MAX))
         mpl_error(mpl, "exp(%.*g); floating-point overflow", DBL_DIG, x);
      return exp(x);
}

/*----------------------------------------------------------------------
-- fp_log - floating-point natural logarithm.
--
-- This routine computes the natural logarithm log x. */

double fp_log(MPL *mpl, double x)
{     if (x <= 0.0)
         mpl_error(mpl, "log(%.*g); non-positive argument", DBL_DIG, x);
      return log(x);
}

/*----------------------------------------------------------------------
-- fp_log10 - floating-point common (decimal) logarithm.
--
-- This routine computes the common (decimal) logarithm lg x. */

double fp_log10(MPL *mpl, double x)
{     if (x <= 0.0)
         mpl_error(mpl, "log10(%.*g); non-positive argument", DBL_DIG, x);
      return log10(x);
}

/*----------------------------------------------------------------------
-- fp_sqrt - floating-point square root.
--
-- This routine computes the square root x ** 0.5. */

double fp_sqrt(MPL *mpl, double x)
{     if (x < 0.0)
         mpl_error(mpl, "sqrt(%.*g); negative argument", DBL_DIG, x);
      return sqrt(x);
}

/*----------------------------------------------------------------------
-- fp_sin - floating-point trigonometric sine.
--
-- This routine computes the trigonometric sine sin(x). */

double fp_sin(MPL *mpl, double x)
{     if (!(-1e6 <= x && x <= +1e6))
         mpl_error(mpl, "sin(%.*g); argument too large", DBL_DIG, x);
      return sin(x);
}

/*----------------------------------------------------------------------
-- fp_cos - floating-point trigonometric cosine.
--
-- This routine computes the trigonometric cosine cos(x). */

double fp_cos(MPL *mpl, double x)
{     if (!(-1e6 <= x && x <= +1e6))
         mpl_error(mpl, "cos(%.*g); argument too large", DBL_DIG, x);
      return cos(x);
}

/*----------------------------------------------------------------------
-- fp_atan - floating-point trigonometric arctangent.
--
-- This routine computes the trigonometric arctangent atan(x). */

double fp_atan(MPL *mpl, double x)
{     xassert(mpl == mpl);
      return atan(x);
}

/*----------------------------------------------------------------------
-- fp_atan2 - floating-point trigonometric arctangent.
--
-- This routine computes the trigonometric arctangent atan(y / x). */

double fp_atan2(MPL *mpl, double y, double x)
{     xassert(mpl == mpl);
      return atan2(y, x);
}

/*----------------------------------------------------------------------
-- fp_round - round floating-point value to n fractional digits.
--
-- This routine rounds given floating-point value x to n fractional
-- digits with the formula:
--
--    round(x, n) = floor(x * 10^n + 0.5) / 10^n.
--
-- The parameter n is assumed to be integer. */

double fp_round(MPL *mpl, double x, double n)
{     double ten_to_n;
      if (n != floor(n))
         mpl_error(mpl, "round(%.*g, %.*g); non-integer second argument",
            DBL_DIG, x, DBL_DIG, n);
      if (n <= DBL_DIG + 2)
      {  ten_to_n = pow(10.0, n);
         if (fabs(x) < (0.999 * DBL_MAX) / ten_to_n)
         {  x = floor(x * ten_to_n + 0.5);
            if (x != 0.0) x /= ten_to_n;
         }
      }
      return x;
}

/*----------------------------------------------------------------------
-- fp_trunc - truncate floating-point value to n fractional digits.
--
-- This routine truncates given floating-point value x to n fractional
-- digits with the formula:
--
--                  ( floor(x * 10^n) / 10^n,  if x >= 0
--    trunc(x, n) = <
--                  ( ceil(x * 10^n) / 10^n,   if x < 0
--
-- The parameter n is assumed to be integer. */

double fp_trunc(MPL *mpl, double x, double n)
{     double ten_to_n;
      if (n != floor(n))
         mpl_error(mpl, "trunc(%.*g, %.*g); non-integer second argument",
            DBL_DIG, x, DBL_DIG, n);
      if (n <= DBL_DIG + 2)
      {  ten_to_n = pow(10.0, n);
         if (fabs(x) < (0.999 * DBL_MAX) / ten_to_n)
         {  x = (x >= 0.0 ? floor(x * ten_to_n) : ceil(x * ten_to_n));
            if (x != 0.0) x /= ten_to_n;
         }
      }
      return x;
}

/**********************************************************************/
/* * *              PSEUDO-RANDOM NUMBER GENERATORS               * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- fp_irand224 - pseudo-random integer in the range [0, 2^24).
--
-- This routine returns a next pseudo-random integer (converted to
-- floating-point) which is uniformly distributed between 0 and 2^24-1,
-- inclusive. */

#define two_to_the_24 0x1000000

double fp_irand224(MPL *mpl)
{     return
         (double)rng_unif_rand(mpl->rand, two_to_the_24);
}

/*----------------------------------------------------------------------
-- fp_uniform01 - pseudo-random number in the range [0, 1).
--
-- This routine returns a next pseudo-random number which is uniformly
-- distributed in the range [0, 1). */

#define two_to_the_31 ((unsigned int)0x80000000)

double fp_uniform01(MPL *mpl)
{     return
         (double)rng_next_rand(mpl->rand) / (double)two_to_the_31;
}

/*----------------------------------------------------------------------
-- fp_uniform - pseudo-random number in the range [a, b).
--
-- This routine returns a next pseudo-random number which is uniformly
-- distributed in the range [a, b). */

double fp_uniform(MPL *mpl, double a, double b)
{     double x;
      if (a >= b)
         mpl_error(mpl, "Uniform(%.*g, %.*g); invalid range",
            DBL_DIG, a, DBL_DIG, b);
      x = fp_uniform01(mpl);
#if 0
      x = a * (1.0 - x) + b * x;
#else
      x = fp_add(mpl, a * (1.0 - x), b * x);
#endif
      return x;
}

/*----------------------------------------------------------------------
-- fp_normal01 - Gaussian random variate with mu = 0 and sigma = 1.
--
-- This routine returns a Gaussian random variate with zero mean and
-- unit standard deviation. The polar (Box-Mueller) method is used.
--
-- This code is a modified version of the routine gsl_ran_gaussian from
-- the GNU Scientific Library Version 1.0. */

double fp_normal01(MPL *mpl)
{     double x, y, r2;
      do
      {  /* choose x, y in uniform square (-1,-1) to (+1,+1) */
         x = -1.0 + 2.0 * fp_uniform01(mpl);
         y = -1.0 + 2.0 * fp_uniform01(mpl);
         /* see if it is in the unit circle */
         r2 = x * x + y * y;
      } while (r2 > 1.0 || r2 == 0.0);
      /* Box-Muller transform */
      return y * sqrt(-2.0 * log (r2) / r2);
}

/*----------------------------------------------------------------------
-- fp_normal - Gaussian random variate with specified mu and sigma.
--
-- This routine returns a Gaussian random variate with mean mu and
-- standard deviation sigma. */

double fp_normal(MPL *mpl, double mu, double sigma)
{     double x;
#if 0
      x = mu + sigma * fp_normal01(mpl);
#else
      x = fp_add(mpl, mu, fp_mul(mpl, sigma, fp_normal01(mpl)));
#endif
      return x;
}

/**********************************************************************/
/* * *                SEGMENTED CHARACTER STRINGS                 * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- create_string - create character string.
--
-- This routine creates a segmented character string, which is exactly
-- equivalent to specified character string. */

STRING *create_string
(     MPL *mpl,
      char buf[MAX_LENGTH+1]  /* not changed */
)
#if 0
{     STRING *head, *tail;
      int i, j;
      xassert(buf != NULL);
      xassert(strlen(buf) <= MAX_LENGTH);
      head = tail = dmp_get_atom(mpl->strings, sizeof(STRING));
      for (i = j = 0; ; i++)
      {  if ((tail->seg[j++] = buf[i]) == '\0') break;
         if (j == STRSEG_SIZE)
tail = (tail->next = dmp_get_atom(mpl->strings, sizeof(STRING))), j = 0;
      }
      tail->next = NULL;
      return head;
}
#else
{     STRING *str;
      xassert(strlen(buf) <= MAX_LENGTH);
      str = dmp_get_atom(mpl->strings, strlen(buf)+1);
      strcpy(str, buf);
      return str;
}
#endif

/*----------------------------------------------------------------------
-- copy_string - make copy of character string.
--
-- This routine returns an exact copy of segmented character string. */

STRING *copy_string
(     MPL *mpl,
      STRING *str             /* not changed */
)
#if 0
{     STRING *head, *tail;
      xassert(str != NULL);
      head = tail = dmp_get_atom(mpl->strings, sizeof(STRING));
      for (; str != NULL; str = str->next)
      {  memcpy(tail->seg, str->seg, STRSEG_SIZE);
         if (str->next != NULL)
tail = (tail->next = dmp_get_atom(mpl->strings, sizeof(STRING)));
      }
      tail->next = NULL;
      return head;
}
#else
{     xassert(mpl == mpl);
      return create_string(mpl, str);
}
#endif

/*----------------------------------------------------------------------
-- compare_strings - compare one character string with another.
--
-- This routine compares one segmented character strings with another
-- and returns the result of comparison as follows:
--
-- = 0 - both strings are identical;
-- < 0 - the first string precedes the second one;
-- > 0 - the first string follows the second one. */

int compare_strings
(     MPL *mpl,
      STRING *str1,           /* not changed */
      STRING *str2            /* not changed */
)
#if 0
{     int j, c1, c2;
      xassert(mpl == mpl);
      for (;; str1 = str1->next, str2 = str2->next)
      {  xassert(str1 != NULL);
         xassert(str2 != NULL);
         for (j = 0; j < STRSEG_SIZE; j++)
         {  c1 = (unsigned char)str1->seg[j];
            c2 = (unsigned char)str2->seg[j];
            if (c1 < c2) return -1;
            if (c1 > c2) return +1;
            if (c1 == '\0') goto done;
         }
      }
done: return 0;
}
#else
{     xassert(mpl == mpl);
      return strcmp(str1, str2);
}
#endif

/*----------------------------------------------------------------------
-- fetch_string - extract content of character string.
--
-- This routine returns a character string, which is exactly equivalent
-- to specified segmented character string. */

char *fetch_string
(     MPL *mpl,
      STRING *str,            /* not changed */
      char buf[MAX_LENGTH+1]  /* modified */
)
#if 0
{     int i, j;
      xassert(mpl == mpl);
      xassert(buf != NULL);
      for (i = 0; ; str = str->next)
      {  xassert(str != NULL);
         for (j = 0; j < STRSEG_SIZE; j++)
            if ((buf[i++] = str->seg[j]) == '\0') goto done;
      }
done: xassert(strlen(buf) <= MAX_LENGTH);
      return buf;
}
#else
{     xassert(mpl == mpl);
      return strcpy(buf, str);
}
#endif

/*----------------------------------------------------------------------
-- delete_string - delete character string.
--
-- This routine deletes specified segmented character string. */

void delete_string
(     MPL *mpl,
      STRING *str             /* destroyed */
)
#if 0
{     STRING *temp;
      xassert(str != NULL);
      while (str != NULL)
      {  temp = str;
         str = str->next;
         dmp_free_atom(mpl->strings, temp, sizeof(STRING));
      }
      return;
}
#else
{     dmp_free_atom(mpl->strings, str, strlen(str)+1);
      return;
}
#endif

/**********************************************************************/
/* * *                          SYMBOLS                           * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- create_symbol_num - create symbol of numeric type.
--
-- This routine creates a symbol, which has a numeric value specified
-- as floating-point number. */

SYMBOL *create_symbol_num(MPL *mpl, double num)
{     SYMBOL *sym;
      sym = dmp_get_atom(mpl->symbols, sizeof(SYMBOL));
      sym->num = num;
      sym->str = NULL;
      return sym;
}

/*----------------------------------------------------------------------
-- create_symbol_str - create symbol of abstract type.
--
-- This routine creates a symbol, which has an abstract value specified
-- as segmented character string. */

SYMBOL *create_symbol_str
(     MPL *mpl,
      STRING *str             /* destroyed */
)
{     SYMBOL *sym;
      xassert(str != NULL);
      sym = dmp_get_atom(mpl->symbols, sizeof(SYMBOL));
      sym->num = 0.0;
      sym->str = str;
      return sym;
}

/*----------------------------------------------------------------------
-- copy_symbol - make copy of symbol.
--
-- This routine returns an exact copy of symbol. */

SYMBOL *copy_symbol
(     MPL *mpl,
      SYMBOL *sym             /* not changed */
)
{     SYMBOL *copy;
      xassert(sym != NULL);
      copy = dmp_get_atom(mpl->symbols, sizeof(SYMBOL));
      if (sym->str == NULL)
      {  copy->num = sym->num;
         copy->str = NULL;
      }
      else
      {  copy->num = 0.0;
         copy->str = copy_string(mpl, sym->str);
      }
      return copy;
}

/*----------------------------------------------------------------------
-- compare_symbols - compare one symbol with another.
--
-- This routine compares one symbol with another and returns the result
-- of comparison as follows:
--
-- = 0 - both symbols are identical;
-- < 0 - the first symbol precedes the second one;
-- > 0 - the first symbol follows the second one.
--
-- Note that the linear order, in which symbols follow each other, is
-- implementation-dependent. It may be not an alphabetical order. */

int compare_symbols
(     MPL *mpl,
      SYMBOL *sym1,           /* not changed */
      SYMBOL *sym2            /* not changed */
)
{     xassert(sym1 != NULL);
      xassert(sym2 != NULL);
      /* let all numeric quantities precede all symbolic quantities */
      if (sym1->str == NULL && sym2->str == NULL)
      {  if (sym1->num < sym2->num) return -1;
         if (sym1->num > sym2->num) return +1;
         return 0;
      }
      if (sym1->str == NULL) return -1;
      if (sym2->str == NULL) return +1;
      return compare_strings(mpl, sym1->str, sym2->str);
}

/*----------------------------------------------------------------------
-- delete_symbol - delete symbol.
--
-- This routine deletes specified symbol. */

void delete_symbol
(     MPL *mpl,
      SYMBOL *sym             /* destroyed */
)
{     xassert(sym != NULL);
      if (sym->str != NULL) delete_string(mpl, sym->str);
      dmp_free_atom(mpl->symbols, sym, sizeof(SYMBOL));
      return;
}

/*----------------------------------------------------------------------
-- format_symbol - format symbol for displaying or printing.
--
-- This routine converts specified symbol to a charater string, which
-- is suitable for displaying or printing.
--
-- The resultant string is never longer than 255 characters. If it gets
-- longer, it is truncated from the right and appended by dots. */

char *format_symbol
(     MPL *mpl,
      SYMBOL *sym             /* not changed */
)
{     char *buf = mpl->sym_buf;
      xassert(sym != NULL);
      if (sym->str == NULL)
         sprintf(buf, "%.*g", DBL_DIG, sym->num);
      else
      {  char str[MAX_LENGTH+1];
         int quoted, j, len;
         fetch_string(mpl, sym->str, str);
         if (!(isalpha((unsigned char)str[0]) || str[0] == '_'))
            quoted = 1;
         else
         {  quoted = 0;
            for (j = 1; str[j] != '\0'; j++)
            {  if (!(isalnum((unsigned char)str[j]) ||
                     strchr("+-._", (unsigned char)str[j]) != NULL))
               {  quoted = 1;
                  break;
               }
            }
         }
#        define safe_append(c) \
            (void)(len < 255 ? (buf[len++] = (char)(c)) : 0)
         buf[0] = '\0', len = 0;
         if (quoted) safe_append('\'');
         for (j = 0; str[j] != '\0'; j++)
         {  if (quoted && str[j] == '\'') safe_append('\'');
            safe_append(str[j]);
         }
         if (quoted) safe_append('\'');
#        undef safe_append
         buf[len] = '\0';
         if (len == 255) strcpy(buf+252, "...");
      }
      xassert(strlen(buf) <= 255);
      return buf;
}

/*----------------------------------------------------------------------
-- concat_symbols - concatenate one symbol with another.
--
-- This routine concatenates values of two given symbols and assigns
-- the resultant character string to a new symbol, which is returned on
-- exit. Both original symbols are destroyed. */

SYMBOL *concat_symbols
(     MPL *mpl,
      SYMBOL *sym1,           /* destroyed */
      SYMBOL *sym2            /* destroyed */
)
{     char str1[MAX_LENGTH+1], str2[MAX_LENGTH+1];
      xassert(MAX_LENGTH >= DBL_DIG + DBL_DIG);
      if (sym1->str == NULL)
         sprintf(str1, "%.*g", DBL_DIG, sym1->num);
      else
         fetch_string(mpl, sym1->str, str1);
      if (sym2->str == NULL)
         sprintf(str2, "%.*g", DBL_DIG, sym2->num);
      else
         fetch_string(mpl, sym2->str, str2);
      if (strlen(str1) + strlen(str2) > MAX_LENGTH)
      {  char buf[255+1];
         strcpy(buf, format_symbol(mpl, sym1));
         xassert(strlen(buf) < sizeof(buf));
         mpl_error(mpl, "%s & %s; resultant symbol exceeds %d characters",
            buf, format_symbol(mpl, sym2), MAX_LENGTH);
      }
      delete_symbol(mpl, sym1);
      delete_symbol(mpl, sym2);
      return create_symbol_str(mpl, create_string(mpl, strcat(str1,
         str2)));
}

/**********************************************************************/
/* * *                          N-TUPLES                          * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- create_tuple - create n-tuple.
--
-- This routine creates a n-tuple, which initially has no components,
-- i.e. which is 0-tuple. */

TUPLE *create_tuple(MPL *mpl)
{     TUPLE *tuple;
      xassert(mpl == mpl);
      tuple = NULL;
      return tuple;
}

/*----------------------------------------------------------------------
-- expand_tuple - append symbol to n-tuple.
--
-- This routine expands n-tuple appending to it a given symbol, which
-- becomes its new last component. */

TUPLE *expand_tuple
(     MPL *mpl,
      TUPLE *tuple,           /* destroyed */
      SYMBOL *sym             /* destroyed */
)
{     TUPLE *tail, *temp;
      xassert(sym != NULL);
      /* create a new component */
      tail = dmp_get_atom(mpl->tuples, sizeof(TUPLE));
      tail->sym = sym;
      tail->next = NULL;
      /* and append it to the component list */
      if (tuple == NULL)
         tuple = tail;
      else
      {  for (temp = tuple; temp->next != NULL; temp = temp->next);
         temp->next = tail;
      }
      return tuple;
}

/*----------------------------------------------------------------------
-- tuple_dimen - determine dimension of n-tuple.
--
-- This routine returns dimension of n-tuple, i.e. number of components
-- in the n-tuple. */

int tuple_dimen
(     MPL *mpl,
      TUPLE *tuple            /* not changed */
)
{     TUPLE *temp;
      int dim = 0;
      xassert(mpl == mpl);
      for (temp = tuple; temp != NULL; temp = temp->next) dim++;
      return dim;
}

/*----------------------------------------------------------------------
-- copy_tuple - make copy of n-tuple.
--
-- This routine returns an exact copy of n-tuple. */

TUPLE *copy_tuple
(     MPL *mpl,
      TUPLE *tuple            /* not changed */
)
{     TUPLE *head, *tail;
      if (tuple == NULL)
         head = NULL;
      else
      {  head = tail = dmp_get_atom(mpl->tuples, sizeof(TUPLE));
         for (; tuple != NULL; tuple = tuple->next)
         {  xassert(tuple->sym != NULL);
            tail->sym = copy_symbol(mpl, tuple->sym);
            if (tuple->next != NULL)
tail = (tail->next = dmp_get_atom(mpl->tuples, sizeof(TUPLE)));
         }
         tail->next = NULL;
      }
      return head;
}

/*----------------------------------------------------------------------
-- compare_tuples - compare one n-tuple with another.
--
-- This routine compares two given n-tuples, which must have the same
-- dimension (not checked for the sake of efficiency), and returns one
-- of the following codes:
--
-- = 0 - both n-tuples are identical;
-- < 0 - the first n-tuple precedes the second one;
-- > 0 - the first n-tuple follows the second one.
--
-- Note that the linear order, in which n-tuples follow each other, is
-- implementation-dependent. It may be not an alphabetical order. */

int compare_tuples
(     MPL *mpl,
      TUPLE *tuple1,          /* not changed */
      TUPLE *tuple2           /* not changed */
)
{     TUPLE *item1, *item2;
      int ret;
      xassert(mpl == mpl);
      for (item1 = tuple1, item2 = tuple2; item1 != NULL;
           item1 = item1->next, item2 = item2->next)
      {  xassert(item2 != NULL);
         xassert(item1->sym != NULL);
         xassert(item2->sym != NULL);
         ret = compare_symbols(mpl, item1->sym, item2->sym);
         if (ret != 0) return ret;
      }
      xassert(item2 == NULL);
      return 0;
}

/*----------------------------------------------------------------------
-- build_subtuple - build subtuple of given n-tuple.
--
-- This routine builds subtuple, which consists of first dim components
-- of given n-tuple. */

TUPLE *build_subtuple
(     MPL *mpl,
      TUPLE *tuple,           /* not changed */
      int dim
)
{     TUPLE *head, *temp;
      int j;
      head = create_tuple(mpl);
      for (j = 1, temp = tuple; j <= dim; j++, temp = temp->next)
      {  xassert(temp != NULL);
         head = expand_tuple(mpl, head, copy_symbol(mpl, temp->sym));
      }
      return head;
}

/*----------------------------------------------------------------------
-- delete_tuple - delete n-tuple.
--
-- This routine deletes specified n-tuple. */

void delete_tuple
(     MPL *mpl,
      TUPLE *tuple            /* destroyed */
)
{     TUPLE *temp;
      while (tuple != NULL)
      {  temp = tuple;
         tuple = temp->next;
         xassert(temp->sym != NULL);
         delete_symbol(mpl, temp->sym);
         dmp_free_atom(mpl->tuples, temp, sizeof(TUPLE));
      }
      return;
}

/*----------------------------------------------------------------------
-- format_tuple - format n-tuple for displaying or printing.
--
-- This routine converts specified n-tuple to a character string, which
-- is suitable for displaying or printing.
--
-- The resultant string is never longer than 255 characters. If it gets
-- longer, it is truncated from the right and appended by dots. */

char *format_tuple
(     MPL *mpl,
      int c,
      TUPLE *tuple            /* not changed */
)
{     TUPLE *temp;
      int dim, j, len;
      char *buf = mpl->tup_buf, str[255+1], *save;
#     define safe_append(c) \
         (void)(len < 255 ? (buf[len++] = (char)(c)) : 0)
      buf[0] = '\0', len = 0;
      dim = tuple_dimen(mpl, tuple);
      if (c == '[' && dim > 0) safe_append('[');
      if (c == '(' && dim > 1) safe_append('(');
      for (temp = tuple; temp != NULL; temp = temp->next)
      {  if (temp != tuple) safe_append(',');
         xassert(temp->sym != NULL);
         save = mpl->sym_buf;
         mpl->sym_buf = str;
         format_symbol(mpl, temp->sym);
         mpl->sym_buf = save;
         xassert(strlen(str) < sizeof(str));
         for (j = 0; str[j] != '\0'; j++) safe_append(str[j]);
      }
      if (c == '[' && dim > 0) safe_append(']');
      if (c == '(' && dim > 1) safe_append(')');
#     undef safe_append
      buf[len] = '\0';
      if (len == 255) strcpy(buf+252, "...");
      xassert(strlen(buf) <= 255);
      return buf;
}

/**********************************************************************/
/* * *                       ELEMENTAL SETS                       * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- create_elemset - create elemental set.
--
-- This routine creates an elemental set, whose members are n-tuples of
-- specified dimension. Being created the set is initially empty. */

ELEMSET *create_elemset(MPL *mpl, int dim)
{     ELEMSET *set;
      xassert(dim > 0);
      set = create_array(mpl, A_NONE, dim);
      return set;
}

/*----------------------------------------------------------------------
-- find_tuple - check if elemental set contains given n-tuple.
--
-- This routine finds given n-tuple in specified elemental set in order
-- to check if the set contains that n-tuple. If the n-tuple is found,
-- the routine returns pointer to corresponding array member. Otherwise
-- null pointer is returned. */

MEMBER *find_tuple
(     MPL *mpl,
      ELEMSET *set,           /* not changed */
      TUPLE *tuple            /* not changed */
)
{     xassert(set != NULL);
      xassert(set->type == A_NONE);
      xassert(set->dim == tuple_dimen(mpl, tuple));
      return find_member(mpl, set, tuple);
}

/*----------------------------------------------------------------------
-- add_tuple - add new n-tuple to elemental set.
--
-- This routine adds given n-tuple to specified elemental set.
--
-- For the sake of efficiency this routine doesn't check whether the
-- set already contains the same n-tuple or not. Therefore the calling
-- program should use the routine find_tuple (if necessary) in order to
-- make sure that the given n-tuple is not contained in the set, since
-- duplicate n-tuples within the same set are not allowed. */

MEMBER *add_tuple
(     MPL *mpl,
      ELEMSET *set,           /* modified */
      TUPLE *tuple            /* destroyed */
)
{     MEMBER *memb;
      xassert(set != NULL);
      xassert(set->type == A_NONE);
      xassert(set->dim == tuple_dimen(mpl, tuple));
      memb = add_member(mpl, set, tuple);
      memb->value.none = NULL;
      return memb;
}

/*----------------------------------------------------------------------
-- check_then_add - check and add new n-tuple to elemental set.
--
-- This routine is equivalent to the routine add_tuple except that it
-- does check for duplicate n-tuples. */

MEMBER *check_then_add
(     MPL *mpl,
      ELEMSET *set,           /* modified */
      TUPLE *tuple            /* destroyed */
)
{     if (find_tuple(mpl, set, tuple) != NULL)
         mpl_error(mpl, "duplicate tuple %s detected", format_tuple(mpl,
            '(', tuple));
      return add_tuple(mpl, set, tuple);
}

/*----------------------------------------------------------------------
-- copy_elemset - make copy of elemental set.
--
-- This routine makes an exact copy of elemental set. */

ELEMSET *copy_elemset
(     MPL *mpl,
      ELEMSET *set            /* not changed */
)
{     ELEMSET *copy;
      MEMBER *memb;
      xassert(set != NULL);
      xassert(set->type == A_NONE);
      xassert(set->dim > 0);
      copy = create_elemset(mpl, set->dim);
      for (memb = set->head; memb != NULL; memb = memb->next)
         add_tuple(mpl, copy, copy_tuple(mpl, memb->tuple));
      return copy;
}

/*----------------------------------------------------------------------
-- delete_elemset - delete elemental set.
--
-- This routine deletes specified elemental set. */

void delete_elemset
(     MPL *mpl,
      ELEMSET *set            /* destroyed */
)
{     xassert(set != NULL);
      xassert(set->type == A_NONE);
      delete_array(mpl, set);
      return;
}

/*----------------------------------------------------------------------
-- arelset_size - compute size of "arithmetic" elemental set.
--
-- This routine computes the size of "arithmetic" elemental set, which
-- is specified in the form of arithmetic progression:
--
--    { t0 .. tf by dt }.
--
-- The size is computed using the formula:
--
--    n = max(0, floor((tf - t0) / dt) + 1). */

int arelset_size(MPL *mpl, double t0, double tf, double dt)
{     double temp;
      if (dt == 0.0)
         mpl_error(mpl, "%.*g .. %.*g by %.*g; zero stride not allowed",
            DBL_DIG, t0, DBL_DIG, tf, DBL_DIG, dt);
      if (tf > 0.0 && t0 < 0.0 && tf > + 0.999 * DBL_MAX + t0)
         temp = +DBL_MAX;
      else if (tf < 0.0 && t0 > 0.0 && tf < - 0.999 * DBL_MAX + t0)
         temp = -DBL_MAX;
      else
         temp = tf - t0;
      if (fabs(dt) < 1.0 && fabs(temp) > (0.999 * DBL_MAX) * fabs(dt))
      {  if (temp > 0.0 && dt > 0.0 || temp < 0.0 && dt < 0.0)
            temp = +DBL_MAX;
         else
            temp = 0.0;
      }
      else
      {  temp = floor(temp / dt) + 1.0;
         if (temp < 0.0) temp = 0.0;
      }
      xassert(temp >= 0.0);
      if (temp > (double)(INT_MAX - 1))
         mpl_error(mpl, "%.*g .. %.*g by %.*g; set too large",
            DBL_DIG, t0, DBL_DIG, tf, DBL_DIG, dt);
      return (int)(temp + 0.5);
}

/*----------------------------------------------------------------------
-- arelset_member - compute member of "arithmetic" elemental set.
--
-- This routine returns a numeric value of symbol, which is equivalent
-- to j-th member of given "arithmetic" elemental set specified in the
-- form of arithmetic progression:
--
--    { t0 .. tf by dt }.
--
-- The symbol value is computed with the formula:
--
--    j-th member = t0 + (j - 1) * dt,
--
-- The number j must satisfy to the restriction 1 <= j <= n, where n is
-- the set size computed by the routine arelset_size. */

double arelset_member(MPL *mpl, double t0, double tf, double dt, int j)
{     xassert(1 <= j && j <= arelset_size(mpl, t0, tf, dt));
      return t0 + (double)(j - 1) * dt;
}

/*----------------------------------------------------------------------
-- create_arelset - create "arithmetic" elemental set.
--
-- This routine creates "arithmetic" elemental set, which is specified
-- in the form of arithmetic progression:
--
--    { t0 .. tf by dt }.
--
-- Components of this set are 1-tuples. */

ELEMSET *create_arelset(MPL *mpl, double t0, double tf, double dt)
{     ELEMSET *set;
      int j, n;
      set = create_elemset(mpl, 1);
      n = arelset_size(mpl, t0, tf, dt);
      for (j = 1; j <= n; j++)
      {  add_tuple
         (  mpl,
            set,
            expand_tuple
            (  mpl,
               create_tuple(mpl),
               create_symbol_num
               (  mpl,
                  arelset_member(mpl, t0, tf, dt, j)
               )
            )
         );
      }
      return set;
}

/*----------------------------------------------------------------------
-- set_union - union of two elemental sets.
--
-- This routine computes the union:
--
--    X U Y = { j | (j in X) or (j in Y) },
--
-- where X and Y are given elemental sets (destroyed on exit). */

ELEMSET *set_union
(     MPL *mpl,
      ELEMSET *X,             /* destroyed */
      ELEMSET *Y              /* destroyed */
)
{     MEMBER *memb;
      xassert(X != NULL);
      xassert(X->type == A_NONE);
      xassert(X->dim > 0);
      xassert(Y != NULL);
      xassert(Y->type == A_NONE);
      xassert(Y->dim > 0);
      xassert(X->dim == Y->dim);
      for (memb = Y->head; memb != NULL; memb = memb->next)
      {  if (find_tuple(mpl, X, memb->tuple) == NULL)
            add_tuple(mpl, X, copy_tuple(mpl, memb->tuple));
      }
      delete_elemset(mpl, Y);
      return X;
}

/*----------------------------------------------------------------------
-- set_diff - difference between two elemental sets.
--
-- This routine computes the difference:
--
--    X \ Y = { j | (j in X) and (j not in Y) },
--
-- where X and Y are given elemental sets (destroyed on exit). */

ELEMSET *set_diff
(     MPL *mpl,
      ELEMSET *X,             /* destroyed */
      ELEMSET *Y              /* destroyed */
)
{     ELEMSET *Z;
      MEMBER *memb;
      xassert(X != NULL);
      xassert(X->type == A_NONE);
      xassert(X->dim > 0);
      xassert(Y != NULL);
      xassert(Y->type == A_NONE);
      xassert(Y->dim > 0);
      xassert(X->dim == Y->dim);
      Z = create_elemset(mpl, X->dim);
      for (memb = X->head; memb != NULL; memb = memb->next)
      {  if (find_tuple(mpl, Y, memb->tuple) == NULL)
            add_tuple(mpl, Z, copy_tuple(mpl, memb->tuple));
      }
      delete_elemset(mpl, X);
      delete_elemset(mpl, Y);
      return Z;
}

/*----------------------------------------------------------------------
-- set_symdiff - symmetric difference between two elemental sets.
--
-- This routine computes the symmetric difference:
--
--    X (+) Y = (X \ Y) U (Y \ X),
--
-- where X and Y are given elemental sets (destroyed on exit). */

ELEMSET *set_symdiff
(     MPL *mpl,
      ELEMSET *X,             /* destroyed */
      ELEMSET *Y              /* destroyed */
)
{     ELEMSET *Z;
      MEMBER *memb;
      xassert(X != NULL);
      xassert(X->type == A_NONE);
      xassert(X->dim > 0);
      xassert(Y != NULL);
      xassert(Y->type == A_NONE);
      xassert(Y->dim > 0);
      xassert(X->dim == Y->dim);
      /* Z := X \ Y */
      Z = create_elemset(mpl, X->dim);
      for (memb = X->head; memb != NULL; memb = memb->next)
      {  if (find_tuple(mpl, Y, memb->tuple) == NULL)
            add_tuple(mpl, Z, copy_tuple(mpl, memb->tuple));
      }
      /* Z := Z U (Y \ X) */
      for (memb = Y->head; memb != NULL; memb = memb->next)
      {  if (find_tuple(mpl, X, memb->tuple) == NULL)
            add_tuple(mpl, Z, copy_tuple(mpl, memb->tuple));
      }
      delete_elemset(mpl, X);
      delete_elemset(mpl, Y);
      return Z;
}

/*----------------------------------------------------------------------
-- set_inter - intersection of two elemental sets.
--
-- This routine computes the intersection:
--
--    X ^ Y = { j | (j in X) and (j in Y) },
--
-- where X and Y are given elemental sets (destroyed on exit). */

ELEMSET *set_inter
(     MPL *mpl,
      ELEMSET *X,             /* destroyed */
      ELEMSET *Y              /* destroyed */
)
{     ELEMSET *Z;
      MEMBER *memb;
      xassert(X != NULL);
      xassert(X->type == A_NONE);
      xassert(X->dim > 0);
      xassert(Y != NULL);
      xassert(Y->type == A_NONE);
      xassert(Y->dim > 0);
      xassert(X->dim == Y->dim);
      Z = create_elemset(mpl, X->dim);
      for (memb = X->head; memb != NULL; memb = memb->next)
      {  if (find_tuple(mpl, Y, memb->tuple) != NULL)
            add_tuple(mpl, Z, copy_tuple(mpl, memb->tuple));
      }
      delete_elemset(mpl, X);
      delete_elemset(mpl, Y);
      return Z;
}

/*----------------------------------------------------------------------
-- set_cross - cross (Cartesian) product of two elemental sets.
--
-- This routine computes the cross (Cartesian) product:
--
--    X x Y = { (i,j) | (i in X) and (j in Y) },
--
-- where X and Y are given elemental sets (destroyed on exit). */

ELEMSET *set_cross
(     MPL *mpl,
      ELEMSET *X,             /* destroyed */
      ELEMSET *Y              /* destroyed */
)
{     ELEMSET *Z;
      MEMBER *memx, *memy;
      TUPLE *tuple, *temp;
      xassert(X != NULL);
      xassert(X->type == A_NONE);
      xassert(X->dim > 0);
      xassert(Y != NULL);
      xassert(Y->type == A_NONE);
      xassert(Y->dim > 0);
      Z = create_elemset(mpl, X->dim + Y->dim);
      for (memx = X->head; memx != NULL; memx = memx->next)
      {  for (memy = Y->head; memy != NULL; memy = memy->next)
         {  tuple = copy_tuple(mpl, memx->tuple);
            for (temp = memy->tuple; temp != NULL; temp = temp->next)
               tuple = expand_tuple(mpl, tuple, copy_symbol(mpl,
                  temp->sym));
            add_tuple(mpl, Z, tuple);
         }
      }
      delete_elemset(mpl, X);
      delete_elemset(mpl, Y);
      return Z;
}

/**********************************************************************/
/* * *                    ELEMENTAL VARIABLES                     * * */
/**********************************************************************/

/* (there are no specific routines for elemental variables) */

/**********************************************************************/
/* * *                        LINEAR FORMS                        * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- constant_term - create constant term.
--
-- This routine creates the linear form, which is a constant term. */

FORMULA *constant_term(MPL *mpl, double coef)
{     FORMULA *form;
      if (coef == 0.0)
         form = NULL;
      else
      {  form = dmp_get_atom(mpl->formulae, sizeof(FORMULA));
         form->coef = coef;
         form->var = NULL;
         form->next = NULL;
      }
      return form;
}

/*----------------------------------------------------------------------
-- single_variable - create single variable.
--
-- This routine creates the linear form, which is a single elemental
-- variable. */

FORMULA *single_variable
(     MPL *mpl,
      ELEMVAR *var            /* referenced */
)
{     FORMULA *form;
      xassert(var != NULL);
      form = dmp_get_atom(mpl->formulae, sizeof(FORMULA));
      form->coef = 1.0;
      form->var = var;
      form->next = NULL;
      return form;
}

/*----------------------------------------------------------------------
-- copy_formula - make copy of linear form.
--
-- This routine returns an exact copy of linear form. */

FORMULA *copy_formula
(     MPL *mpl,
      FORMULA *form           /* not changed */
)
{     FORMULA *head, *tail;
      if (form == NULL)
         head = NULL;
      else
      {  head = tail = dmp_get_atom(mpl->formulae, sizeof(FORMULA));
         for (; form != NULL; form = form->next)
         {  tail->coef = form->coef;
            tail->var = form->var;
            if (form->next != NULL)
tail = (tail->next = dmp_get_atom(mpl->formulae, sizeof(FORMULA)));
         }
         tail->next = NULL;
      }
      return head;
}

/*----------------------------------------------------------------------
-- delete_formula - delete linear form.
--
-- This routine deletes specified linear form. */

void delete_formula
(     MPL *mpl,
      FORMULA *form           /* destroyed */
)
{     FORMULA *temp;
      while (form != NULL)
      {  temp = form;
         form = form->next;
         dmp_free_atom(mpl->formulae, temp, sizeof(FORMULA));
      }
      return;
}

/*----------------------------------------------------------------------
-- linear_comb - linear combination of two linear forms.
--
-- This routine computes the linear combination:
--
--    a * fx + b * fy,
--
-- where a and b are numeric coefficients, fx and fy are linear forms
-- (destroyed on exit). */

FORMULA *linear_comb
(     MPL *mpl,
      double a, FORMULA *fx,  /* destroyed */
      double b, FORMULA *fy   /* destroyed */
)
{     FORMULA *form = NULL, *term, *temp;
      double c0 = 0.0;
      for (term = fx; term != NULL; term = term->next)
      {  if (term->var == NULL)
            c0 = fp_add(mpl, c0, fp_mul(mpl, a, term->coef));
         else
            term->var->temp =
               fp_add(mpl, term->var->temp, fp_mul(mpl, a, term->coef));
      }
      for (term = fy; term != NULL; term = term->next)
      {  if (term->var == NULL)
            c0 = fp_add(mpl, c0, fp_mul(mpl, b, term->coef));
         else
            term->var->temp =
               fp_add(mpl, term->var->temp, fp_mul(mpl, b, term->coef));
      }
      for (term = fx; term != NULL; term = term->next)
      {  if (term->var != NULL && term->var->temp != 0.0)
         {  temp = dmp_get_atom(mpl->formulae, sizeof(FORMULA));
            temp->coef = term->var->temp, temp->var = term->var;
            temp->next = form, form = temp;
            term->var->temp = 0.0;
         }
      }
      for (term = fy; term != NULL; term = term->next)
      {  if (term->var != NULL && term->var->temp != 0.0)
         {  temp = dmp_get_atom(mpl->formulae, sizeof(FORMULA));
            temp->coef = term->var->temp, temp->var = term->var;
            temp->next = form, form = temp;
            term->var->temp = 0.0;
         }
      }
      if (c0 != 0.0)
      {  temp = dmp_get_atom(mpl->formulae, sizeof(FORMULA));
         temp->coef = c0, temp->var = NULL;
         temp->next = form, form = temp;
      }
      delete_formula(mpl, fx);
      delete_formula(mpl, fy);
      return form;
}

/*----------------------------------------------------------------------
-- remove_constant - remove constant term from linear form.
--
-- This routine removes constant term from linear form and stores its
-- value to given location. */

FORMULA *remove_constant
(     MPL *mpl,
      FORMULA *form,          /* destroyed */
      double *coef            /* modified */
)
{     FORMULA *head = NULL, *temp;
      *coef = 0.0;
      while (form != NULL)
      {  temp = form;
         form = form->next;
         if (temp->var == NULL)
         {  /* constant term */
            *coef = fp_add(mpl, *coef, temp->coef);
            dmp_free_atom(mpl->formulae, temp, sizeof(FORMULA));
         }
         else
         {  /* linear term */
            temp->next = head;
            head = temp;
         }
      }
      return head;
}

/*----------------------------------------------------------------------
-- reduce_terms - reduce identical terms in linear form.
--
-- This routine reduces identical terms in specified linear form. */

FORMULA *reduce_terms
(     MPL *mpl,
      FORMULA *form           /* destroyed */
)
{     FORMULA *term, *next_term;
      double c0 = 0.0;
      for (term = form; term != NULL; term = term->next)
      {  if (term->var == NULL)
            c0 = fp_add(mpl, c0, term->coef);
         else
            term->var->temp = fp_add(mpl, term->var->temp, term->coef);
      }
      next_term = form, form = NULL;
      for (term = next_term; term != NULL; term = next_term)
      {  next_term = term->next;
         if (term->var == NULL && c0 != 0.0)
         {  term->coef = c0, c0 = 0.0;
            term->next = form, form = term;
         }
         else if (term->var != NULL && term->var->temp != 0.0)
         {  term->coef = term->var->temp, term->var->temp = 0.0;
            term->next = form, form = term;
         }
         else
            dmp_free_atom(mpl->formulae, term, sizeof(FORMULA));
      }
      return form;
}

/**********************************************************************/
/* * *                   ELEMENTAL CONSTRAINTS                    * * */
/**********************************************************************/

/* (there are no specific routines for elemental constraints) */

/**********************************************************************/
/* * *                       GENERIC VALUES                       * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- delete_value - delete generic value.
--
-- This routine deletes specified generic value.
--
-- NOTE: The generic value to be deleted must be valid. */

void delete_value
(     MPL *mpl,
      int type,
      VALUE *value            /* content destroyed */
)
{     xassert(value != NULL);
      switch (type)
      {  case A_NONE:
            value->none = NULL;
            break;
         case A_NUMERIC:
            value->num = 0.0;
            break;
         case A_SYMBOLIC:
            delete_symbol(mpl, value->sym), value->sym = NULL;
            break;
         case A_LOGICAL:
            value->bit = 0;
            break;
         case A_TUPLE:
            delete_tuple(mpl, value->tuple), value->tuple = NULL;
            break;
         case A_ELEMSET:
            delete_elemset(mpl, value->set), value->set = NULL;
            break;
         case A_ELEMVAR:
            value->var = NULL;
            break;
         case A_FORMULA:
            delete_formula(mpl, value->form), value->form = NULL;
            break;
         case A_ELEMCON:
            value->con = NULL;
            break;
         default:
            xassert(type != type);
      }
      return;
}

/**********************************************************************/
/* * *                SYMBOLICALLY INDEXED ARRAYS                 * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- create_array - create array.
--
-- This routine creates an array of specified type and dimension. Being
-- created the array is initially empty.
--
-- The type indicator determines generic values, which can be assigned
-- to the array members:
--
-- A_NONE     - none (members have no assigned values)
-- A_NUMERIC  - floating-point numbers
-- A_SYMBOLIC - symbols
-- A_ELEMSET  - elemental sets
-- A_ELEMVAR  - elemental variables
-- A_ELEMCON  - elemental constraints
--
-- The dimension may be 0, in which case the array consists of the only
-- member (such arrays represent 0-dimensional objects). */

ARRAY *create_array(MPL *mpl, int type, int dim)
{     ARRAY *array;
      xassert(type == A_NONE || type == A_NUMERIC ||
             type == A_SYMBOLIC || type == A_ELEMSET ||
             type == A_ELEMVAR || type == A_ELEMCON);
      xassert(dim >= 0);
      array = dmp_get_atom(mpl->arrays, sizeof(ARRAY));
      array->type = type;
      array->dim = dim;
      array->size = 0;
      array->head = NULL;
      array->tail = NULL;
      array->tree = NULL;
      array->prev = NULL;
      array->next = mpl->a_list;
      /* include the array in the global array list */
      if (array->next != NULL) array->next->prev = array;
      mpl->a_list = array;
      return array;
}

/*----------------------------------------------------------------------
-- find_member - find array member with given n-tuple.
--
-- This routine finds an array member, which has given n-tuple. If the
-- array is short, the linear search is used. Otherwise the routine
-- autimatically creates the search tree (i.e. the array index) to find
-- members for logarithmic time. */

static int compare_member_tuples(void *info, const void *key1,
      const void *key2)
{     /* this is an auxiliary routine used to compare keys, which are
         n-tuples assigned to array members */
      return compare_tuples((MPL *)info, (TUPLE *)key1, (TUPLE *)key2);
}

MEMBER *find_member
(     MPL *mpl,
      ARRAY *array,           /* not changed */
      TUPLE *tuple            /* not changed */
)
{     MEMBER *memb;
      xassert(array != NULL);
      /* the n-tuple must have the same dimension as the array */
      xassert(tuple_dimen(mpl, tuple) == array->dim);
      /* if the array is large enough, create the search tree and index
         all existing members of the array */
      if (array->size > 30 && array->tree == NULL)
      {  array->tree = avl_create_tree(compare_member_tuples, mpl);
         for (memb = array->head; memb != NULL; memb = memb->next)
avl_set_node_link(avl_insert_node(array->tree, memb->tuple),
               (void *)memb);
      }
      /* find a member, which has the given tuple */
      if (array->tree == NULL)
      {  /* the search tree doesn't exist; use the linear search */
         for (memb = array->head; memb != NULL; memb = memb->next)
            if (compare_tuples(mpl, memb->tuple, tuple) == 0) break;
      }
      else
      {  /* the search tree exists; use the binary search */
         AVLNODE *node;
         node = avl_find_node(array->tree, tuple);
memb = (MEMBER *)(node == NULL ? NULL : avl_get_node_link(node));
      }
      return memb;
}

/*----------------------------------------------------------------------
-- add_member - add new member to array.
--
-- This routine creates a new member with given n-tuple and adds it to
-- specified array.
--
-- For the sake of efficiency this routine doesn't check whether the
-- array already contains a member with the given n-tuple or not. Thus,
-- if necessary, the calling program should use the routine find_member
-- in order to be sure that the array contains no member with the same
-- n-tuple, because members with duplicate n-tuples are not allowed.
--
-- This routine assigns no generic value to the new member, because the
-- calling program must do that. */

MEMBER *add_member
(     MPL *mpl,
      ARRAY *array,           /* modified */
      TUPLE *tuple            /* destroyed */
)
{     MEMBER *memb;
      xassert(array != NULL);
      /* the n-tuple must have the same dimension as the array */
      xassert(tuple_dimen(mpl, tuple) == array->dim);
      /* create new member */
      memb = dmp_get_atom(mpl->members, sizeof(MEMBER));
      memb->tuple = tuple;
      memb->next = NULL;
      memset(&memb->value, '?', sizeof(VALUE));
      /* and append it to the member list */
      array->size++;
      if (array->head == NULL)
         array->head = memb;
      else
         array->tail->next = memb;
      array->tail = memb;
      /* if the search tree exists, index the new member */
      if (array->tree != NULL)
avl_set_node_link(avl_insert_node(array->tree, memb->tuple),
            (void *)memb);
      return memb;
}

/*----------------------------------------------------------------------
-- delete_array - delete array.
--
-- This routine deletes specified array.
--
-- Generic values assigned to the array members are not deleted by this
-- routine. The calling program itself must delete all assigned generic
-- values before deleting the array. */

void delete_array
(     MPL *mpl,
      ARRAY *array            /* destroyed */
)
{     MEMBER *memb;
      xassert(array != NULL);
      /* delete all existing array members */
      while (array->head != NULL)
      {  memb = array->head;
         array->head = memb->next;
         delete_tuple(mpl, memb->tuple);
         dmp_free_atom(mpl->members, memb, sizeof(MEMBER));
      }
      /* if the search tree exists, also delete it */
      if (array->tree != NULL) avl_delete_tree(array->tree);
      /* remove the array from the global array list */
      if (array->prev == NULL)
         mpl->a_list = array->next;
      else
         array->prev->next = array->next;
      if (array->next == NULL)
         ;
      else
         array->next->prev = array->prev;
      /* delete the array descriptor */
      dmp_free_atom(mpl->arrays, array, sizeof(ARRAY));
      return;
}

/**********************************************************************/
/* * *                 DOMAINS AND DUMMY INDICES                  * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- assign_dummy_index - assign new value to dummy index.
--
-- This routine assigns new value to specified dummy index and, that is
-- important, invalidates all temporary resultant values, which depends
-- on that dummy index. */

void assign_dummy_index
(     MPL *mpl,
      DOMAIN_SLOT *slot,      /* modified */
      SYMBOL *value           /* not changed */
)
{     CODE *leaf, *code;
      xassert(slot != NULL);
      xassert(value != NULL);
      /* delete the current value assigned to the dummy index */
      if (slot->value != NULL)
      {  /* if the current value and the new one are identical, actual
            assignment is not needed */
         if (compare_symbols(mpl, slot->value, value) == 0) goto done;
         /* delete a symbol, which is the current value */
         delete_symbol(mpl, slot->value), slot->value = NULL;
      }
      /* now walk through all the pseudo-codes with op = O_INDEX, which
         refer to the dummy index to be changed (these pseudo-codes are
         leaves in the forest of *all* expressions in the database) */
      for (leaf = slot->list; leaf != NULL; leaf = leaf->arg.index.
         next)
      {  xassert(leaf->op == O_INDEX);
         /* invalidate all resultant values, which depend on the dummy
            index, walking from the current leaf toward the root of the
            corresponding expression tree */
         for (code = leaf; code != NULL; code = code->up)
         {  if (code->valid)
            {  /* invalidate and delete resultant value */
               code->valid = 0;
               delete_value(mpl, code->type, &code->value);
            }
         }
      }
      /* assign new value to the dummy index */
      slot->value = copy_symbol(mpl, value);
done: return;
}

/*----------------------------------------------------------------------
-- update_dummy_indices - update current values of dummy indices.
--
-- This routine assigns components of "backup" n-tuple to dummy indices
-- of specified domain block. If no "backup" n-tuple is defined for the
-- domain block, values of the dummy indices remain untouched. */

void update_dummy_indices
(     MPL *mpl,
      DOMAIN_BLOCK *block     /* not changed */
)
{     DOMAIN_SLOT *slot;
      TUPLE *temp;
      if (block->backup != NULL)
      {  for (slot = block->list, temp = block->backup; slot != NULL;
            slot = slot->next, temp = temp->next)
         {  xassert(temp != NULL);
            xassert(temp->sym != NULL);
            assign_dummy_index(mpl, slot, temp->sym);
         }
      }
      return;
}

/*----------------------------------------------------------------------
-- enter_domain_block - enter domain block.
--
-- Let specified domain block have the form:
--
--    { ..., (j1, j2, ..., jn) in J, ... }
--
-- where j1, j2, ..., jn are dummy indices, J is a basic set.
--
-- This routine does the following:
--
-- 1. Checks if the given n-tuple is a member of the basic set J. Note
--    that J being *out of the scope* of the domain block cannot depend
--    on the dummy indices in the same and inner domain blocks, so it
--    can be computed before the dummy indices are assigned new values.
--    If this check fails, the routine returns with non-zero code.
--
-- 2. Saves current values of the dummy indices j1, j2, ..., jn.
--
-- 3. Assigns new values, which are components of the given n-tuple, to
--    the dummy indices j1, j2, ..., jn. If dimension of the n-tuple is
--    larger than n, its extra components n+1, n+2, ... are not used.
--
-- 4. Calls the formal routine func which either enters the next domain
--    block or evaluates some code within the domain scope.
--
-- 5. Restores former values of the dummy indices j1, j2, ..., jn.
--
-- Since current values assigned to the dummy indices on entry to this
-- routine are restored on exit, the formal routine func is allowed to
-- call this routine recursively. */

int enter_domain_block
(     MPL *mpl,
      DOMAIN_BLOCK *block,    /* not changed */
      TUPLE *tuple,           /* not changed */
      void *info, void (*func)(MPL *mpl, void *info)
)
{     TUPLE *backup;
      int ret = 0;
      /* check if the given n-tuple is a member of the basic set */
      xassert(block->code != NULL);
      if (!is_member(mpl, block->code, tuple))
      {  ret = 1;
         goto done;
      }
      /* save reference to "backup" n-tuple, which was used to assign
         current values of the dummy indices (it is sufficient to save
         reference, not value, because that n-tuple is defined in some
         outer level of recursion and therefore cannot be changed on
         this and deeper recursive calls) */
      backup = block->backup;
      /* set up new "backup" n-tuple, which defines new values of the
         dummy indices */
      block->backup = tuple;
      /* assign new values to the dummy indices */
      update_dummy_indices(mpl, block);
      /* call the formal routine that does the rest part of the job */
      func(mpl, info);
      /* restore reference to the former "backup" n-tuple */
      block->backup = backup;
      /* restore former values of the dummy indices; note that if the
         domain block just escaped has no other active instances which
         may exist due to recursion (it is indicated by a null pointer
         to the former n-tuple), former values of the dummy indices are
         undefined; therefore in this case the routine keeps currently
         assigned values of the dummy indices that involves keeping all
         dependent temporary results and thereby, if this domain block
         is not used recursively, allows improving efficiency */
      update_dummy_indices(mpl, block);
done: return ret;
}

/*----------------------------------------------------------------------
-- eval_within_domain - perform evaluation within domain scope.
--
-- This routine assigns new values (symbols) to all dummy indices of
-- specified domain and calls the formal routine func, which is used to
-- evaluate some code in the domain scope. Each free dummy index in the
-- domain is assigned a value specified in the corresponding component
-- of given n-tuple. Non-free dummy indices are assigned values, which
-- are computed by this routine.
--
-- Number of components in the given n-tuple must be the same as number
-- of free indices in the domain.
--
-- If the given n-tuple is not a member of the domain set, the routine
-- func is not called, and non-zero code is returned.
--
-- For the sake of convenience it is allowed to specify domain as NULL
-- (then n-tuple also must be 0-tuple, i.e. empty), in which case this
-- routine just calls the routine func and returns zero.
--
-- This routine allows recursive calls from the routine func providing
-- correct values of dummy indices for each instance.
--
-- NOTE: The n-tuple passed to this routine must not be changed by any
--       other routines called from the formal routine func until this
--       routine has returned. */

struct eval_domain_info
{     /* working info used by the routine eval_within_domain */
      DOMAIN *domain;
      /* domain, which has to be entered */
      DOMAIN_BLOCK *block;
      /* domain block, which is currently processed */
      TUPLE *tuple;
      /* tail of original n-tuple, whose components have to be assigned
         to free dummy indices in the current domain block */
      void *info;
      /* transit pointer passed to the formal routine func */
      void (*func)(MPL *mpl, void *info);
      /* routine, which has to be executed in the domain scope */
      int failure;
      /* this flag indicates that given n-tuple is not a member of the
         domain set */
};

static void eval_domain_func(MPL *mpl, void *_my_info)
{     /* this routine recursively enters into the domain scope and then
         calls the routine func */
      struct eval_domain_info *my_info = _my_info;
      if (my_info->block != NULL)
      {  /* the current domain block to be entered exists */
         DOMAIN_BLOCK *block;
         DOMAIN_SLOT *slot;
         TUPLE *tuple = NULL, *temp = NULL;
         /* save pointer to the current domain block */
         block = my_info->block;
         /* and get ready to enter the next block (if it exists) */
         my_info->block = block->next;
         /* construct temporary n-tuple, whose components correspond to
            dummy indices (slots) of the current domain; components of
            the temporary n-tuple that correspond to free dummy indices
            are assigned references (not values!) to symbols specified
            in the corresponding components of the given n-tuple, while
            other components that correspond to non-free dummy indices
            are assigned symbolic values computed here */
         for (slot = block->list; slot != NULL; slot = slot->next)
         {  /* create component that corresponds to the current slot */
            if (tuple == NULL)
               tuple = temp = dmp_get_atom(mpl->tuples, sizeof(TUPLE));
            else
temp = (temp->next = dmp_get_atom(mpl->tuples, sizeof(TUPLE)));
            if (slot->code == NULL)
            {  /* dummy index is free; take reference to symbol, which
                  is specified in the corresponding component of given
                  n-tuple */
               xassert(my_info->tuple != NULL);
               temp->sym = my_info->tuple->sym;
               xassert(temp->sym != NULL);
               my_info->tuple = my_info->tuple->next;
            }
            else
            {  /* dummy index is non-free; compute symbolic value to be
                  temporarily assigned to the dummy index */
               temp->sym = eval_symbolic(mpl, slot->code);
            }
         }
         temp->next = NULL;
         /* enter the current domain block */
         if (enter_domain_block(mpl, block, tuple, my_info,
               eval_domain_func)) my_info->failure = 1;
         /* delete temporary n-tuple as well as symbols that correspond
            to non-free dummy indices (they were computed here) */
         for (slot = block->list; slot != NULL; slot = slot->next)
         {  xassert(tuple != NULL);
            temp = tuple;
            tuple = tuple->next;
            if (slot->code != NULL)
            {  /* dummy index is non-free; delete symbolic value */
               delete_symbol(mpl, temp->sym);
            }
            /* delete component that corresponds to the current slot */
            dmp_free_atom(mpl->tuples, temp, sizeof(TUPLE));
         }
      }
      else
      {  /* there are no more domain blocks, i.e. we have reached the
            domain scope */
         xassert(my_info->tuple == NULL);
         /* check optional predicate specified for the domain */
         if (my_info->domain->code != NULL && !eval_logical(mpl,
            my_info->domain->code))
         {  /* the predicate is false */
            my_info->failure = 2;
         }
         else
         {  /* the predicate is true; do the job */
            my_info->func(mpl, my_info->info);
         }
      }
      return;
}

int eval_within_domain
(     MPL *mpl,
      DOMAIN *domain,         /* not changed */
      TUPLE *tuple,           /* not changed */
      void *info, void (*func)(MPL *mpl, void *info)
)
{     /* this routine performs evaluation within domain scope */
      struct eval_domain_info _my_info, *my_info = &_my_info;
      if (domain == NULL)
      {  xassert(tuple == NULL);
         func(mpl, info);
         my_info->failure = 0;
      }
      else
      {  xassert(tuple != NULL);
         my_info->domain = domain;
         my_info->block = domain->list;
         my_info->tuple = tuple;
         my_info->info = info;
         my_info->func = func;
         my_info->failure = 0;
         /* enter the very first domain block */
         eval_domain_func(mpl, my_info);
      }
      return my_info->failure;
}

/*----------------------------------------------------------------------
-- loop_within_domain - perform iterations within domain scope.
--
-- This routine iteratively assigns new values (symbols) to the dummy
-- indices of specified domain by enumerating all n-tuples, which are
-- members of the domain set, and for every n-tuple it calls the formal
-- routine func to evaluate some code within the domain scope.
--
-- If the routine func returns non-zero, enumeration within the domain
-- is prematurely terminated.
--
-- For the sake of convenience it is allowed to specify domain as NULL,
-- in which case this routine just calls the routine func only once and
-- returns zero.
--
-- This routine allows recursive calls from the routine func providing
-- correct values of dummy indices for each instance. */

struct loop_domain_info
{     /* working info used by the routine loop_within_domain */
      DOMAIN *domain;
      /* domain, which has to be entered */
      DOMAIN_BLOCK *block;
      /* domain block, which is currently processed */
      int looping;
      /* clearing this flag leads to terminating enumeration */
      void *info;
      /* transit pointer passed to the formal routine func */
      int (*func)(MPL *mpl, void *info);
      /* routine, which needs to be executed in the domain scope */
};

static void loop_domain_func(MPL *mpl, void *_my_info)
{     /* this routine enumerates all n-tuples in the basic set of the
         current domain block, enters recursively into the domain scope
         for every n-tuple, and then calls the routine func */
      struct loop_domain_info *my_info = _my_info;
      if (my_info->block != NULL)
      {  /* the current domain block to be entered exists */
         DOMAIN_BLOCK *block;
         DOMAIN_SLOT *slot;
         TUPLE *bound;
         /* save pointer to the current domain block */
         block = my_info->block;
         /* and get ready to enter the next block (if it exists) */
         my_info->block = block->next;
         /* compute symbolic values, at which non-free dummy indices of
            the current domain block are bound; since that values don't
            depend on free dummy indices of the current block, they can
            be computed once out of the enumeration loop */
         bound = create_tuple(mpl);
         for (slot = block->list; slot != NULL; slot = slot->next)
         {  if (slot->code != NULL)
               bound = expand_tuple(mpl, bound, eval_symbolic(mpl,
                  slot->code));
         }
         /* start enumeration */
         xassert(block->code != NULL);
         if (block->code->op == O_DOTS)
         {  /* the basic set is "arithmetic", in which case it doesn't
               need to be computed explicitly */
            TUPLE *tuple;
            int n, j;
            double t0, tf, dt;
            /* compute "parameters" of the basic set */
            t0 = eval_numeric(mpl, block->code->arg.arg.x);
            tf = eval_numeric(mpl, block->code->arg.arg.y);
            if (block->code->arg.arg.z == NULL)
               dt = 1.0;
            else
               dt = eval_numeric(mpl, block->code->arg.arg.z);
            /* determine cardinality of the basic set */
            n = arelset_size(mpl, t0, tf, dt);
            /* create dummy 1-tuple for members of the basic set */
            tuple = expand_tuple(mpl, create_tuple(mpl),
               create_symbol_num(mpl, 0.0));
            /* in case of "arithmetic" set there is exactly one dummy
               index, which cannot be non-free */
            xassert(bound == NULL);
            /* walk through 1-tuples of the basic set */
            for (j = 1; j <= n && my_info->looping; j++)
            {  /* construct dummy 1-tuple for the current member */
               tuple->sym->num = arelset_member(mpl, t0, tf, dt, j);
               /* enter the current domain block */
               enter_domain_block(mpl, block, tuple, my_info,
                  loop_domain_func);
            }
            /* delete dummy 1-tuple */
            delete_tuple(mpl, tuple);
         }
         else
         {  /* the basic set is of general kind, in which case it needs
               to be explicitly computed */
            ELEMSET *set;
            MEMBER *memb;
            TUPLE *temp1, *temp2;
            /* compute the basic set */
            set = eval_elemset(mpl, block->code);
            /* walk through all n-tuples of the basic set */
            for (memb = set->head; memb != NULL && my_info->looping;
               memb = memb->next)
            {  /* all components of the current n-tuple that correspond
                  to non-free dummy indices must be feasible; otherwise
                  the n-tuple is not in the basic set */
               temp1 = memb->tuple;
               temp2 = bound;
               for (slot = block->list; slot != NULL; slot = slot->next)
               {  xassert(temp1 != NULL);
                  if (slot->code != NULL)
                  {  /* non-free dummy index */
                     xassert(temp2 != NULL);
                     if (compare_symbols(mpl, temp1->sym, temp2->sym)
                        != 0)
                     {  /* the n-tuple is not in the basic set */
                        goto skip;
                     }
                     temp2 = temp2->next;
                  }
                  temp1 = temp1->next;
               }
               xassert(temp1 == NULL);
               xassert(temp2 == NULL);
               /* enter the current domain block */
               enter_domain_block(mpl, block, memb->tuple, my_info,
                  loop_domain_func);
skip:          ;
            }
            /* delete the basic set */
            delete_elemset(mpl, set);
         }
         /* delete symbolic values binding non-free dummy indices */
         delete_tuple(mpl, bound);
         /* restore pointer to the current domain block */
         my_info->block = block;
      }
      else
      {  /* there are no more domain blocks, i.e. we have reached the
            domain scope */
         /* check optional predicate specified for the domain */
         if (my_info->domain->code != NULL && !eval_logical(mpl,
            my_info->domain->code))
         {  /* the predicate is false */
            /* nop */;
         }
         else
         {  /* the predicate is true; do the job */
            my_info->looping = !my_info->func(mpl, my_info->info);
         }
      }
      return;
}

void loop_within_domain
(     MPL *mpl,
      DOMAIN *domain,         /* not changed */
      void *info, int (*func)(MPL *mpl, void *info)
)
{     /* this routine performs iterations within domain scope */
      struct loop_domain_info _my_info, *my_info = &_my_info;
      if (domain == NULL)
         func(mpl, info);
      else
      {  my_info->domain = domain;
         my_info->block = domain->list;
         my_info->looping = 1;
         my_info->info = info;
         my_info->func = func;
         /* enter the very first domain block */
         loop_domain_func(mpl, my_info);
      }
      return;
}

/*----------------------------------------------------------------------
-- out_of_domain - raise domain exception.
--
-- This routine is called when a reference is made to a member of some
-- model object, but its n-tuple is out of the object domain. */

void out_of_domain
(     MPL *mpl,
      char *name,             /* not changed */
      TUPLE *tuple            /* not changed */
)
{     xassert(name != NULL);
      xassert(tuple != NULL);
      mpl_error(mpl, "%s%s out of domain", name, format_tuple(mpl, '[',
         tuple));
      /* no return */
}

/*----------------------------------------------------------------------
-- get_domain_tuple - obtain current n-tuple from domain.
--
-- This routine constructs n-tuple, whose components are current values
-- assigned to *free* dummy indices of specified domain.
--
-- For the sake of convenience it is allowed to specify domain as NULL,
-- in which case this routine returns 0-tuple.
--
-- NOTE: This routine must not be called out of domain scope. */

TUPLE *get_domain_tuple
(     MPL *mpl,
      DOMAIN *domain          /* not changed */
)
{     DOMAIN_BLOCK *block;
      DOMAIN_SLOT *slot;
      TUPLE *tuple;
      tuple = create_tuple(mpl);
      if (domain != NULL)
      {  for (block = domain->list; block != NULL; block = block->next)
         {  for (slot = block->list; slot != NULL; slot = slot->next)
            {  if (slot->code == NULL)
               {  xassert(slot->value != NULL);
                  tuple = expand_tuple(mpl, tuple, copy_symbol(mpl,
                     slot->value));
               }
            }
         }
      }
      return tuple;
}

/*----------------------------------------------------------------------
-- clean_domain - clean domain.
--
-- This routine cleans specified domain that assumes deleting all stuff
-- dynamically allocated during the generation phase. */

void clean_domain(MPL *mpl, DOMAIN *domain)
{     DOMAIN_BLOCK *block;
      DOMAIN_SLOT *slot;
      /* if no domain is specified, do nothing */
      if (domain == NULL) goto done;
      /* clean all domain blocks */
      for (block = domain->list; block != NULL; block = block->next)
      {  /* clean all domain slots */
         for (slot = block->list; slot != NULL; slot = slot->next)
         {  /* clean pseudo-code for computing bound value */
            clean_code(mpl, slot->code);
            /* delete symbolic value assigned to dummy index */
            if (slot->value != NULL)
               delete_symbol(mpl, slot->value), slot->value = NULL;
         }
         /* clean pseudo-code for computing basic set */
         clean_code(mpl, block->code);
      }
      /* clean pseudo-code for computing domain predicate */
      clean_code(mpl, domain->code);
done: return;
}

/**********************************************************************/
/* * *                         MODEL SETS                         * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- check_elem_set - check elemental set assigned to set member.
--
-- This routine checks if given elemental set being assigned to member
-- of specified model set satisfies to all restrictions.
--
-- NOTE: This routine must not be called out of domain scope. */

void check_elem_set
(     MPL *mpl,
      SET *set,               /* not changed */
      TUPLE *tuple,           /* not changed */
      ELEMSET *refer          /* not changed */
)
{     WITHIN *within;
      MEMBER *memb;
      int eqno;
      /* elemental set must be within all specified supersets */
      for (within = set->within, eqno = 1; within != NULL; within =
         within->next, eqno++)
      {  xassert(within->code != NULL);
         for (memb = refer->head; memb != NULL; memb = memb->next)
         {  if (!is_member(mpl, within->code, memb->tuple))
            {  char buf[255+1];
               strcpy(buf, format_tuple(mpl, '(', memb->tuple));
               xassert(strlen(buf) < sizeof(buf));
               mpl_error(mpl, "%s%s contains %s which not within specified "
                  "set; see (%d)", set->name, format_tuple(mpl, '[',
                     tuple), buf, eqno);
            }
         }
      }
      return;
}

/*----------------------------------------------------------------------
-- take_member_set - obtain elemental set assigned to set member.
--
-- This routine obtains a reference to elemental set assigned to given
-- member of specified model set and returns it on exit.
--
-- NOTE: This routine must not be called out of domain scope. */

ELEMSET *take_member_set      /* returns reference, not value */
(     MPL *mpl,
      SET *set,               /* not changed */
      TUPLE *tuple            /* not changed */
)
{     MEMBER *memb;
      ELEMSET *refer;
      /* find member in the set array */
      memb = find_member(mpl, set->array, tuple);
      if (memb != NULL)
      {  /* member exists, so just take the reference */
         refer = memb->value.set;
      }
      else if (set->assign != NULL)
      {  /* compute value using assignment expression */
         refer = eval_elemset(mpl, set->assign);
add:     /* check that the elemental set satisfies to all restrictions,
            assign it to new member, and add the member to the array */
         check_elem_set(mpl, set, tuple, refer);
         memb = add_member(mpl, set->array, copy_tuple(mpl, tuple));
         memb->value.set = refer;
      }
      else if (set->option != NULL)
      {  /* compute default elemental set */
         refer = eval_elemset(mpl, set->option);
         goto add;
      }
      else
      {  /* no value (elemental set) is provided */
         mpl_error(mpl, "no value for %s%s", set->name, format_tuple(mpl,
            '[', tuple));
      }
      return refer;
}

/*----------------------------------------------------------------------
-- eval_member_set - evaluate elemental set assigned to set member.
--
-- This routine evaluates a reference to elemental set assigned to given
-- member of specified model set and returns it on exit. */

struct eval_set_info
{     /* working info used by the routine eval_member_set */
      SET *set;
      /* model set */
      TUPLE *tuple;
      /* n-tuple, which defines set member */
      MEMBER *memb;
      /* normally this pointer is NULL; the routine uses this pointer
         to check data provided in the data section, in which case it
         points to a member currently checked; this check is performed
         automatically only once when a reference to any member occurs
         for the first time */
      ELEMSET *refer;
      /* evaluated reference to elemental set */
};

static void eval_set_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine to work within domain scope */
      struct eval_set_info *info = _info;
      if (info->memb != NULL)
      {  /* checking call; check elemental set being assigned */
         check_elem_set(mpl, info->set, info->memb->tuple,
            info->memb->value.set);
      }
      else
      {  /* normal call; evaluate member, which has given n-tuple */
         info->refer = take_member_set(mpl, info->set, info->tuple);
      }
      return;
}

ELEMSET *eval_member_set      /* returns reference, not value */
(     MPL *mpl,
      SET *set,               /* not changed */
      TUPLE *tuple            /* not changed */
)
{     /* this routine evaluates set member */
      struct eval_set_info _info, *info = &_info;
      xassert(set->dim == tuple_dimen(mpl, tuple));
      info->set = set;
      info->tuple = tuple;
      if (set->data == 1)
      {  /* check data, which are provided in the data section, but not
            checked yet */
         /* save pointer to the last array member; note that during the
            check new members may be added beyond the last member due to
            references to the same parameter from default expression as
            well as from expressions that define restricting supersets;
            however, values assigned to the new members will be checked
            by other routine, so we don't need to check them here */
         MEMBER *tail = set->array->tail;
         /* change the data status to prevent infinite recursive loop
            due to references to the same set during the check */
         set->data = 2;
         /* check elemental sets assigned to array members in the data
            section until the marked member has been reached */
         for (info->memb = set->array->head; info->memb != NULL;
            info->memb = info->memb->next)
         {  if (eval_within_domain(mpl, set->domain, info->memb->tuple,
               info, eval_set_func))
               out_of_domain(mpl, set->name, info->memb->tuple);
            if (info->memb == tail) break;
         }
         /* the check has been finished */
      }
      /* evaluate member, which has given n-tuple */
      info->memb = NULL;
      if (eval_within_domain(mpl, info->set->domain, info->tuple, info,
         eval_set_func))
      out_of_domain(mpl, set->name, info->tuple);
      /* bring evaluated reference to the calling program */
      return info->refer;
}

/*----------------------------------------------------------------------
-- eval_whole_set - evaluate model set over entire domain.
--
-- This routine evaluates all members of specified model set over entire
-- domain. */

static int whole_set_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      SET *set = (SET *)info;
      TUPLE *tuple = get_domain_tuple(mpl, set->domain);
      eval_member_set(mpl, set, tuple);
      delete_tuple(mpl, tuple);
      return 0;
}

void eval_whole_set(MPL *mpl, SET *set)
{     loop_within_domain(mpl, set->domain, set, whole_set_func);
      return;
}

/*----------------------------------------------------------------------
-- clean set - clean model set.
--
-- This routine cleans specified model set that assumes deleting all
-- stuff dynamically allocated during the generation phase. */

void clean_set(MPL *mpl, SET *set)
{     WITHIN *within;
      MEMBER *memb;
      /* clean subscript domain */
      clean_domain(mpl, set->domain);
      /* clean pseudo-code for computing supersets */
      for (within = set->within; within != NULL; within = within->next)
         clean_code(mpl, within->code);
      /* clean pseudo-code for computing assigned value */
      clean_code(mpl, set->assign);
      /* clean pseudo-code for computing default value */
      clean_code(mpl, set->option);
      /* reset data status flag */
      set->data = 0;
      /* delete content array */
      for (memb = set->array->head; memb != NULL; memb = memb->next)
         delete_value(mpl, set->array->type, &memb->value);
      delete_array(mpl, set->array), set->array = NULL;
      return;
}

/**********************************************************************/
/* * *                      MODEL PARAMETERS                      * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- check_value_num - check numeric value assigned to parameter member.
--
-- This routine checks if numeric value being assigned to some member
-- of specified numeric model parameter satisfies to all restrictions.
--
-- NOTE: This routine must not be called out of domain scope. */

void check_value_num
(     MPL *mpl,
      PARAMETER *par,         /* not changed */
      TUPLE *tuple,           /* not changed */
      double value
)
{     CONDITION *cond;
      WITHIN *in;
      int eqno;
      /* the value must satisfy to the parameter type */
      switch (par->type)
      {  case A_NUMERIC:
            break;
         case A_INTEGER:
            if (value != floor(value))
               mpl_error(mpl, "%s%s = %.*g not integer", par->name,
                  format_tuple(mpl, '[', tuple), DBL_DIG, value);
            break;
         case A_BINARY:
            if (!(value == 0.0 || value == 1.0))
               mpl_error(mpl, "%s%s = %.*g not binary", par->name,
                  format_tuple(mpl, '[', tuple), DBL_DIG, value);
            break;
         default:
            xassert(par != par);
      }
      /* the value must satisfy to all specified conditions */
      for (cond = par->cond, eqno = 1; cond != NULL; cond = cond->next,
         eqno++)
      {  double bound;
         char *rho;
         xassert(cond->code != NULL);
         bound = eval_numeric(mpl, cond->code);
         switch (cond->rho)
         {  case O_LT:
               if (!(value < bound))
               {  rho = "<";
err:              mpl_error(mpl, "%s%s = %.*g not %s %.*g; see (%d)",
                     par->name, format_tuple(mpl, '[', tuple), DBL_DIG,
                     value, rho, DBL_DIG, bound, eqno);
               }
               break;
            case O_LE:
               if (!(value <= bound)) { rho = "<="; goto err; }
               break;
            case O_EQ:
               if (!(value == bound)) { rho = "="; goto err; }
               break;
            case O_GE:
               if (!(value >= bound)) { rho = ">="; goto err; }
               break;
            case O_GT:
               if (!(value > bound)) { rho = ">"; goto err; }
               break;
            case O_NE:
               if (!(value != bound)) { rho = "<>"; goto err; }
               break;
            default:
               xassert(cond != cond);
         }
      }
      /* the value must be in all specified supersets */
      for (in = par->in, eqno = 1; in != NULL; in = in->next, eqno++)
      {  TUPLE *dummy;
         xassert(in->code != NULL);
         xassert(in->code->dim == 1);
         dummy = expand_tuple(mpl, create_tuple(mpl),
            create_symbol_num(mpl, value));
         if (!is_member(mpl, in->code, dummy))
            mpl_error(mpl, "%s%s = %.*g not in specified set; see (%d)",
               par->name, format_tuple(mpl, '[', tuple), DBL_DIG,
               value, eqno);
         delete_tuple(mpl, dummy);
      }
      return;
}

/*----------------------------------------------------------------------
-- take_member_num - obtain num. value assigned to parameter member.
--
-- This routine obtains a numeric value assigned to member of specified
-- numeric model parameter and returns it on exit.
--
-- NOTE: This routine must not be called out of domain scope. */

double take_member_num
(     MPL *mpl,
      PARAMETER *par,         /* not changed */
      TUPLE *tuple            /* not changed */
)
{     MEMBER *memb;
      double value;
      /* find member in the parameter array */
      memb = find_member(mpl, par->array, tuple);
      if (memb != NULL)
      {  /* member exists, so just take its value */
         value = memb->value.num;
      }
      else if (par->assign != NULL)
      {  /* compute value using assignment expression */
         value = eval_numeric(mpl, par->assign);
add:     /* check that the value satisfies to all restrictions, assign
            it to new member, and add the member to the array */
         check_value_num(mpl, par, tuple, value);
         memb = add_member(mpl, par->array, copy_tuple(mpl, tuple));
         memb->value.num = value;
      }
      else if (par->option != NULL)
      {  /* compute default value */
         value = eval_numeric(mpl, par->option);
         goto add;
      }
      else if (par->defval != NULL)
      {  /* take default value provided in the data section */
         if (par->defval->str != NULL)
            mpl_error(mpl, "cannot convert %s to floating-point number",
               format_symbol(mpl, par->defval));
         value = par->defval->num;
         goto add;
      }
      else
      {  /* no value is provided */
         mpl_error(mpl, "no value for %s%s", par->name, format_tuple(mpl,
            '[', tuple));
      }
      return value;
}

/*----------------------------------------------------------------------
-- eval_member_num - evaluate num. value assigned to parameter member.
--
-- This routine evaluates a numeric value assigned to given member of
-- specified numeric model parameter and returns it on exit. */

struct eval_num_info
{     /* working info used by the routine eval_member_num */
      PARAMETER *par;
      /* model parameter  */
      TUPLE *tuple;
      /* n-tuple, which defines parameter member */
      MEMBER *memb;
      /* normally this pointer is NULL; the routine uses this pointer
         to check data provided in the data section, in which case it
         points to a member currently checked; this check is performed
         automatically only once when a reference to any member occurs
         for the first time */
      double value;
      /* evaluated numeric value */
};

static void eval_num_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine to work within domain scope */
      struct eval_num_info *info = _info;
      if (info->memb != NULL)
      {  /* checking call; check numeric value being assigned */
         check_value_num(mpl, info->par, info->memb->tuple,
            info->memb->value.num);
      }
      else
      {  /* normal call; evaluate member, which has given n-tuple */
         info->value = take_member_num(mpl, info->par, info->tuple);
      }
      return;
}

double eval_member_num
(     MPL *mpl,
      PARAMETER *par,         /* not changed */
      TUPLE *tuple            /* not changed */
)
{     /* this routine evaluates numeric parameter member */
      struct eval_num_info _info, *info = &_info;
      xassert(par->type == A_NUMERIC || par->type == A_INTEGER ||
             par->type == A_BINARY);
      xassert(par->dim == tuple_dimen(mpl, tuple));
      info->par = par;
      info->tuple = tuple;
      if (par->data == 1)
      {  /* check data, which are provided in the data section, but not
            checked yet */
         /* save pointer to the last array member; note that during the
            check new members may be added beyond the last member due to
            references to the same parameter from default expression as
            well as from expressions that define restricting conditions;
            however, values assigned to the new members will be checked
            by other routine, so we don't need to check them here */
         MEMBER *tail = par->array->tail;
         /* change the data status to prevent infinite recursive loop
            due to references to the same parameter during the check */
         par->data = 2;
         /* check values assigned to array members in the data section
            until the marked member has been reached */
         for (info->memb = par->array->head; info->memb != NULL;
            info->memb = info->memb->next)
         {  if (eval_within_domain(mpl, par->domain, info->memb->tuple,
               info, eval_num_func))
               out_of_domain(mpl, par->name, info->memb->tuple);
            if (info->memb == tail) break;
         }
         /* the check has been finished */
      }
      /* evaluate member, which has given n-tuple */
      info->memb = NULL;
      if (eval_within_domain(mpl, info->par->domain, info->tuple, info,
         eval_num_func))
         out_of_domain(mpl, par->name, info->tuple);
      /* bring evaluated value to the calling program */
      return info->value;
}

/*----------------------------------------------------------------------
-- check_value_sym - check symbolic value assigned to parameter member.
--
-- This routine checks if symbolic value being assigned to some member
-- of specified symbolic model parameter satisfies to all restrictions.
--
-- NOTE: This routine must not be called out of domain scope. */

void check_value_sym
(     MPL *mpl,
      PARAMETER *par,         /* not changed */
      TUPLE *tuple,           /* not changed */
      SYMBOL *value           /* not changed */
)
{     CONDITION *cond;
      WITHIN *in;
      int eqno;
      /* the value must satisfy to all specified conditions */
      for (cond = par->cond, eqno = 1; cond != NULL; cond = cond->next,
         eqno++)
      {  SYMBOL *bound;
         char buf[255+1];
         xassert(cond->code != NULL);
         bound = eval_symbolic(mpl, cond->code);
         switch (cond->rho)
         {  case O_EQ:
               if (!(compare_symbols(mpl, value, bound) == 0))
               {  strcpy(buf, format_symbol(mpl, bound));
                  xassert(strlen(buf) < sizeof(buf));
                  mpl_error(mpl, "%s%s = %s not = %s",
                     par->name, format_tuple(mpl, '[', tuple),
                     format_symbol(mpl, value), buf, eqno);
               }
               break;
            case O_NE:
               if (!(compare_symbols(mpl, value, bound) != 0))
               {  strcpy(buf, format_symbol(mpl, bound));
                  xassert(strlen(buf) < sizeof(buf));
                  mpl_error(mpl, "%s%s = %s not <> %s",
                     par->name, format_tuple(mpl, '[', tuple),
                     format_symbol(mpl, value), buf, eqno);
               }
               break;
            default:
               xassert(cond != cond);
         }
         delete_symbol(mpl, bound);
      }
      /* the value must be in all specified supersets */
      for (in = par->in, eqno = 1; in != NULL; in = in->next, eqno++)
      {  TUPLE *dummy;
         xassert(in->code != NULL);
         xassert(in->code->dim == 1);
         dummy = expand_tuple(mpl, create_tuple(mpl), copy_symbol(mpl,
            value));
         if (!is_member(mpl, in->code, dummy))
            mpl_error(mpl, "%s%s = %s not in specified set; see (%d)",
               par->name, format_tuple(mpl, '[', tuple),
               format_symbol(mpl, value), eqno);
         delete_tuple(mpl, dummy);
      }
      return;
}

/*----------------------------------------------------------------------
-- take_member_sym - obtain symb. value assigned to parameter member.
--
-- This routine obtains a symbolic value assigned to member of specified
-- symbolic model parameter and returns it on exit.
--
-- NOTE: This routine must not be called out of domain scope. */

SYMBOL *take_member_sym       /* returns value, not reference */
(     MPL *mpl,
      PARAMETER *par,         /* not changed */
      TUPLE *tuple            /* not changed */
)
{     MEMBER *memb;
      SYMBOL *value;
      /* find member in the parameter array */
      memb = find_member(mpl, par->array, tuple);
      if (memb != NULL)
      {  /* member exists, so just take its value */
         value = copy_symbol(mpl, memb->value.sym);
      }
      else if (par->assign != NULL)
      {  /* compute value using assignment expression */
         value = eval_symbolic(mpl, par->assign);
add:     /* check that the value satisfies to all restrictions, assign
            it to new member, and add the member to the array */
         check_value_sym(mpl, par, tuple, value);
         memb = add_member(mpl, par->array, copy_tuple(mpl, tuple));
         memb->value.sym = copy_symbol(mpl, value);
      }
      else if (par->option != NULL)
      {  /* compute default value */
         value = eval_symbolic(mpl, par->option);
         goto add;
      }
      else if (par->defval != NULL)
      {  /* take default value provided in the data section */
         value = copy_symbol(mpl, par->defval);
         goto add;
      }
      else
      {  /* no value is provided */
         mpl_error(mpl, "no value for %s%s", par->name, format_tuple(mpl,
            '[', tuple));
      }
      return value;
}

/*----------------------------------------------------------------------
-- eval_member_sym - evaluate symb. value assigned to parameter member.
--
-- This routine evaluates a symbolic value assigned to given member of
-- specified symbolic model parameter and returns it on exit. */

struct eval_sym_info
{     /* working info used by the routine eval_member_sym */
      PARAMETER *par;
      /* model parameter */
      TUPLE *tuple;
      /* n-tuple, which defines parameter member */
      MEMBER *memb;
      /* normally this pointer is NULL; the routine uses this pointer
         to check data provided in the data section, in which case it
         points to a member currently checked; this check is performed
         automatically only once when a reference to any member occurs
         for the first time */
      SYMBOL *value;
      /* evaluated symbolic value */
};

static void eval_sym_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine to work within domain scope */
      struct eval_sym_info *info = _info;
      if (info->memb != NULL)
      {  /* checking call; check symbolic value being assigned */
         check_value_sym(mpl, info->par, info->memb->tuple,
            info->memb->value.sym);
      }
      else
      {  /* normal call; evaluate member, which has given n-tuple */
         info->value = take_member_sym(mpl, info->par, info->tuple);
      }
      return;
}

SYMBOL *eval_member_sym       /* returns value, not reference */
(     MPL *mpl,
      PARAMETER *par,         /* not changed */
      TUPLE *tuple            /* not changed */
)
{     /* this routine evaluates symbolic parameter member */
      struct eval_sym_info _info, *info = &_info;
      xassert(par->type == A_SYMBOLIC);
      xassert(par->dim == tuple_dimen(mpl, tuple));
      info->par = par;
      info->tuple = tuple;
      if (par->data == 1)
      {  /* check data, which are provided in the data section, but not
            checked yet */
         /* save pointer to the last array member; note that during the
            check new members may be added beyond the last member due to
            references to the same parameter from default expression as
            well as from expressions that define restricting conditions;
            however, values assigned to the new members will be checked
            by other routine, so we don't need to check them here */
         MEMBER *tail = par->array->tail;
         /* change the data status to prevent infinite recursive loop
            due to references to the same parameter during the check */
         par->data = 2;
         /* check values assigned to array members in the data section
            until the marked member has been reached */
         for (info->memb = par->array->head; info->memb != NULL;
            info->memb = info->memb->next)
         {  if (eval_within_domain(mpl, par->domain, info->memb->tuple,
               info, eval_sym_func))
               out_of_domain(mpl, par->name, info->memb->tuple);
            if (info->memb == tail) break;
         }
         /* the check has been finished */
      }
      /* evaluate member, which has given n-tuple */
      info->memb = NULL;
      if (eval_within_domain(mpl, info->par->domain, info->tuple, info,
         eval_sym_func))
         out_of_domain(mpl, par->name, info->tuple);
      /* bring evaluated value to the calling program */
      return info->value;
}

/*----------------------------------------------------------------------
-- eval_whole_par - evaluate model parameter over entire domain.
--
-- This routine evaluates all members of specified model parameter over
-- entire domain. */

static int whole_par_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      PARAMETER *par = (PARAMETER *)info;
      TUPLE *tuple = get_domain_tuple(mpl, par->domain);
      switch (par->type)
      {  case A_NUMERIC:
         case A_INTEGER:
         case A_BINARY:
            eval_member_num(mpl, par, tuple);
            break;
         case A_SYMBOLIC:
            delete_symbol(mpl, eval_member_sym(mpl, par, tuple));
            break;
         default:
            xassert(par != par);
      }
      delete_tuple(mpl, tuple);
      return 0;
}

void eval_whole_par(MPL *mpl, PARAMETER *par)
{     loop_within_domain(mpl, par->domain, par, whole_par_func);
      return;
}

/*----------------------------------------------------------------------
-- clean_parameter - clean model parameter.
--
-- This routine cleans specified model parameter that assumes deleting
-- all stuff dynamically allocated during the generation phase. */

void clean_parameter(MPL *mpl, PARAMETER *par)
{     CONDITION *cond;
      WITHIN *in;
      MEMBER *memb;
      /* clean subscript domain */
      clean_domain(mpl, par->domain);
      /* clean pseudo-code for computing restricting conditions */
      for (cond = par->cond; cond != NULL; cond = cond->next)
         clean_code(mpl, cond->code);
      /* clean pseudo-code for computing restricting supersets */
      for (in = par->in; in != NULL; in = in->next)
         clean_code(mpl, in->code);
      /* clean pseudo-code for computing assigned value */
      clean_code(mpl, par->assign);
      /* clean pseudo-code for computing default value */
      clean_code(mpl, par->option);
      /* reset data status flag */
      par->data = 0;
      /* delete default symbolic value */
      if (par->defval != NULL)
         delete_symbol(mpl, par->defval), par->defval = NULL;
      /* delete content array */
      for (memb = par->array->head; memb != NULL; memb = memb->next)
         delete_value(mpl, par->array->type, &memb->value);
      delete_array(mpl, par->array), par->array = NULL;
      return;
}

/**********************************************************************/
/* * *                      MODEL VARIABLES                       * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- take_member_var - obtain reference to elemental variable.
--
-- This routine obtains a reference to elemental variable assigned to
-- given member of specified model variable and returns it on exit. If
-- necessary, new elemental variable is created.
--
-- NOTE: This routine must not be called out of domain scope. */

ELEMVAR *take_member_var      /* returns reference */
(     MPL *mpl,
      VARIABLE *var,          /* not changed */
      TUPLE *tuple            /* not changed */
)
{     MEMBER *memb;
      ELEMVAR *refer;
      /* find member in the variable array */
      memb = find_member(mpl, var->array, tuple);
      if (memb != NULL)
      {  /* member exists, so just take the reference */
         refer = memb->value.var;
      }
      else
      {  /* member is referenced for the first time and therefore does
            not exist; create new elemental variable, assign it to new
            member, and add the member to the variable array */
         memb = add_member(mpl, var->array, copy_tuple(mpl, tuple));
         refer = (memb->value.var =
            dmp_get_atom(mpl->elemvars, sizeof(ELEMVAR)));
         refer->j = 0;
         refer->var = var;
         refer->memb = memb;
         /* compute lower bound */
         if (var->lbnd == NULL)
            refer->lbnd = 0.0;
         else
            refer->lbnd = eval_numeric(mpl, var->lbnd);
         /* compute upper bound */
         if (var->ubnd == NULL)
            refer->ubnd = 0.0;
         else if (var->ubnd == var->lbnd)
            refer->ubnd = refer->lbnd;
         else
            refer->ubnd = eval_numeric(mpl, var->ubnd);
         /* nullify working quantity */
         refer->temp = 0.0;
         /* numerical value has not been obtained by the solver yet */
         refer->value = 0.0;
      }
      return refer;
}

/*----------------------------------------------------------------------
-- eval_member_var - evaluate reference to elemental variable.
--
-- This routine evaluates a reference to elemental variable assigned to
-- member of specified model variable and returns it on exit. */

struct eval_var_info
{     /* working info used by the routine eval_member_var */
      VARIABLE *var;
      /* model variable */
      TUPLE *tuple;
      /* n-tuple, which defines variable member */
      ELEMVAR *refer;
      /* evaluated reference to elemental variable */
};

static void eval_var_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine to work within domain scope */
      struct eval_var_info *info = _info;
      info->refer = take_member_var(mpl, info->var, info->tuple);
      return;
}

ELEMVAR *eval_member_var      /* returns reference */
(     MPL *mpl,
      VARIABLE *var,          /* not changed */
      TUPLE *tuple            /* not changed */
)
{     /* this routine evaluates variable member */
      struct eval_var_info _info, *info = &_info;
      xassert(var->dim == tuple_dimen(mpl, tuple));
      info->var = var;
      info->tuple = tuple;
      /* evaluate member, which has given n-tuple */
      if (eval_within_domain(mpl, info->var->domain, info->tuple, info,
         eval_var_func))
         out_of_domain(mpl, var->name, info->tuple);
      /* bring evaluated reference to the calling program */
      return info->refer;
}

/*----------------------------------------------------------------------
-- eval_whole_var - evaluate model variable over entire domain.
--
-- This routine evaluates all members of specified model variable over
-- entire domain. */

static int whole_var_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      VARIABLE *var = (VARIABLE *)info;
      TUPLE *tuple = get_domain_tuple(mpl, var->domain);
      eval_member_var(mpl, var, tuple);
      delete_tuple(mpl, tuple);
      return 0;
}

void eval_whole_var(MPL *mpl, VARIABLE *var)
{     loop_within_domain(mpl, var->domain, var, whole_var_func);
      return;
}

/*----------------------------------------------------------------------
-- clean_variable - clean model variable.
--
-- This routine cleans specified model variable that assumes deleting
-- all stuff dynamically allocated during the generation phase. */

void clean_variable(MPL *mpl, VARIABLE *var)
{     MEMBER *memb;
      /* clean subscript domain */
      clean_domain(mpl, var->domain);
      /* clean code for computing lower bound */
      clean_code(mpl, var->lbnd);
      /* clean code for computing upper bound */
      if (var->ubnd != var->lbnd) clean_code(mpl, var->ubnd);
      /* delete content array */
      for (memb = var->array->head; memb != NULL; memb = memb->next)
         dmp_free_atom(mpl->elemvars, memb->value.var, sizeof(ELEMVAR));
      delete_array(mpl, var->array), var->array = NULL;
      return;
}

/**********************************************************************/
/* * *              MODEL CONSTRAINTS AND OBJECTIVES              * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- take_member_con - obtain reference to elemental constraint.
--
-- This routine obtains a reference to elemental constraint assigned
-- to given member of specified model constraint and returns it on exit.
-- If necessary, new elemental constraint is created.
--
-- NOTE: This routine must not be called out of domain scope. */

ELEMCON *take_member_con      /* returns reference */
(     MPL *mpl,
      CONSTRAINT *con,        /* not changed */
      TUPLE *tuple            /* not changed */
)
{     MEMBER *memb;
      ELEMCON *refer;
      /* find member in the constraint array */
      memb = find_member(mpl, con->array, tuple);
      if (memb != NULL)
      {  /* member exists, so just take the reference */
         refer = memb->value.con;
      }
      else
      {  /* member is referenced for the first time and therefore does
            not exist; create new elemental constraint, assign it to new
            member, and add the member to the constraint array */
         memb = add_member(mpl, con->array, copy_tuple(mpl, tuple));
         refer = (memb->value.con =
            dmp_get_atom(mpl->elemcons, sizeof(ELEMCON)));
         refer->i = 0;
         refer->con = con;
         refer->memb = memb;
         /* compute linear form */
         xassert(con->code != NULL);
         refer->form = eval_formula(mpl, con->code);
         /* compute lower and upper bounds */
         if (con->lbnd == NULL && con->ubnd == NULL)
         {  /* objective has no bounds */
            double temp;
            xassert(con->type == A_MINIMIZE || con->type == A_MAXIMIZE);
            /* carry the constant term to the right-hand side */
            refer->form = remove_constant(mpl, refer->form, &temp);
            refer->lbnd = refer->ubnd = - temp;
         }
         else if (con->lbnd != NULL && con->ubnd == NULL)
         {  /* constraint a * x + b >= c * y + d is transformed to the
               standard form a * x - c * y >= d - b */
            double temp;
            xassert(con->type == A_CONSTRAINT);
            refer->form = linear_comb(mpl,
               +1.0, refer->form,
               -1.0, eval_formula(mpl, con->lbnd));
            refer->form = remove_constant(mpl, refer->form, &temp);
            refer->lbnd = - temp;
            refer->ubnd = 0.0;
         }
         else if (con->lbnd == NULL && con->ubnd != NULL)
         {  /* constraint a * x + b <= c * y + d is transformed to the
               standard form a * x - c * y <= d - b */
            double temp;
            xassert(con->type == A_CONSTRAINT);
            refer->form = linear_comb(mpl,
               +1.0, refer->form,
               -1.0, eval_formula(mpl, con->ubnd));
            refer->form = remove_constant(mpl, refer->form, &temp);
            refer->lbnd = 0.0;
            refer->ubnd = - temp;
         }
         else if (con->lbnd == con->ubnd)
         {  /* constraint a * x + b = c * y + d is transformed to the
               standard form a * x - c * y = d - b */
            double temp;
            xassert(con->type == A_CONSTRAINT);
            refer->form = linear_comb(mpl,
               +1.0, refer->form,
               -1.0, eval_formula(mpl, con->lbnd));
            refer->form = remove_constant(mpl, refer->form, &temp);
            refer->lbnd = refer->ubnd = - temp;
         }
         else
         {  /* ranged constraint c <= a * x + b <= d is transformed to
               the standard form c - b <= a * x <= d - b */
            double temp, temp1, temp2;
            xassert(con->type == A_CONSTRAINT);
            refer->form = remove_constant(mpl, refer->form, &temp);
            xassert(remove_constant(mpl, eval_formula(mpl, con->lbnd),
               &temp1) == NULL);
            xassert(remove_constant(mpl, eval_formula(mpl, con->ubnd),
               &temp2) == NULL);
            refer->lbnd = fp_sub(mpl, temp1, temp);
            refer->ubnd = fp_sub(mpl, temp2, temp);
         }
      }
      return refer;
}

/*----------------------------------------------------------------------
-- eval_member_con - evaluate reference to elemental constraint.
--
-- This routine evaluates a reference to elemental constraint assigned
-- to member of specified model constraint and returns it on exit. */

struct eval_con_info
{     /* working info used by the routine eval_member_con */
      CONSTRAINT *con;
      /* model constraint */
      TUPLE *tuple;
      /* n-tuple, which defines constraint member */
      ELEMCON *refer;
      /* evaluated reference to elemental constraint */
};

static void eval_con_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine to work within domain scope */
      struct eval_con_info *info = _info;
      info->refer = take_member_con(mpl, info->con, info->tuple);
      return;
}

ELEMCON *eval_member_con      /* returns reference */
(     MPL *mpl,
      CONSTRAINT *con,        /* not changed */
      TUPLE *tuple            /* not changed */
)
{     /* this routine evaluates constraint member */
      struct eval_con_info _info, *info = &_info;
      xassert(con->dim == tuple_dimen(mpl, tuple));
      info->con = con;
      info->tuple = tuple;
      /* evaluate member, which has given n-tuple */
      if (eval_within_domain(mpl, info->con->domain, info->tuple, info,
         eval_con_func))
         out_of_domain(mpl, con->name, info->tuple);
      /* bring evaluated reference to the calling program */
      return info->refer;
}

/*----------------------------------------------------------------------
-- eval_whole_con - evaluate model constraint over entire domain.
--
-- This routine evaluates all members of specified model constraint over
-- entire domain. */

static int whole_con_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      CONSTRAINT *con = (CONSTRAINT *)info;
      TUPLE *tuple = get_domain_tuple(mpl, con->domain);
      eval_member_con(mpl, con, tuple);
      delete_tuple(mpl, tuple);
      return 0;
}

void eval_whole_con(MPL *mpl, CONSTRAINT *con)
{     loop_within_domain(mpl, con->domain, con, whole_con_func);
      return;
}

/*----------------------------------------------------------------------
-- clean_constraint - clean model constraint.
--
-- This routine cleans specified model constraint that assumes deleting
-- all stuff dynamically allocated during the generation phase. */

void clean_constraint(MPL *mpl, CONSTRAINT *con)
{     MEMBER *memb;
      /* clean subscript domain */
      clean_domain(mpl, con->domain);
      /* clean code for computing main linear form */
      clean_code(mpl, con->code);
      /* clean code for computing lower bound */
      clean_code(mpl, con->lbnd);
      /* clean code for computing upper bound */
      if (con->ubnd != con->lbnd) clean_code(mpl, con->ubnd);
      /* delete content array */
      for (memb = con->array->head; memb != NULL; memb = memb->next)
      {  delete_formula(mpl, memb->value.con->form);
         dmp_free_atom(mpl->elemcons, memb->value.con, sizeof(ELEMCON));
      }
      delete_array(mpl, con->array), con->array = NULL;
      return;
}

/**********************************************************************/
/* * *                        PSEUDO-CODE                         * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- eval_numeric - evaluate pseudo-code to determine numeric value.
--
-- This routine evaluates specified pseudo-code to determine resultant
-- numeric value, which is returned on exit. */

struct iter_num_info
{     /* working info used by the routine iter_num_func */
      CODE *code;
      /* pseudo-code for iterated operation to be performed */
      double value;
      /* resultant value */
};

static int iter_num_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine used to perform iterated operation
         on numeric "integrand" within domain scope */
      struct iter_num_info *info = _info;
      double temp;
      temp = eval_numeric(mpl, info->code->arg.loop.x);
      switch (info->code->op)
      {  case O_SUM:
            /* summation over domain */
            info->value = fp_add(mpl, info->value, temp);
            break;
         case O_PROD:
            /* multiplication over domain */
            info->value = fp_mul(mpl, info->value, temp);
            break;
         case O_MINIMUM:
            /* minimum over domain */
            if (info->value > temp) info->value = temp;
            break;
         case O_MAXIMUM:
            /* maximum over domain */
            if (info->value < temp) info->value = temp;
            break;
         default:
            xassert(info != info);
      }
      return 0;
}

double eval_numeric(MPL *mpl, CODE *code)
{     double value;
      xassert(code != NULL);
      xassert(code->type == A_NUMERIC);
      xassert(code->dim == 0);
      /* if the operation has a side effect, invalidate and delete the
         resultant value */
      if (code->vflag && code->valid)
      {  code->valid = 0;
         delete_value(mpl, code->type, &code->value);
      }
      /* if resultant value is valid, no evaluation is needed */
      if (code->valid)
      {  value = code->value.num;
         goto done;
      }
      /* evaluate pseudo-code recursively */
      switch (code->op)
      {  case O_NUMBER:
            /* take floating-point number */
            value = code->arg.num;
            break;
         case O_MEMNUM:
            /* take member of numeric parameter */
            {  TUPLE *tuple;
               ARG_LIST *e;
               tuple = create_tuple(mpl);
               for (e = code->arg.par.list; e != NULL; e = e->next)
                  tuple = expand_tuple(mpl, tuple, eval_symbolic(mpl,
                     e->x));
               value = eval_member_num(mpl, code->arg.par.par, tuple);
               delete_tuple(mpl, tuple);
            }
            break;
         case O_MEMVAR:
            /* take computed value of elemental variable */
            {  TUPLE *tuple;
               ARG_LIST *e;
               tuple = create_tuple(mpl);
               for (e = code->arg.var.list; e != NULL; e = e->next)
                  tuple = expand_tuple(mpl, tuple, eval_symbolic(mpl,
                     e->x));
               value = eval_member_var(mpl, code->arg.var.var, tuple)
                  ->value;
               delete_tuple(mpl, tuple);
            }
            break;
         case O_IRAND224:
            /* pseudo-random in [0, 2^24-1] */
            value = fp_irand224(mpl);
            break;
         case O_UNIFORM01:
            /* pseudo-random in [0, 1) */
            value = fp_uniform01(mpl);
            break;
         case O_NORMAL01:
            /* gaussian random, mu = 0, sigma = 1 */
            value = fp_normal01(mpl);
            break;
         case O_CVTNUM:
            /* conversion to numeric */
            {  SYMBOL *sym;
               sym = eval_symbolic(mpl, code->arg.arg.x);
               if (sym->str != NULL)
                  mpl_error(mpl, "cannot convert %s to floating-point numbe"
                     "r", format_symbol(mpl, sym));
               value = sym->num;
               delete_symbol(mpl, sym);
            }
            break;
         case O_PLUS:
            /* unary plus */
            value = + eval_numeric(mpl, code->arg.arg.x);
            break;
         case O_MINUS:
            /* unary minus */
            value = - eval_numeric(mpl, code->arg.arg.x);
            break;
         case O_ABS:
            /* absolute value */
            value = fabs(eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_CEIL:
            /* round upward ("ceiling of x") */
            value = ceil(eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_FLOOR:
            /* round downward ("floor of x") */
            value = floor(eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_EXP:
            /* base-e exponential */
            value = fp_exp(mpl, eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_LOG:
            /* natural logarithm */
            value = fp_log(mpl, eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_LOG10:
            /* common (decimal) logarithm */
            value = fp_log10(mpl, eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_SQRT:
            /* square root */
            value = fp_sqrt(mpl, eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_SIN:
            /* trigonometric sine */
            value = fp_sin(mpl, eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_COS:
            /* trigonometric cosine */
            value = fp_cos(mpl, eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_ATAN:
            /* trigonometric arctangent (one argument) */
            value = fp_atan(mpl, eval_numeric(mpl, code->arg.arg.x));
            break;
         case O_ATAN2:
            /* trigonometric arctangent (two arguments) */
            value = fp_atan2(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_ROUND:
            /* round to nearest integer */
            value = fp_round(mpl,
               eval_numeric(mpl, code->arg.arg.x), 0.0);
            break;
         case O_ROUND2:
            /* round to n fractional digits */
            value = fp_round(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_TRUNC:
            /* truncate to nearest integer */
            value = fp_trunc(mpl,
               eval_numeric(mpl, code->arg.arg.x), 0.0);
            break;
         case O_TRUNC2:
            /* truncate to n fractional digits */
            value = fp_trunc(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_ADD:
            /* addition */
            value = fp_add(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_SUB:
            /* subtraction */
            value = fp_sub(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_LESS:
            /* non-negative subtraction */
            value = fp_less(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_MUL:
            /* multiplication */
            value = fp_mul(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_DIV:
            /* division */
            value = fp_div(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_IDIV:
            /* quotient of exact division */
            value = fp_idiv(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_MOD:
            /* remainder of exact division */
            value = fp_mod(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_POWER:
            /* exponentiation (raise to power) */
            value = fp_power(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_UNIFORM:
            /* pseudo-random in [a, b) */
            value = fp_uniform(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_NORMAL:
            /* gaussian random, given mu and sigma */
            value = fp_normal(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y));
            break;
#if 1 /* 15/VII-2006 */
         case O_CARD:
            {  ELEMSET *set;
               set = eval_elemset(mpl, code->arg.arg.x);
               value = set->size;
               delete_array(mpl, set);
            }
            break;
         case O_LENGTH:
            {  SYMBOL *sym;
               char str[MAX_LENGTH+1];
               sym = eval_symbolic(mpl, code->arg.arg.x);
               if (sym->str == NULL)
                  sprintf(str, "%.*g", DBL_DIG, sym->num);
               else
                  fetch_string(mpl, sym->str, str);
               delete_symbol(mpl, sym);
               value = strlen(str);
            }
            break;
#endif
         case O_FORK:
            /* if-then-else */
            if (eval_logical(mpl, code->arg.arg.x))
               value = eval_numeric(mpl, code->arg.arg.y);
            else if (code->arg.arg.z == NULL)
               value = 0.0;
            else
               value = eval_numeric(mpl, code->arg.arg.z);
            break;
         case O_MIN:
            /* minimal value (n-ary) */
            {  ARG_LIST *e;
               double temp;
               value = +DBL_MAX;
               for (e = code->arg.list; e != NULL; e = e->next)
               {  temp = eval_numeric(mpl, e->x);
                  if (value > temp) value = temp;
               }
            }
            break;
         case O_MAX:
            /* maximal value (n-ary) */
            {  ARG_LIST *e;
               double temp;
               value = -DBL_MAX;
               for (e = code->arg.list; e != NULL; e = e->next)
               {  temp = eval_numeric(mpl, e->x);
                  if (value < temp) value = temp;
               }
            }
            break;
         case O_SUM:
            /* summation over domain */
            {  struct iter_num_info _info, *info = &_info;
               info->code = code;
               info->value = 0.0;
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_num_func);
               value = info->value;
            }
            break;
         case O_PROD:
            /* multiplication over domain */
            {  struct iter_num_info _info, *info = &_info;
               info->code = code;
               info->value = 1.0;
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_num_func);
               value = info->value;
            }
            break;
         case O_MINIMUM:
            /* minimum over domain */
            {  struct iter_num_info _info, *info = &_info;
               info->code = code;
               info->value = +DBL_MAX;
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_num_func);
               if (info->value == +DBL_MAX)
                  mpl_error(mpl, "min{} over empty set; result undefined");
               value = info->value;
            }
            break;
         case O_MAXIMUM:
            /* maximum over domain */
            {  struct iter_num_info _info, *info = &_info;
               info->code = code;
               info->value = -DBL_MAX;
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_num_func);
               if (info->value == -DBL_MAX)
                  mpl_error(mpl, "max{} over empty set; result undefined");
               value = info->value;
            }
            break;
         default:
            xassert(code != code);
      }
      /* save resultant value */
      xassert(!code->valid);
      code->valid = 1;
      code->value.num = value;
done: return value;
}

/*----------------------------------------------------------------------
-- eval_symbolic - evaluate pseudo-code to determine symbolic value.
--
-- This routine evaluates specified pseudo-code to determine resultant
-- symbolic value, which is returned on exit. */

SYMBOL *eval_symbolic(MPL *mpl, CODE *code)
{     SYMBOL *value;
      xassert(code != NULL);
      xassert(code->type == A_SYMBOLIC);
      xassert(code->dim == 0);
      /* if the operation has a side effect, invalidate and delete the
         resultant value */
      if (code->vflag && code->valid)
      {  code->valid = 0;
         delete_value(mpl, code->type, &code->value);
      }
      /* if resultant value is valid, no evaluation is needed */
      if (code->valid)
      {  value = copy_symbol(mpl, code->value.sym);
         goto done;
      }
      /* evaluate pseudo-code recursively */
      switch (code->op)
      {  case O_STRING:
            /* take character string */
            value = create_symbol_str(mpl, create_string(mpl,
               code->arg.str));
            break;
         case O_INDEX:
            /* take dummy index */
            xassert(code->arg.index.slot->value != NULL);
            value = copy_symbol(mpl, code->arg.index.slot->value);
            break;
         case O_MEMSYM:
            /* take member of symbolic parameter */
            {  TUPLE *tuple;
               ARG_LIST *e;
               tuple = create_tuple(mpl);
               for (e = code->arg.par.list; e != NULL; e = e->next)
                  tuple = expand_tuple(mpl, tuple, eval_symbolic(mpl,
                     e->x));
               value = eval_member_sym(mpl, code->arg.par.par, tuple);
               delete_tuple(mpl, tuple);
            }
            break;
         case O_CVTSYM:
            /* conversion to symbolic */
            value = create_symbol_num(mpl, eval_numeric(mpl,
               code->arg.arg.x));
            break;
         case O_CONCAT:
            /* concatenation */
            value = concat_symbols(mpl,
               eval_symbolic(mpl, code->arg.arg.x),
               eval_symbolic(mpl, code->arg.arg.y));
            break;
         case O_FORK:
            /* if-then-else */
            if (eval_logical(mpl, code->arg.arg.x))
               value = eval_symbolic(mpl, code->arg.arg.y);
            else if (code->arg.arg.z == NULL)
               value = create_symbol_num(mpl, 0.0);
            else
               value = eval_symbolic(mpl, code->arg.arg.z);
            break;
#if 1 /* 15/VII-2006 */
         case O_SUBSTR:
         case O_SUBSTR3:
            {  double pos, len;
               char str[MAX_LENGTH+1];
               value = eval_symbolic(mpl, code->arg.arg.x);
               if (value->str == NULL)
                  sprintf(str, "%.*g", DBL_DIG, value->num);
               else
                  fetch_string(mpl, value->str, str);
               delete_symbol(mpl, value);
               if (code->op == O_SUBSTR)
               {  pos = eval_numeric(mpl, code->arg.arg.y);
                  if (pos != floor(pos))
                     mpl_error(mpl, "substr('...', %.*g); non-integer secon"
                        "d argument", DBL_DIG, pos);
                  if (pos < 1 || pos > strlen(str) + 1)
                     mpl_error(mpl, "substr('...', %.*g); substring out of "
                        "range", DBL_DIG, pos);
               }
               else
               {  pos = eval_numeric(mpl, code->arg.arg.y);
                  len = eval_numeric(mpl, code->arg.arg.z);
                  if (pos != floor(pos) || len != floor(len))
                     mpl_error(mpl, "substr('...', %.*g, %.*g); non-integer"
                        " second and/or third argument", DBL_DIG, pos,
                        DBL_DIG, len);
                  if (pos < 1 || len < 0 || pos + len > strlen(str) + 1)
                     mpl_error(mpl, "substr('...', %.*g, %.*g); substring o"
                        "ut of range", DBL_DIG, pos, DBL_DIG, len);
                  str[(int)pos + (int)len - 1] = '\0';
               }
               value = create_symbol_str(mpl, create_string(mpl, str +
                  (int)pos - 1));
            }
            break;
#endif
         default:
            xassert(code != code);
      }
      /* save resultant value */
      xassert(!code->valid);
      code->valid = 1;
      code->value.sym = copy_symbol(mpl, value);
done: return value;
}

/*----------------------------------------------------------------------
-- eval_logical - evaluate pseudo-code to determine logical value.
--
-- This routine evaluates specified pseudo-code to determine resultant
-- logical value, which is returned on exit. */

struct iter_log_info
{     /* working info used by the routine iter_log_func */
      CODE *code;
      /* pseudo-code for iterated operation to be performed */
      int value;
      /* resultant value */
};

static int iter_log_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine used to perform iterated operation
         on logical "integrand" within domain scope */
      struct iter_log_info *info = _info;
      int ret = 0;
      switch (info->code->op)
      {  case O_FORALL:
            /* conjunction over domain */
            info->value &= eval_logical(mpl, info->code->arg.loop.x);
            if (!info->value) ret = 1;
            break;
         case O_EXISTS:
            /* disjunction over domain */
            info->value |= eval_logical(mpl, info->code->arg.loop.x);
            if (info->value) ret = 1;
            break;
         default:
            xassert(info != info);
      }
      return ret;
}

int eval_logical(MPL *mpl, CODE *code)
{     int value;
      xassert(code->type == A_LOGICAL);
      xassert(code->dim == 0);
      /* if the operation has a side effect, invalidate and delete the
         resultant value */
      if (code->vflag && code->valid)
      {  code->valid = 0;
         delete_value(mpl, code->type, &code->value);
      }
      /* if resultant value is valid, no evaluation is needed */
      if (code->valid)
      {  value = code->value.bit;
         goto done;
      }
      /* evaluate pseudo-code recursively */
      switch (code->op)
      {  case O_CVTLOG:
            /* conversion to logical */
            value = (eval_numeric(mpl, code->arg.arg.x) != 0.0);
            break;
         case O_NOT:
            /* negation (logical "not") */
            value = !eval_logical(mpl, code->arg.arg.x);
            break;
         case O_LT:
            /* comparison on 'less than' */
            value = (eval_numeric(mpl, code->arg.arg.x) <
                     eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_LE:
            /* comparison on 'not greater than' */
            value = (eval_numeric(mpl, code->arg.arg.x) <=
                     eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_EQ:
            /* comparison on 'equal to' */
            xassert(code->arg.arg.x != NULL);
            if (code->arg.arg.x->type == A_NUMERIC)
               value = (eval_numeric(mpl, code->arg.arg.x) ==
                        eval_numeric(mpl, code->arg.arg.y));
            else
            {  SYMBOL *sym1 = eval_symbolic(mpl, code->arg.arg.x);
               SYMBOL *sym2 = eval_symbolic(mpl, code->arg.arg.y);
               value = (compare_symbols(mpl, sym1, sym2) == 0);
               delete_symbol(mpl, sym1);
               delete_symbol(mpl, sym2);
            }
            break;
         case O_GE:
            /* comparison on 'not less than' */
            value = (eval_numeric(mpl, code->arg.arg.x) >=
                     eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_GT:
            /* comparison on 'greater than' */
            value = (eval_numeric(mpl, code->arg.arg.x) >
                     eval_numeric(mpl, code->arg.arg.y));
            break;
         case O_NE:
            /* comparison on 'not equal to' */
            xassert(code->arg.arg.x != NULL);
            if (code->arg.arg.x->type == A_NUMERIC)
               value = (eval_numeric(mpl, code->arg.arg.x) !=
                        eval_numeric(mpl, code->arg.arg.y));
            else
            {  SYMBOL *sym1 = eval_symbolic(mpl, code->arg.arg.x);
               SYMBOL *sym2 = eval_symbolic(mpl, code->arg.arg.y);
               value = (compare_symbols(mpl, sym1, sym2) != 0);
               delete_symbol(mpl, sym1);
               delete_symbol(mpl, sym2);
            }
            break;
         case O_AND:
            /* conjunction (logical "and") */
            value = eval_logical(mpl, code->arg.arg.x) &&
                    eval_logical(mpl, code->arg.arg.y);
            break;
         case O_OR:
            /* disjunction (logical "or") */
            value = eval_logical(mpl, code->arg.arg.x) ||
                    eval_logical(mpl, code->arg.arg.y);
            break;
         case O_IN:
            /* test on 'x in Y' */
            {  TUPLE *tuple;
               tuple = eval_tuple(mpl, code->arg.arg.x);
               value = is_member(mpl, code->arg.arg.y, tuple);
               delete_tuple(mpl, tuple);
            }
            break;
         case O_NOTIN:
            /* test on 'x not in Y' */
            {  TUPLE *tuple;
               tuple = eval_tuple(mpl, code->arg.arg.x);
               value = !is_member(mpl, code->arg.arg.y, tuple);
               delete_tuple(mpl, tuple);
            }
            break;
         case O_WITHIN:
            /* test on 'X within Y' */
            {  ELEMSET *set;
               MEMBER *memb;
               set = eval_elemset(mpl, code->arg.arg.x);
               value = 1;
               for (memb = set->head; memb != NULL; memb = memb->next)
               {  if (!is_member(mpl, code->arg.arg.y, memb->tuple))
                  {  value = 0;
                     break;
                  }
               }
               delete_elemset(mpl, set);
            }
            break;
         case O_NOTWITHIN:
            /* test on 'X not within Y' */
            {  ELEMSET *set;
               MEMBER *memb;
               set = eval_elemset(mpl, code->arg.arg.x);
               value = 1;
               for (memb = set->head; memb != NULL; memb = memb->next)
               {  if (is_member(mpl, code->arg.arg.y, memb->tuple))
                  {  value = 0;
                     break;
                  }
               }
               delete_elemset(mpl, set);
            }
            break;
         case O_FORALL:
            /* conjunction (A-quantification) */
            {  struct iter_log_info _info, *info = &_info;
               info->code = code;
               info->value = 1;
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_log_func);
               value = info->value;
            }
            break;
         case O_EXISTS:
            /* disjunction (E-quantification) */
            {  struct iter_log_info _info, *info = &_info;
               info->code = code;
               info->value = 0;
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_log_func);
               value = info->value;
            }
            break;
         default:
            xassert(code != code);
      }
      /* save resultant value */
      xassert(!code->valid);
      code->valid = 1;
      code->value.bit = value;
done: return value;
}

/*----------------------------------------------------------------------
-- eval_tuple - evaluate pseudo-code to construct n-tuple.
--
-- This routine evaluates specified pseudo-code to construct resultant
-- n-tuple, which is returned on exit. */

TUPLE *eval_tuple(MPL *mpl, CODE *code)
{     TUPLE *value;
      xassert(code != NULL);
      xassert(code->type == A_TUPLE);
      xassert(code->dim > 0);
      /* if the operation has a side effect, invalidate and delete the
         resultant value */
      if (code->vflag && code->valid)
      {  code->valid = 0;
         delete_value(mpl, code->type, &code->value);
      }
      /* if resultant value is valid, no evaluation is needed */
      if (code->valid)
      {  value = copy_tuple(mpl, code->value.tuple);
         goto done;
      }
      /* evaluate pseudo-code recursively */
      switch (code->op)
      {  case O_TUPLE:
            /* make n-tuple */
            {  ARG_LIST *e;
               value = create_tuple(mpl);
               for (e = code->arg.list; e != NULL; e = e->next)
                  value = expand_tuple(mpl, value, eval_symbolic(mpl,
                     e->x));
            }
            break;
         case O_CVTTUP:
            /* convert to 1-tuple */
            value = expand_tuple(mpl, create_tuple(mpl),
               eval_symbolic(mpl, code->arg.arg.x));
            break;
         default:
            xassert(code != code);
      }
      /* save resultant value */
      xassert(!code->valid);
      code->valid = 1;
      code->value.tuple = copy_tuple(mpl, value);
done: return value;
}

/*----------------------------------------------------------------------
-- eval_elemset - evaluate pseudo-code to construct elemental set.
--
-- This routine evaluates specified pseudo-code to construct resultant
-- elemental set, which is returned on exit. */

struct iter_set_info
{     /* working info used by the routine iter_set_func */
      CODE *code;
      /* pseudo-code for iterated operation to be performed */
      ELEMSET *value;
      /* resultant value */
};

static int iter_set_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine used to perform iterated operation
         on n-tuple "integrand" within domain scope */
      struct iter_set_info *info = _info;
      TUPLE *tuple;
      switch (info->code->op)
      {  case O_SETOF:
            /* compute next n-tuple and add it to the set; in this case
               duplicate n-tuples are silently ignored */
            tuple = eval_tuple(mpl, info->code->arg.loop.x);
            if (find_tuple(mpl, info->value, tuple) == NULL)
               add_tuple(mpl, info->value, tuple);
            else
               delete_tuple(mpl, tuple);
            break;
         case O_BUILD:
            /* construct next n-tuple using current values assigned to
               *free* dummy indices as its components and add it to the
               set; in this case duplicate n-tuples cannot appear */
            add_tuple(mpl, info->value, get_domain_tuple(mpl,
               info->code->arg.loop.domain));
            break;
         default:
            xassert(info != info);
      }
      return 0;
}

ELEMSET *eval_elemset(MPL *mpl, CODE *code)
{     ELEMSET *value;
      xassert(code != NULL);
      xassert(code->type == A_ELEMSET);
      xassert(code->dim > 0);
      /* if the operation has a side effect, invalidate and delete the
         resultant value */
      if (code->vflag && code->valid)
      {  code->valid = 0;
         delete_value(mpl, code->type, &code->value);
      }
      /* if resultant value is valid, no evaluation is needed */
      if (code->valid)
      {  value = copy_elemset(mpl, code->value.set);
         goto done;
      }
      /* evaluate pseudo-code recursively */
      switch (code->op)
      {  case O_MEMSET:
            /* take member of set */
            {  TUPLE *tuple;
               ARG_LIST *e;
               tuple = create_tuple(mpl);
               for (e = code->arg.set.list; e != NULL; e = e->next)
                  tuple = expand_tuple(mpl, tuple, eval_symbolic(mpl,
                     e->x));
               value = copy_elemset(mpl,
                  eval_member_set(mpl, code->arg.set.set, tuple));
               delete_tuple(mpl, tuple);
            }
            break;
         case O_MAKE:
            /* make elemental set of n-tuples */
            {  ARG_LIST *e;
               value = create_elemset(mpl, code->dim);
               for (e = code->arg.list; e != NULL; e = e->next)
                  check_then_add(mpl, value, eval_tuple(mpl, e->x));
            }
            break;
         case O_UNION:
            /* union of two elemental sets */
            value = set_union(mpl,
               eval_elemset(mpl, code->arg.arg.x),
               eval_elemset(mpl, code->arg.arg.y));
            break;
         case O_DIFF:
            /* difference between two elemental sets */
            value = set_diff(mpl,
               eval_elemset(mpl, code->arg.arg.x),
               eval_elemset(mpl, code->arg.arg.y));
            break;
         case O_SYMDIFF:
            /* symmetric difference between two elemental sets */
            value = set_symdiff(mpl,
               eval_elemset(mpl, code->arg.arg.x),
               eval_elemset(mpl, code->arg.arg.y));
            break;
         case O_INTER:
            /* intersection of two elemental sets */
            value = set_inter(mpl,
               eval_elemset(mpl, code->arg.arg.x),
               eval_elemset(mpl, code->arg.arg.y));
            break;
         case O_CROSS:
            /* cross (Cartesian) product of two elemental sets */
            value = set_cross(mpl,
               eval_elemset(mpl, code->arg.arg.x),
               eval_elemset(mpl, code->arg.arg.y));
            break;
         case O_DOTS:
            /* build "arithmetic" elemental set */
            value = create_arelset(mpl,
               eval_numeric(mpl, code->arg.arg.x),
               eval_numeric(mpl, code->arg.arg.y),
               code->arg.arg.z == NULL ? 1.0 : eval_numeric(mpl,
                  code->arg.arg.z));
            break;
         case O_FORK:
            /* if-then-else */
            if (eval_logical(mpl, code->arg.arg.x))
               value = eval_elemset(mpl, code->arg.arg.y);
            else
               value = eval_elemset(mpl, code->arg.arg.z);
            break;
         case O_SETOF:
            /* compute elemental set */
            {  struct iter_set_info _info, *info = &_info;
               info->code = code;
               info->value = create_elemset(mpl, code->dim);
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_set_func);
               value = info->value;
            }
            break;
         case O_BUILD:
            /* build elemental set identical to domain set */
            {  struct iter_set_info _info, *info = &_info;
               info->code = code;
               info->value = create_elemset(mpl, code->dim);
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_set_func);
               value = info->value;
            }
            break;
         default:
            xassert(code != code);
      }
      /* save resultant value */
      xassert(!code->valid);
      code->valid = 1;
      code->value.set = copy_elemset(mpl, value);
done: return value;
}

/*----------------------------------------------------------------------
-- is_member - check if n-tuple is in set specified by pseudo-code.
--
-- This routine checks if given n-tuple is a member of elemental set
-- specified in the form of pseudo-code (i.e. by expression).
--
-- The n-tuple may have more components that dimension of the elemental
-- set, in which case the extra components are ignored. */

static void null_func(MPL *mpl, void *info)
{     /* this is dummy routine used to enter the domain scope */
      xassert(mpl == mpl);
      xassert(info == NULL);
      return;
}

int is_member(MPL *mpl, CODE *code, TUPLE *tuple)
{     int value;
      xassert(code != NULL);
      xassert(code->type == A_ELEMSET);
      xassert(code->dim > 0);
      xassert(tuple != NULL);
      switch (code->op)
      {  case O_MEMSET:
            /* check if given n-tuple is member of elemental set, which
               is assigned to member of model set */
            {  ARG_LIST *e;
               TUPLE *temp;
               ELEMSET *set;
               /* evaluate reference to elemental set */
               temp = create_tuple(mpl);
               for (e = code->arg.set.list; e != NULL; e = e->next)
                  temp = expand_tuple(mpl, temp, eval_symbolic(mpl,
                     e->x));
               set = eval_member_set(mpl, code->arg.set.set, temp);
               delete_tuple(mpl, temp);
               /* check if the n-tuple is contained in the set array */
               temp = build_subtuple(mpl, tuple, set->dim);
               value = (find_tuple(mpl, set, temp) != NULL);
               delete_tuple(mpl, temp);
            }
            break;
         case O_MAKE:
            /* check if given n-tuple is member of literal set */
            {  ARG_LIST *e;
               TUPLE *temp, *that;
               value = 0;
               temp = build_subtuple(mpl, tuple, code->dim);
               for (e = code->arg.list; e != NULL; e = e->next)
               {  that = eval_tuple(mpl, e->x);
                  value = (compare_tuples(mpl, temp, that) == 0);
                  delete_tuple(mpl, that);
                  if (value) break;
               }
               delete_tuple(mpl, temp);
            }
            break;
         case O_UNION:
            value = is_member(mpl, code->arg.arg.x, tuple) ||
                    is_member(mpl, code->arg.arg.y, tuple);
            break;
         case O_DIFF:
            value = is_member(mpl, code->arg.arg.x, tuple) &&
                   !is_member(mpl, code->arg.arg.y, tuple);
            break;
         case O_SYMDIFF:
            {  int in1 = is_member(mpl, code->arg.arg.x, tuple);
               int in2 = is_member(mpl, code->arg.arg.y, tuple);
               value = (in1 && !in2) || (!in1 && in2);
            }
            break;
         case O_INTER:
            value = is_member(mpl, code->arg.arg.x, tuple) &&
                    is_member(mpl, code->arg.arg.y, tuple);
            break;
         case O_CROSS:
            {  int j;
               value = is_member(mpl, code->arg.arg.x, tuple);
               if (value)
               {  for (j = 1; j <= code->arg.arg.x->dim; j++)
                  {  xassert(tuple != NULL);
                     tuple = tuple->next;
                  }
                  value = is_member(mpl, code->arg.arg.y, tuple);
               }
            }
            break;
         case O_DOTS:
            /* check if given 1-tuple is member of "arithmetic" set */
            {  int j;
               double x, t0, tf, dt;
               xassert(code->dim == 1);
               /* compute "parameters" of the "arithmetic" set */
               t0 = eval_numeric(mpl, code->arg.arg.x);
               tf = eval_numeric(mpl, code->arg.arg.y);
               if (code->arg.arg.z == NULL)
                  dt = 1.0;
               else
                  dt = eval_numeric(mpl, code->arg.arg.z);
               /* make sure the parameters are correct */
               arelset_size(mpl, t0, tf, dt);
               /* if component of 1-tuple is symbolic, not numeric, the
                  1-tuple cannot be member of "arithmetic" set */
               xassert(tuple->sym != NULL);
               if (tuple->sym->str != NULL)
               {  value = 0;
                  break;
               }
               /* determine numeric value of the component */
               x = tuple->sym->num;
               /* if the component value is out of the set range, the
                  1-tuple is not in the set */
               if (dt > 0.0 && !(t0 <= x && x <= tf) ||
                   dt < 0.0 && !(tf <= x && x <= t0))
               {  value = 0;
                  break;
               }
               /* estimate ordinal number of the 1-tuple in the set */
               j = (int)(((x - t0) / dt) + 0.5) + 1;
               /* perform the main check */
               value = (arelset_member(mpl, t0, tf, dt, j) == x);
            }
            break;
         case O_FORK:
            /* check if given n-tuple is member of conditional set */
            if (eval_logical(mpl, code->arg.arg.x))
               value = is_member(mpl, code->arg.arg.y, tuple);
            else
               value = is_member(mpl, code->arg.arg.z, tuple);
            break;
         case O_SETOF:
            /* check if given n-tuple is member of computed set */
            /* it is not clear how to efficiently perform the check not
               computing the entire elemental set :+( */
            mpl_error(mpl, "implementation restriction; in/within setof{} n"
               "ot allowed");
            break;
         case O_BUILD:
            /* check if given n-tuple is member of domain set */
            {  TUPLE *temp;
               temp = build_subtuple(mpl, tuple, code->dim);
               /* try to enter the domain scope; if it is successful,
                  the n-tuple is in the domain set */
               value = (eval_within_domain(mpl, code->arg.loop.domain,
                  temp, NULL, null_func) == 0);
               delete_tuple(mpl, temp);
            }
            break;
         default:
            xassert(code != code);
      }
      return value;
}

/*----------------------------------------------------------------------
-- eval_formula - evaluate pseudo-code to construct linear form.
--
-- This routine evaluates specified pseudo-code to construct resultant
-- linear form, which is returned on exit. */

struct iter_form_info
{     /* working info used by the routine iter_form_func */
      CODE *code;
      /* pseudo-code for iterated operation to be performed */
      FORMULA *value;
      /* resultant value */
      FORMULA *tail;
      /* pointer to the last term */
};

static int iter_form_func(MPL *mpl, void *_info)
{     /* this is auxiliary routine used to perform iterated operation
         on linear form "integrand" within domain scope */
      struct iter_form_info *info = _info;
      switch (info->code->op)
      {  case O_SUM:
            /* summation over domain */
#if 0
            info->value =
               linear_comb(mpl,
                  +1.0, info->value,
                  +1.0, eval_formula(mpl, info->code->arg.loop.x));
#else
            /* the routine linear_comb needs to look through all terms
               of both linear forms to reduce identical terms, so using
               it here is not a good idea (for example, evaluation of
               sum{i in 1..n} x[i] required quadratic time); the better
               idea is to gather all terms of the integrand in one list
               and reduce identical terms only once after all terms of
               the resultant linear form have been evaluated */
            {  FORMULA *form, *term;
               form = eval_formula(mpl, info->code->arg.loop.x);
               if (info->value == NULL)
               {  xassert(info->tail == NULL);
                  info->value = form;
               }
               else
               {  xassert(info->tail != NULL);
                  info->tail->next = form;
               }
               for (term = form; term != NULL; term = term->next)
                  info->tail = term;
            }
#endif
            break;
         default:
            xassert(info != info);
      }
      return 0;
}

FORMULA *eval_formula(MPL *mpl, CODE *code)
{     FORMULA *value;
      xassert(code != NULL);
      xassert(code->type == A_FORMULA);
      xassert(code->dim == 0);
      /* if the operation has a side effect, invalidate and delete the
         resultant value */
      if (code->vflag && code->valid)
      {  code->valid = 0;
         delete_value(mpl, code->type, &code->value);
      }
      /* if resultant value is valid, no evaluation is needed */
      if (code->valid)
      {  value = copy_formula(mpl, code->value.form);
         goto done;
      }
      /* evaluate pseudo-code recursively */
      switch (code->op)
      {  case O_MEMVAR:
            /* take member of variable */
            {  TUPLE *tuple;
               ARG_LIST *e;
               tuple = create_tuple(mpl);
               for (e = code->arg.var.list; e != NULL; e = e->next)
                  tuple = expand_tuple(mpl, tuple, eval_symbolic(mpl,
                     e->x));
               value = single_variable(mpl,
                  eval_member_var(mpl, code->arg.var.var, tuple));
               delete_tuple(mpl, tuple);
            }
            break;
         case O_CVTLFM:
            /* convert to linear form */
            value = constant_term(mpl, eval_numeric(mpl,
               code->arg.arg.x));
            break;
         case O_PLUS:
            /* unary plus */
            value = linear_comb(mpl,
                0.0, constant_term(mpl, 0.0),
               +1.0, eval_formula(mpl, code->arg.arg.x));
            break;
         case O_MINUS:
            /* unary minus */
            value = linear_comb(mpl,
                0.0, constant_term(mpl, 0.0),
               -1.0, eval_formula(mpl, code->arg.arg.x));
            break;
         case O_ADD:
            /* addition */
            value = linear_comb(mpl,
               +1.0, eval_formula(mpl, code->arg.arg.x),
               +1.0, eval_formula(mpl, code->arg.arg.y));
            break;
         case O_SUB:
            /* subtraction */
            value = linear_comb(mpl,
               +1.0, eval_formula(mpl, code->arg.arg.x),
               -1.0, eval_formula(mpl, code->arg.arg.y));
            break;
         case O_MUL:
            /* multiplication */
            xassert(code->arg.arg.x != NULL);
            xassert(code->arg.arg.y != NULL);
            if (code->arg.arg.x->type == A_NUMERIC)
            {  xassert(code->arg.arg.y->type == A_FORMULA);
               value = linear_comb(mpl,
                  eval_numeric(mpl, code->arg.arg.x),
                  eval_formula(mpl, code->arg.arg.y),
                  0.0, constant_term(mpl, 0.0));
            }
            else
            {  xassert(code->arg.arg.x->type == A_FORMULA);
               xassert(code->arg.arg.y->type == A_NUMERIC);
               value = linear_comb(mpl,
                  eval_numeric(mpl, code->arg.arg.y),
                  eval_formula(mpl, code->arg.arg.x),
                  0.0, constant_term(mpl, 0.0));
            }
            break;
         case O_DIV:
            /* division */
            value = linear_comb(mpl,
               fp_div(mpl, 1.0, eval_numeric(mpl, code->arg.arg.y)),
               eval_formula(mpl, code->arg.arg.x),
               0.0, constant_term(mpl, 0.0));
            break;
         case O_FORK:
            /* if-then-else */
            if (eval_logical(mpl, code->arg.arg.x))
               value = eval_formula(mpl, code->arg.arg.y);
            else if (code->arg.arg.z == NULL)
               value = constant_term(mpl, 0.0);
            else
               value = eval_formula(mpl, code->arg.arg.z);
            break;
         case O_SUM:
            /* summation over domain */
            {  struct iter_form_info _info, *info = &_info;
               info->code = code;
               info->value = constant_term(mpl, 0.0);
               info->tail = NULL;
               loop_within_domain(mpl, code->arg.loop.domain, info,
                  iter_form_func);
               value = reduce_terms(mpl, info->value);
            }
            break;
         default:
            xassert(code != code);
      }
      /* save resultant value */
      xassert(!code->valid);
      code->valid = 1;
      code->value.form = copy_formula(mpl, value);
done: return value;
}

/*----------------------------------------------------------------------
-- clean_code - clean pseudo-code.
--
-- This routine recursively cleans specified pseudo-code that assumes
-- deleting all temporary resultant values. */

void clean_code(MPL *mpl, CODE *code)
{     ARG_LIST *e;
      /* if no pseudo-code is specified, do nothing */
      if (code == NULL) goto done;
      /* if resultant value is valid (exists), delete it */
      if (code->valid)
      {  code->valid = 0;
         delete_value(mpl, code->type, &code->value);
      }
      /* recursively clean pseudo-code for operands */
      switch (code->op)
      {  case O_NUMBER:
         case O_STRING:
         case O_INDEX:
            break;
         case O_MEMNUM:
         case O_MEMSYM:
            for (e = code->arg.par.list; e != NULL; e = e->next)
               clean_code(mpl, e->x);
            break;
         case O_MEMSET:
            for (e = code->arg.set.list; e != NULL; e = e->next)
               clean_code(mpl, e->x);
            break;
         case O_MEMVAR:
            for (e = code->arg.var.list; e != NULL; e = e->next)
               clean_code(mpl, e->x);
            break;
         case O_TUPLE:
         case O_MAKE:
            for (e = code->arg.list; e != NULL; e = e->next)
               clean_code(mpl, e->x);
            break;
         case O_SLICE:
            xassert(code != code);
         case O_IRAND224:
         case O_UNIFORM01:
         case O_NORMAL01:
            break;
         case O_CVTNUM:
         case O_CVTSYM:
         case O_CVTLOG:
         case O_CVTTUP:
         case O_CVTLFM:
         case O_PLUS:
         case O_MINUS:
         case O_NOT:
         case O_ABS:
         case O_CEIL:
         case O_FLOOR:
         case O_EXP:
         case O_LOG:
         case O_LOG10:
         case O_SQRT:
         case O_SIN:
         case O_COS:
         case O_ATAN:
         case O_ROUND:
         case O_TRUNC:
#if 1 /* 15/VII-2006 */
         case O_CARD:
         case O_LENGTH:
#endif
            /* unary operation */
            clean_code(mpl, code->arg.arg.x);
            break;
         case O_ADD:
         case O_SUB:
         case O_LESS:
         case O_MUL:
         case O_DIV:
         case O_IDIV:
         case O_MOD:
         case O_POWER:
         case O_ATAN2:
         case O_ROUND2:
         case O_TRUNC2:
         case O_UNIFORM:
         case O_NORMAL:
         case O_CONCAT:
         case O_LT:
         case O_LE:
         case O_EQ:
         case O_GE:
         case O_GT:
         case O_NE:
         case O_AND:
         case O_OR:
         case O_UNION:
         case O_DIFF:
         case O_SYMDIFF:
         case O_INTER:
         case O_CROSS:
         case O_IN:
         case O_NOTIN:
         case O_WITHIN:
         case O_NOTWITHIN:
#if 1 /* 15/VII-2006 */
         case O_SUBSTR:
#endif
            /* binary operation */
            clean_code(mpl, code->arg.arg.x);
            clean_code(mpl, code->arg.arg.y);
            break;
         case O_DOTS:
         case O_FORK:
#if 1 /* 15/VII-2006 */
         case O_SUBSTR3:
#endif
            /* ternary operation */
            clean_code(mpl, code->arg.arg.x);
            clean_code(mpl, code->arg.arg.y);
            clean_code(mpl, code->arg.arg.z);
            break;
         case O_MIN:
         case O_MAX:
            /* n-ary operation */
            for (e = code->arg.list; e != NULL; e = e->next)
               clean_code(mpl, e->x);
            break;
         case O_SUM:
         case O_PROD:
         case O_MINIMUM:
         case O_MAXIMUM:
         case O_FORALL:
         case O_EXISTS:
         case O_SETOF:
         case O_BUILD:
            /* iterated operation */
            clean_domain(mpl, code->arg.loop.domain);
            clean_code(mpl, code->arg.loop.x);
            break;
         default:
            xassert(code->op != code->op);
      }
done: return;
}

#if 1 /* 11/II-2008 */
/**********************************************************************/
/* * *                        DATA TABLES                         * * */
/**********************************************************************/

int mpl_tab_num_args(TABDCA *dca)
{     /* returns the number of arguments */
      return dca->na;
}

const char *mpl_tab_get_arg(TABDCA *dca, int k)
{     /* returns pointer to k-th argument */
      xassert(1 <= k && k <= dca->na);
      return dca->arg[k];
}

int mpl_tab_num_flds(TABDCA *dca)
{     /* returns the number of fields */
      return dca->nf;
}

const char *mpl_tab_get_name(TABDCA *dca, int k)
{     /* returns pointer to name of k-th field */
      xassert(1 <= k && k <= dca->nf);
      return dca->name[k];
}

int mpl_tab_get_type(TABDCA *dca, int k)
{     /* returns type of k-th field */
      xassert(1 <= k && k <= dca->nf);
      return dca->type[k];
}

double mpl_tab_get_num(TABDCA *dca, int k)
{     /* returns numeric value of k-th field */
      xassert(1 <= k && k <= dca->nf);
      xassert(dca->type[k] == 'N');
      return dca->num[k];
}

const char *mpl_tab_get_str(TABDCA *dca, int k)
{     /* returns pointer to string value of k-th field */
      xassert(1 <= k && k <= dca->nf);
      xassert(dca->type[k] == 'S');
      xassert(dca->str[k] != NULL);
      return dca->str[k];
}

void mpl_tab_set_num(TABDCA *dca, int k, double num)
{     /* assign numeric value to k-th field */
      xassert(1 <= k && k <= dca->nf);
      xassert(dca->type[k] == '?');
      dca->type[k] = 'N';
      dca->num[k] = num;
      return;
}

void mpl_tab_set_str(TABDCA *dca, int k, const char *str)
{     /* assign string value to k-th field */
      xassert(1 <= k && k <= dca->nf);
      xassert(dca->type[k] == '?');
      xassert(strlen(str) <= MAX_LENGTH);
      xassert(dca->str[k] != NULL);
      dca->type[k] = 'S';
      strcpy(dca->str[k], str);
      return;
}

static int write_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      TABLE *tab = info;
      TABDCA *dca = mpl->dca;
      TABOUT *out;
      SYMBOL *sym;
      int k;
      char buf[MAX_LENGTH+1];
      /* evaluate field values */
      k = 0;
      for (out = tab->u.out.list; out != NULL; out = out->next)
      {  k++;
         switch (out->code->type)
         {  case A_NUMERIC:
               dca->type[k] = 'N';
               dca->num[k] = eval_numeric(mpl, out->code);
               dca->str[k][0] = '\0';
               break;
            case A_SYMBOLIC:
               sym = eval_symbolic(mpl, out->code);
               if (sym->str == NULL)
               {  dca->type[k] = 'N';
                  dca->num[k] = sym->num;
                  dca->str[k][0] = '\0';
               }
               else
               {  dca->type[k] = 'S';
                  dca->num[k] = 0.0;
                  fetch_string(mpl, sym->str, buf);
                  strcpy(dca->str[k], buf);
               }
               delete_symbol(mpl, sym);
               break;
            default:
               xassert(out != out);
         }
      }
      /* write record to output table */
      mpl_tab_drv_write(mpl);
      return 0;
}

void execute_table(MPL *mpl, TABLE *tab)
{     /* execute table statement */
      TABARG *arg;
      TABFLD *fld;
      TABIN *in;
      TABOUT *out;
      TABDCA *dca;
      SET *set;
      int k;
      char buf[MAX_LENGTH+1];
      /* allocate table driver communication area */
      xassert(mpl->dca == NULL);
      mpl->dca = dca = xmalloc(sizeof(TABDCA));
      dca->id = 0;
      dca->link = NULL;
      dca->na = 0;
      dca->arg = NULL;
      dca->nf = 0;
      dca->name = NULL;
      dca->type = NULL;
      dca->num = NULL;
      dca->str = NULL;
      /* allocate arguments */
      xassert(dca->na == 0);
      for (arg = tab->arg; arg != NULL; arg = arg->next)
         dca->na++;
      dca->arg = xcalloc(1+dca->na, sizeof(char *));
      /* evaluate argument values */
      k = 0;
      for (arg = tab->arg; arg != NULL; arg = arg->next)
      {  SYMBOL *sym;
         k++;
         xassert(arg->code->type == A_SYMBOLIC);
         sym = eval_symbolic(mpl, arg->code);
         if (sym->str == NULL)
            sprintf(buf, "%.*g", DBL_DIG, sym->num);
         else
            fetch_string(mpl, sym->str, buf);
         delete_symbol(mpl, sym);
         dca->arg[k] = xmalloc(strlen(buf)+1);
         strcpy(dca->arg[k], buf);
      }
      /* perform table input/output */
      switch (tab->type)
      {  case A_INPUT:  goto read_table;
         case A_OUTPUT: goto write_table;
         default:       xassert(tab != tab);
      }
read_table:
      /* read data from input table */
      /* add the only member to the control set and assign it empty
         elemental set */
      set = tab->u.in.set;
      if (set != NULL)
      {  if (set->data)
            mpl_error(mpl, "%s already provided with data", set->name);
         xassert(set->array->head == NULL);
         add_member(mpl, set->array, NULL)->value.set =
            create_elemset(mpl, set->dimen);
         set->data = 1;
      }
      /* check parameters specified in the input list */
      for (in = tab->u.in.list; in != NULL; in = in->next)
      {  if (in->par->data)
            mpl_error(mpl, "%s already provided with data", in->par->name);
         in->par->data = 1;
      }
      /* allocate and initialize fields */
      xassert(dca->nf == 0);
      for (fld = tab->u.in.fld; fld != NULL; fld = fld->next)
         dca->nf++;
      for (in = tab->u.in.list; in != NULL; in = in->next)
         dca->nf++;
      dca->name = xcalloc(1+dca->nf, sizeof(char *));
      dca->type = xcalloc(1+dca->nf, sizeof(int));
      dca->num = xcalloc(1+dca->nf, sizeof(double));
      dca->str = xcalloc(1+dca->nf, sizeof(char *));
      k = 0;
      for (fld = tab->u.in.fld; fld != NULL; fld = fld->next)
      {  k++;
         dca->name[k] = fld->name;
         dca->type[k] = '?';
         dca->num[k] = 0.0;
         dca->str[k] = xmalloc(MAX_LENGTH+1);
         dca->str[k][0] = '\0';
      }
      for (in = tab->u.in.list; in != NULL; in = in->next)
      {  k++;
         dca->name[k] = in->name;
         dca->type[k] = '?';
         dca->num[k] = 0.0;
         dca->str[k] = xmalloc(MAX_LENGTH+1);
         dca->str[k][0] = '\0';
      }
      /* open input table */
      mpl_tab_drv_open(mpl, 'R');
      /* read and process records */
      for (;;)
      {  TUPLE *tup;
         /* reset field types */
         for (k = 1; k <= dca->nf; k++)
            dca->type[k] = '?';
         /* read next record */
         if (mpl_tab_drv_read(mpl)) break;
         /* all fields must be set by the driver */
         for (k = 1; k <= dca->nf; k++)
         {  if (dca->type[k] == '?')
               mpl_error(mpl, "field %s missing in input table",
                  dca->name[k]);
         }
         /* construct n-tuple */
         tup = create_tuple(mpl);
         k = 0;
         for (fld = tab->u.in.fld; fld != NULL; fld = fld->next)
         {  k++;
            xassert(k <= dca->nf);
            switch (dca->type[k])
            {  case 'N':
                  tup = expand_tuple(mpl, tup, create_symbol_num(mpl,
                     dca->num[k]));
                  break;
               case 'S':
                  xassert(strlen(dca->str[k]) <= MAX_LENGTH);
                  tup = expand_tuple(mpl, tup, create_symbol_str(mpl,
                     create_string(mpl, dca->str[k])));
                  break;
               default:
                  xassert(dca != dca);
            }
         }
         /* add n-tuple just read to the control set */
         if (tab->u.in.set != NULL)
            check_then_add(mpl, tab->u.in.set->array->head->value.set,
               copy_tuple(mpl, tup));
         /* assign values to the parameters in the input list */
         for (in = tab->u.in.list; in != NULL; in = in->next)
         {  MEMBER *memb;
            k++;
            xassert(k <= dca->nf);
            /* there must be no member with the same n-tuple */
            if (find_member(mpl, in->par->array, tup) != NULL)
               mpl_error(mpl, "%s%s already defined", in->par->name,
               format_tuple(mpl, '[', tup));
            /* create new parameter member with given n-tuple */
            memb = add_member(mpl, in->par->array, copy_tuple(mpl, tup))
               ;
            /* assign value to the parameter member */
            switch (in->par->type)
            {  case A_NUMERIC:
               case A_INTEGER:
               case A_BINARY:
                  if (dca->type[k] != 'N')
                     mpl_error(mpl, "%s requires numeric data",
                        in->par->name);
                  memb->value.num = dca->num[k];
                  break;
               case A_SYMBOLIC:
                  switch (dca->type[k])
                  {  case 'N':
                        memb->value.sym = create_symbol_num(mpl,
                           dca->num[k]);
                        break;
                     case 'S':
                        xassert(strlen(dca->str[k]) <= MAX_LENGTH);
                        memb->value.sym = create_symbol_str(mpl,
                           create_string(mpl,dca->str[k]));
                        break;
                     default:
                        xassert(dca != dca);
                  }
                  break;
               default:
                  xassert(in != in);
            }
         }
         /* n-tuple is no more needed */
         delete_tuple(mpl, tup);
      }
      /* close input table */
      mpl_tab_drv_close(mpl);
      goto done;
write_table:
      /* write data to output table */
      /* allocate and initialize fields */
      xassert(dca->nf == 0);
      for (out = tab->u.out.list; out != NULL; out = out->next)
         dca->nf++;
      dca->name = xcalloc(1+dca->nf, sizeof(char *));
      dca->type = xcalloc(1+dca->nf, sizeof(int));
      dca->num = xcalloc(1+dca->nf, sizeof(double));
      dca->str = xcalloc(1+dca->nf, sizeof(char *));
      k = 0;
      for (out = tab->u.out.list; out != NULL; out = out->next)
      {  k++;
         dca->name[k] = out->name;
         dca->type[k] = '?';
         dca->num[k] = 0.0;
         dca->str[k] = xmalloc(MAX_LENGTH+1);
         dca->str[k][0] = '\0';
      }
      /* open output table */
      mpl_tab_drv_open(mpl, 'W');
      /* evaluate fields and write records */
      loop_within_domain(mpl, tab->u.out.domain, tab, write_func);
      /* close output table */
      mpl_tab_drv_close(mpl);
done: /* free table driver communication area */
      free_dca(mpl);
      return;
}

void free_dca(MPL *mpl)
{     /* free table driver communucation area */
      TABDCA *dca = mpl->dca;
      int k;
      if (dca != NULL)
      {  if (dca->link != NULL)
            mpl_tab_drv_close(mpl);
         if (dca->arg != NULL)
         {  for (k = 1; k <= dca->na; k++)
               xfree(dca->arg[k]);
            xfree(dca->arg);
         }
         if (dca->name != NULL) xfree(dca->name);
         if (dca->type != NULL) xfree(dca->type);
         if (dca->num != NULL) xfree(dca->num);
         if (dca->str != NULL)
         {  for (k = 1; k <= dca->nf; k++)
               xfree(dca->str[k]);
            xfree(dca->str);
         }
         xfree(dca), mpl->dca = NULL;
      }
      return;
}

void clean_table(MPL *mpl, TABLE *tab)
{     /* clean table statement */
      TABARG *arg;
      TABOUT *out;
      /* clean string list */
      for (arg = tab->arg; arg != NULL; arg = arg->next)
         clean_code(mpl, arg->code);
      switch (tab->type)
      {  case A_INPUT:
            break;
         case A_OUTPUT:
            /* clean subscript domain */
            clean_domain(mpl, tab->u.out.domain);
            /* clean output list */
            for (out = tab->u.out.list; out != NULL; out = out->next)
               clean_code(mpl, out->code);
            break;
         default:
            xassert(tab != tab);
      }
      return;
}
#endif

/**********************************************************************/
/* * *                      MODEL STATEMENTS                      * * */
/**********************************************************************/

/*----------------------------------------------------------------------
-- execute_check - execute check statement.
--
-- This routine executes specified check statement. */

static int check_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      CHECK *chk = (CHECK *)info;
      if (!eval_logical(mpl, chk->code))
         mpl_error(mpl, "check%s failed", format_tuple(mpl, '[',
            get_domain_tuple(mpl, chk->domain)));
      return 0;
}

void execute_check(MPL *mpl, CHECK *chk)
{     loop_within_domain(mpl, chk->domain, chk, check_func);
      return;
}

/*----------------------------------------------------------------------
-- clean_check - clean check statement.
--
-- This routine cleans specified check statement that assumes deleting
-- all stuff dynamically allocated on generating/postsolving phase. */

void clean_check(MPL *mpl, CHECK *chk)
{     /* clean subscript domain */
      clean_domain(mpl, chk->domain);
      /* clean pseudo-code for computing predicate */
      clean_code(mpl, chk->code);
      return;
}

/*----------------------------------------------------------------------
-- execute_display - execute display statement.
--
-- This routine executes specified display statement. */

static void display_set(MPL *mpl, SET *set, MEMBER *memb)
{     /* display member of model set */
      ELEMSET *s = memb->value.set;
      MEMBER *m;
      write_text(mpl, "%s%s%s\n", set->name,
         format_tuple(mpl, '[', memb->tuple),
         s->head == NULL ? " is empty" : ":");
      for (m = s->head; m != NULL; m = m->next)
         write_text(mpl, "   %s\n", format_tuple(mpl, '(', m->tuple));
      return;
}

static void display_par(MPL *mpl, PARAMETER *par, MEMBER *memb)
{     /* display member of model parameter */
      switch (par->type)
      {  case A_NUMERIC:
         case A_INTEGER:
         case A_BINARY:
            write_text(mpl, "%s%s = %.*g\n", par->name,
               format_tuple(mpl, '[', memb->tuple),
               DBL_DIG, memb->value.num);
            break;
         case A_SYMBOLIC:
            write_text(mpl, "%s%s = %s\n", par->name,
               format_tuple(mpl, '[', memb->tuple),
               format_symbol(mpl, memb->value.sym));
            break;
         default:
            xassert(par != par);
      }
      return;
}

static void display_var(MPL *mpl, VARIABLE *var, MEMBER *memb)
{     /* display member of model variable */
      if (mpl->flag_p)
         write_text(mpl, "%s%s = %.*g\n",
            var->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.var->value);
      else if (var->lbnd == NULL && var->ubnd == NULL)
         write_text(mpl, "%s%s\n",
            var->name, format_tuple(mpl, '[', memb->tuple));
      else if (var->ubnd == NULL)
         write_text(mpl, "%s%s >= %.*g\n",
            var->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.var->lbnd);
      else if (var->lbnd == NULL)
         write_text(mpl, "%s%s <= %.*g\n",
            var->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.var->ubnd);
      else if (var->lbnd == var->ubnd)
         write_text(mpl, "%s%s = %.*g\n",
            var->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.var->lbnd);
      else
         write_text(mpl, "%.*g <= %s%s <= %.*g\n",
            DBL_DIG, memb->value.var->lbnd,
            var->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.var->ubnd);
      return;
}

static void display_con(MPL *mpl, CONSTRAINT *con, MEMBER *memb)
{     /* display member of model constraint */
      FORMULA *term;
      if (con->lbnd == NULL && con->ubnd == NULL)
         write_text(mpl, "%s%s:\n",
            con->name, format_tuple(mpl, '[', memb->tuple));
      else if (con->ubnd == NULL)
         write_text(mpl, "%s%s >= %.*g:\n",
            con->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.con->lbnd);
      else if (con->lbnd == NULL)
         write_text(mpl, "%s%s <= %.*g:\n",
            con->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.con->ubnd);
      else if (con->lbnd == con->ubnd)
         write_text(mpl, "%s%s = %.*g:\n",
            con->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.con->lbnd);
      else
         write_text(mpl, "%.*g <= %s%s <= %.*g:\n",
            DBL_DIG, memb->value.con->lbnd,
            con->name, format_tuple(mpl, '[', memb->tuple),
            DBL_DIG, memb->value.con->ubnd);
      if ((con->type == A_MINIMIZE || con->type == A_MAXIMIZE) &&
         memb->value.con->lbnd != 0)
         write_text(mpl, "   %.*g\n", DBL_DIG, -memb->value.con->lbnd);
      else if (memb->value.con->form == NULL)
         write_text(mpl, "   empty linear form\n");
      for (term = memb->value.con->form; term != NULL; term =
         term->next)
      {  xassert(term->var != NULL);
         write_text(mpl, "   %.*g %s%s\n", DBL_DIG, term->coef,
            term->var->var->name, format_tuple(mpl, '[',
            term->var->memb->tuple));
      }
      return;
}

static void display_memb(MPL *mpl, CODE *code)
{     /* display member specified by pseudo-code */
      MEMBER memb;
      ARG_LIST *e;
      xassert(code->op == O_MEMNUM || code->op == O_MEMSYM ||
             code->op == O_MEMSET || code->op == O_MEMVAR);
      memb.tuple = create_tuple(mpl);
      for (e = code->arg.par.list; e != NULL; e = e->next)
         memb.tuple = expand_tuple(mpl, memb.tuple, eval_symbolic(mpl,
            e->x));
      switch (code->op)
      {  case O_MEMNUM:
            memb.value.num = eval_member_num(mpl, code->arg.par.par,
               memb.tuple);
            display_par(mpl, code->arg.par.par, &memb);
            break;
         case O_MEMSYM:
            memb.value.sym = eval_member_sym(mpl, code->arg.par.par,
               memb.tuple);
            display_par(mpl, code->arg.par.par, &memb);
            delete_symbol(mpl, memb.value.sym);
            break;
         case O_MEMSET:
            memb.value.set = eval_member_set(mpl, code->arg.set.set,
               memb.tuple);
            display_set(mpl, code->arg.set.set, &memb);
            break;
         case O_MEMVAR:
            memb.value.var = eval_member_var(mpl, code->arg.var.var,
               memb.tuple);
            display_var(mpl, code->arg.var.var, &memb);
            break;
         default:
            xassert(code != code);
      }
      delete_tuple(mpl, memb.tuple);
      return;
}

static void display_code(MPL *mpl, CODE *code)
{     /* display value of expression */
      switch (code->type)
      {  case A_NUMERIC:
            /* numeric value */
            {  double num;
               num = eval_numeric(mpl, code);
               write_text(mpl, "%.*g\n", DBL_DIG, num);
            }
            break;
         case A_SYMBOLIC:
            /* symbolic value */
            {  SYMBOL *sym;
               sym = eval_symbolic(mpl, code);
               write_text(mpl, "%s\n", format_symbol(mpl, sym));
               delete_symbol(mpl, sym);
            }
            break;
         case A_LOGICAL:
            /* logical value */
            {  int bit;
               bit = eval_logical(mpl, code);
               write_text(mpl, "%s\n", bit ? "true" : "false");
            }
            break;
         case A_TUPLE:
            /* n-tuple */
            {  TUPLE *tuple;
               tuple = eval_tuple(mpl, code);
               write_text(mpl, "%s\n", format_tuple(mpl, '(', tuple));
               delete_tuple(mpl, tuple);
            }
            break;
         case A_ELEMSET:
            /* elemental set */
            {  ELEMSET *set;
               MEMBER *memb;
               set = eval_elemset(mpl, code);
               if (set->head == 0)
                  write_text(mpl, "set is empty\n");
               for (memb = set->head; memb != NULL; memb = memb->next)
                  write_text(mpl, "   %s\n", format_tuple(mpl, '(',
                     memb->tuple));
               delete_elemset(mpl, set);
            }
            break;
         case A_FORMULA:
            /* linear form */
            {  FORMULA *form, *term;
               form = eval_formula(mpl, code);
               if (form == NULL)
                  write_text(mpl, "linear form is empty\n");
               for (term = form; term != NULL; term = term->next)
               {  if (term->var == NULL)
                     write_text(mpl, "   %.*g\n", term->coef);
                  else
                     write_text(mpl, "   %.*g %s%s\n", DBL_DIG,
                        term->coef, term->var->var->name,
                        format_tuple(mpl, '[', term->var->memb->tuple));
               }
               delete_formula(mpl, form);
            }
            break;
         default:
            xassert(code != code);
      }
      return;
}

static int display_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      DISPLAY *dpy = (DISPLAY *)info;
      DISPLAY1 *entry;
      for (entry = dpy->list; entry != NULL; entry = entry->next)
      {  if (entry->type == A_INDEX)
         {  /* dummy index */
            DOMAIN_SLOT *slot = entry->u.slot;
            write_text(mpl, "%s = %s\n", slot->name,
            format_symbol(mpl, slot->value));
         }
         else if (entry->type == A_SET)
         {  /* model set */
            SET *set = entry->u.set;
            MEMBER *memb;
            if (set->assign != NULL)
            {  /* the set has assignment expression; evaluate all its
                  members over entire domain */
               eval_whole_set(mpl, set);
            }
            else
            {  /* the set has no assignment expression; refer to its
                  any existing member ignoring resultant value to check
                  the data provided the data section */
               if (set->array->head != NULL)
                  eval_member_set(mpl, set, set->array->head->tuple);
            }
            /* display all members of the set array */
            if (set->array->head == NULL)
               write_text(mpl, "%s has empty content\n", set->name);
            for (memb = set->array->head; memb != NULL; memb =
               memb->next) display_set(mpl, set, memb);
         }
         else if (entry->type == A_PARAMETER)
         {  /* model parameter */
            PARAMETER *par = entry->u.par;
            MEMBER *memb;
            if (par->assign != NULL)
            {  /* the parameter has an assignment expression; evaluate
                  all its member over entire domain */
               eval_whole_par(mpl, par);
            }
            else
            {  /* the parameter has no assignment expression; refer to
                  its any existing member ignoring resultant value to
                  check the data provided in the data section */
               if (par->array->head != NULL)
               {  if (par->type != A_SYMBOLIC)
                     eval_member_num(mpl, par, par->array->head->tuple);
                  else
                     delete_symbol(mpl, eval_member_sym(mpl, par,
                        par->array->head->tuple));
               }
            }
            /* display all members of the parameter array */
            if (par->array->head == NULL)
               write_text(mpl, "%s has empty content\n", par->name);
            for (memb = par->array->head; memb != NULL; memb =
               memb->next) display_par(mpl, par, memb);
         }
         else if (entry->type == A_VARIABLE)
         {  /* model variable */
            VARIABLE *var = entry->u.var;
            MEMBER *memb;
            /* display all members of the variable array */
            if (var->array->head == NULL)
               write_text(mpl, "%s has empty content\n", var->name);
            for (memb = var->array->head; memb != NULL; memb =
               memb->next) display_var(mpl, var, memb);
         }
         else if (entry->type == A_CONSTRAINT)
         {  /* model constraint */
            CONSTRAINT *con = entry->u.con;
            if (entry->list == NULL)
            {  /* display the whole constraint */
               MEMBER *memb;
               eval_whole_con(mpl, con);
               if (con->array->head == NULL)
                  write_text(mpl, "%s has empty content\n", con->name);
               for (memb = con->array->head; memb != NULL; memb =
                  memb->next) display_con(mpl, con, memb);
            }
            else
            {  /* display the constraint member */
               TUPLE *tuple;
               ARG_LIST *e;
               ELEMCON *c;
               tuple = create_tuple(mpl);
               for (e = entry->list; e != NULL; e = e->next)
                  tuple = expand_tuple(mpl, tuple, eval_symbolic(mpl,
                     e->x));
               c = eval_member_con(mpl, con, tuple);
               delete_tuple(mpl, tuple);
               display_con(mpl, con, c->memb);
            }
         }
         else if (entry->type == A_EXPRESSION)
         {  /* expression */
            CODE *code = entry->u.code;
            if (code->op == O_MEMNUM || code->op == O_MEMSYM ||
                code->op == O_MEMSET || code->op == O_MEMVAR)
               display_memb(mpl, code);
            else
               display_code(mpl, code);
         }
         else
            xassert(entry != entry);
      }
      return 0;
}

void execute_display(MPL *mpl, DISPLAY *dpy)
{     loop_within_domain(mpl, dpy->domain, dpy, display_func);
      return;
}

/*----------------------------------------------------------------------
-- clean_display - clean display statement.
--
-- This routine cleans specified display statement that assumes deleting
-- all stuff dynamically allocated on generating/postsolving phase. */

void clean_display(MPL *mpl, DISPLAY *dpy)
{     DISPLAY1 *d;
      ARG_LIST *e;
      /* clean subscript domain */
      clean_domain(mpl, dpy->domain);
      /* clean display list */
      for (d = dpy->list; d != NULL; d = d->next)
      {  /* clean pseudo-code for computing expression */
         if (d->type == A_EXPRESSION)
            clean_code(mpl, d->u.code);
         /* clean pseudo-code for computing subscripts */
         for (e = d->list; e != NULL; e = e->next)
            clean_code(mpl, e->x);
      }
      return;
}

/*----------------------------------------------------------------------
-- execute_printf - execute printf statement.
--
-- This routine executes specified printf statement. */

#if 1 /* 14/VII-2006 */
static void print_char(MPL *mpl, int c)
{     if (mpl->prt_fp == NULL)
         write_char(mpl, c);
      else
         fputc(c, mpl->prt_fp);
      return;
}

static void print_text(MPL *mpl, char *fmt, ...)
{     va_list arg;
      char buf[OUTBUF_SIZE], *c;
      va_start(arg, fmt);
      vsprintf(buf, fmt, arg);
      xassert(strlen(buf) < sizeof(buf));
      va_end(arg);
      for (c = buf; *c != '\0'; c++) print_char(mpl, *c);
      return;
}
#endif

static int printf_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      PRINTF *prt = (PRINTF *)info;
      PRINTF1 *entry;
      SYMBOL *sym;
      char fmt[MAX_LENGTH+1], *c, *from, save;
      /* evaluate format control string */
      sym = eval_symbolic(mpl, prt->fmt);
      if (sym->str == NULL)
         sprintf(fmt, "%.*g", DBL_DIG, sym->num);
      else
         fetch_string(mpl, sym->str, fmt);
      delete_symbol(mpl, sym);
      /* scan format control string and perform formatting output */
      entry = prt->list;
      for (c = fmt; *c != '\0'; c++)
      {  if (*c == '%')
         {  /* scan format specifier */
            from = c++;
            if (*c == '%')
            {  print_char(mpl, '%');
               continue;
            }
            if (entry == NULL) break;
            /* scan optional flags */
            while (*c == '-' || *c == '+' || *c == ' ' || *c == '#' ||
                   *c == '0') c++;
            /* scan optional minimum field width */
            while (isdigit((unsigned char)*c)) c++;
            /* scan optional precision */
            if (*c == '.')
            {  c++;
               while (isdigit((unsigned char)*c)) c++;
            }
            /* scan conversion specifier and perform formatting */
            save = *(c+1), *(c+1) = '\0';
            if (*c == 'd' || *c == 'i' || *c == 'e' || *c == 'E' ||
                *c == 'f' || *c == 'F' || *c == 'g' || *c == 'G')
            {  /* the specifier requires numeric value */
               double value;
               xassert(entry != NULL);
               switch (entry->code->type)
               {  case A_NUMERIC:
                     value = eval_numeric(mpl, entry->code);
                     break;
                  case A_SYMBOLIC:
                     sym = eval_symbolic(mpl, entry->code);
                     if (sym->str != NULL)
                        mpl_error(mpl, "cannot convert %s to floating-point"
                           " number", format_symbol(mpl, sym));
                     value = sym->num;
                     delete_symbol(mpl, sym);
                     break;
                  case A_LOGICAL:
                     if (eval_logical(mpl, entry->code))
                        value = 1.0;
                     else
                        value = 0.0;
                     break;
                  default:
                     xassert(entry != entry);
               }
               if (*c == 'd' || *c == 'i')
               {  double int_max = (double)INT_MAX;
                  if (!(-int_max <= value && value <= +int_max))
                     mpl_error(mpl, "cannot convert %.*g to integer",
                        DBL_DIG, value);
                  print_text(mpl, from, (int)floor(value + 0.5));
               }
               else
                  print_text(mpl, from, value);
            }
            else if (*c == 's')
            {  /* the specifier requires symbolic value */
               char value[MAX_LENGTH+1];
               switch (entry->code->type)
               {  case A_NUMERIC:
                     sprintf(value, "%.*g", DBL_DIG, eval_numeric(mpl,
                        entry->code));
                     break;
                  case A_LOGICAL:
                     if (eval_logical(mpl, entry->code))
                        strcpy(value, "T");
                     else
                        strcpy(value, "F");
                     break;
                  case A_SYMBOLIC:
                     sym = eval_symbolic(mpl, entry->code);
                     if (sym->str == NULL)
                        sprintf(value, "%.*g", DBL_DIG, sym->num);
                     else
                        fetch_string(mpl, sym->str, value);
                     delete_symbol(mpl, sym);
                     break;
                  default:
                     xassert(entry != entry);
               }
               print_text(mpl, from, value);
            }
            else
               mpl_error(mpl, "format specifier missing or invalid");
            *(c+1) = save;
            entry = entry->next;
         }
         else if (*c == '\\')
         {  /* write some control character */
            c++;
            if (*c == 't')
               print_char(mpl, '\t');
            else if (*c == 'n')
               print_char(mpl, '\n');
            else
               print_char(mpl, *c);
         }
         else
         {  /* write character without formatting */
            print_char(mpl, *c);
         }
      }
      return 0;
}

#if 0 /* 14/VII-2006 */
void execute_printf(MPL *mpl, PRINTF *prt)
{     loop_within_domain(mpl, prt->domain, prt, printf_func);
      return;
}
#else
void execute_printf(MPL *mpl, PRINTF *prt)
{     if (prt->fname == NULL)
      {  /* switch to the standard output */
         if (mpl->prt_fp != NULL)
         {  fclose(mpl->prt_fp), mpl->prt_fp = NULL;
            xfree(mpl->prt_file), mpl->prt_file = NULL;
         }
      }
      else
      {  /* evaluate file name string */
         SYMBOL *sym;
         char fname[MAX_LENGTH+1];
         sym = eval_symbolic(mpl, prt->fname);
         if (sym->str == NULL)
            sprintf(fname, "%.*g", DBL_DIG, sym->num);
         else
            fetch_string(mpl, sym->str, fname);
         delete_symbol(mpl, sym);
         /* close the current print file, if necessary */
         if (mpl->prt_fp != NULL &&
            (!prt->app || strcmp(mpl->prt_file, fname) != 0))
         {  fclose(mpl->prt_fp), mpl->prt_fp = NULL;
            xfree(mpl->prt_file), mpl->prt_file = NULL;
         }
         /* open the specified print file, if necessary */
         if (mpl->prt_fp == NULL)
         {  mpl->prt_fp = fopen(fname, prt->app ? "a" : "w");
            if (mpl->prt_fp == NULL)
               mpl_error(mpl, "unable to open `%s' for writing - %s",
                  fname, strerror(errno));
            mpl->prt_file = xmalloc(strlen(fname)+1);
            strcpy(mpl->prt_file, fname);
         }
      }
      loop_within_domain(mpl, prt->domain, prt, printf_func);
      if (mpl->prt_fp != NULL)
      {  fflush(mpl->prt_fp);
         if (ferror(mpl->prt_fp))
            mpl_error(mpl, "writing error to `%s' - %s", mpl->prt_file,
               strerror(errno));
      }
      return;
}
#endif

/*----------------------------------------------------------------------
-- clean_printf - clean printf statement.
--
-- This routine cleans specified printf statement that assumes deleting
-- all stuff dynamically allocated on generating/postsolving phase. */

void clean_printf(MPL *mpl, PRINTF *prt)
{     PRINTF1 *p;
      /* clean subscript domain */
      clean_domain(mpl, prt->domain);
      /* clean pseudo-code for computing format string */
      clean_code(mpl, prt->fmt);
      /* clean printf list */
      for (p = prt->list; p != NULL; p = p->next)
      {  /* clean pseudo-code for computing value to be printed */
         clean_code(mpl, p->code);
      }
#if 1 /* 14/VII-2006 */
      /* clean pseudo-code for computing file name string */
      clean_code(mpl, prt->fname);
#endif
      return;
}

/*----------------------------------------------------------------------
-- execute_for - execute for statement.
--
-- This routine executes specified for statement. */

static int for_func(MPL *mpl, void *info)
{     /* this is auxiliary routine to work within domain scope */
      FOR *fur = (FOR *)info;
      STATEMENT *stmt, *save;
      save = mpl->stmt;
      for (stmt = fur->list; stmt != NULL; stmt = stmt->next)
         execute_statement(mpl, stmt);
      mpl->stmt = save;
      return 0;
}

void execute_for(MPL *mpl, FOR *fur)
{     loop_within_domain(mpl, fur->domain, fur, for_func);
      return;
}

/*----------------------------------------------------------------------
-- clean_for - clean for statement.
--
-- This routine cleans specified for statement that assumes deleting all
-- stuff dynamically allocated on generating/postsolving phase. */

void clean_for(MPL *mpl, FOR *fur)
{     STATEMENT *stmt;
      /* clean subscript domain */
      clean_domain(mpl, fur->domain);
      /* clean all sub-statements */
      for (stmt = fur->list; stmt != NULL; stmt = stmt->next)
         clean_statement(mpl, stmt);
      return;
}

/*----------------------------------------------------------------------
-- execute_statement - execute specified model statement.
--
-- This routine executes specified model statement. */

void execute_statement(MPL *mpl, STATEMENT *stmt)
{     mpl->stmt = stmt;
      switch (stmt->type)
      {  case A_SET:
         case A_PARAMETER:
         case A_VARIABLE:
            break;
         case A_CONSTRAINT:
            xprintf("Generating %s...\n", stmt->u.con->name);
            eval_whole_con(mpl, stmt->u.con);
            break;
#if 1 /* 11/II-2008 */
         case A_TABLE:
            switch (stmt->u.tab->type)
            {  case A_INPUT:
                  xprintf("Reading %s...\n", stmt->u.tab->name);
                  break;
               case A_OUTPUT:
                  xprintf("Writing %s...\n", stmt->u.tab->name);
                  break;
               default:
                  xassert(stmt != stmt);
            }
            execute_table(mpl, stmt->u.tab);
            break;
#endif
         case A_SOLVE:
            break;
         case A_CHECK:
            execute_check(mpl, stmt->u.chk);
            break;
         case A_DISPLAY:
            write_text(mpl, "Display statement at line %d\n",
               stmt->line);
            execute_display(mpl, stmt->u.dpy);
            break;
         case A_PRINTF:
            execute_printf(mpl, stmt->u.prt);
            break;
         case A_FOR:
            execute_for(mpl, stmt->u.fur);
            break;
         default:
            xassert(stmt != stmt);
      }
      return;
}

/*----------------------------------------------------------------------
-- clean_statement - clean specified model statement.
--
-- This routine cleans specified model statement that assumes deleting
-- all stuff dynamically allocated on generating/postsolving phase. */

void clean_statement(MPL *mpl, STATEMENT *stmt)
{     switch(stmt->type)
      {  case A_SET:
            clean_set(mpl, stmt->u.set); break;
         case A_PARAMETER:
            clean_parameter(mpl, stmt->u.par); break;
         case A_VARIABLE:
            clean_variable(mpl, stmt->u.var); break;
         case A_CONSTRAINT:
            clean_constraint(mpl, stmt->u.con); break;
#if 1 /* 11/II-2008 */
         case A_TABLE:
            clean_table(mpl, stmt->u.tab); break;
#endif
         case A_SOLVE:
            break;
         case A_CHECK:
            clean_check(mpl, stmt->u.chk); break;
         case A_DISPLAY:
            clean_display(mpl, stmt->u.dpy); break;
         case A_PRINTF:
            clean_printf(mpl, stmt->u.prt); break;
         case A_FOR:
            clean_for(mpl, stmt->u.fur); break;
         default:
            xassert(stmt != stmt);
      }
      return;
}

/* eof */
