src/ast/interpreter/dimension.c

    Dimensional analysis

    This file contains the code implementing dimensional analysis in Basilisk.

    We will assume in what follows that the reader is familiar with the basic concepts of dimensional analysis and of the conventions used to represent dimensions within Basilisk. These are introduced in the Dimension tutorial.

    Introduction

    The primary goal of the following code is to check that all (arithmetic) operations done by a program written in Basilisk C are dimensionally consistent.

    Interestingly, while dimensional consistency is clearly a cornerstone of physics, there does not seem to be a widely used language which ensure this consistency at the programming level. The code below is an attempt to fill this gap, at least in the context of code written with Basilisk.

    A reasonably simple way to impose this consistency would be to associate different types to arithmetic variables, depending on their dimensions. For example instead of writing

    double L = 10, T = 4, H = 30, G = 9.81, U = L/T + sqrt(G*H);

    one would write

    Length L = 10, H = 30;
    Time T = 4;
    Acceleration G = 9.81;
    Speed U = L/T + sqrt(G*H);

    the compiler would then be able to check that operations (such as the addition above) only happen between compatible types (i.e. Speed in the example).

    An important drawback of this approach (which has been used in the past) is that it obviously requires major code rewriting, as well as important constraints on the way code is written. In particular, all dimensions must be known and correctly imposed beforehand: writing code which is “dimension-independent” is not possible anymore.

    It also seems obvious that associating dimensions with variables (via their types or otherwise) is not the most general approach. Dimensions (and units) should be associated with numerical values (i.e. with the content of variables). One could for example design CPU chips which would carry out operations on floating point numbers with associated dimensions. This chip could then return errors (i.e. a “Floating point exception”) in the case of operations done on values with incompatible dimensions. Building a new special-purpose chip is clearly not a very practical solution however. Furthermore this approach would still require specifying the dimensions of every numerical value used as input to the program, which could also be quite cumbersome.

    The approach taken here, called “Dimensional Inference”, also relies on associating dimensions with values (and not variables or types) and has been known since at least the work of Wand and O’Keefe, 1991. The principle is simple:

    1. collect all “dimensional constraints” i.e. relationships necessary to impose consistency when performing a given arithmetic operation. By definition, these constraints will always involve the dimensions of the input constants.

    2. this collection of constraints linearly relates the dimensions of all the numerical constants used in the program i.e. they form a linear system of equations.

    3. try to solve this system. There are three main cases: a) if the system does not have a solution, then there is a dimensional inconsistency somewhere. b) if the system has more than one solution it requires additional constraints (i.e. directly setting the dimensions of more input constants). c) if the system has a solution then the dimensions of all input constants can be uniquely determined.

    A major advantage of this approach is that the dimensions of most constants can usually be “inferred” from the explicit specifications of only a few input constants. Minimal code modification is thus necessary to ensure full dimensional consistency.

    References

    [farrimond2007]

    Brian Farrimond and John Collins. Dimensional inference using symbol lives. In SETP, pages 152–159, 2007.

    [sandberg2003]

    Mikael Sandberg, Daniel Persson, and Björn Lisper. Automatic dimensional consistency checking for simulation specifications. Group, 1(2):M1, 2003.

    [wand1991]

    Mitchell Wand and Patrick O’Keefe. Automatic dimensional inference. In Computational Logic-Essays in Honor of Alan Robinson, pages 479–483. Citeseer, 1991.

    Implementation

    The primary reason for associating dimensions with types is that this allows for “static analysis” of dimensional consistency i.e. the compiler can check the consistency directly. When dimensions are associated with values, the only way to ensure consistency is to “run” the code (“dynamic analysis”), at least partially, and check that the operations performed involve values which are dimensionally consistent.

    To do so, the implementation below uses the interpreter to “run” the code being analysed. Of course running the full code within an interpreter just to check for consistency would be too expensive, so a simplified version of the code is run. The major simplifications are: the mesh contains a single grid point and the number of times loops are iterated is limited. This includes of course the number of timesteps which are performed. This “run” is performed at compilation time and usually completes in a reasonable amount of time, compared to the total compilation time.

    During this runtime phase, all arithmetic operations involving types to which dimensions can be associated (essentially floating point numbers, see below for details) are intercepted and the corresponding dimensional constraints are added to the linear system. Some obvious dimensional errors can also be detected directly within this first pass of analysis.

    A potential problem with the simplifications above is that they may miss some branches which will be taken when the full code is run. For example, an upwind scheme may take a branch which will depend on the sign of the value of a field. If a single grid point is used, this field will have a single value and only one of the branches will be taken. If this happens, dimensional analysis will not be exhaustive. To avoid this, the interpreter implements a sophisticated scheme where values can be specified as “unset” i.e. having an undefined value. If a condition depends on such an “unset” value, the interpreter makes sure to take both branches and to properly propagate “unset” values through their chains of dependencies.

    Note that this approach imposes some constraints on the dimensions of values defined within conditional branches.

    More details on implementation are given in the documentation associated with each section of the code below.

    Dimension tests

    Test cases for dimensional analysis are in /src/ast/interpreter/dimension-tests/test*.c they are run when doing

    cd src/ast/interpreter/
    make check

    They also include some documentation of various “corner cases”.

    Data structures

    #if 0
    # define DEBUG(...) (__VA_ARGS__)
    #else
    # define DEBUG(...)
    #endif
    
    typedef struct _List List;
    typedef struct _Key Key;
    typedef struct _Dimension Dimension;
    typedef struct _System System;
    
    struct _List {
      Dimension * d;
      List * next;
    };
    
    struct _Key {
      Ast * parent;
      char * field, * label;
      int j, refs, used;
      List * dimensions;
    };
    
    static
    void key_add_dimension (Key * k, Dimension * d)
    {
      k->refs++;
      if (!k->dimensions) {
        k->dimensions = calloc (1, sizeof (List)); // fixme: memory leak
        k->dimensions->d = d;
        return;
      }
      List * l = k->dimensions;
      while (l->next) l = l->next;
      l->next = calloc (1, sizeof (List)); // fixme: memory leak
      l->next->d = d;
    }
    
    static
    bool key_remove_dimension (Key * k, const Dimension * d)
    {
      for (List * l = k->dimensions, * prev = NULL; l; prev = l, l = l->next)
        if (l->d == d) {
          if (!prev)
    	k->dimensions = l->next;
          else
    	prev->next = l->next;
          free (l);
          k->refs--;
          return true;
        }
      return false;
    }

    The dimension is \displaystyle \mathbf{a} + \sum b_i [c_i]

    struct _Dimension {
      double * a, * b;
      Key ** c;
      const Ast * origin;
      int row;
      System * s;
    };

    The system of dimensional constraints.

    struct _System {
      Dimension ** r;
      Allocator * alloc;
      Key ** index;
      int m, n;
      FILE * output;
      int redundant, finite, lineno, warn;
      bool dimensionless;
    };

    A special value for [*].

    static
    Dimension * const dimension_any = (Dimension *) 128;

    Outputs

    const char * ast_file_crop (const char * file)
    {
      int len = strlen (BASILISK);
      if (strlen (file) > len && !strncmp (file, BASILISK, len))
        return file + len - 4;
      return file;
    }
    
    static inline
    bool is_identifier (char c)
    {
      return c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
    }

    The function below just replaces ‘val(s,0,1,0)’ with ‘s[0,1]’ and ’_attribute[s.i].‘with ’s.’.

    static
    char * simplified_expression (const char * string)
    {
      char * smp = malloc (strlen (string) + 1), * fp = smp;
      const char * s = string;
      while (*s != '\0') {
        if (!strncmp (s, "val(", 4) && (s == string || !is_identifier (*(s - 1)))) {
          s += 4;
          while (*s != ',') *fp++ = *s++;
          s++;
          *fp++ = '[';
          int para = 1;
          const char * end = s;
          while (para) {
    	if (*end == '(') para++;
    	if (*end == ')') para--;
    	end++;
          }
          const char * start = end - 2;
          while (*start == '0' || *start == ',') start--;
          while (s <= start) *fp++ = *s++;
          *fp++ = ']';
          s = end - 1;
        }
        else if (!strncmp (s, "_attribute[", 11) && (s == string || !is_identifier (*(s - 1)))) {
          s += 11;
          while (strncmp (s, ".i]", 3)) *fp++ = *s++;
          s += 2;
        }
        else
          *fp++ = *s;
        s++;
      }
      *fp = *s;
      return smp;
    }
    
    static
    void print_simplified_expression (const char * string, FILE * fp)
    {
      char * s = simplified_expression (string);
      fputs (s, fp);
      free (s);
    }
    
    #define EXPR_MAGIC "10293847566574839201."
    
    enum {
      LINENO   = 1,
      CARRIAGE = 1 << 1,
      INDEX    = 1 << 2,
      NORIGIN  = 1 << 3,
      REFS     = 1 << 4
    };
    
    static
    Ast * get_explicit_dimension (const Ast * n)
    {
      Ast * array = ast_ancestor (ast_parent (n, sym_primary_expression), 2);
      if (ast_schema (array, sym_array_access,
    		  0, sym_postfix_expression,
    		  0, sym_primary_expression) &&
          array->child[3])
        return array;
      return NULL;
    }
    
    static
    Ast * parent_expression (const Key * c)
    {
      Ast * n = c->parent;
      Ast * parent = ast_parent (n, sym_assignment_expression);
      if (ast_evaluate_constant_expression (parent) < DBL_MAX) {
        if (ast_schema (ast_ancestor (parent, 2), sym_init_declarator,
    		    2, sym_initializer))
          n = ast_ancestor (parent, 2);
        else if (ast_schema (parent->parent, sym_assignment_expression,
    			 1, sym_assignment_operator,
    			 0, token_symbol ('=')))
          n = parent->parent;
      }
      Ast * array = get_explicit_dimension (n);
      if (array)
        return array;
      return n;
    }
    
    static
    char * key_label (const Key * c, char sep, int flags)
    {
      Ast * n = parent_expression (c);
      AstTerminal * t = ast_left_terminal (n);
      if (t->start && !strcmp (t->start, EXPR_MAGIC))
        n = ast_parent (n, sym_additive_expression)->child[0];
      const char * file = ast_file_crop (t->file);
      int len = strlen (file) + (c->field ? strlen(c->field): 0) + 30;
      char * label = malloc (len);
      snprintf (label, len, "%s:%d:%c'%s%s",
    	    ast_file_crop (t->file), (flags & LINENO) ? t->line : 0, sep,
    	    c->field && c->field[0] != '\0' ? c->field : "",
    	    c->field && c->field[0] != '\0' ? "[] = " : "");
      char * s = ast_str_append (n, NULL);
      if (s) {
        char * s1 = simplified_expression (ast_crop_before (s));
        str_append (label, s1, "\'");
        free (s), free (s1);
      }
      else
        str_append (label, "0'");
      return label;
    }
    
    static
    void print_key_label (const Key * c, char sep, FILE * fp, int flags)
    {
      char * label = key_label (c, ' ', true);
      fputs (label, fp);
      free (label);
      if (flags & INDEX)
        fprintf (fp, " %d", c->j);
      if (flags & REFS)
        fprintf (fp, " %d", c->refs);
    #if 0
      fprintf (fp, " %d", c->flags);
    #endif
    #if 0
      fprintf (fp, " %d", c->refs);
    #endif
    }
    
    #define foreach_key(a, b) if (a->c) for (Key ** _p = a->c, * b = *_p; b; b = *++_p)
    
    #define END 1e10
    
    static
    void dimension_print (const Dimension * d, FILE * fp, double coef, int flags)
    {
      if (d == dimension_any) {
        fputs ("[*]", fp);
        return;
      }
      if (!d || (!d->a && !d->b)) {
        fputs ("[0]", fp);
        return;
      }
      if (d->a) {
        fputc ('[', fp);
        for (double * i = d->a; *i < END; i++) {
          if (*i)
    	fprintf (fp, "%g", (*i)*coef);
          else
    	fputs ("0", fp);
          if (*(i + 1) < END)
    	fputc (',', fp);
        }
        fputc (']', fp);
      }
      if (d->b) {
        double * i = d->b;
        foreach_key (d, c) {
          //      assert ((*i) != 0.);
          fprintf (fp, " %s ", (*i)*coef > 0. ? "+" : "-");
          if (fabs ((*i)*coef) != 1.)
    	fprintf (fp, "%g*[", fabs ((*i)*coef));
          else
    	fputc ('[', fp);
          print_key_label (c, ' ', fp, flags);
          fputc (']', fp);
          i++;
        }
      }
    }

    Various utility functions

    static inline
    bool can_have_dimension (const Value * v) {
      return v && !v->pointer && (v->type->sym == sym_LONG ||
    			      v->type->sym == sym_DOUBLE ||
    			      v->type->sym == sym_FLOAT);
    }
    
    static inline
    bool can_assign_dimension (const Value * v) {
      return v && !v->pointer && (v->type->sym == sym_DOUBLE ||
    			      v->type->sym == sym_FLOAT);
    }
    
    static
    int dimension_type_size (const Ast * type)
    {
      int size = ast_base_type_size (type);
      switch (type->sym) {
      case sym_LONG: case sym_FLOAT: case sym_DOUBLE:
        // case sym_COMPLEX: // fixme: need to be added below and tested
        size += sizeof (Ast *);
        break;
      }
      return size;
    }
    
    static inline
    int unknowns (const Dimension * d)
    {
      int n = 0;
      if (d && d != dimension_any)
        foreach_key (d, c)
          n++;
      return n;
    }
    
    static
    Ast * binary_expression_parent (Ast * n)
    {
      n = n->parent;
      while (!n->child[1] ||
    	 n->sym == sym_primary_expression ||
    	 n->sym == sym_conditional_expression ||
    	 n->child[0]->sym == sym_unary_operator)
        n = n->parent;
      for (int * op = (int[]){
          sym_multiplicative_expression,
          sym_additive_expression,
          sym_shift_expression,
          sym_relational_expression,
          sym_equality_expression,
          -1 }; *op > 0; op++) {
        if (ast_schema (n, *op, 0, *op))
          return n;
        if (ast_schema (n->parent, *op, 0, *op))
          return n->parent;
      }
      if (ast_schema (n, sym_assignment_expression,
    		  1, sym_assignment_operator))
        return n;
      return NULL;
    }
    
    static
    int unique_unknowns (const Dimension * a, const Dimension *b)
    {
      if (unknowns (b) > unknowns (a)) {
        const Dimension * tmp = a; a = b; b = tmp;
      }
      int nt = 0;
      if (a && a != dimension_any && a->c) {
        nt = unknowns (a);
        if (b && b != dimension_any && b->c)
          foreach_key (b, c) {
    	Key ** found;
    	for (found = a->c; found[0] && found[0] != c; found++);
    	if (!found[0])
    	  nt++;
          }
      }
      return nt;
    }
    
    static inline
    int dimensions (const Dimension * d)
    {
      int n = 0;
      if (d && d != dimension_any && d->a)
        for (double * a = d->a; *a != END; a++, n++);
      return n;
    }
    
    #define value_dimension(v) (*((Dimension **)(((char *) (v)->data.p) + ast_base_type_size ((v)->type))))
    
    static void constraint_print (Dimension * d, FILE * fp, int flags);
    
    static
    Dimension * dimension_zero (Allocator * alloc, Ast * origin)
    {
      Dimension * d = allocate (alloc, sizeof (Dimension));
      d->a = NULL;
      d->b = NULL;
      d->c = NULL;
      d->origin = origin;
      return d;
    }
    
    #define ROUNDOFF 1e-12
    
    static
    Dimension * dimension_simplify (Dimension * d)
    {
      int n = dimensions (d);
      double end = END;
      for (int i = n - 1; i >= 0; i--)
        if (fabs (d->a[i]) <= ROUNDOFF)
          d->a[i] = end;
        else
          end = 0.;
      if (d->a && d->a[0] == END)
        d->a = NULL;
    
      n = unknowns (d);
      for (int i = 0; i < n; i++)
        if (fabs (d->b[i]) <= ROUNDOFF) {
          for (int j = i; j < n - 1; j++)
    	d->b[j] = d->b[j + 1];
          for (int j = i; j < n; j++)
    	d->c[j] = d->c[j + 1];
          n--, i--;
        }
      if (n == 0)
        d->b = NULL, d->c = NULL;
      
      return d;
    }
    
    static
    void add_constraint (System * s, Dimension * constraint, Stack * stack);
    
    static
    Dimension * new_dimension (Ast * n, Stack * stack)
    {
      Allocator * alloc = stack_static_alloc (stack);
      Dimension * dimension = allocate (alloc, sizeof (Dimension));
      dimension->a = NULL;
      dimension->b = allocate (alloc, sizeof (double));
      dimension->b[0] = 1.;
      dimension->c = allocate (alloc, 2*sizeof (Key *));
      dimension->c[0] = allocate (alloc, sizeof (Key));
      Ast * prev = n;
      for (Ast ** c = n->parent->child; *c; c++)
        if (c[0]->sym == n->sym) {
          assert (n == prev);
          n = c[0];
          break;
        }
      *dimension->c[0] = (Key) { .parent = n };
      dimension->c[1] = NULL;
      dimension->origin = n;
      return dimension;
    }
    
    static
    bool in_c_file (const Ast * n)
    {
      AstTerminal * t = ast_left_terminal (n);
      if (!t->file)
        return false;
      int len = strlen (t->file);
      return !strcmp (t->file + len - 2, ".c");
    }
    
    static
    Dimension * get_dimension (Value * v, Stack * stack)
    {
      if (!can_have_dimension (v))
        return NULL;
      
      if (value_dimension (v))
        return value_dimension (v);
    
      if (is_constant_expression (v)) {
        Ast * n = (Ast *) v;

    Character constants have no dimension.

        if (n->sym == sym_I_CONSTANT && ast_terminal (n)->start[0] == '\'')
          return (value_dimension (v) = NULL);

    Read an explicitly-set dimension.

        Ast * array = get_explicit_dimension (n);
        if (array) {
          Ast * expr = array->child[2];
          if (expr->sym != sym_expression)
    	return (value_dimension (v) = dimension_any);
          Dimension * dimension = new_dimension (array, stack);
          Allocator * alloc = stack_static_alloc (stack);
          Dimension * rhs = allocate (alloc, sizeof (Dimension));
          rhs->b = NULL;
          int m = 0;
          foreach_item (expr, 2, d)
    	m++;
          rhs->a = allocate (alloc, (m + 1)*sizeof (double));
          m = 0;
          foreach_item_r (expr, sym_assignment_expression, d) {
    	Value * v = run (d, stack);
    	bool error;
    	rhs->a[m++] = value_double (v, &error, stack);
          }
          rhs->a[m] = END;
          rhs = dimension_simplify (rhs);
          if (rhs->a) {
    	dimension->a = rhs->a;
    	dimension->b[0] = - 1.;
          }
          add_constraint (interpreter_get_data (stack), dimension, stack);
          dimension->c[0]->used = 0;
          rhs->a = NULL;
          rhs->b = allocate (alloc, sizeof (double));
          rhs->b[0] = 1.;
          rhs->c = dimension->c;
          return (value_dimension (v) = rhs);
        }

    Constants in multiplicative expressions are dimensionless.

        Ast * parent = binary_expression_parent (n);
        if (parent && (parent->sym == sym_multiplicative_expression ||
    		   (parent->sym == sym_assignment_expression &&
    		    (parent->child[1]->child[0]->sym == sym_MUL_ASSIGN ||
    		     parent->child[1]->child[0]->sym == sym_DIV_ASSIGN))))
          return (value_dimension (v) = dimension_zero (stack_static_alloc (stack), (Ast *) v));

    In general, return a new unknown dimension for this constant.

        return (value_dimension (v) = new_dimension (n, stack));
      }

    Array members without a dimension have the same dimension as array member zero.

      if (ast_schema ((Ast *) v, sym_array_access)) {
        Ast * n = (Ast *) v;
        assert (n->child[2]);
        Value * a = run (n->child[0], stack);
        assert (a);
        Value * value = array_member_value (n, a, 0, false, stack);
        return (value_dimension (v) = value_dimension (value));
      }

    Everything else is dimensionless.

      System * system = interpreter_get_data (stack);
      if ((value_flags (v) & unset) && v->type->sym != sym_LONG && system->output) {
        Ast * n = (Ast *) v;
        AstTerminal * t = ast_left_terminal (n);
        char * s = ast_str_append (n, NULL);
        fprintf (system->output, "%s:%d: warning: '%s' is unset: assuming it has dimension [0]\n",
    	     ast_file_crop (t->file), t->line, ast_crop_before (s));
        free (s);
      }
    
      return (value_dimension (v) = dimension_zero (stack_static_alloc (stack), (Ast *) v));
    }
    
    static
    void value_print_dimension (const Value * v, bool zero, int flags, FILE * fp)
    {
      if (can_have_dimension (v)) {
        Dimension * dimension = value_dimension (v);
        if (dimension) {
          fputc (' ', fp);
          dimension_print (dimension, fp, 1., flags);
        }
        else if (zero)
          fputs (" [0]", fp);
      }
    }
    
    static
    void value_print_dimension_hook (const Value * v, FILE * fp)
    {
      value_print_dimension (v, false, LINENO, fp);
    }
    
    static
    Dimension * dimensions_multiply (const Ast * origin, Allocator * alloc,
    				 const Dimension * da, const Dimension * db,
    				 double v)
    {

    Multiplicative expressions between quantities with any dimension has any dimension.

      if (da == dimension_any || db == dimension_any)
        return dimension_any;
      
      Dimension * dimension = allocate (alloc, sizeof (Dimension));
      dimension->origin = origin;
      
      int na = dimensions (da), nb = dimensions (db), n = na > nb ? na : nb;
      dimension->a = n ? allocate (alloc, sizeof (double)*(n + 1)) : NULL;
      if (n > 0)
        dimension->a[n] = END;
      for (int i = 0; i < n; i++)
        dimension->a[i] = 0.;
      for (int i = 0; i < na; i++)
        dimension->a[i] += da->a[i];
      for (int i = 0; i < nb; i++)
        dimension->a[i] += v*db->a[i];
    
      n = unique_unknowns (da, db);
      if (!n) {
        dimension->b = NULL;
        dimension->c = NULL;
        return dimension_simplify (dimension);
      }
      
      dimension->b = allocate (alloc, sizeof (double)*n);
      dimension->c = allocate (alloc, sizeof (Key *)*(n + 1));
      dimension->c[n] = NULL;
    
      double va = 1., vb = v;
      na = unknowns (da), nb = unknowns (db);
      if (nb > na) {
        const Dimension * d = da; da = db; db = d;
        va = v, vb = 1.;
        int n = na; na = nb; nb = n;
      }
      n = 0;
      for (int i = 0; i < na; i++)
        dimension->b[n] = va*da->b[i], dimension->c[n++] = da->c[i];
      for (int i = 0; i < nb; i++) {
        int j;
        for (j = 0; j < n && dimension->c[j] != db->c[i]; j++);
        if (j < n)
          dimension->b[j] += vb*db->b[i];
        else
          dimension->b[n] = vb*db->b[i], dimension->c[n++] = db->c[i];
      }
      return dimension_simplify (dimension);
    }
    
    static
    void * not_homogeneous (Ast * n, Value * va, Value * vb, Stack * stack)
    {
      AstTerminal * t = ast_left_terminal (n);
      System * s = interpreter_get_data (stack);
      const char * msg = s->warn ? "warning" : "error";
      fprintf (stderr, "%s:%d: %s: '", t->file, t->line, msg);
      print_simplified_expression (ast_crop_before (ast_str_append (n, NULL)), stderr);
      fputs ("' is not an homogeneous expression\n", stderr);
      t = ast_left_terminal ((Ast *)va);
      fprintf (stderr, "%s:%d: %s: '", t->file, t->line, msg);
      print_simplified_expression (ast_crop_before (ast_str_append ((Ast *)va, NULL)), stderr);
      fputs ("' has dimensions", stderr);
      value_print_dimension (va, true, LINENO, stderr);
      fputs ("\n", stderr);
      t = ast_left_terminal ((Ast *)vb);
      fprintf (stderr, "%s:%d: %s: '", t->file, t->line, msg);
      print_simplified_expression (ast_crop_before (ast_str_append ((Ast *)vb, NULL)), stderr);
      fputs ("' has dimensions", stderr);
      value_print_dimension (vb, true, LINENO, stderr);
      fputs ("\n", stderr);
      return NULL;
    }

    System of dimensional constraints

    static
    System * system_new()
    {
      System * system = calloc (1, sizeof (System));
      system->r = calloc (1, sizeof (Dimension *));
      system->dimensionless = true;
      system->lineno = true;
      return system;
    }
    
    static
    void system_destroy (System * system)
    {
      free (system->r);
      free (system->index);
      free (system);
    }
    
    static void constraint_print (Dimension * d, FILE * fp, int flags)
    {
      if (d != dimension_any) {
        if (d->origin && !(flags & NORIGIN)) {
          AstTerminal * t = ast_left_terminal (d->origin);
          assert (t->file);
          char * s = ast_str_append (d->origin, NULL);
          fprintf (fp, "%s:%d: '", t->file ? ast_file_crop (t->file) : "null", t->line);
          print_simplified_expression (ast_crop_before (s), fp);
          fprintf (fp, "'%s", (flags & CARRIAGE) ? "\n\t└─ " : ": ");
          free (s);
        }
        if (unknowns (d) == 1) {
          fputc ('[', fp);
          print_key_label (*d->c, ' ', fp, flags | LINENO);
          fputs ("] = ", fp);
          double * b = d->b;
          assert (b[0]);
          double coef = - 1./b[0];;
          d->b = NULL;
          dimension_print (d, fp, coef, flags);
          d->b = b;
          fputc ('\n', fp);
        }
        else {
          dimension_print (d, fp, 1., flags);
          fputs (" = [0]\n", fp);
        }
      }
    }
    
    #define foreach_constraint(s, i) if (s->r) for (Dimension ** _p = s->r, * i = *_p; i; i = *++_p)
    
    void system_print (System * s, FILE * fp)
    {
      foreach_constraint (s, r)
        constraint_print (r, fp, LINENO | INDEX | REFS);
    }
    
    static
    bool system_append (System * s, Dimension * constraint)
    {
      int n = 0;
      foreach_constraint(s, r) {
        if (r == constraint)
          return false;
        n++;
      }
      s->r = realloc (s->r, (n + 2)*sizeof (Dimension *));
      s->r[n] = constraint;
      constraint->s = s;
      s->r[n + 1] = NULL;
      if (constraint->a)
        s->dimensionless = false;
      return true;
    }
    
    static
    void add_constraint (System * s, Dimension * constraint, Stack * stack)
    {
      if (!constraint || (!constraint->a && !constraint->b))
        return;
      if (stack_verbosity (stack) > 2)
        constraint_print (constraint, stderr, LINENO);
      if (system_append (s, constraint)) {
        foreach_key (constraint, c) {
          key_add_dimension (c, constraint);
          c->used = 1;
        }
      }
    }
    
    static Dimension * row_replacement (Dimension * d, Dimension * with, Allocator * alloc)
    {
      if (!d->c)
        return NULL;
      Key * p = with->c[0], ** c;
      double * j = d->b;
      for (c = d->c; c[0] && c[0] != p; c++, j++);
      if (!c[0])
        return NULL; // p not in d
      
      assert (with->b[0]);
      return dimensions_multiply (d->origin, alloc, d, with, - j[0]/with->b[0]);
    }
    
    static inline
    double column (const Dimension * r, int j)
    {
      if (!r->c)
        return 0.;
      double * b = r->b;
      foreach_key (r, c) {
        if (c->j == j)
          return *b;
        b++;
      }
      return 0.;
    }
    
    static inline
    double matrix (const System * s, int i, int j)
    {
      return column (s->r[i], j);
    }
    
    static
    int compare_unknowns (const void * pa, const void * pb)
    {
      Dimension * const * a = pa, * const * b = pb;
      int na = unknowns (a[0]), nb = unknowns (b[0]);
      if (na > nb)
        return 1;
      if (na < nb)
        return -1;
      return pa > pb ? 1 : -1;
    }
    
    static
    void system_index (System * s)
    {
      s->m = 0;
      foreach_constraint (s, r) {
        s->m++;
        foreach_key (r, c)
          c->j = -1;
      }

    Sort equations by increasing order of number of unknowns so that pivoting always uses the simplest pivot. This changes the pivoted system when it is row-deficient (i.e. unconstrained) but should not change the result otherwise (to within round-off errors).

      qsort (s->r, s->m, sizeof (Dimension *), compare_unknowns);
      
      s->m = 0, s->n = 0;
      int row = 0;
      foreach_constraint (s, r) {
        r->row = row++;
        if (r->c || r->a) {
          s->m++;
          foreach_key (r, c)
    	if (c->j == -1) {
    	  c->j = s->n++;
    	  s->index = realloc (s->index, s->n*sizeof (Key *));
    	  s->index[s->n - 1] = c;
    	}
        }
      }
    }
    
    #define WRITE_GRAPH 0
    
    #if WRITE_GRAPH
    # include "graph.c"
    
    void system_write_graph (System * s, const char * name)
    {
      FILE * fp = fopen (name, "w");
      if (!fp) {
        perror (name);
        return;
      }
      system_index (s);
      Graph * g = system_to_graph (s);
      // while (graph_simplify (g));
      //  graph_simplify (g);
      graph_dot (g, fp);
      fclose (fp);

    gnuplot> plot ‘/tmp/out’ matrix with image

    }
    
    void system_write_matrix (System * s, const char * name)
    {
      FILE * fp = fopen (name, "w");
      if (!fp) {
        perror (name);
        return;
      }
      int i = 0;
      foreach_constraint (s, r) {
        double * b = r->b;
        foreach_key (r, c) {
          fprintf (fp, "%d %d %g ", c->j, i, *b);
          print_key_label (c, ' ', fp, LINENO | INDEX);
          fputc ('\n', fp);
          b++;
        }
        i++;
      }
      fclose (fp);
    }
    #endif
    
    static
    void set_system (Dimension * j, System * sub)
    {
      if (system_append (sub, j)) {
        foreach_key (j, c)
          for (List * l = c->dimensions; l; l = l->next)
    	if (!l->d->s)
    	  set_system (l->d, sub);
      }
    }
    
    static
    bool remove_dimensionless_subsystems (System * s)
    {
      bool found = false;
      foreach_constraint (s, j)
        j->s = NULL;
      foreach_constraint (s, j)
        if (!j->s && j->c && j->c[1]) {
          System * su = system_new();
          su->alloc = s->alloc;
          set_system (j, su);
          if (su->dimensionless) {
    	found = true;
    	foreach_constraint (su, j)
    	  j->a = j->b = NULL, j->c = NULL;
          }
          system_destroy (su);
        }
      return found;
    }
    
    static inline
    void set_row (System * s, int row, Dimension * d)
    {
      s->r[row] = d; d->row = row;
    }
    
    static bool system_pivot (System * s)
    {
      system_index (s);
    
      if (!s->m)
        return true;

    Gaussian elimination, algorithm directly adapted from wikipedia.

      int h = 0; /* Initialization of the pivot row */
      int k = 0; /* Initialization of the pivot column */
      while (h < s->m && k < s->n) {

    Find the k-th pivot: we just take the first non-zero value. This relies on the equations already being sorted in order of increasing complexity (see system_index() above).

        int i_piv = 0;
        double piv = 0.;
        for (int i = h; i < s->m && !piv; i++) {
          double f = matrix (s, i, k);
          if (f)
    	piv = f, i_piv = i;
        }
        if (!piv) {
          /* No pivot in this column */
          DEBUG (fprintf (stderr, "no pivot %d: ", k),
    	     print_key_label (s->index[k], ' ', stderr, LINENO | INDEX),
    	     fputc ('\n', stderr));
          k++; // pass to next column
        }
        else {
          /* swap rows(h, i_min) */
          Dimension * r = s->r[h];
          set_row (s, h, s->r[i_piv]);
          set_row (s, i_piv, r);
          /* Do for all rows below pivot: */
          int nd = s->index[k]->refs;
          if (nd > 1) {
    	Dimension * list[nd], ** i = list;
    	for (List * _l = s->index[k]->dimensions; _l; _l = _l->next)
    	  *i++ = _l->d;	
    	for (int j = 0; j < nd; j++)
    	  if (list[j]->row > h) {
    	    Dimension * r = list[j];
    	    double f = column (r, k)/piv;
    	    /* Substract the scaled pivot row from the current row */
    	    DEBUG (fprintf (stderr, "pivoting %d ", h), constraint_print (s->r[h], stderr, LINENO | INDEX | NORIGIN),
    		   fprintf (stderr, "         in "), constraint_print (r, stderr, LINENO | INDEX | NORIGIN));
    	    foreach_key(r, c)
    	      assert (key_remove_dimension (c, r));
    	    Dimension * d = dimensions_multiply (r->origin, s->alloc, r, s->r[h], - f);
    	    foreach_key(d, c)
    	      key_add_dimension (c, d);
    	    DEBUG (fprintf (stderr, "      gives "), constraint_print (d, stderr, LINENO | INDEX | NORIGIN));
    	    /* If l.h.s. is zero and r.h.s. is not zero the system does not have a solution */
    	    if (!d->c && d->a) {
    	      AstTerminal * t = ast_left_terminal (s->r[h]->origin);
    	      fprintf (stderr, "%s:%d: %s: the dimensional constraints below are not compatible\n",
    		       t->file, t->line, s->warn ? "warning" : "error");
    	      constraint_print (s->r[h], stderr, CARRIAGE | LINENO);
    	      constraint_print (r, stderr, CARRIAGE | LINENO);
    	      return false;
    	    }
    	    set_row (s, r->row, d);
    	  }
          }
          /* Increase pivot row and column */
          h++, k++;
        }
      }

    Eliminate non-independent constraints

      s->m = 0;
      for (Dimension ** r = s->r; *r; r++) {
        // constraint_print (r[0], stderr, LINENO | INDEX);
        if (r[0]->c)
          s->m++;
        else { // zero unknowns
          assert (!r[0]->a); // r.h.s. is zero
          r[0] = NULL;
          r++;
          for (; r[0]; r++)
    	assert (!r[0]->c && !r[0]->a);
          break;
        }
      }
    
      return true;
    }
    
    static
    Key ** system_unconstrained (const System * s)
    {
      if (s->n == s->m)
        return NULL;
      Key ** unconstrained = NULL;
      int nu = 0;
      Dimension ** i;
      for (i = s->r; *i; i++);
      for (i--; i >= s->r; i--)
        if (unknowns (i[0]) > 1)
          foreach_key (i[0], c) {
    	Key ** found;
    	for (found = unconstrained; found && found[0] && found[0]->parent != c->parent; found++);
    	if (!found || !found[0]) {
    	  nu++;
    	  unconstrained = realloc (unconstrained, (nu + 1)*sizeof (Key *));
    	  unconstrained[nu - 1] = c;
    	  unconstrained[nu] = NULL;
    	}
          }
      return unconstrained;
    }
    
    static bool system_solve (System * s)
    {
    #if WRITE_GRAPH  
      system_write_graph (s, "dimensions0.dot");
      { FILE * fp = fopen ("system0", "w"); system_print (s, fp), fclose (fp); }
    #endif
      
      if (!system_pivot (s))
        return false;
      
      DEBUG (fprintf (stderr, "@@ %d unknowns, %d constraints\n", s->n, s->m));
      if (s->m) {
    
    #if WRITE_GRAPH
        { FILE * fp = fopen ("system1", "w"); system_print (s, fp), fclose (fp); }
    #endif

    Backward substitution

        for (Dimension ** i = s->r + s->m - 1; i >= s->r; i--)
          if (unknowns (i[0]) == 1 && i[0]->c[0]->refs > 1)
    	for (Dimension ** j = s->r; j < i; j++) {
    	  Dimension * r = row_replacement (*j, *i, s->alloc);
    	  if (r) {
    	    assert (r->c || !r->a);
    	    DEBUG (fprintf (stderr, "replacing "), constraint_print (*i, stderr, LINENO | INDEX | NORIGIN),
    		   fprintf (stderr, "in        "), constraint_print (*j, stderr, LINENO | INDEX | NORIGIN),
    		   fprintf (stderr, "gives     "), constraint_print (r, stderr, LINENO | INDEX | NORIGIN));
    	    *j = r;
    	  }
    	}
        
    #if WRITE_GRAPH    
        system_write_graph (s, "dimensions2.dot");
        { FILE * fp = fopen ("system2", "w"); system_print (s, fp), fclose (fp); }
    #endif
    
        if (remove_dimensionless_subsystems (s))
          system_index (s);
    
    #if WRITE_GRAPH
        system_write_graph (s, "dimensions3.dot");
        { FILE * fp = fopen ("system3", "w"); system_print (s, fp), fclose (fp); }
    #endif
        
      }
    
      if (s->output)
        fprintf (s->output, "%d constraints, %d unknowns\n", s->m, s->n);
    
      return true;
    }

    Dimension hooks to the interpreter

    static
    Value * dimension_run (Ast * n, Stack * stack)
    {
      if (!n)
        return NULL;
    
      switch (n->sym) {
    
      case sym_primary_expression: {
        Value * v = ast_run_node (n, stack);
        if (n->child[0]->sym == sym_constant &&
    	(n->child[0]->child[0]->sym == sym_I_CONSTANT ||
    	 n->child[0]->child[0]->sym == sym_F_CONSTANT))
          get_dimension (v, stack);
        else if (n->child[0]->sym == sym_IDENTIFIER &&
    	     !strcmp (ast_terminal (n->child[0])->start, "_val_higher_dimension"))
          value_dimension (v) = dimension_any;
        return v;
      }
        
      default:
        return ast_run_node (n, stack);
        
      }
      return NULL;
    }
    
    static
    Value * dimension_assign (Ast * n, Value * dst, Value * src, Stack * stack)
    {
      if (can_assign_dimension (dst)) {
        Dimension * d = get_dimension (src, stack);
        if (!d)

    If the dimension is undefined we are (most probably) casting from values which cannot have dimensions i.e. ’int’s etc. so we set the dimension to zero.

          d = dimension_zero (stack_static_alloc (stack), (Ast *) src);
    
        value_dimension (dst) = d;
      }
      return dst;
    }
    
    static
    Dimension * homogeneous_dimensions (Ast * n, Value * va, Value * vb, Stack * stack)
    {
      Dimension * da = get_dimension (va, stack), * db = get_dimension (vb, stack);
      if (da == dimension_any)
        return db;
      if (db == dimension_any)
        return da;

    We first consider the case where all dimensions are known and check that they are identical.

      int nt = unique_unknowns (da, db);
      if (!nt) {
        if (dimensions (da) != dimensions (db))
          return not_homogeneous (n, va, vb, stack);
        else if (da && da->a)
          for (double * i = da->a, * j = db->a; *i < END; i++, j++)
    	if (*i != *j)
    	  return not_homogeneous (n, va, vb, stack);
        return da ? da : db;
      }

    One or both dimensions are unknown, we add the corresponding constraint to the system (if it is compatible).

      Dimension * constraint = dimensions_multiply (n, stack_static_alloc (stack), da, db, -1.);
      if (!constraint->c && constraint->a)
        return not_homogeneous (n, va, vb, stack);
      add_constraint (interpreter_get_data (stack), constraint, stack);

    We return either the non-constant dimension or the dimension with the smallest number of unknowns.

      Dimension * d;
      if (is_constant_expression (va))
        d = db;
      else if (is_constant_expression (vb))
        d = da;  
      else if (unknowns (da) < unknowns (db))
        d = da;
      else
        d = db;

    If d is NULL, we explicitly set the dimension to zero.

      if (!d)
        d = dimension_zero (stack_static_alloc (stack), n);
    
      return d;
    }
    
    static inline
    bool is_finite (const Value * v)
    {
      if (!is_constant_expression (v))
        return true;
      double val =fabs (v->type->sym == sym_LONG ? value_data (v, long) :
    		    v->type->sym == sym_FLOAT ? value_data (v, float) :
    		    v->type->sym == sym_DOUBLE ? value_data (v, double) : 0.);
      if (val > 1e-30 && val < 1e30 && val != 1234567890)
        return true;
      return false;
    }
    
    static void dimension_error (Stack * stack)
    {
      ((StackData *)stack_get_data (stack))->maxcalls = -1;
    }
    
    static
    Value * dimension_binary_operation (Ast * n, Stack * stack, Value * a, Value * b, Value * value)
    {
      if ((!can_assign_dimension (a) && !can_assign_dimension (b)) ||
          (is_constant_expression (a) && is_constant_expression (b) &&
           !get_explicit_dimension ((Ast *)a) && !get_explicit_dimension ((Ast *)b)))
        return value;
    
      int op = n->child[1]->sym;
      int mul = ((op == token_symbol('*') ||
    	      ast_schema (n->child[1], sym_assignment_operator, 0, sym_MUL_ASSIGN)) ? 1 :
    	     (op == token_symbol('/') ||
    	      ast_schema (n->child[1], sym_assignment_operator, 0, sym_DIV_ASSIGN)) ? -1 :
    	     0);
      if (op == token_symbol('+') || op == token_symbol('-') ||
          ast_schema (n->child[1], sym_assignment_operator, 0, sym_ADD_ASSIGN) ||
          ast_schema (n->child[1], sym_assignment_operator, 0, sym_SUB_ASSIGN)) {
        if (can_assign_dimension (value) &&
    	!(value_dimension (value) = homogeneous_dimensions (n, a, b, stack)))
          dimension_error (stack);
      }
      else if (mul) {
        if (can_assign_dimension (value))
          value_dimension (value) = dimensions_multiply (n, stack_static_alloc (stack),
    						     get_dimension (a, stack), get_dimension (b, stack), mul);
      }
      else if (can_have_dimension (a) && can_have_dimension (b) &&
    	   !homogeneous_dimensions (n, a, b, stack))
        dimension_error (stack);
      return value;
    }
    
    static
    Dimension * dimension_pow (Ast * origin, Stack * stack, Dimension * d, double e)
    {
      if (d == dimension_any)
        return d;
      Dimension * r = dimension_zero (stack_static_alloc (stack), origin);
      if (e) {
        Allocator * alloc = stack_static_alloc (stack); 
        if (d->a) {
          int na = 0;
          for (double * a = d->a; *a != END; a++, na++);
          r->a = allocate (alloc, sizeof (double)*(na + 1));
          memcpy (r->a, d->a, sizeof (double)*(na + 1));
          for (double * a = r->a; *a != END; a++)
    	*a *= e;
        }
        if (d->c) {
          int nc = 0;
          foreach_key (d, c) nc++;
          r->c = allocate (alloc, sizeof (Key *)*(nc + 1));
          memcpy (r->c, d->c, sizeof (Key *)*(nc + 1));
          r->b = allocate (alloc, sizeof (double)*(nc + 1));
          memcpy (r->b, d->b, sizeof (double)*nc);
          double * b = r->b;
          foreach_key (r, c)
    	*b++ *= e;
        }
      }
      return r;
    }
    
    static
    void print_params (Ast * call, Value ** params, const char * msg)
    {
      Ast * list = call->child[2];
      int i = 0;
      foreach_item_r (list, sym_argument_expression_list_item, arg) {
        AstTerminal * t = ast_left_terminal (arg);
        fprintf (stderr, "%s:%d: %s: '%s' has dimensions",
    	     t->file, t->line, msg, ast_crop_before (ast_str_append (arg, NULL)));
        value_print_dimension (params[i++], true, LINENO, stderr);
        fputc ('\n', stderr);
      }
    }
    
    static
    Value * dimension_internal_functions (Ast * call, Ast * identifier, Value ** params, Stack * stack, Value * value)
    {
      const char * name = ast_terminal (identifier)->start;
      if (!strcmp (name, "sq"))
        value_dimension (value) = dimension_pow (call, stack, get_dimension (params[0], stack), 2.);
      else if (!strcmp (name, "cube"))
        value_dimension (value) = dimension_pow (call, stack, get_dimension (params[0], stack), 3.);
      else if (!strcmp (name, "sqrt"))
        value_dimension (value) = dimension_pow (call, stack, get_dimension (params[0], stack), 0.5);
      else if (!strcmp (name, "atan2")) {
        Dimension * constraint = dimensions_multiply (call, stack_static_alloc (stack),
    						  get_dimension (params[0], stack),
    						  get_dimension (params[1], stack), -1.);
        if (constraint && constraint->a && !constraint->b) {
          AstTerminal * t = ast_left_terminal (call);
          System * s = interpreter_get_data (stack);
          const char * msg = s->warn ? "warning" : "error";
          fprintf (stderr, "%s:%d: %s: the arguments of 'atan2' must have the same dimensions\n",
    	       t->file, t->line, msg);
          print_params (call, params, msg);
          dimension_error (stack);
          return value;
        }
        add_constraint (interpreter_get_data (stack), constraint, stack);
        value_dimension (value) = dimension_zero (stack_static_alloc (stack), call);
      }
      else if (!strcmp (name, "pow")) {
        bool error;
        value_dimension (value) = dimension_pow (call, stack, get_dimension (params[0], stack),
    					     value_double (params[1], &error, stack));
      }
      else if (!strcmp (name, "fabs"))
        value_dimension (value) = get_dimension (params[0], stack);
      else if (!strcmp (name, "reset_field_value")) {
        char * field = value_data (params[0], char *);
        Dimension * d = *((Dimension **)(field + ast_base_type_size (params[2]->type)));
        const char * name = value_data (params[1], char *);
        if (name) {
          d->c[0]->field = allocate (stack_static_alloc (stack), (strlen(name) + 1)*sizeof (char));
          strcpy (d->c[0]->field, name);
        }
        else
          d->c[0]->field = NULL;
      }
      else {
        static char * funcs[] = {
          "exp", "log", "log10",
          "sin", "cos", "tan",
          "asin", "acos", "atan",
          "sinh", "cosh", "tanh",
          "asinh", "acosh", "atanh",
          NULL
        };
        for (char ** i = funcs; *i; i++)
          if (!strcmp (name, *i)) {
    	Dimension * constraint = get_dimension (params[0], stack);
    	if (constraint && constraint->a && !constraint->b) {
    	  AstTerminal * t = ast_left_terminal (call);
    	  System * s = interpreter_get_data (stack);
    	  const char * msg = s->warn ? "warning" : "error";
    	  fprintf (stderr, "%s:%d: %s: the argument of '%s' must be dimensionless\n",
    		   t->file, t->line, msg, ast_crop_before (ast_str_append (call, NULL)));
    	  print_params (call, params, msg);
    	  dimension_error (stack);
    	  return value;
    	}
    	constraint->origin = call;
    	add_constraint (interpreter_get_data (stack), constraint, stack);
    	value_dimension (value) = dimension_zero (stack_static_alloc (stack), call);
    	return value;
          }
      }
      return value;
    }
    
    static
    Value * dimension_choose (Ast * n, Stack * stack, Value * a, Value * b)
    {
      if (!can_have_dimension (a)) return b;
      if (!can_have_dimension (b)) return a;
      if ((value_flags (a) & unset) && !value_dimension (a))
        return b;
      if ((value_flags (b) & unset) && !value_dimension (b))
        return a;
      homogeneous_dimensions (n, a, b, stack);
      return value_dimension (a) == dimension_any ? b :
        unknowns (get_dimension (a, stack)) < unknowns (get_dimension (b, stack)) ? a : b;
    }
    
    static
    bool is_expression (Ast * n)
    {
      AstTerminal * t = ast_right_terminal (n);
      return (t->start && !strcmp (t->start, EXPR_MAGIC));
    }
    
    static
    int compare_dimensions (const void * pa, const void * pb)
    {
      const Dimension * a = *((Dimension * const *) pa);
      const Dimension * b = *((Dimension * const *) pb);
    
      if (unknowns (a) != 1)
        return 1;
      if (unknowns (b) != 1)
        return -1;

    Expressions last.

      const Key * ac = a->c[0], * bc = b->c[0];
      if (is_expression (ac->parent)) {
        if (!is_expression (bc->parent))
          return 1;
      }
      else if (is_expression (bc->parent))
        return -1;

    Sort first according to the number of non-zero dimensions.

      int na = 0, nb = 0;
      if (a->a)
        for (double * i = a->a; *i != END; i++)
          if (*i) na++;
      if (b->a)
        for (double * i = b->a; *i != END; i++)
          if (*i) nb++;
      if (na != nb)
        return na > nb ? 1 : -1;

    Then sort according to the sum of the absolute values of the dimensions.

      double sa = 0., sb = 0.;
      if (a->a)
        for (double * i = a->a; *i != END; i++)
          sa += fabs (*i);
      if (b->a)
        for (double * i = b->a; *i != END; i++)
          sb += fabs (*i);
      sa /= fabs (*a->b), sb /= fabs (*b->b);
      if (fabs (sa - sb) > ROUNDOFF)
        return sa > sb ? 1 : -1;

    Then sort according to the number of dimensions.

      na = dimensions (a), nb = dimensions (b);
      if (na != nb)
        return na > nb ? 1 : -1;

    Then sort according to the sum of the dimensions.

      sa = 0., sb = 0.;
      if (a->a)
        for (double * i = a->a; *i != END; i++)
          sa += *i;
      if (b->a)
        for (double * i = b->a; *i != END; i++)
          sb += *i;
      sa /= *a->b, sb /= *b->b;
      if (fabs (sa - sb) > ROUNDOFF)
        return sa > sb ? 1 : -1;

    “Internal” parser values are displayed first.

      AstTerminal * ta = ast_left_terminal (ac->parent), * tb = ast_left_terminal (bc->parent);
      na = strncmp ("ast/", ta->file, 4), nb = strncmp ("ast/", tb->file, 4);
      if (!na && nb) return -1;
      if (na && !nb) return  1;

    Values which are “deeper” in the file system are displayed first.

      na = 0; for (const char * s = ta->file; *s != '\0'; s++) if (*s == '/') na++;
      nb = 0; for (const char * s = tb->file; *s != '\0'; s++) if (*s == '/') nb++;
      if (na != nb)
        return na > nb ? -1 : 1;

    .c files are displayed last.

      na = strcmp (".c", ta->file + strlen (ta->file) - 2);
      nb = strcmp (".c", tb->file + strlen (tb->file) - 2);
      if (!na && nb) return  1;
      if (na && !nb) return -1;

    Alphabetic order of file names.

      na = strcmp (ta->file, tb->file);
      if (na)
        return na;

    Order by line number.

      if (ta->line != tb->line)
        return ta->line > tb->line ? 1 : -1;

    Order by label name.

      return strcmp (ac->label, bc->label);
    }
    
    static
    bool is_finite_constant (const Key * c)
    {
      double val = ast_evaluate_constant_expression (c->parent);
      return val > 1e-30 && val < 1e30 && val != 1234567890;
    }
    
    static
    void dimension_after_run (Ast * n, Stack * stack)
    {
      if (((StackData *)stack_get_data (stack))->maxcalls < 0)
        return;
      System * s = interpreter_get_data (stack);
      s->alloc = stack_alloc (stack);
      if (!s->output) {
        if (!system_pivot (s))
          ((StackData *)stack_get_data (stack))->maxcalls = -1;
        return;
      }
      if (!system_solve (s)) {
        ((StackData *)stack_get_data (stack))->maxcalls = -1;
        return;
      }
      int u = 0;
      foreach_constraint (s, r) {
        u++;
        if (r->c)
          r->c[0]->label = key_label (r->c[0], ' ', LINENO);
      }
      qsort (s->r, u, sizeof (Dimension *), compare_dimensions);
      FILE * fp = s->output;
      Key ** unconstrained = system_unconstrained (s);
      if (unconstrained) {
        int nfinite = 0;
        for (Key ** u = unconstrained; u[0]; u++)
          if (!s->finite || is_finite_constant (u[0]))
    	nfinite++;
        if (nfinite == 1 && s->n - s->m == 1)
          fputs ("The following constant is unconstrained\n", fp);
        else {
          int n = 0;
          for (Key ** u = unconstrained; u[0]; u++)
    	if (!s->finite || nfinite < s->n - s->m || is_finite_constant (u[0]))
    	  n++;
          fprintf (fp, "There %s %d unconstrained constant%s within the following %d\n",
    	       s->n - s->m > 1 ? "are" : "is",
    	       s->n - s->m, s->n - s->m > 1 ? "s" : "",
    	       n);
        }
        for (Key ** u = unconstrained; u[0]; u++)
          if (!s->finite || nfinite < s->n - s->m || is_finite_constant (u[0])) {
    	fputs ("  ", fp); print_key_label (u[0], ' ', fp, LINENO /*| INDEX*/);
    	fputc ('\n', fp);
          }
        free (unconstrained);
      }
      double * a = NULL, ca = 0.;
      bool firstconst = true, firstexpr = true;
      foreach_constraint (s, r) {
        if (unknowns (r) > 1) {
          // constraint_print (r, fp, LINENO | INDEX | NORIGIN);
          continue;
        }
        if (r->c && r->c[0]->used &&
    	(!s->finite ||
    	 (is_finite_constant (r->c[0]) // only lists finite constants
    	  && (r->a || in_c_file (r->c[0]->parent)) // list dimensionless constants only in C files
    	  ))) {
          assert (r->b[0]);
          double coef = - 1./r->b[0];
          bool identical;
          if (a && r->a) {
    	double * i, * j;
    	for (i = r->a, j = a; fabs ((*i)*coef - (*j)*ca) <= ROUNDOFF && *i != END; i++, j++);
    	identical = (*i == END && *j == END);
          }
          else
    	identical = (fabs (ca) > ROUNDOFF && a == NULL && r->a == NULL);
          if (!identical) {
    	a = r->a, ca = coef;
    	if (s->lineno) {
    	  if (is_expression (r->c[0]->parent)) {
    	    if (firstexpr) {
    	      fputs ("Dimensions of expressions\n", fp);
    	      firstexpr = false;
    	    }
    	  }
    	  else if (firstconst) {
    	    fprintf (fp, "Dimensions of %sconstants\n", s->finite ? "finite " : "");
    	    firstconst = false;
    	  }
    	  double * b = r->b;
    	  r->b = NULL;
    	  fputs ("  ", fp), dimension_print (r, fp, coef, true);
    	  r->b = b;
    	  fputc ('\n', fp);
    	}
          }
    
          if (s->redundant || !identical || _p == s->r ||
    	  strcmp (r->c[0]->label, (_p - 1)[0]->c[0]->label)) {
    	if (!s->lineno) {
    	  double * b = r->b;
    	  r->b = NULL;
    	  dimension_print (r, fp, coef, true);
    	  r->b = b;
    	}
    	fputs (s->lineno ? "    " : "  ", fp);
    	fputs (r->c[0]->label, fp);
    	fputc ('\n', fp);
          }
        }
      }
      foreach_constraint (s, r)
        if (r->c) {
          free (r->c[0]->label);
          r->c[0]->label = NULL;
        }
    }

    Interface

    This function performs dimensional analysis and returns true if the code defined by root and n is dimensionally correct and false otherwise. The parameters are:

    • verbosity : verbosity level for the interpreter (1 to 4).
    • maxcalls : maximum number of calls or instructions interpreted.
    • dimensions : where to output a summary of the dimensions of constants.
    • finite : if true only “finite” constants (i.e. constants
    •      different from "zero" or "infinity") are displayed.
    • redundant : if true all constants are listed, even repeated ones.
    • lineno : if true a different output format is used.
    • warn : if true the code will only warn on dimensional errors.

    See also interpreter.c.

    bool ast_check_dimensions (AstRoot * root, Ast * n, int verbosity, int maxcalls,
    			   FILE * dimensions, int finite, int redundant, int lineno,
    			   int warn)
    {
      ast_type_size = dimension_type_size;
      run = dimension_run;
      after_run = dimension_after_run;
      ast_value_print_hook = value_print_dimension_hook;
      ast_assign_hook = dimension_assign;
      ast_binary_operation_hook = dimension_binary_operation;
      ast_internal_functions_hook = dimension_internal_functions;
      ast_choose_hook = dimension_choose;
      System * system = system_new();
      system->output = dimensions;
      system->finite = finite;
      system->redundant = redundant;
      system->lineno = lineno;
      system->warn = warn;
      int remainingcalls = ast_run (root, n, verbosity, maxcalls, system);
      if (!remainingcalls && dimensions) {
        AstTerminal * t = ast_left_terminal (n);
        fprintf (dimensions, "%s:%d: warning (dimensions): run stopped after %d instructions (see also -maxcalls)\n",
    	     t->file, t->line, maxcalls);
      }
      system_destroy (system);
      return remainingcalls >= 0;
    }