void EvaluatableExpr::setupEval(const EvalContext& context) const
{
  Tabs tabs0(0);
  int verb = context.evalSetupVerbosity();
  SUNDANCE_MSG1(verb, tabs0 << "setupEval() for " << this->toString());
  if (!evaluators_.containsKey(context))
  {
    Tabs tabs;
    SUNDANCE_MSG2(verb, tabs << "creating new evaluator...");
    SUNDANCE_MSG2(verb, tabs << "my sparsity superset = " << std::endl
      << *sparsitySuperset(context));
    RCP<Evaluator> eval;
    if (sparsitySuperset(context)->numDerivs()>0)
    {
      SUNDANCE_MSG2(verb, tabs << "calling createEvaluator()");
      eval = rcp(createEvaluator(this, context));
    }
    else
    {
      SUNDANCE_MSG2(verb, 
        tabs << "EE: no results needed... creating null evaluator");
      eval = rcp(new NullEvaluator());
    }
    evaluators_.put(context, eval);
  }
  else
  {
    Tabs tabs;
    SUNDANCE_MSG2(verb, tabs << "reusing existing evaluator...");
  }
}
ProductEvaluator::ProductEvaluator(const ProductExpr* expr,
  const EvalContext& context)
  : BinaryEvaluator<ProductExpr>(expr, context),
    maxOrder_(this->sparsity()->maxOrder()),
    resultIndex_(maxOrder_+1),
    resultIsConstant_(maxOrder_+1),
    hasWorkspace_(maxOrder_+1),
    workspaceIsLeft_(maxOrder_+1),
    workspaceIndex_(maxOrder_+1),
    workspaceCoeffIndex_(maxOrder_+1),
    workspaceCoeffIsConstant_(maxOrder_+1),
    ccTerms_(maxOrder_+1),
    cvTerms_(maxOrder_+1),
    vcTerms_(maxOrder_+1),
    vvTerms_(maxOrder_+1),
    startingVectors_(maxOrder_+1),
    startingParities_(maxOrder_+1)
{
  int verb = context.evalSetupVerbosity();

  try
  {
    Tabs tabs(0);
    {
      Tabs tab;
      Tabs tabz;
      SUNDANCE_MSG1(verb,
        tabs << "initializing product evaluator for " 
        << expr->toString());

      SUNDANCE_MSG2(verb,
        tab << "return sparsity " << std::endl
        << tabz << *(this->sparsity)());

      SUNDANCE_MSG2(verb,
        tab << "left sparsity " << std::endl 
        << tabz << *(leftSparsity()) << std::endl
        << tabz << "right sparsity " << std::endl 
        << tabz << *(rightSparsity()));
  
      SUNDANCE_MSG3(verb,
        tab << "left vector index map " 
        << leftEval()->vectorIndexMap() << std::endl
        << tabz << "right vector index map " 
        << rightEval()->vectorIndexMap() << std::endl
        << tabz << "left constant index map " 
        << leftEval()->constantIndexMap() << std::endl
        << tabz << "right constant index map " 
        << rightEval()->constantIndexMap());
    }                
    int vecResultIndex = 0;
    int constResultIndex = 0;

    for (int i=0; i<this->sparsity()->numDerivs(); i++)
    {
      Tabs tab0;
      const MultipleDeriv& d = this->sparsity()->deriv(i);

      SUNDANCE_MSG2(verb,
        tabs << std::endl 
        << tabs << "finding rules for deriv " << d);

      int order = d.order();

      /* Determine the index into which the result will be written */
      bool resultIsConstant = this->sparsity()->state(i)==ConstantDeriv; 
      resultIsConstant_[order].append(resultIsConstant);
      if (resultIsConstant)
      {
        SUNDANCE_MSG3(verb,
          tab0 << std::endl 
          << tab0 << "result will be in constant index " << constResultIndex);
        resultIndex_[order].append(constResultIndex);
        addConstantIndex(i, constResultIndex);
        constResultIndex++;
      }
      else
      {
        SUNDANCE_MSG3(verb,
          tab0 << std::endl 
          << tab0 << "result will be in constant index " << vecResultIndex);
        resultIndex_[order].append(vecResultIndex);
        addVectorIndex(i, vecResultIndex);
        vecResultIndex++;
      }

      /* If possible, we want to do the calculations in-place, writing into
       * one of the two operand's results vectors for the same derivative.
       * Provided that we process derivatives in descending order, it is
       * safe to destroy the operands' result vectors.
       */
       
      int dnLeftIndex = leftSparsity()->getIndex(d);
      int dnRightIndex = rightSparsity()->getIndex(d);

      
      bool hasVectorWorkspace = false;
      bool workspaceIsLeft = false;
      int workspaceIndex = -1;
      int workspaceCoeffIndex = -1;
      bool workspaceCoeffIsConstant = false;


      bool dnLeftIsConst = false;
      if (dnLeftIndex != -1) 
      {
        dnLeftIsConst = leftSparsity()->state(dnLeftIndex)==ConstantDeriv;
      }
      bool dnRightIsConst = false;
      if (dnRightIndex != -1)
      {
        dnRightIsConst = rightSparsity()->state(dnRightIndex)==ConstantDeriv;
      }

      /* First try to use the left result as a workspace */
      if (dnLeftIndex != -1 && !dnLeftIsConst 
        && rightSparsity()->containsDeriv(MultipleDeriv()))
      {
        /* We can write onto left vector */
        hasVectorWorkspace = true;
        workspaceIndex = leftEval()->vectorIndexMap().get(dnLeftIndex);       
        SUNDANCE_MSG3(verb,
          tab0 << "using left as workspace");
        workspaceIsLeft = true;
        int d0RightIndex = rightSparsity()->getIndex(MultipleDeriv());
        bool d0RightIsConst = rightSparsity()->state(d0RightIndex)==ConstantDeriv;
        workspaceCoeffIsConstant = d0RightIsConst;
        if (d0RightIsConst)
        {
          workspaceCoeffIndex 
            = rightEval()->constantIndexMap().get(d0RightIndex);
        }
        else
        {
          workspaceCoeffIndex 
            = rightEval()->vectorIndexMap().get(d0RightIndex);
        }
      }

      /* If the left didn't provide a workspace, try the right */
      if (!hasVectorWorkspace && dnRightIndex != -1 && !dnRightIsConst
        && leftSparsity()->containsDeriv(MultipleDeriv()))
      {
        /* We can write onto right vector */
        hasVectorWorkspace = true;
        workspaceIndex = rightEval()->vectorIndexMap().get(dnRightIndex); 
        workspaceIsLeft = false;
        SUNDANCE_MSG3(verb,
          tab0 << "using right as workspace");
        int d0LeftIndex = leftSparsity()->getIndex(MultipleDeriv());
        bool d0LeftIsConst = leftSparsity()->state(d0LeftIndex)==ConstantDeriv;
        workspaceCoeffIsConstant = d0LeftIsConst;
        if (d0LeftIsConst)
        {
          workspaceCoeffIndex 
            = leftEval()->constantIndexMap().get(d0LeftIndex);
        }
        else
        {
          workspaceCoeffIndex 
            = leftEval()->vectorIndexMap().get(d0LeftIndex);
        }
      }
      
      if (hasVectorWorkspace && verb > 2)
      {
        std::string wSide = "right";
        MultipleDeriv wsDeriv;
        if (workspaceIsLeft) 
        {
          wSide = "left";
          wsDeriv = leftSparsity()->deriv(dnLeftIndex);
        }
        else
        {
          wsDeriv = rightSparsity()->deriv(dnRightIndex);
        }
        SUNDANCE_MSG2(verb, tab0 << "has workspace vector: "
          << wSide << " deriv= " 
          << wsDeriv
          << ", coeff index= " << workspaceCoeffIndex);
      }
      else
      {
        SUNDANCE_MSG2(verb, tab0 << "has no workspace vector");
      }

      hasWorkspace_[order].append(hasVectorWorkspace);
      workspaceIsLeft_[order].append(workspaceIsLeft);
      workspaceIndex_[order].append(workspaceIndex);
      workspaceCoeffIndex_[order].append(workspaceCoeffIndex);
      workspaceCoeffIsConstant_[order].append(workspaceCoeffIsConstant);
      
      ProductRulePerms perms;
      d.productRulePermutations(perms);
      SUNDANCE_MSG4(verb,
        tabs << "product rule permutations " << perms);

      Array<Array<int> > ccTerms;
      Array<Array<int> > cvTerms;
      Array<Array<int> > vcTerms;
      Array<Array<int> > vvTerms;
      Array<int> startingVector;
      ProductParity startingParity;

      bool hasStartingVector = false;

      for (ProductRulePerms::const_iterator 
             iter = perms.begin(); iter != perms.end(); iter++)
      {
        Tabs tab1;
        MultipleDeriv dLeft = iter->first.first();
        MultipleDeriv dRight = iter->first.second();
        int multiplicity = iter->second;
          
        /* skip this combination if we've already pulled it out 
         * for workspace */
        if (hasVectorWorkspace && workspaceIsLeft 
          && dLeft.order()==order) continue;
        if (hasVectorWorkspace && !workspaceIsLeft 
          && dRight.order()==order) continue;

        if (!leftSparsity()->containsDeriv(dLeft)
          || !rightSparsity()->containsDeriv(dRight)) continue;
        int iLeft = leftSparsity()->getIndex(dLeft);
        int iRight = rightSparsity()->getIndex(dRight);

        if (iLeft==-1 || iRight==-1) continue;
        SUNDANCE_MSG4(verb,
          tab1 << "left deriv=" << dLeft);
        SUNDANCE_MSG4(verb,
          tab1 << "right deriv=" << dRight);

        bool leftIsConst = leftSparsity()->state(iLeft)==ConstantDeriv;
        bool rightIsConst = rightSparsity()->state(iRight)==ConstantDeriv;

        if (leftIsConst)
        {
          Tabs tab2;
          SUNDANCE_MSG4(verb,
            tab2 << "left is const");
          int leftIndex = leftEval()->constantIndexMap().get(iLeft);
          if (rightIsConst)
          {
            SUNDANCE_MSG4(verb,
              tab2 << "right is const");
            int rightIndex = rightEval()->constantIndexMap().get(iRight);
            ccTerms.append(tuple(leftIndex, rightIndex, multiplicity));
          }
          else
          {
            SUNDANCE_MSG4(verb,
              tab2 << "right is vec");
            int rightIndex = rightEval()->vectorIndexMap().get(iRight);
            if (!hasVectorWorkspace && !hasStartingVector)
            {
              SUNDANCE_MSG4(verb,
                tab1 << "found c-v starting vec");
              startingVector = tuple(leftIndex, rightIndex, 
                multiplicity);
              hasStartingVector = true;
              startingParity = ConstVec;
            }
            else
            {
              SUNDANCE_MSG4(verb,
                tab1 << "found c-v term");
              cvTerms.append(tuple(leftIndex, rightIndex, 
                  multiplicity));
            }
          }
        }
        else
        {
          Tabs tab2;
          SUNDANCE_MSG4(verb,
            tab2 << "left is vec");
          int leftIndex = leftEval()->vectorIndexMap().get(iLeft);
          if (rightIsConst)
          {
            SUNDANCE_MSG4(verb,
              tab2 << "right is const");
            int rightIndex = rightEval()->constantIndexMap().get(iRight);
            if (!hasVectorWorkspace && !hasStartingVector)
            {
              SUNDANCE_MSG4(verb,
                tab1 << "found v-c starting vec");
              startingVector = tuple(leftIndex, rightIndex, 
                multiplicity);
              hasStartingVector = true;
              startingParity = VecConst;
            }
            else
            {
              SUNDANCE_MSG4(verb,
                tab1 << "found v-c term");
              vcTerms.append(tuple(leftIndex, rightIndex, 
                  multiplicity));
            }
          }
          else
          {
            SUNDANCE_MSG4(verb,
              tab2 << "right is vec");
            int rightIndex = rightEval()->vectorIndexMap().get(iRight);
            if (!hasVectorWorkspace && !hasStartingVector)
            {
              SUNDANCE_MSG4(verb,
                tab1 << "found v-v starting vec");
              startingVector = tuple(leftIndex, rightIndex, 
                multiplicity);
              hasStartingVector = true;
              startingParity = VecVec;
            }
            else
            {
              SUNDANCE_MSG4(verb,
                tab1 << "found v-v term");
              vvTerms.append(tuple(leftIndex, rightIndex, 
                  multiplicity));
            }
          }
        }
      }
      ccTerms_[order].append(ccTerms);
      cvTerms_[order].append(cvTerms);
      vcTerms_[order].append(vcTerms);
      vvTerms_[order].append(vvTerms);
      startingVectors_[order].append(startingVector);
      startingParities_[order].append(startingParity);

      if (verb > 2)
      {
        Tabs tab0;
        Out::os() << tab0 << "deriv " << i << " order=" << order ;
        if (resultIsConstant)
        {
          Out::os() << " constant result, index= ";
        }
        else
        {
          Out::os() << " vector result, index= ";
        }
        Out::os() << resultIndex_[order] << std::endl;
        {
          Tabs tab1;
          if (hasStartingVector) 
          {
            Out::os() << tab1 << "starting vector " << startingVector << std::endl;
          }
          Out::os() << tab1 << "c-c terms " << ccTerms << std::endl;
          Out::os() << tab1 << "c-v terms " << cvTerms << std::endl;
          Out::os() << tab1 << "v-c terms " << vcTerms << std::endl;
          Out::os() << tab1 << "v-v terms " << vvTerms << std::endl;
        }
      }
    }

    if (verb > 2)
    {
      Out::os() << tabs << "maps: " << std::endl;
      Out::os() << tabs << "vector index map " << vectorIndexMap() << std::endl;
      Out::os() << tabs << "constant index map " << constantIndexMap() << std::endl;
    }
  }
  catch(std::exception& e)
  {
    TEUCHOS_TEST_FOR_EXCEPTION(true, std::runtime_error, 
      "exception detected in ProductEvaluator: expr="
      << expr->toString() << std::endl
      << "exception=" << e.what());
  }
}