Program Listing for File EigenLab.hpp

Return to documentation for file (/tmp/ws/src/grid_map/grid_map_filters/include/EigenLab/EigenLab.hpp)

// --*-  Mode: C++; c-basic-offset:4; indent-tabs-mode:t; tab-width:4 -*--
// EigenLab
// Version: 1.0.0
// Author: Dr. Marcel Paz Goldschen-Ohm
// Email:  marcel.goldschen@gmail.com
// Copyright (c) 2015 by Dr. Marcel Paz Goldschen-Ohm.
// Licence: MIT
//----------------------------------------

#ifndef EIGENLAB__EIGENLAB_HPP_
#define EIGENLAB__EIGENLAB_HPP_

#include <Eigen/Dense>
#include <iomanip>
#include <type_traits>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

// Define both DEBUG and EIGENLAB_DEBUG for step-by-step equation parsing printouts.
#ifndef DEBUG
// #define DEBUG
#endif

#ifndef EIGENLAB_DEBUG
// #define EIGENLAB_DEBUG
#endif

#ifdef DEBUG
# include <iostream>
#endif

namespace EigenLab
{
//----------------------------------------
// A wrapper for a matrix whose data is either stored locally or shared.
//
// Template typename Derived can be any dynamically sized matrix type supported by Eigen.
//
// !!! matrix() promises to ALWAYS return a map to the matrix data whether it's
// stored locally or externally in some shared memory.
//
// !!! local() provides direct access to the local data, but this data is
// ONLY valid when isLocal() is true. In most cases, you're best off
// accessing the matrix data via matrix() instead.
//----------------------------------------
template<typename Derived = Eigen::MatrixXd>
class Value
{
private:
  // Local matrix data.
  Derived mLocal;

  // Map to shared matrix data (map points to mLocal if the data is local).
  // !!! This map promises to ALWAYS point to the matrix data whether it's
  // stored locally in mLocal or externally in some shared memory.
  Eigen::Map<Derived> mShared;

  // Flag indicating whether the local data is being used.
  bool mIsLocal;

public:
  // Access mapped data (whether its local or shared).
  // !!! matrix() promises to ALWAYS return a map to the matrix data whether it's
  // stored locally in mLocal or externally in some shared memory.
  inline Eigen::Map<Derived> & matrix()
  {
    return mShared;
  }
  inline const Eigen::Map<Derived> & matrix() const {return mShared;}

  // Access local data.
  // !!! WARNING! This data is ONLY valid if isLocal() is true.
  // !!! WARNING! If you change the local data via this method,
  // you MUST call mapLocal() immediately afterwards.
  // In most cases, you're best off accessing the matrix data via matrix() instead.
  inline Derived & local() {return mLocal;}
  inline const Derived & local() const {return mLocal;}

  // Is mapped data local?
  inline bool isLocal() const {return mIsLocal;}

  // Set mapped data to point to local data.
  inline void mapLocal()
  {
    new(&mShared) Eigen::Map<Derived>(mLocal.data(), mLocal.rows(), mLocal.cols());
    mIsLocal = true;
  }

  //  Copy shared data to local data (if needed).
  inline void copySharedToLocal() {if (!isLocal()) {mLocal = mShared; mapLocal();}}

  // Set local data.
  Value()
  : mLocal(1, 1), mShared(mLocal.data(), mLocal.rows(), mLocal.cols()), mIsLocal(true)
  {
  }
  explicit Value(const typename Derived::Scalar s)
  : mLocal(Derived::Constant(1, 1, s)), mShared(
      mLocal.data(), mLocal.rows(), mLocal.cols()), mIsLocal(true)
  {
  }
  explicit Value(const Derived & mat)
  : mLocal(mat), mShared(mLocal.data(), mLocal.rows(), mLocal.cols()),
    mIsLocal(true)
  {
  }
  inline void setLocal(const typename Derived::Scalar s)
  {
    mLocal = Derived::Constant(1, 1, s); mapLocal();
  }
  inline void setLocal(const Eigen::MatrixBase<Derived> & mat) {mLocal = mat; mapLocal();}
  inline void setLocal(const Value & val) {mLocal = val.matrix(); mapLocal();}
  inline void setLocal(const typename Derived::Scalar * data, size_t rows = 1, size_t cols = 1)
  {
    setShared(data, rows, cols); copySharedToLocal();
  }
  inline Value & operator=(const typename Derived::Scalar s) {setLocal(s); return *this;}
  inline Value & operator=(const Derived & mat) {setLocal(mat); return *this;}

  // Set shared data.
  explicit Value(const typename Derived::Scalar * data, size_t rows = 1, size_t cols = 1)
  : mShared(
      const_cast<typename Derived::Scalar *>(data), rows, cols), mIsLocal(false)
  {
  }
  inline void setShared(const typename Derived::Scalar * data, size_t rows = 1, size_t cols = 1)
  {
    new(&mShared) Eigen::Map<Derived>(const_cast<typename Derived::Scalar *>(data), rows, cols);
    mIsLocal = false;
  }
  inline void setShared(const Derived & mat) {setShared(mat.data(), mat.rows(), mat.cols());}
  inline void setShared(const Value & val)
  {
    setShared(val.matrix().data(), val.matrix().rows(), val.matrix().cols());
  }

  // Set to local or shared data dependent on whether val
  // maps its own local data or some other shared data.
  Value(const Value & val)
  : mLocal(1, 1), mShared(mLocal.data(), mLocal.rows(), mLocal.cols())
  {
    (*this) = val;
  }
  inline Value & operator=(const Value & val)
  {
    if (val.isLocal()) {
      setLocal(val);
    } else {
      setShared(val);
    } return *this;
  }
};
typedef Value<Eigen::MatrixXd> ValueXd;
typedef Value<Eigen::MatrixXf> ValueXf;
typedef Value<Eigen::MatrixXi> ValueXi;

// check if a class has a comparison operator (ie. std::complex does not)
template<typename T>
struct has_operator_lt_impl
{
  template<class U>
  // deepcode ignore CopyPasteError: We will not change third party code yet.
  static auto test(U *)->decltype(std::declval<U>() < std::declval<U>());
  template<typename>
  static auto test(...)->std::false_type;
  using type = typename std::is_same<bool, decltype(test<T>(0))>::type;
};
template<typename T>
struct has_operator_lt : has_operator_lt_impl<T>::type {};

//----------------------------------------
// Equation parser.
//
// Template typename Derived can be any dynamically sized matrix type supported by Eigen.
//----------------------------------------
template<typename Derived = Eigen::MatrixXd>
class Parser
{
public:
  // A map to hold named values.
  typedef std::map<std::string, Value<Derived>> ValueMap;

private:
  // Variables are stored in a map of named values.
  ValueMap mVariables;

  // Operator symbols and function names used by the parser.
  std::string mOperators1, mOperators2;
  std::vector<std::string> mFunctions;

  // Expressions are parsed by first splitting them into chunks.
  struct Chunk
  {
    std::string field;
    int type;
    Value<Derived> value;
    int row0, col0, rows, cols;
    Chunk(
      const std::string & str = "", int t = -1,
      const Value<Derived> & val = Value<Derived>())
    : field(str), type(t), value(val),
      row0(-1), col0(-1), rows(-1), cols(-1)
    {
    }
  };
  enum ChunkType { VALUE = 0, VARIABLE, OPERATOR, FUNCTION };
  typedef std::vector<Chunk> ChunkArray;
  typedef typename Derived::Index Index;
  bool mCacheChunkedExpressions;
  std::map<std::string, ChunkArray> mCachedChunkedExpressions;

public:
  // Access to named variables.
  // !!! WARNING! var(name) will create the variable name if it does not already exist.
  inline ValueMap & vars() {return mVariables;}
  inline Value<Derived> & var(const std::string & name)
  {
    return mVariables[name];
  }

  // Check if a variable exists.
  inline bool hasVar(const std::string & name) {return isVariable(name);}

  // Delete a variable.
  inline void clearVar(const std::string & name)
  {
    typename ValueMap::iterator it = mVariables.find(name); if (it != mVariables.end()) {
      mVariables.erase(it);
    }
  }

  // Expression chunk caching.
  inline bool cacheExpressions() const {return mCacheChunkedExpressions;}
  inline void setCacheExpressions(bool b) {mCacheChunkedExpressions = b;}
  inline void clearCachedExpressions() {mCachedChunkedExpressions.clear();}

  Parser();
  ~Parser()
  {
    clearCachedExpressions();
  }

  // Evaluate an expression and return the result in a value wrapper.
  Value<Derived> eval(const std::string & expression);

private:
  void splitEquationIntoChunks(
    const std::string & expression, ChunkArray & chunks,
    std::string & code);
  std::string::const_iterator findClosingBracket(
    const std::string & str,
    const std::string::const_iterator openingBracket,
    const char closingBracket) const;
  std::vector<std::string> splitArguments(const std::string & str, const char delimeter) const;
  void evalIndexRange(const std::string & str, int * first, int * last, int numIndices);
  void evalMatrixExpression(const std::string & str, Value<Derived> & mat);
  void evalFunction(
    const std::string & name, std::vector<std::string> & args,
    Value<Derived> & result);
  bool evalFunction_1_lt(
    const std::string & name, Value<Derived> & arg,
    Value<Derived> & result, std::false_type);
  bool evalFunction_1_lt(
    const std::string & name, Value<Derived> & arg,
    Value<Derived> & result, std::true_type);
  bool evalFunction_2_lt(
    const std::string & name, Value<Derived> & arg0,
    Value<Derived> & arg1, Value<Derived> & result, std::false_type);
  bool evalFunction_2_lt(
    const std::string & name, Value<Derived> & arg0,
    Value<Derived> & arg1, Value<Derived> & result, std::true_type);

  void evalNumericRange(const std::string & str, Value<Derived> & mat);
  inline bool isVariable(const std::string & name) const {return mVariables.count(name) > 0;}
  inline bool isOperator(const char c) const
  {
    return std::find(mOperators1.begin(), mOperators1.end(), c) != mOperators1.end();
  }
  bool isOperator(const std::string & str) const;
  inline bool isFunction(const std::string & str) const
  {
    return std::find(mFunctions.begin(), mFunctions.end(), str) != mFunctions.end();
  }
  void evalIndices(ChunkArray & chunks);
  void evalNegations(ChunkArray & chunks);
  void evalPowers(ChunkArray & chunks);
  void evalMultiplication(ChunkArray & chunks);
  void evalAddition(ChunkArray & chunks);
  void evalAssignment(ChunkArray & chunks);
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  void printChunks(
    ChunkArray & chunks, size_t maxRows = 2, size_t maxCols = 2,
    int precision = 0);
  void printVars(size_t maxRows = 2, size_t maxCols = 2, int precision = 0);
  std::string textRepresentation(
    Value<Derived> & val, size_t maxRows = 2, size_t maxCols = 2,
    int precision = 0);
#       endif
#endif

public:
  static std::string trim(const std::string & str);
  static std::vector<std::string> split(const std::string & str, const char delimeter);
  template<typename T>
  static bool isNumber(const std::string & str, T * num = 0);
  template<typename T>
  static T stringToNumber(const std::string & str);
  template<typename T>
  static std::string numberToString(T num, int precision = 0);
#ifdef DEBUG
  void test_w_lt(
    size_t & numFails, typename Derived::Scalar & s, Derived & a34, Derived & b34,
    Derived & c43, Derived & v, std::true_type);
  void test_w_lt(
    size_t & numFails, typename Derived::Scalar & s, Derived & a34, Derived & b34,
    Derived & c43, Derived & v, std::false_type);
  size_t test();
#endif
};
typedef Parser<Eigen::MatrixXd> ParserXd;
typedef Parser<Eigen::MatrixXf> ParserXf;
typedef Parser<Eigen::MatrixXi> ParserXi;

//----------------------------------------
// Function definitions.
//----------------------------------------
template<typename Derived>
Parser<Derived>::Parser()
: mOperators1("+-*/^()[]="),
  mOperators2(".+.-.*./.^"),
  mCacheChunkedExpressions(false)
{
  // Coefficient-wise operations.
  mFunctions.push_back("abs");
  mFunctions.push_back("sqrt");
  mFunctions.push_back("square");
  mFunctions.push_back("exp");
  mFunctions.push_back("log");
  mFunctions.push_back("log10");
  mFunctions.push_back("sin");
  mFunctions.push_back("cos");
  mFunctions.push_back("tan");
  mFunctions.push_back("asin");
  mFunctions.push_back("acos");

  // Matrix reduction operations.
  mFunctions.push_back("trace");
  mFunctions.push_back("norm");
  mFunctions.push_back("size");
  if (has_operator_lt<typename Derived::Scalar>::value) {
    mFunctions.push_back("min");
    mFunctions.push_back("minOfFinites");
    mFunctions.push_back("max");
    mFunctions.push_back("maxOfFinites");
    mFunctions.push_back("absmax");
    mFunctions.push_back("cwiseMin");
    mFunctions.push_back("cwiseMax");
  }
  mFunctions.push_back("mean");
  mFunctions.push_back("meanOfFinites");
  mFunctions.push_back("sum");
  mFunctions.push_back("sumOfFinites");
  mFunctions.push_back("prod");
  mFunctions.push_back("numberOfFinites");

  // Matrix operations.
  mFunctions.push_back("transpose");
  mFunctions.push_back("conjugate");
  mFunctions.push_back("adjoint");

  // Matrix initializers.
  mFunctions.push_back("zeros");
  mFunctions.push_back("ones");
  mFunctions.push_back("eye");
}

template<typename Derived>
Value<Derived> Parser<Derived>::eval(const std::string & expression)
{
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  std::cout << "---" << std::endl;
  std::cout << "EXPRESSION: " << expression << std::endl;
#       endif
#endif
  ChunkArray chunks;
  std::string code;
  splitEquationIntoChunks(trim(expression), chunks, code);
  evalIndices(chunks);
  evalNegations(chunks);
  evalPowers(chunks);
  evalMultiplication(chunks);
  evalAddition(chunks);
  evalAssignment(chunks);
  if (chunks.size() != 1) {
    throw std::runtime_error(
            "Failed to reduce expression '" + expression + "' to a single value.");
  }
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  std::cout << "---" << std::endl;
#       endif
#endif
  if (chunks[0].type == VARIABLE) {
    if (!isVariable(chunks[0].field)) {
      throw std::runtime_error("Unknown variable '" + chunks[0].field + "'.");
    }
    return mVariables[chunks[0].field];
  }
  return chunks[0].value;
}

template<typename Derived>
void Parser<Derived>::splitEquationIntoChunks(
  const std::string & expression,
  ChunkArray & chunks, std::string & code)
{
  if (cacheExpressions()) {
    if (mCachedChunkedExpressions.count(expression) > 0) {
      chunks = mCachedChunkedExpressions.at(expression);
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
      std::cout << "CACHED CHUNKS: "; printChunks(chunks); std::cout << std::endl;
#       endif
#endif
      return;
    }
  }

  for (std::string::const_iterator it = expression.begin(); it != expression.end(); ) {
    int prevType = (chunks.size() ? chunks.back().type : -1);
    char ci = (*it);
    if (isspace(ci)) {
      // Ignore whitespace.
      it++;
    } else if (ci == '(' && (prevType == VALUE || prevType == VARIABLE)) {
      // Index group.
      std::string::const_iterator jt = findClosingBracket(expression, it, ')');
      if (jt == expression.end()) {
        throw std::runtime_error("Missing closing bracket for '" + std::string(it, jt) + "'.");
      }
      std::string field = std::string(it + 1, jt);    // Outer brackets stripped.
      if (prevType == VARIABLE) {
        if (!isVariable(chunks.back().field)) {
          throw std::runtime_error("Unknown variable '" + chunks.back().field + "'.");
        }
        chunks.back().value.setShared(var(chunks.back().field));
      }
      int first, last;
      int rows = static_cast<int>(chunks.back().value.matrix().rows());
      int cols = static_cast<int>(chunks.back().value.matrix().cols());
      std::vector<std::string> args = splitArguments(field, ',');
      if (args.size() == 1) {
        if (cols == 1) {
          evalIndexRange(args[0], &first, &last, rows);
          chunks.back().row0 = first;
          chunks.back().col0 = 0;
          // (last == -1 ? int(chunks.back().value.matrix().rows()) : last + 1) - first;
          chunks.back().rows = last + 1 - first;
          chunks.back().cols = 1;
        } else if (rows == 1) {
          evalIndexRange(args[0], &first, &last, cols);
          chunks.back().row0 = 0;
          chunks.back().col0 = first;
          chunks.back().rows = 1;
          // (last == -1 ? int(chunks.back().value.matrix().cols()) : last + 1) - first;
          chunks.back().cols = last + 1 - first;
        } else {
          throw std::runtime_error(
                  "Missing row or column indices for '(" + chunks.back().field + "(" + field +
                  ")'.");
        }
      } else if (args.size() == 2) {
        evalIndexRange(args[0], &first, &last, rows);
        chunks.back().row0 = first;
        // (last == -1 ? int(chunks.back().value.matrix().rows()) : last + 1) - first;
        chunks.back().rows = last + 1 - first;
        evalIndexRange(args[1], &first, &last, cols);
        chunks.back().col0 = first;
        // (last == -1 ? int(chunks.back().value.matrix().cols()) : last + 1) - first;
        chunks.back().cols = last + 1 - first;
      } else {
        throw std::runtime_error(
                "Invalid index expression '" + chunks.back().field + "(" + field + ")'.");
      }
      code += "i";
      it = jt + 1;
    } else if (ci == '(' && prevType == FUNCTION) {
      // Function argument group.
      std::string::const_iterator jt = findClosingBracket(expression, it, ')');
      if (jt == expression.end()) {
        throw std::runtime_error("Missing closing bracket for '" + std::string(it, jt) + "'.");
      }
      std::string field = std::string(it + 1, jt);    // Outer brackets stripped.
      std::vector<std::string> args = splitArguments(field, ',');
      evalFunction(chunks.back().field, args, chunks.back().value);
      chunks.back().field += "(" + field + ")";
      chunks.back().type = VALUE;
      code += (chunks.back().value.matrix().size() == 1 ? "n" : "M");
      it = jt + 1;
    } else if (ci == '(') {
      // Recursively evaluate group to a single value.
      std::string::const_iterator jt = findClosingBracket(expression, it, ')');
      if (jt == expression.end()) {
        throw std::runtime_error("Missing closing bracket for '" + std::string(it, jt) + "'.");
      }
      std::string field = std::string(it + 1, jt);    // Outer brackets stripped.
      chunks.push_back(Chunk(field, prevType = VALUE, eval(field)));
      code += (chunks.back().value.matrix().size() == 1 ? "n" : "M");
      it = jt + 1;
    } else if (ci == '[') {
      // Evaluate matrix.
      if (prevType == VALUE || prevType == VARIABLE) {
        throw std::runtime_error(
                "Invalid operation '" + chunks.back().field + std::string(1, ci) + "'.");
      }
      std::string::const_iterator jt = findClosingBracket(expression, it, ']');
      if (jt == expression.end()) {
        throw std::runtime_error("Missing closing bracket for '" + std::string(it, jt) + "'.");
      }
      std::string field = std::string(it + 1, jt);    // Outer brackets stripped.
      chunks.push_back(Chunk("[" + field + "]", prevType = VALUE));
      evalMatrixExpression(field, chunks.back().value);
      code += (chunks.back().value.matrix().size() == 1 ? "n" : "M");
      it = jt + 1;
    } else if (it + 1 != expression.end() && isOperator(std::string(it, it + 2))) {
      // Double character operator.
      std::string field = std::string(it, it + 2);
      chunks.push_back(Chunk(field, prevType = OPERATOR));
      code += field;
      it += 2;
    } else if (isOperator(ci)) {
      // Single character operator.
      std::string field = std::string(1, ci);
      chunks.push_back(Chunk(field, prevType = OPERATOR));
      code += field;
      it++;
    } else {
      // Non-operator: value range, number, function, or variable name.
      std::string::const_iterator jt = it + 1;
      // accept fp-strings, ie [+-]
      unsigned char state = 1;
      for (std::string::const_iterator kt = it; state && kt != expression.end(); kt++) {
        unsigned char token;
        if (*kt == ' ') {token = 0;} else if (*kt == '+' || *kt == '-') {
          token = 1;
        } else if (isdigit(*kt)) {token = 2;} else if (*kt == '.') {
          token = 3;
        } else if (*kt == 'e' || *kt == 'E') {token = 4;} else {break;}
        static const char nextstate[9][5] = {{0},
          {1, 2, 3, 4, 0},
          {0, 0, 3, 4, 0},
          {0, 0, 3, 5, 6},
          {0, 0, 5, 0, 0},
          {0, 0, 5, 0, 6},
          {0, 7, 8, 0, 0},
          {0, 0, 8, 0, 0},
          {0, 0, 8, 0, 0}};
        // WARN("state=" << (int)state << " token(" << *kt << ")=" << (int)token
        //   << " nextstate = " << (int)nextstate[state][token] << "\n");
        state = nextstate[state][token];
        if (state == 8) {jt = kt;}
      }
      for (; jt != expression.end(); jt++) {
        if (isOperator(*jt) ||
          (jt + 1 != expression.end() && isOperator(std::string(jt, jt + 2))))
        {
          break;
        }
      }
      std::string field = trim(std::string(it, jt));
      if (prevType == VALUE || prevType == VARIABLE) {
        throw std::runtime_error("Invalid operation '" + chunks.back().field + field + "'.");
      }
      double num;
      if (field.find(":") != std::string::npos) {
        // Numeric range.
        chunks.push_back(Chunk(field, prevType = VALUE));
        evalNumericRange(field, chunks.back().value);
        code += (chunks.back().value.matrix().size() == 1 ? "n" : "M");
      } else if (isNumber<double>(field, &num)) {
        // Number.
        chunks.push_back(Chunk(field, prevType = VALUE, Value<Derived>(num)));
        code += "n";
      } else if (isVariable(field)) {
        // Local variable.
        chunks.push_back(Chunk(field, prevType = VARIABLE));
        code += (mVariables[field].matrix().size() == 1 ? "vn" : "vM");
      } else if (isFunction(field)) {
        // Function.
        chunks.push_back(Chunk(field, prevType = FUNCTION));
      } else {
        // New undefined variable.
        chunks.push_back(Chunk(field, prevType = VARIABLE));
        code += "vU";
      }
      it = jt;
    }
  }             // it
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  std::cout << "CHUNKS: "; printChunks(chunks); std::cout << std::endl;
  std::cout << "CODE: " << code << std::endl;
#       endif
#endif
  if (cacheExpressions()) {
    mCachedChunkedExpressions[expression] = chunks;
  }
}

template<typename Derived>
std::string::const_iterator Parser<Derived>::findClosingBracket(
  const std::string & str,
  const std::string::const_iterator openingBracket,
  const char closingBracket) const
{
  int depth = 1;
  std::string::const_iterator it = openingBracket + 1;
  for (; it != str.end(); it++) {
    if ((*it) == (*openingBracket)) {depth++;} else if ((*it) == closingBracket) {depth--;}
    if (depth == 0) {return it;}
  }
  return str.end();
}

template<typename Derived>
std::vector<std::string> Parser<Derived>::splitArguments(
  const std::string & str,
  const char delimeter) const
{
  std::vector<std::string> args;
  std::string::const_iterator i0 = str.begin();
  for (std::string::const_iterator it = str.begin(); it != str.end(); it++) {
    if ((*it) == '(') {it = findClosingBracket(str, it, ')');} else if ((*it) == '[') {
      it = findClosingBracket(str, it, ']');
    } else if ((*it) == delimeter) {
      args.push_back(trim(std::string(i0, it)));
      i0 = it + 1;
    }
  }
  args.push_back(std::string(i0, str.end()));
  return args;
}

template<typename Derived>
void Parser<Derived>::evalIndexRange(
  const std::string & str, int * first, int * last,
  int numIndices)
{
  if (str.empty()) {
    throw std::runtime_error("Empty index range.");
  }
  ValueXi valuei;
  ParserXi parseri;
  size_t pos;
  for (std::string::const_iterator it = str.begin(); it != str.end(); it++) {
    if ((*it) == ':') {
      std::string firstStr = trim(std::string(str.begin(), it));
      std::string lastStr = trim(std::string(it + 1, str.end()));
      if (firstStr.empty() && lastStr.empty()) {
        (*first) = 0;
        (*last) = numIndices - 1;
        return;
      }
      if (firstStr.empty() || lastStr.empty()) {
        throw std::runtime_error("Missing indices for '" + str + "'.");
      }

      pos = firstStr.find("end");
      if (pos != std::string::npos) {
        firstStr =
          firstStr.substr(0, pos) + numberToString<int>(numIndices - 1) + firstStr.substr(
          pos + 3);
      }
      pos = lastStr.find("end");
      if (pos != std::string::npos) {
        lastStr =
          lastStr.substr(0, pos) + numberToString<int>(numIndices - 1) + lastStr.substr(
          pos + 3);
      }

      valuei = parseri.eval(firstStr);
      if (valuei.matrix().size() != 1) {
        throw std::runtime_error("Invalid indices '" + str + "'.");
      }
      (*first) = valuei.matrix()(0, 0);

      valuei = parseri.eval(lastStr);
      if (valuei.matrix().size() != 1) {
        throw std::runtime_error("Invalid indices '" + str + "'.");
      }
      (*last) = valuei.matrix()(0, 0);

      return;
    }
  }
  std::string firstStr = str;

  pos = firstStr.find("end");
  if (pos != std::string::npos) {
    firstStr =
      firstStr.substr(
      0,
      pos) + numberToString<int>(numIndices - 1) + firstStr.substr(pos + 3);
  }

  valuei = parseri.eval(firstStr);
  if (valuei.matrix().size() != 1) {
    throw std::runtime_error("Invalid index '" + str + "'.");
  }
  (*first) = valuei.matrix()(0, 0);
  (*last) = (*first);
}

template<typename Derived>
void Parser<Derived>::evalMatrixExpression(const std::string & str, Value<Derived> & mat)
{
  // !!! Expression may NOT include outer brackets, although brackets for individual rows are OK.
  std::vector<std::string> rows = splitArguments(str, ';');
  std::vector<std::vector<typename Derived::Scalar>> temp;
  Value<Derived> submatrix;
  size_t row0 = 0, col0 = 0, nrows = 0, ncols = 0;
  size_t pos;
  for (size_t i = 0; i < rows.size(); i++) {
    // Strip row brackets if they exist.
    if (rows[i][0] == '[' && rows[i].back() == ']') {
      rows[i] = rows[i].substr(1, static_cast<int>(rows[i].size()) - 2);
    }
    std::vector<std::string> cols = splitArguments(rows[i], ',');
    col0 = 0;
    ncols = 0;
    for (size_t j = 0; j < cols.size(); j++) {
      pos = cols[j].find(":");
      if (pos != std::string::npos) {
        std::string firstStr = cols[j].substr(0, pos);
        std::string lastStr = cols[j].substr(pos + 1);
        pos = lastStr.find(":");
        if (pos != std::string::npos) {
          std::string stepStr = lastStr.substr(0, pos);
          lastStr = lastStr.substr(pos + 1);
          if (lastStr.find(":") != std::string::npos) {
            throw std::runtime_error(
                    "Invalid matrix definition '[" + str + "]'. Invalid range '" + cols[j] + "'.");
          }
          // first:step:last
          Value<Derived> first = eval(firstStr);
          Value<Derived> step = eval(stepStr);
          Value<Derived> last = eval(lastStr);
          if (first.matrix().size() != 1 || step.matrix().size() != 1 ||
            last.matrix().size() != 1)
          {
            throw std::runtime_error(
                    "Invalid matrix definition '[" + str + "]'. Invalid range '" + cols[j] + "'.");
          }
          typename Derived::RealScalar sfirst = std::real(first.matrix()(0));
          typename Derived::RealScalar sstep = std::real(step.matrix()(0));
          typename Derived::RealScalar slast = std::real(last.matrix()(0));
          if (sfirst == slast) {
            submatrix.local().setConstant(1, 1, sfirst);
            submatrix.mapLocal();
          } else if ((slast - sfirst >= 0 && sstep > 0) || (slast - sfirst <= 0 && sstep < 0)) {
            int n = floor((slast - sfirst) / sstep) + 1;
            submatrix.local().resize(1, n);
            for (int k = 0; k < n; ++k) {
              submatrix.local()(0, k) = sfirst + k * sstep;
            }
            submatrix.mapLocal();
          } else {
            throw std::runtime_error(
                    "Invalid matrix definition '[" + str + "]'. Invalid range '" + cols[j] + "'.");
          }
        } else {
          // first:last => first:1:last
          Value<Derived> first = eval(firstStr);
          Value<Derived> last = eval(lastStr);
          if (first.matrix().size() != 1 || last.matrix().size() != 1) {
            throw std::runtime_error(
                    "Invalid matrix definition '[" + str + "]'. Invalid range '" + cols[j] + "'.");
          }
          typename Derived::RealScalar sfirst = std::real(first.matrix()(0));
          typename Derived::RealScalar slast = std::real(last.matrix()(0));
          if (sfirst == slast) {
            submatrix.local().setConstant(1, 1, sfirst);
            submatrix.mapLocal();
          } else if (slast - sfirst >= 0) {
            int n = floor(slast - sfirst) + 1;
            submatrix.local().resize(1, n);
            for (int k = 0; k < n; ++k) {
              submatrix.local()(0, k) = sfirst + k;
            }
            submatrix.mapLocal();
          } else {
            throw std::runtime_error(
                    "Invalid matrix definition '[" + str + "]'. Invalid range '" + cols[j] + "'.");
          }
        }
      } else {
        submatrix = eval(cols[j]);
      }
      if (j > 0 && size_t(submatrix.matrix().cols()) != nrows) {
        throw std::runtime_error(
                "Invalid matrix definition '[" + str + "]'. Successive column entries '" +
                cols[static_cast<int>(j) - 1] + "' and '" + cols[j] +
                "' do not have the same number of rows.");
      }
      nrows = submatrix.matrix().rows();
      ncols += submatrix.matrix().cols();
      temp.resize(row0 + submatrix.matrix().rows());
      for (size_t row = 0; row < size_t(submatrix.matrix().rows()); row++) {
        temp[row0 + row].resize(col0 + submatrix.matrix().cols());
        for (size_t col = 0; col < size_t(submatrix.matrix().cols()); col++) {
          temp[row0 + row][col0 + col] = submatrix.matrix()(row, col);
        }
      }
      col0 += submatrix.matrix().cols();
    }
    if (row0 > 0 && ncols != temp[static_cast<int>(row0) - 1].size()) {
      throw std::runtime_error(
              "Invalid matrix definition '[" + str + "]'. Successive row entries '" +
              rows[static_cast<int>(i) - 1] + "' and '" + rows[i] +
              "' do not have the same number of columns.");
    }
    row0 += nrows;
  }
  if (temp.empty()) {return;}
  nrows = temp.size();
  ncols = temp[0].size();
  mat.setLocal(Derived(nrows, ncols));
  for (size_t row = 0; row < nrows; row++) {
    for (size_t col = 0; col < ncols; col++) {
      mat.matrix()(row, col) = temp[row][col];
    }
  }
  mat.mapLocal();
}

template<typename Derived>
bool Parser<Derived>::evalFunction_1_lt(
  const std::string & name, Value<Derived> & arg,
  Value<Derived> & result, std::true_type)
{
  if (name == "min") {
    result.setLocal(arg.matrix().minCoeff());
    return true;
  } else if (name == "minOfFinites") {
    result.setLocal(arg.matrix().minCoeffOfFinites());
    return true;
  } else if (name == "max") {
    result.setLocal(arg.matrix().maxCoeff());
    return true;
  } else if (name == "maxOfFinites") {
    result.setLocal(arg.matrix().maxCoeffOfFinites());
    return true;
  } else if (name == "absmax") {
    typename Derived::Scalar minimum = arg.matrix().minCoeff();
    typename Derived::Scalar maximum = arg.matrix().maxCoeff();
    result.setLocal(std::abs(maximum) >= std::abs(minimum) ? maximum : minimum);
    return true;
  }
  return false;
}

template<typename Derived>
bool Parser<Derived>::evalFunction_1_lt(
  const std::string & /*name*/,
  Value<Derived> & /*arg0*/,
  Value<Derived> & /*result*/, std::false_type)
{
  return false;
}

template<typename Derived>
bool Parser<Derived>::evalFunction_2_lt(
  const std::string & name, Value<Derived> & arg0,
  Value<Derived> & arg1, Value<Derived> & result,
  std::true_type)
{
  if (name == "min") {
    if (arg1.matrix().size() != 1) {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
    int dim = floor(std::real(arg1.matrix()(0, 0)));
    if ((dim != 0 && dim != 1) || dim != std::real(arg1.matrix()(0, 0))) {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
    if (dim == 0) {
      result.local() = arg0.matrix().colwise().minCoeff();
      result.mapLocal();
      return true;
    } else if (dim == 1) {
      result.local() = arg0.matrix().rowwise().minCoeff();
      result.mapLocal();
      return true;
    }
  } else if (name == "max") {
    if (arg1.matrix().size() != 1) {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
    int dim = floor(std::real(arg1.matrix()(0, 0)));
    if ((dim != 0 && dim != 1) || dim != std::real(arg1.matrix()(0, 0))) {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
    if (dim == 0) {
      result.local() = arg0.matrix().colwise().maxCoeff();
      result.mapLocal();
      return true;
    } else if (dim == 1) {
      result.local() = arg0.matrix().rowwise().maxCoeff();
      result.mapLocal();
      return true;
    }
  } else if (name == "absmax") {
    if (arg1.matrix().size() != 1) {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
    int dim = floor(std::real(arg1.matrix()(0, 0)));
    if ((dim != 0 && dim != 1) || dim != std::real(arg1.matrix()(0, 0))) {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
    if (dim == 0) {
      result.local() = arg0.matrix().colwise().maxCoeff();
      result.mapLocal();
      Derived minimum = arg0.matrix().colwise().minCoeff();
      for (size_t i = 0; i < size_t(result.matrix().size()); i++) {
        if (std::abs(result.matrix()(i)) < std::abs(minimum(i))) {
          result.matrix()(i) = minimum(i);
        }
      }
      return true;
    } else if (dim == 1) {
      result.local() = arg0.matrix().rowwise().maxCoeff();
      result.mapLocal();
      Derived minimum = arg0.matrix().rowwise().minCoeff();
      for (size_t i = 0; i < size_t(result.matrix().size()); i++) {
        if (std::abs(result.matrix()(i)) < std::abs(minimum(i))) {
          result.matrix()(i) = minimum(i);
        }
      }
      return true;
    }
  } else if (name == "cwiseMin") {
    if (arg1.matrix().size() == 1) {
      typename Derived::Scalar arg1scalar = arg1.matrix()(0, 0);
      Derived arg1matrix = Derived::Constant(
        arg0.matrix().rows(),
        arg0.matrix().cols(), arg1scalar);
      result.local() = arg0.matrix().cwiseMin(arg1matrix);
      result.mapLocal();
      return true;
    } else if (  // NOLINT
      arg0.matrix().cols() == arg1.matrix().cols() &&
      arg0.matrix().rows() == arg1.matrix().rows())
    {
      result.local() = arg0.matrix().cwiseMin(arg1.matrix());
      result.mapLocal();
      return true;
    } else {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
  } else if (name == "cwiseMax") {
    if (arg1.matrix().size() == 1) {
      typename Derived::Scalar arg1scalar = arg1.matrix()(0, 0);
      Derived arg1matrix = Derived::Constant(
        arg0.matrix().rows(),
        arg0.matrix().cols(), arg1scalar);
      result.local() = arg0.matrix().cwiseMax(arg1matrix);
      result.mapLocal();
      return true;
    } else if (  // NOLINT
      arg0.matrix().cols() == arg1.matrix().cols() &&
      arg0.matrix().rows() == arg1.matrix().rows())
    {
      result.local() = arg0.matrix().cwiseMax(arg1.matrix());
      result.mapLocal();
      return true;
    } else {
      throw std::runtime_error("Invalid dimension argument for function '" + name + "(...)'.");
    }
  }
  return false;
}

template<typename Derived>
bool Parser<Derived>::evalFunction_2_lt(
  const std::string & /*name*/,
  Value<Derived> & /*arg0*/,
  Value<Derived> & /*arg1*/,
  Value<Derived> & /*result*/, std::false_type)
{
  return false;
}

template<typename Derived>
void Parser<Derived>::evalFunction(
  const std::string & name,
  std::vector<std::string> & args,
  Value<Derived> & result)
{
  if (args.size() == 1) {
    Value<Derived> arg = eval(args[0]);
    if (name == "abs") {
      result.local() = arg.matrix().array().abs().template cast<typename Derived::Scalar>();
      result.mapLocal();
      return;
    } else if (name == "sqrt") {
      result.local() = arg.matrix().array().sqrt();
      result.mapLocal();
      return;
    } else if (name == "square") {
      result.local() = arg.matrix().array().square();
      result.mapLocal();
      return;
    } else if (name == "exp") {
      result.local() = arg.matrix().array().exp();
      result.mapLocal();
      return;
    } else if (name == "log") {
      result.local() = arg.matrix().array().log();
      result.mapLocal();
      return;
    } else if (name == "log10") {
      result.local() = arg.matrix().array().log();
      result.local() *= (1.0 / log(10));
      result.mapLocal();
      return;
    } else if (name == "sin") {
      result.local() = arg.matrix().array().sin();
      result.mapLocal();
      return;
    } else if (name == "cos") {
      result.local() = arg.matrix().array().cos();
      result.mapLocal();
      return;
    } else if (name == "tan") {
      result.local() = arg.matrix().array().tan();
      result.mapLocal();
      return;
    } else if (name == "acos") {
      result.local() = arg.matrix().array().acos();
      result.mapLocal();
      return;
    } else if (name == "asin") {
      result.local() = arg.matrix().array().asin();
      result.mapLocal();
      return;
    } else if (name == "trace") {
      result.setLocal(arg.matrix().trace());
      return;
    } else if (name == "norm") {
      result.setLocal(arg.matrix().norm());
      return;
    } else if (  // NOLINT
      evalFunction_1_lt(
        name, arg, result,
        has_operator_lt<typename Derived::Scalar>()))
    {
      return;
    } else if (name == "mean") {
      result.setLocal(arg.matrix().mean());
      return;
    } else if (name == "meanOfFinites") {
      result.setLocal(arg.matrix().meanOfFinites());
      return;
    } else if (name == "sum") {
      result.setLocal(arg.matrix().sum());
      return;
    } else if (name == "sumOfFinites") {
      result.setLocal(arg.matrix().sumOfFinites());
      return;
    } else if (name == "prod") {
      result.setLocal(arg.matrix().prod());
      return;
    } else if (name == "numberOfFinites") {
      result.setLocal(arg.matrix().numberOfFinites());
      return;
    } else if (name == "transpose") {
      result.local() = arg.matrix().transpose();
      result.mapLocal();
      return;
    } else if (name == "conjugate") {
      result.local() = arg.matrix().conjugate();
      result.mapLocal();
      return;
    } else if (name == "adjoint") {
      result.local() = arg.matrix().adjoint();
      result.mapLocal();
      return;
    } else if (name == "zeros") {
      if (arg.matrix().size() != 1) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + ")'.");
      }
      int N = floor(std::real(arg.matrix()(0, 0)));
      if (N <= 0 || N != std::real(arg.matrix()(0, 0))) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + ")'.");
      }
      result.local() = Derived::Zero(N, N);
      result.mapLocal();
      return;
    } else if (name == "ones") {
      if (arg.matrix().size() != 1) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + ")'.");
      }
      int N = floor(std::real(arg.matrix()(0, 0)));
      if (N <= 0 || N != std::real(arg.matrix()(0, 0))) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + ")'.");
      }
      result.local() = Derived::Ones(N, N);
      result.mapLocal();
      return;
    } else if (name == "eye") {
      if (arg.matrix().size() != 1) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + ")'.");
      }
      int N = floor(std::real(arg.matrix()(0, 0)));
      if (N <= 0 || N != std::real(arg.matrix()(0, 0))) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + ")'.");
      }
      result.local() = Derived::Identity(N, N);
      result.mapLocal();
      return;
    } else {
      throw std::runtime_error(
              "Invalid function '" +
              name + "(" + args[0] + ")'.");
    }
  } else if (args.size() == 2) {
    Value<Derived> arg0 = eval(args[0]);
    Value<Derived> arg1 = eval(args[1]);
    if (name == "size") {
      if (arg1.matrix().size() != 1) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      int dim = floor(std::real(arg1.matrix()(0, 0)));
      if ((dim != 0 && dim != 1) || dim != std::real(arg1.matrix()(0, 0))) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      if (dim == 0) {
        result.setLocal((typename Derived::Scalar)arg0.matrix().rows());
        return;
      } else if (dim == 1) {
        result.setLocal((typename Derived::Scalar)arg0.matrix().cols());
        return;
      }
    } else if (  // NOLINT
      evalFunction_2_lt(
        name, arg0, arg1, result,
        has_operator_lt<typename Derived::Scalar>()))
    {
      return;
    } else if (name == "mean") {
      if (arg1.matrix().size() != 1) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      int dim = floor(std::real(arg1.matrix()(0, 0)));
      if ((dim != 0 && dim != 1) || dim != std::real(arg1.matrix()(0, 0))) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      if (dim == 0) {
        result.local() = arg0.matrix().colwise().mean();
        result.mapLocal();
        return;
      } else if (dim == 1) {
        result.local() = arg0.matrix().rowwise().mean();
        result.mapLocal();
        return;
      }
    } else if (name == "sum") {
      if (arg1.matrix().size() != 1) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      int dim = floor(std::real(arg1.matrix()(0, 0)));
      if ((dim != 0 && dim != 1) || dim != std::real(arg1.matrix()(0, 0))) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      if (dim == 0) {
        result.local() = arg0.matrix().colwise().sum();
        result.mapLocal();
        return;
      } else if (dim == 1) {
        result.local() = arg0.matrix().rowwise().sum();
        result.mapLocal();
        return;
      }
    } else if (name == "prod") {
      if (arg1.matrix().size() != 1) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      int dim = floor(std::real(arg1.matrix()(0, 0)));
      if ((dim != 0 && dim != 1) || dim != std::real(arg1.matrix()(0, 0))) {
        throw std::runtime_error(
                "Invalid dimension argument for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      if (dim == 0) {
        result.local() = arg0.matrix().colwise().prod();
        result.mapLocal();
        return;
      } else if (dim == 1) {
        result.local() = arg0.matrix().rowwise().prod();
        result.mapLocal();
        return;
      }
    } else if (name == "zeros") {
      if ((arg0.matrix().size() != 1) || (arg1.matrix().size() != 1)) {
        throw std::runtime_error(
                "Invalid dimension arguments for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      int rows = floor(std::real(arg0.matrix()(0, 0)));
      int cols = floor(std::real(arg1.matrix()(0, 0)));
      if (rows <= 0 || cols <= 0 ||
        rows != std::real(arg0.matrix()(0, 0)) || cols != std::real(arg1.matrix()(0, 0)))
      {
        throw std::runtime_error(
                "Invalid dimension arguments for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      result.local() = Derived::Zero(rows, cols);
      result.mapLocal();
      return;
    } else if (name == "ones") {
      if ((arg0.matrix().size() != 1) || (arg1.matrix().size() != 1)) {
        throw std::runtime_error(
                "Invalid dimension arguments for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      int rows = floor(std::real(arg0.matrix()(0, 0)));
      int cols = floor(std::real(arg1.matrix()(0, 0)));
      if (rows <= 0 || cols <= 0 ||
        rows != std::real(arg0.matrix()(0, 0)) || cols != std::real(arg1.matrix()(0, 0)))
      {
        throw std::runtime_error(
                "Invalid dimension arguments for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      result.local() = Derived::Ones(rows, cols);
      result.mapLocal();
      return;
    } else if (name == "eye") {
      if ((arg0.matrix().size() != 1) || (arg1.matrix().size() != 1)) {
        throw std::runtime_error(
                "Invalid dimension arguments for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      int rows = floor(std::real(arg0.matrix()(0, 0)));
      int cols = floor(std::real(arg1.matrix()(0, 0)));
      if (rows <= 0 || cols <= 0 ||
        rows != std::real(arg0.matrix()(0, 0)) || cols != std::real(arg1.matrix()(0, 0)))
      {
        throw std::runtime_error(
                "Invalid dimension arguments for function '" +
                name + "(" + args[0] + "," + args[1] + ")'.");
      }
      result.local() = Derived::Identity(rows, cols);
      result.mapLocal();
      return;
    } else {
      throw std::runtime_error(
              "Invalid function '" + name + "(" + args[0] + "," + args[1] + ")'.");
    }
  }
  std::string argsStr = "(";
  for (size_t i = 0; i < args.size(); i++) {
    if (i > 0) {argsStr += ",";}
    argsStr += args[i];
  }
  argsStr += ")";
  throw std::runtime_error("Invalid function/arguments for '" + name + argsStr + "'.");
}

template<typename Derived>
void Parser<Derived>::evalNumericRange(const std::string & str, Value<Derived> & mat)
{
  size_t pos = str.find(":");
  if (pos == std::string::npos) {
    throw std::runtime_error("Invalid numeric range '" + str + "'.");
  }
  size_t pos2 = str.substr(pos + 1).find(":");
  if (pos2 == std::string::npos) {
    // first:last
    std::string firstStr = str.substr(0, pos);
    std::string lastStr = str.substr(pos + 1);
    Value<Derived> first = eval(firstStr);
    Value<Derived> last = eval(lastStr);
    if (first.matrix().size() != 1 || last.matrix().size() != 1) {
      throw std::runtime_error("Invalid numeric range '" + str + "'.");
    }
    typename Derived::RealScalar sfirst = std::real(first.matrix()(0, 0));
    typename Derived::RealScalar slast = std::real(last.matrix()(0, 0));
    if (sfirst > slast) {
      throw std::runtime_error("Invalid numeric range '" + str + "'. Must not reverse.");
    }
    int n = 1 + floor(slast - sfirst);
    mat.local().resize(1, n);
    for (int i = 0; i < n; i++) {
      mat.local()(0, i) = sfirst + i;
    }
    mat.mapLocal();
  } else {
    // first:step:last
    pos2 += pos + 1;
    std::string firstStr = str.substr(0, pos);
    std::string stepStr = str.substr(pos + 1, pos2 - pos - 1);
    std::string lastStr = str.substr(pos2 + 1);
    Value<Derived> first = eval(firstStr);
    Value<Derived> step = eval(stepStr);
    Value<Derived> last = eval(lastStr);
    if (first.matrix().size() != 1 || step.matrix().size() != 1 || last.matrix().size() != 1) {
      throw std::runtime_error("Invalid numeric range '" + str + "'.");
    }
    typename Derived::RealScalar sfirst = std::real(first.matrix()(0, 0));
    typename Derived::RealScalar sstep = std::real(step.matrix()(0, 0));
    typename Derived::RealScalar slast = std::real(last.matrix()(0, 0));
    if (sfirst == slast) {
      mat = sfirst;
    } else if (sfirst < slast && sstep > 0) {
      int n = 1 + floor((slast - sfirst) / sstep);
      mat.local().resize(1, n);
      for (int i = 0; i < n; i++) {
        mat.local()(0, i) = sfirst + i * sstep;
      }
      mat.mapLocal();
    } else if (sfirst > slast && sstep < 0) {
      int n = 1 + floor((slast - sfirst) / sstep);
      mat.local().resize(1, n);
      for (int i = 0; i < n; i++) {
        mat.local()(0, i) = sfirst + i * sstep;
      }
      mat.mapLocal();
    } else {
      throw std::runtime_error("Invalid numeric range '" + str + "'.");
    }
  }
}

template<typename Derived>
bool Parser<Derived>::isOperator(const std::string & str) const
{
  if (str.size() == 1) {
    return isOperator(str[0]);
  } else if (str.size() == 2) {
    size_t pos = mOperators2.find(str);
    return pos != std::string::npos && pos % 2 == 0;
  }
  return false;
}

template<typename Derived>
void Parser<Derived>::evalIndices(ChunkArray & chunks)
{
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  bool operationPerformed = false;
#       endif
#endif
  for (typename ChunkArray::iterator it = chunks.begin(); it != chunks.end(); it++) {
    if (it->row0 != -1 &&
      (it->type == VALUE ||
      (it->type == VARIABLE &&
      (it + 1 == chunks.end() || (it + 1)->type != OPERATOR || (it + 1)->field != "="))))
    {
      if (it->type == VALUE) {
        Derived temp = it->value.local().block(it->row0, it->col0, it->rows, it->cols);
        it->value.local() = temp;
        it->value.mapLocal();
      } else {    // if(it->type == VARIABLE) {
        if (!isVariable(it->field)) {
          throw std::runtime_error(
                  "Attempted indexing into uninitialized variable '" + it->field + "'.");
        }
        it->value.local() = mVariables[it->field].matrix().block(
          it->row0, it->col0, it->rows,
          it->cols);
        it->value.mapLocal();
        it->type = VALUE;
      }
      it->row0 = -1;
      it->col0 = -1;
      it->rows = -1;
      it->cols = -1;
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
      operationPerformed = true;
#       endif
#endif
    }
  }
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  if (operationPerformed) {std::cout << "i: "; printChunks(chunks); std::cout << std::endl;}
#       endif
#endif
}

template<typename Derived>
void Parser<Derived>::evalNegations(ChunkArray & chunks)
{
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  bool operationPerformed = false;
#       endif
#endif
  if (chunks.size() < 2) {return;}
  typename ChunkArray::iterator lhs = chunks.begin(), op = chunks.begin(), rhs = op + 1;
  for (; lhs != chunks.end() && op != chunks.end() && rhs != chunks.end(); ) {
    if (op->type == OPERATOR && op->field == "-" &&
      (op == chunks.begin() || (lhs->type != VALUE && lhs->type != VARIABLE)) &&
      (rhs->type == VALUE || rhs->type == VARIABLE))
    {
      if (rhs->type == VALUE) {
        rhs->value.matrix().array() *= -1;
      } else if (rhs->type == VARIABLE) {
        if (!isVariable(rhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + op->field + rhs->field +
                  "' on uninitialized variable '" + rhs->field + "'.");
        }
        rhs->value.local() = mVariables[rhs->field].matrix().array() * -1;
        rhs->value.mapLocal();
        rhs->type = VALUE;
      }
      lhs = chunks.erase(op);
      op = (lhs != chunks.end()) ? lhs + 1 : lhs;
      rhs = (op != chunks.end()) ? op + 1 : op;
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
      operationPerformed = true;
#       endif
#endif
    } else {
      lhs = op;
      op = rhs;
      rhs++;
    }
  }
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  if (operationPerformed) {std::cout << "-: "; printChunks(chunks); std::cout << std::endl;}
#       endif
#endif
}

template<typename Derived>
void Parser<Derived>::evalPowers(ChunkArray & chunks)
{
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  bool operationPerformed = false;
#       endif
#endif
  if (chunks.size() < 3) {return;}
  typename ChunkArray::iterator lhs = chunks.begin(), op = lhs + 1, rhs = op + 1;
  for (; lhs != chunks.end() && op != chunks.end() && rhs != chunks.end(); ) {
    if ((op->type == OPERATOR) && (op->field == "^" || op->field == ".^")) {
      if (lhs->type == VARIABLE) {
        if (!isVariable(lhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + lhs->field + op->field + rhs->field +
                  "' on uninitialized variable '" + lhs->field + "'.");
        }
        lhs->value.setShared(mVariables[lhs->field]);
      }
      if (rhs->type == VARIABLE) {
        if (!isVariable(rhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + lhs->field + op->field + rhs->field +
                  "' on uninitialized variable '" + rhs->field + "'.");
        }
        rhs->value.setShared(mVariables[rhs->field]);
      }
      if (rhs->value.matrix().size() == 1) {
        lhs->value.local() = lhs->value.matrix().array().pow(rhs->value.matrix()(0, 0));
        lhs->value.mapLocal();
        lhs->type = VALUE;
      } else if (lhs->value.matrix().size() == 1) {
        typename Derived::Scalar temp = lhs->value.matrix()(0, 0);
        lhs->value.local().resize(rhs->value.matrix().rows(), rhs->value.matrix().cols());
        for (size_t row = 0; row < size_t(rhs->value.matrix().rows()); row++) {
          for (size_t col = 0; col < size_t(rhs->value.matrix().cols()); col++) {
            lhs->value.local()(row, col) = pow(temp, rhs->value.matrix()(row, col));
          }
        }
        lhs->value.mapLocal();
        lhs->type = VALUE;
      } else if (  // NOLINT
        op->field == ".^" && lhs->value.matrix().rows() == rhs->value.matrix().rows() &&
        lhs->value.matrix().cols() == rhs->value.matrix().cols())
      {
        lhs->value.local().resize(rhs->value.matrix().rows(), rhs->value.matrix().cols());
        for (size_t row = 0; row < size_t(rhs->value.matrix().rows()); row++) {
          for (size_t col = 0; col < size_t(rhs->value.matrix().cols()); col++) {
            lhs->value.local()(
              row,
              col) = pow(lhs->value.matrix()(row, col), rhs->value.matrix()(row, col));
          }
        }
        lhs->value.mapLocal();
        lhs->type = VALUE;
      } else {
        throw std::runtime_error(
                "Invalid operand dimensions for operation '" +
                lhs->field + op->field + rhs->field + "'.");
      }
      chunks.erase(op, rhs + 1);
      op = lhs + 1;
      rhs = (op != chunks.end()) ? op + 1 : op;
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
      operationPerformed = true;
#       endif
#endif
    } else {
      lhs = op;
      op = rhs;
      rhs++;
    }
  }
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  if (operationPerformed) {std::cout << "^: "; printChunks(chunks); std::cout << std::endl;}
#       endif
#endif
}

template<typename Derived>
void Parser<Derived>::evalMultiplication(ChunkArray & chunks)
{
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  bool operationPerformed = false;
#       endif
#endif
  if (chunks.size() < 3) {return;}
  typename ChunkArray::iterator lhs = chunks.begin(), op = lhs + 1, rhs = op + 1;
  for (; lhs != chunks.end() && op != chunks.end() && rhs != chunks.end(); ) {
    if ((op->type == OPERATOR) &&
      (op->field == "*" || op->field == "/" || op->field == ".*" || op->field == "./"))
    {
      if (lhs->type == VARIABLE) {
        if (!isVariable(lhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + lhs->field + op->field + rhs->field +
                  "' on uninitialized variable '" + lhs->field + "'.");
        }
        lhs->value.setShared(mVariables[lhs->field]);
      }
      if (rhs->type == VARIABLE) {
        if (!isVariable(rhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + lhs->field + op->field + rhs->field +
                  "' on uninitialized variable '" + rhs->field + "'.");
        }
        rhs->value.setShared(mVariables[rhs->field]);
      }
      if (rhs->value.matrix().size() == 1) {
        if (lhs->value.isLocal()) {
          if (op->field == "*" || op->field == ".*") {
            lhs->value.local().array() *= rhs->value.matrix()(0, 0);
          } else {                                 // if(op->field == "/" || op->field == "./")
            lhs->value.local().array() /= rhs->value.matrix()(0, 0);
          }
        } else {
          if (op->field == "*" || op->field == ".*") {
            lhs->value.local() = lhs->value.matrix().array() * rhs->value.matrix()(0, 0);
          } else {                                 // if(op->field == "/" || op->field == "./")
            lhs->value.local() = lhs->value.matrix().array() / rhs->value.matrix()(0, 0);
          }
          lhs->value.mapLocal();
          lhs->type = VALUE;
        }
      } else if (lhs->value.matrix().size() == 1) {
        typename Derived::Scalar temp = lhs->value.matrix()(0, 0);
        if (op->field == "*" || op->field == ".*") {
          lhs->value.local() = rhs->value.matrix().array() * temp;
        } else {                           // if(op->field == "/" || op->field == "./")
          lhs->value.local() = Derived::Constant(
            rhs->value.matrix().rows(),
            rhs->value.matrix().cols(), temp).array() / rhs->value.matrix().array();
        }
        lhs->value.mapLocal();
        lhs->type = VALUE;
      } else if (  // NOLINT
        (op->field == ".*" || op->field == "./") &&
        lhs->value.matrix().rows() == rhs->value.matrix().rows() &&
        lhs->value.matrix().cols() == rhs->value.matrix().cols())
      {
        if (lhs->value.isLocal()) {
          if (op->field == ".*") {
            lhs->value.local().array() *= rhs->value.matrix().array();
          } else {                                 // if(op->field == "./")
            lhs->value.local().array() /= rhs->value.matrix().array();
          }
        } else {
          if (op->field == ".*") {
            lhs->value.local() = lhs->value.matrix().array() * rhs->value.matrix().array();
          } else {                                 // if(op->field == "./")
            lhs->value.local() = lhs->value.matrix().array() / rhs->value.matrix().array();
          }
          lhs->value.mapLocal();
          lhs->type = VALUE;
        }
      } else if (op->field == "*" && lhs->value.matrix().cols() == rhs->value.matrix().rows()) {
        if (lhs->value.isLocal()) {
          lhs->value.local() *= rhs->value.matrix();
          lhs->value.mapLocal();
        } else {
          lhs->value.local() = lhs->value.matrix() * rhs->value.matrix();
          lhs->value.mapLocal();
          lhs->type = VALUE;
        }
      } else {
        throw std::runtime_error(
                "Invalid operand dimensions for operation '" +
                lhs->field + op->field + rhs->field + "'.");
      }
      chunks.erase(op, rhs + 1);
      op = lhs + 1;
      rhs = (op != chunks.end()) ? op + 1 : op;
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
      operationPerformed = true;
#       endif
#endif
    } else {
      lhs = op;
      op = rhs;
      rhs++;
    }
  }
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  if (operationPerformed) {std::cout << "*: "; printChunks(chunks); std::cout << std::endl;}
#       endif
#endif
}

template<typename Derived>
void Parser<Derived>::evalAddition(ChunkArray & chunks)
{
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  bool operationPerformed = false;
#       endif
#endif
  if (chunks.size() < 3) {return;}
  typename ChunkArray::iterator lhs = chunks.begin(), op = lhs + 1, rhs = op + 1;
  for (; lhs != chunks.end() && op != chunks.end() && rhs != chunks.end(); ) {
    if ((op->type == OPERATOR) &&
      (op->field == "+" || op->field == "-" || op->field == ".+" || op->field == ".-"))
    {
      if (lhs->type == VARIABLE) {
        if (!isVariable(lhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + lhs->field + op->field + rhs->field +
                  "' on uninitialized variable '" + lhs->field + "'.");
        }
        lhs->value.setShared(mVariables[lhs->field]);
      }
      if (rhs->type == VARIABLE) {
        if (!isVariable(rhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + lhs->field + op->field + rhs->field +
                  "' on uninitialized variable '" + rhs->field + "'.");
        }
        rhs->value.setShared(mVariables[rhs->field]);
      }
      if (rhs->value.matrix().size() == 1) {
        if (lhs->value.isLocal()) {
          if (op->field == "+" || op->field == ".+") {
            lhs->value.local().array() += rhs->value.matrix()(0, 0);
          } else {                                 // if(op->field == "-" || op->field == ".-")
            lhs->value.local().array() -= rhs->value.matrix()(0, 0);
          }
        } else {
          if (op->field == "+" || op->field == ".+") {
            lhs->value.local() = lhs->value.matrix().array() + rhs->value.matrix()(0, 0);
          } else {                                 // if(op->field == "-" || op->field == ".-")
            lhs->value.local() = lhs->value.matrix().array() - rhs->value.matrix()(0, 0);
          }
          lhs->value.mapLocal();
          lhs->type = VALUE;
        }
      } else if (lhs->value.matrix().size() == 1) {
        typename Derived::Scalar temp = lhs->value.matrix()(0, 0);
        if (op->field == "+" || op->field == ".+") {
          lhs->value.local() = rhs->value.matrix().array() + temp;
        } else {                           // if(op->field == "-" || op->field == ".-")
          lhs->value.local() = Derived::Constant(
            rhs->value.matrix().rows(),
            rhs->value.matrix().cols(), temp).array() - rhs->value.matrix().array();
        }
        lhs->value.mapLocal();
        lhs->type = VALUE;
      } else if (  // NOLINT
        lhs->value.matrix().rows() == rhs->value.matrix().rows() &&
        lhs->value.matrix().cols() == rhs->value.matrix().cols())
      {
        if (lhs->value.isLocal()) {
          if (op->field == "+" || op->field == ".+") {
            lhs->value.local().array() += rhs->value.matrix().array();
          } else {                                 // if(op->field == "-" || op->field == ".-")
            lhs->value.local().array() -= rhs->value.matrix().array();
          }
        } else {
          if (op->field == "+" || op->field == ".+") {
            lhs->value.local() = lhs->value.matrix().array() + rhs->value.matrix().array();
          } else {                                 // if(op->field == "-" || op->field == ".-")
            lhs->value.local() = lhs->value.matrix().array() - rhs->value.matrix().array();
          }
          lhs->value.mapLocal();
          lhs->type = VALUE;
        }
      } else {
        throw std::runtime_error(
                "Invalid operand dimensions for operation '" +
                lhs->field + op->field + rhs->field + "'.");
      }
      chunks.erase(op, rhs + 1);
      op = lhs + 1;
      rhs = (op != chunks.end()) ? op + 1 : op;
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
      operationPerformed = true;
#       endif
#endif
    } else {
      lhs = op;
      op = rhs;
      rhs++;
    }
  }
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  if (operationPerformed) {std::cout << "+: "; printChunks(chunks); std::cout << std::endl;}
#       endif
#endif
}

template<typename Derived>
void Parser<Derived>::evalAssignment(ChunkArray & chunks)
{
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  bool operationPerformed = false;
#       endif
#endif
  if (chunks.size() < 3) {return;}
  typename ChunkArray::iterator rhs = chunks.end() - 1, op = rhs - 1, lhs = op - 1;
  for (; op != chunks.begin() && rhs != chunks.begin(); ) {
    if (op->type == OPERATOR && op->field == "=" &&
      (lhs->type == VALUE || lhs->type == VARIABLE) &&
      (rhs->type == VALUE || rhs->type == VARIABLE))
    {
      if (rhs->type == VARIABLE) {
        if (!isVariable(rhs->field)) {
          throw std::runtime_error(
                  "Attempted operation '" + lhs->field + op->field +
                  rhs->field + "' on uninitialized variable '" + rhs->field + "'.");
        }
        rhs->value.setShared(mVariables[rhs->field]);
      }
      if (lhs->type == VALUE) {
        lhs->value.local() = rhs->value.matrix();
        lhs->value.mapLocal();
      } else {    // if(lhs->type == VARIABLE) {
        if (isVariable(lhs->field)) {
          lhs->value.setShared(mVariables[lhs->field]);
          if (lhs->row0 == -1) {
            if (lhs->value.matrix().rows() == rhs->value.matrix().rows() &&
              lhs->value.matrix().cols() == rhs->value.matrix().cols())
            {
              lhs->value.matrix() = rhs->value.matrix();
            } else {
              mVariables[lhs->field].local() = rhs->value.matrix();
              mVariables[lhs->field].mapLocal();
            }
          } else {    // if(lhs->row0 != -1) {
            lhs->value.matrix().block(
              lhs->row0, lhs->col0, lhs->rows,
              lhs->cols) = rhs->value.matrix();
          }
        } else {
          mVariables[lhs->field].local() = rhs->value.matrix();
          mVariables[lhs->field].mapLocal();
        }
      }
      rhs = chunks.erase(op, rhs + 1);
      op = (rhs != chunks.begin()) ? rhs - 1 : rhs;
      if (op != chunks.begin()) {lhs = op - 1;}
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
      operationPerformed = true;
#       endif
#endif
    } else {
      rhs = op;
      op = lhs;
      lhs--;
    }
  }
#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
  if (operationPerformed) {std::cout << "=: "; printChunks(chunks); std::cout << std::endl;}
#       endif
#endif
}

#ifdef DEBUG
#       ifdef EIGENLAB_DEBUG
template<typename Derived>
void Parser<Derived>::printChunks(
  ChunkArray & chunks, size_t maxRows, size_t maxCols,
  int precision)
{
  std::cout << "__";
  for (typename ChunkArray::iterator it = chunks.begin(); it != chunks.end(); it++) {
    switch (it->type) {
      case VALUE:
        std::cout << textRepresentation(it->value, maxRows, maxCols, precision);
        if (it->row0 != -1) {
          std::cout << "(" << it->row0 << ":" << it->row0 + it->rows - 1 << "," << it->col0 <<
            ":" << it->col0 + it->cols - 1 << ")";
        }
        break;
      case VARIABLE:
        std::cout << it->field;
        // << "=" << textRepresentation(mVariables[it->field], maxRows, maxCols, precision);
        if (it->row0 != -1) {
          std::cout << "(" << it->row0 << ":" << it->row0 + it->rows - 1 << "," << it->col0 <<
            ":" << it->col0 + it->cols - 1 << ")";
        }
        break;
      case OPERATOR:
        std::cout << it->field;
        break;
      case FUNCTION:
        std::cout << "f()=" << it->field;
        break;
    }
    std::cout << "__";
  }
}

template<typename Derived>
void Parser<Derived>::printVars(size_t maxRows, size_t maxCols, int precision)
{
  for (typename ValueMap::iterator it = mVariables.begin(); it != mVariables.end(); it++) {
    std::cout << it->first << " (" << it->second.matrix().rows() << "x" <<
      it->second.matrix().cols() << ") = " << textRepresentation(
      it->second, maxRows, maxCols,
      precision) << std::endl;
  }
}

template<typename Derived>
std::string Parser<Derived>::textRepresentation(
  Value<Derived> & val, size_t maxRows,
  size_t maxCols, int precision)
{
  if (val.matrix().size() == 1) {
    return numberToString<typename Derived::Scalar>(val.matrix()(0, 0), precision);
  } else {
    std::string str = "[";
    for (size_t row = 0; row < val.matrix().rows() && row < maxRows; row++) {
      str += (row > 0 ? ";[" : "[");
      for (size_t col = 0; col < val.matrix().cols() && col < maxCols; col++) {
        if (col > 0) {str += ",";}
        str += numberToString<typename Derived::Scalar>(val.matrix()(row, col), precision);
      }
      str += (val.matrix().cols() > maxCols ? "...]" : "]");
    }
    str += (val.matrix().rows() > maxRows ? "...]" : "]");
    return str;
  }
}
#       endif  // #ifdef EIGENLAB_DEBUG
#endif  // #ifdef DEBUG

template<typename Derived>
std::string Parser<Derived>::trim(const std::string & str)
{
  if (str.empty()) {return str;}
  std::string::const_iterator first = str.begin();
  std::string::const_iterator last = str.end() - 1;
  while (first < last && isspace(*first)) {first++;}
  while (first < last && isspace(*last)) {last--;}
  return std::string(first, last + 1);
}

template<typename Derived>
std::vector<std::string> Parser<Derived>::split(
  const std::string & str,
  const char delimeter)
{
  std::vector<std::string> args;
  std::string::const_iterator i0 = str.begin();
  for (std::string::const_iterator it = str.begin(); it != str.end(); it++) {
    if ((*it) == delimeter) {
      args.push_back(trim(std::string(i0, it)));
      i0 = it + 1;
    }
  }
  args.push_back(std::string(i0, str.end()));
  return args;
}

template<typename Derived>
template<typename T>
bool Parser<Derived>::isNumber(const std::string & str, T * num)
{
  std::istringstream iss(str);
  if (num) {
    iss >> (*num);
  } else {
    T number;
    iss >> number;
  }
  return !iss.fail() && !iss.bad() && iss.eof();
}

template<typename Derived>
template<typename T>
T Parser<Derived>::stringToNumber(const std::string & str)
{
  std::istringstream iss(str);
  T number;
  iss >> number;
  if (iss.fail() || iss.bad() || !iss.eof()) {
    throw std::runtime_error("Failed to convert " + str + " to a number.");
  }
  return number;
}

template<typename Derived>
template<typename T>
std::string Parser<Derived>::numberToString(T num, int precision)
{
  std::ostringstream oss;
  if (precision) {
    oss << std::setprecision(precision) << num;
  } else {
    oss << num;
  }
  return oss.str();
}

#ifdef DEBUG
template<typename Derived>
void
Parser<Derived>::test_w_lt(
  size_t & numFails,
  typename Derived::Scalar & /* s */,
  Derived & a34,
  Derived & b34,
  Derived & /* c43 */,
  Derived & /* v */, std::true_type)
{
  //
  // tests that only work if Derived::Scalar has operator<
  //
  Value<Derived> resultValue;
  Derived resultMatrix;
  Derived temp;
  std::cout << "Test min(a): ";
  resultValue = eval("min(a)");
  resultMatrix.setConstant(1, 1, a34.minCoeff());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test min(a, 0): ";
  resultValue = eval("min(a, 0)");
  resultMatrix = a34.colwise().minCoeff();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test min(a, 1): ";
  resultValue = eval("min(a, 1)");
  resultMatrix = a34.rowwise().minCoeff();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test max(a): ";
  resultValue = eval("max(a)");
  resultMatrix.setConstant(1, 1, a34.maxCoeff());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test max(a, 0): ";
  resultValue = eval("max(a, 0)");
  resultMatrix = a34.colwise().maxCoeff();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test max(a, 1): ";
  resultValue = eval("max(a, 1)");
  resultMatrix = a34.rowwise().maxCoeff();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test absmax(a): ";
  resultValue = eval("absmax(a)");
  resultMatrix.setConstant(
    1, 1, std::abs(a34.maxCoeff()) >= std::abs(
      a34.minCoeff()) ? a34.maxCoeff() : a34.minCoeff());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test absmax(a, 0): ";
  resultValue = eval("absmax(a, 0)");
  resultMatrix = a34.colwise().maxCoeff();
  temp = a34.colwise().minCoeff();
  for (Index i = 0; i < resultMatrix.size(); ++i) {
    if (std::abs(resultMatrix(i)) < std::abs(temp(i))) {
      resultMatrix(i) = temp(i);
    }
  }
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test absmax(a, 1): ";
  resultValue = eval("absmax(a, 1)");
  resultMatrix = a34.rowwise().maxCoeff();
  temp = a34.rowwise().minCoeff();
  for (Index i = 0; i < resultMatrix.size(); ++i) {
    if (std::abs(resultMatrix(i)) < std::abs(temp(i))) {
      resultMatrix(i) = temp(i);
    }
  }
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test cwiseMin(a, b): ";
  resultValue = eval("cwiseMin(a, b)");
  resultMatrix = a34.cwiseMin(b34);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test cwiseMax(a, b): ";
  resultValue = eval("cwiseMax(a, b)");
  resultMatrix = a34.cwiseMax(b34);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }
}

template<typename Derived>
void
Parser<Derived>::test_w_lt(
  size_t & /* numFails */,
  typename Derived::Scalar & /* s */,
  Derived & /* a34 */,
  Derived & /* b34 */,
  Derived & /* c43 */,
  Derived & /* v */, std::false_type)
{
  // do nothing
}

template<typename Derived>
size_t Parser<Derived>::test()
{
  std::cout << std::endl;
  std::cout << "BEGIN unit test for EigenLab..." << std::endl;
  std::cout << "Make sure this function completes successfuly and prints the message "
    "'Successfully completed unit test for EigenLab with no failures.'" << std::endl;
  std::cout << std::endl;

  size_t numFails = 0;
  Value<Derived> resultValue;
  Derived resultMatrix;
  Derived temp;
  typename Derived::Scalar s = 2;

  Derived a34 = Derived::Random(3, 4);
  Derived b34 = Derived::Random(3, 4);
  Derived c43 = Derived::Random(4, 3);
  Derived v = Derived::Random(1, 10);
  // std::cout << "a34=" << std::endl << a34 << std::endl << std::endl;
  // std::cout << "b34=" << std::endl << b34 << std::endl << std::endl;
  // std::cout << "c43=" << std::endl << c43 << std::endl << std::endl;
  // std::cout << "v=" << std::endl << v << std::endl << std::endl;

  var("a").setShared(a34);
  var("b").setShared(b34);
  var("c").setShared(c43);
  var("v").setShared(v);
  var("s").setShared(&s);

  std::cout << "Testing basic operations..." << std::endl << std::endl;

  std::cout << "Test matrix addition a + b: ";
  resultValue = eval("a + b");
  resultMatrix = a34 + b34;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar addition a + s: ";
  resultValue = eval("a + s");
  resultMatrix = a34.array() + s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix addition s + b: ";
  resultValue = eval("s + b");
  resultMatrix = b34.array() + s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix addition a .+ b: ";
  resultValue = eval("a .+ b");
  resultMatrix = a34 + b34;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar addition a .+ s: ";
  resultValue = eval("a .+ s");
  resultMatrix = a34.array() + s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix addition s .+ b: ";
  resultValue = eval("s .+ b");
  resultMatrix = b34.array() + s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix subtraction a - b: ";
  resultValue = eval("a - b");
  resultMatrix = a34 - b34;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar subtraction a - s: ";
  resultValue = eval("a - s");
  resultMatrix = a34.array() - s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix subtraction s - b: ";
  resultValue = eval("s - b");
  resultMatrix = (-b34.array()) + s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix subtraction a .- b: ";
  resultValue = eval("a .- b");
  resultMatrix = a34 - b34;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar subtraction a .- s: ";
  resultValue = eval("a .- s");
  resultMatrix = a34.array() - s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix subtraction s .- b: ";
  resultValue = eval("s .- b");
  resultMatrix = (-b34.array()) + s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix negation -a: ";
  resultValue = eval("-a");
  resultMatrix = -a34;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar negation -s: ";
  resultValue = eval("-s");
  resultMatrix.setConstant(1, 1, -s);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix coefficient-wise multiplication a .* b: ";
  resultValue = eval("a .* b");
  resultMatrix = a34.array() * b34.array();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar coefficient-wise multiplication a * s: ";
  resultValue = eval("a * s");
  resultMatrix = a34.array() * s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix coefficient-wise multiplication s * b: ";
  resultValue = eval("s * b");
  resultMatrix = b34.array() * s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar coefficient-wise multiplication a .* s: ";
  resultValue = eval("a .* s");
  resultMatrix = a34.array() * s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix coefficient-wise multiplication s .* b: ";
  resultValue = eval("s .* b");
  resultMatrix = b34.array() * s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix coefficient-wise division a ./ b: ";
  resultValue = eval("a ./ b");
  resultMatrix = a34.array() / b34.array();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar coefficient-wise division a / s: ";
  resultValue = eval("a / s");
  resultMatrix = a34.array() / s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix coefficient-wise division s / b: ";
  resultValue = eval("s / b");
  resultMatrix = Derived::Constant(b34.rows(), b34.cols(), s).array() / b34.array();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar coefficient-wise division a ./ s: ";
  resultValue = eval("a ./ s");
  resultMatrix = a34.array() / s;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix coefficient-wise division s ./ b: ";
  resultValue = eval("s ./ b");
  resultMatrix = Derived::Constant(b34.rows(), b34.cols(), s).array() / b34.array();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix coefficient-wise power a .^ b: ";
  resultValue = eval("abs(a) .^ b");
  resultMatrix = a34;
  for (Index i = 0; i < a34.size(); ++i) {
    resultMatrix(i) = pow(std::abs(a34(i)), b34(i));
  }
  // std::cout << std::endl;
  // std::cout << "a=" << std::endl << a34 << std::endl << std::endl;
  // std::cout << "b=" << std::endl << b34 << std::endl << std::endl;
  // std::cout << "val=" << std::endl << resultValue.matrix() << std::endl << std::endl;
  // std::cout << "mat=" << std::endl << resultMatrix << std::endl << std::endl;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar coefficient-wise power a ^ s: ";
  resultValue = eval("abs(a) ^ s");
  resultMatrix = a34;
  for (Index i = 0; i < a34.size(); ++i) {
    resultMatrix(i) = pow(std::abs(a34(i)), s);
  }
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix coefficient-wise power s ^ b: ";
  resultValue = eval("s ^ b");
  resultMatrix = b34;
  for (Index i = 0; i < b34.size(); ++i) {
    resultMatrix(i) = pow(s, b34(i));
  }
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix/scalar coefficient-wise power a .^ s: ";
  resultValue = eval("abs(a) .^ s");
  resultMatrix = a34;
  for (Index i = 0; i < a34.size(); ++i) {
    resultMatrix(i) = pow(std::abs(a34(i)), s);
  }
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test scalar/matrix coefficient-wise power s .^ b: ";
  resultValue = eval("s .^ b");
  resultMatrix = b34;
  for (Index i = 0; i < b34.size(); ++i) {
    resultMatrix(i) = pow(s, b34(i));
  }
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix multiplication a * b: ";
  resultValue = eval("a * c");
  resultMatrix = a34 * c43;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test grouped subexpression (a + b) * c: ";
  resultValue = eval("(a + b) * c");
  resultMatrix = (a34 + b34) * c43;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test nested groups ((a + (b * 3 + 1)) * c).^2: ";
  resultValue = eval("((a + (b * 3 + 1)) * c).^2");
  resultMatrix = ((a34.array() + (b34.array() * 3 + 1)).matrix() * c43).array().pow(2);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl << "Testing coefficient and submatrix block access..." << std::endl <<
    std::endl;

  std::cout << "Test matrix coefficient access a(i,j): ";
  resultValue = eval("a(1,2)");
  resultMatrix.setConstant(1, 1, a34(1, 2));
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test submatrix block access a(i:p,j:q): ";
  resultValue = eval("a(1:2,2:3)");
  resultMatrix = a34.block(1, 2, 2, 2);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test submatrix block access using 'end' and ':' identifiers a(i:end,:): ";
  resultValue = eval("a(1:end,:)");
  resultMatrix = a34.block(1, 0, a34.rows() - 1, a34.cols());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test submatrix block access using subexpressions: ";
  resultValue = eval("a(2-1:2-1,0+1:3-1)");
  resultMatrix = a34.block(1, 1, 1, 2);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test submatrix block access using subexpressions with 'end' keyword: ";
  resultValue = eval("a(2-1:end-1,0+1:end-1)");
  resultMatrix = a34.block(1, 1, a34.rows() - 2, a34.cols() - 2);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test vector coefficient access v(i): ";
  resultValue = eval("v(5)");
  resultMatrix.setConstant(1, 1, v(5));
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test subvector segment access v(i:j): ";
  resultValue = eval("v(3:6)");
  resultMatrix = v.block(0, 3, 1, 4);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test subvector segment access using 'end' identifier v(i:end): ";
  resultValue = eval("v(5:end)");
  resultMatrix = v.block(0, 5, 1, v.cols() - 5);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test subvector segment access using ':' identifier v(:): ";
  resultValue = eval("v(:)");
  resultMatrix = v;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test subvector segment access using subexpressions: ";
  resultValue = eval("v(3-1:5+2)");
  resultMatrix = v.block(0, 2, 1, 6);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test subvector segment access using subexpressions with 'end' keyword: ";
  resultValue = eval("v((end-8)*2:end-3)");
  resultMatrix = v.block(0, 2, 1, 5);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl << "Testing vector/matrix expressions..." << std::endl << std::endl;

  std::cout << "Test numeric range expression [i:j]: ";
  resultValue = eval("[2:5]");
  resultMatrix.resize(1, 4);
  resultMatrix << 2, 3, 4, 5;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test numeric range expression [i:s:j]: ";
  resultValue = eval("[2:2:10]");
  resultMatrix.resize(1, 5);
  resultMatrix << 2, 4, 6, 8, 10;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test numeric range with subexpressions: ";
  resultValue = eval("[6-2:5*2-3]");
  std::cout << "val=" << std::endl << resultValue.matrix() << std::endl << std::endl;
  resultMatrix.resize(1, 4);
  resultMatrix << 4, 5, 6, 7;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix expression [[1, 2]; [3, 4]]: ";
  resultValue = eval("[[1, 2]; [3, 4]]");
  resultMatrix.resize(2, 2);
  resultMatrix << 1, 2, 3, 4;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test matrix expression [  1, 2, 3;  4:6 ]: ";
  resultValue = eval("[1, 2, 3; 4:6]");
  resultMatrix.resize(2, 3);
  resultMatrix << 1, 2, 3, 4, 5, 6;
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl << "Testing coefficient-wise functions..." << std::endl << std::endl;

  std::cout << "Test coefficient-wise abs(a): ";
  resultValue = eval("abs(a)");
  resultMatrix.resize(3, 4);
  resultMatrix.real() = a34.array().abs();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise sqrt(a): ";
  resultValue = eval("sqrt(abs(a))");
  resultMatrix.real() = a34.array().abs().sqrt();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise exp(a): ";
  resultValue = eval("exp(abs(a) + 0.001)");
  resultMatrix.real() = (a34.array().abs() + 0.001).exp();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise log(a): ";
  resultValue = eval("log(abs(a) + 0.001)");
  resultMatrix.real() = (a34.array().abs() + 0.001).log();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise log10(a): ";
  resultValue = eval("log10(abs(a) + 0.001)");
  resultMatrix.real() = (a34.array().abs() + 0.001).log() * (1.0 / log(10));
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise sin(a): ";
  resultValue = eval("sin(a)");
  resultMatrix = a34.array().sin();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise cos(a): ";
  resultValue = eval("cos(a)");
  resultMatrix = a34.array().cos();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise tan(a): ";
  resultValue = eval("tan(a)");
  resultMatrix = a34.array().tan();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise asin(a): ";
  resultValue = eval("asin(a)");
  resultMatrix = a34.array().asin();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test coefficient-wise acos(a): ";
  resultValue = eval("acos(a)");
  resultMatrix = a34.array().acos();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl << "Testing matrix reduction functions..." << std::endl << std::endl;

  std::cout << "Test trace(a): ";
  resultValue = eval("trace(a)");
  resultMatrix.setConstant(1, 1, a34.trace());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test norm(a): ";
  resultValue = eval("norm(a)");
  resultMatrix.setConstant(1, 1, a34.norm());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test size(a, 0): ";
  resultValue = eval("size(a, 0)");
  resultMatrix.setConstant(1, 1, a34.rows());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test size(a, 1): ";
  resultValue = eval("size(a, 1)");
  resultMatrix.setConstant(1, 1, a34.cols());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  test_w_lt(numFails, s, a34, b34, c43, v, has_operator_lt<typename Derived::Scalar>());

  std::cout << "Test mean(a): ";
  resultValue = eval("mean(a)");
  resultMatrix.setConstant(1, 1, a34.mean());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test mean(a, 0): ";
  resultValue = eval("mean(a, 0)");
  resultMatrix = a34.colwise().mean();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test mean(a, 1): ";
  resultValue = eval("mean(a, 1)");
  resultMatrix = a34.rowwise().mean();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test sum(a): ";
  resultValue = eval("sum(a)");
  resultMatrix.setConstant(1, 1, a34.sum());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test sum(a, 0): ";
  resultValue = eval("sum(a, 0)");
  resultMatrix = a34.colwise().sum();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test sum(a, 1): ";
  resultValue = eval("sum(a, 1)");
  resultMatrix = a34.rowwise().sum();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test prod(a): ";
  resultValue = eval("prod(a)");
  resultMatrix.setConstant(1, 1, a34.prod());
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test prod(a, 0): ";
  resultValue = eval("prod(a, 0)");
  resultMatrix = a34.colwise().prod();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test prod(a, 1): ";
  resultValue = eval("prod(a, 1)");
  resultMatrix = a34.rowwise().prod();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl << "Testing matrix functions..." << std::endl << std::endl;

  std::cout << "Test transpose(a): ";
  resultValue = eval("transpose(a)");
  resultMatrix = a34.transpose();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test conjugate(a): ";
  resultValue = eval("conjugate(a)");
  resultMatrix = a34.conjugate();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test adjoint(a): ";
  resultValue = eval("adjoint(a)");
  resultMatrix = a34.adjoint();
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl << "Testing matrix initializers..." << std::endl << std::endl;

  std::cout << "Test zeros(5): ";
  resultValue = eval("zeros(5)");
  resultMatrix = Derived::Zero(5, 5);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test ones(5): ";
  resultValue = eval("ones(5)");
  resultMatrix = Derived::Ones(5, 5);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test eye(5): ";
  resultValue = eval("eye(5)");
  resultMatrix = Derived::Identity(5, 5);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  try {
    std::cout << "Test zeros(5.2): ";
    resultValue = eval("zeros(5.2)");    // <-- Should NOT succeed!!!
    std::cout << "FAIL" << std::endl; ++numFails;
  } catch (std::runtime_error & err) {
    std::cout << err.what() << std::endl;
    std::cout << "Exception caught, so we're OK" << std::endl;
  }

  try {
    std::cout << "Test eye(-3): ";
    resultValue = eval("eye(-3)");    // <-- Should NOT succeed!!!
    std::cout << "FAIL" << std::endl; ++numFails;
  } catch (std::runtime_error & err) {
    std::cout << err.what() << std::endl;
    std::cout << "Exception caught, so we're OK" << std::endl;
  }

  std::cout << "Test zeros(4,7): ";
  resultValue = eval("zeros(4,7)");
  resultMatrix = Derived::Zero(4, 7);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test ones(4,7): ";
  resultValue = eval("ones(4,7)");
  resultMatrix = Derived::Ones(4, 7);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test eye(4,7): ";
  resultValue = eval("eye(4,7)");
  resultMatrix = Derived::Identity(4, 7);
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl << "Testing variable assignment..." << std::endl << std::endl;

  std::cout << "Test assigning to a variable with the same dimensions a = b: ";
  eval("a = b");
  if (a34.isApprox(b34)) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test assigning to a variable with different dimensions a = c: ";
  eval("a = c");
  if (var("a").matrix().isApprox(c43) && a34.isApprox(b34)) {
    std::cout << "OK" << std::endl;
  } else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }
  var("a").setShared(a34);

  std::cout << "Test creating a new variable x = [1,2;3,4]: ";
  resultValue = eval("x = [1,2;3,4]");
  if (var("x").matrix().isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test assigning to a variable coefficient a(i,j) = s: ";
  eval("a(1, 2) = s");
  if (a34(1, 2) == s) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test assigning to a variable submatrix block a(0:1,1:2) = x: ";
  resultValue = eval("a(0:1,1:2) = x");
  if (a34.block(0, 1, 2, 2).isApprox(var("x").matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  try {
    std::cout << "Test bad function call: ";
    resultValue = eval("foobar(-3)");    // <-- Should NOT succeed!!!
    std::cout << "FAIL" << std::endl; ++numFails;
  } catch (std::runtime_error & err) {
    std::cout << err.what() << std::endl;
    std::cout << "Exception caught, so we're OK" << std::endl;
  }
  std::cout << std::endl << "Testing fp parsing..." << std::endl << std::endl;

  std::cout << "Test assigning 1.2e-3: ";
  resultValue = eval("s = 1.2e-3");
  resultMatrix.setConstant(1, 1, typename Derived::Scalar(1.2e-3));
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test assigning 1.e3: ";
  resultValue = eval("s = 1.e3");
  resultMatrix.setConstant(1, 1, typename Derived::Scalar(1000));
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << "Test assigning 12.34e05: ";
  resultValue = eval("s = 12.34e05");
  resultMatrix.setConstant(1, 1, typename Derived::Scalar(123.4e4));
  if (resultMatrix.isApprox(resultValue.matrix())) {std::cout << "OK" << std::endl;} else {
    std::cout << "FAIL" << std::endl; ++numFails;
  }

  std::cout << std::endl;
  if (numFails == 0) {
    std::cout << "Successfully completed unit test for EigenLab with no failures." << std::endl;
  } else {
    std::cout << "Completed unit test for EigenLab with " << numFails <<
      " failures (see above)." << std::endl;
  }
  std::cout << std::endl;
  return numFails;
}
#endif  // DEBUG

}  // namespace EigenLab
#endif  // EIGENLAB__EIGENLAB_HPP_