// $Id: maximizer.cpp,v 1.121 2018/01/03 21:33:02 mkkuhner Exp $

/*
Copyright 2002-2005 Mary K. Kuhner, Peter Beerli, and Joseph Felsenstein

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

/* Authors: Peter Beerli, Mary Kuhner, Jon Yamato and Joseph Felsenstein */


// Broyden-Fletcher-Goldfarb-Shanno maximizer implementation
//
// started 2000 Peter Beerli
// Major changes devised and implemented in 2004 by Eric Rynes.
// Customized method of steepest ascents, probing artificially large steps
// if necessary, has been added for conditional use.
//

#include <cassert>
#include <cmath>
#include <iostream>

#include "vectorx.h"
#include "maximizer.h"
#include "maximizer_strings.h"
#include "likelihood.h"
#include "mathx.h"                      // for system-specific isnan support
#include "parameter.h"
#include "registry.h"
#include "runreport.h"

#ifdef DMALLOC_FUNC_CHECK
#include "/usr/local/include/dmalloc.h"
#endif

#ifndef NDEBUG
#define TEST //This will trigger Eric's debug statements, below.
#endif

using namespace std;

//------------------------------------------------------------------------------------

const unsigned long int NTRIALS = 1000UL;
//const double LAMBDA_EPSILON = 10.0 * DBL_EPSILON;
//const double LAMBDA_EPSILON = 1.0e-50; // BUGBUG erynes--needs further investigation!
const double LAMBDA_EPSILON = DBL_EPSILON;  // EWFIX -- the above is way too small
const unsigned long int MAX_NUM_LONG_PUSHES = 8UL;
const double NORM_EPSILON = 0.0001;
const double lnL_EPSILON = 0.005;

#ifdef TEST
long int nMatrixResets = 0;
long int nLambdaSmall = 0;
long int n_dtg_zero = 0;
unsigned long int nFunc(0UL), nDFunc(0UL); // nIter is now used in all cases
#endif // TEST
unsigned long int nIter(0UL); // declared here for use by steepest ascents _and_ BFGS

//------------------------------------------------------------------------------------
// maximizer class -------------------------------------------------
//
// maximizer initialization

Maximizer::Maximizer (long int thisNparam)
{
    Initialize(thisNparam);
}

//------------------------------------------------------------------------------------

void Maximizer::Initialize(long int thisNparam)
{
    m_pPostLike = NULL;
    m_nparam = (unsigned long int)thisNparam;
    double zero = 0.0;
    m_param = CreateVec1d (m_nparam, zero);       // parameters to maximize
    m_lparam = CreateVec1d (m_nparam, zero);      // parameters to maximize
    m_oldlparam = CreateVec1d (m_nparam, zero);   // old log parameters
    m_gradient = CreateVec1d (m_nparam, zero);    // first derivatives
    m_oldgradient = CreateVec1d (m_nparam, zero); // old first derivatives
    m_paramdelta = CreateVec1d (m_nparam, zero);  // parameter difference
    m_gradientdelta = CreateVec1d (m_nparam, zero);// first derivative difference
    m_direction = CreateVec1d (m_nparam, zero);   // direction to jump uphill
    m_second = CreateVec2d (m_nparam, m_nparam, zero);// approx second derivative

    m_newparam = CreateVec1d (m_nparam, zero);        // temporary storage
    m_newlparam = CreateVec1d (m_nparam, zero);       // for speeding up
    m_temp = CreateVec1d (m_nparam, zero);
    m_dd = CreateVec2d (m_nparam, m_nparam, zero);

    m_isLinearParam = CreateVec1d(m_nparam, 0UL);
    // m_dataHasGrowth = false;
    m_dataHasLinearParam = false;
    m_dataHasGamma = false;
    m_constraintsVectorHasSlotForAlpha = false;
    m_maxAllowedValueForAlpha = 0.0;

    m_lastnormg = 0.0;

    m_constrained = false;
    // Keep m_constraints empty.  Fill it only using SetConstraints.
    if (!m_constraints.empty())
    {
        for (long int i = m_constraints.size() - 1; i > 0; i--)
            m_constraints[i].clear();
        m_constraints.clear();
    }
    m_constraintratio.clear(); // also fill only using SetConstraints.
}

//------------------------------------------------------------------------------------

Maximizer::~Maximizer ()
{
    // intentionally blank
}

//------------------------------------------------------------------------------------

void Maximizer::SetLikelihood (PostLike * thispostlike)
{
    m_pPostLike = thispostlike;
    m_minparamvalues = m_pPostLike->GetMinValues();
    m_maxparamvalues = m_pPostLike->GetMaxValues();
    m_minlparamvalues.clear();
    m_maxlparamvalues.clear();

    // Reset our variables.
    fill(m_isLinearParam.begin(), m_isLinearParam.end(), 0UL);
    m_dataHasLinearParam = false;

    // Record which parameters are to be treated linearly.
    for (unsigned long int i = 0; i < m_minparamvalues.size(); i++)
    {
        if (m_pPostLike->isLinearParam(i))
        {
            m_isLinearParam[i] = 1UL;
            m_dataHasLinearParam = true;
            m_minlparamvalues.push_back(m_minparamvalues[i]);
            m_maxlparamvalues.push_back(m_maxparamvalues[i]);
        }
        else
        {
            m_minlparamvalues.push_back(log(m_minparamvalues[i]));
            m_maxlparamvalues.push_back(log(m_maxparamvalues[i]));
        }
    }

    m_dataHasGamma = (dynamic_cast<GammaRegionPostLike*>(m_pPostLike)) ? true : false;
    if (m_dataHasGamma)
    {
        const RegionGammaInfo *pRGI = registry.GetRegionGammaInfo();
        if (!pRGI)
        {
            string msg = "Maximizer::SetLikelihood(), received a GammaRegionPostLike ";
            msg += "object, but failed to find a RegionGammaInfo object in the registry.";
            throw implementation_error(msg);
        }
        m_maxAllowedValueForAlpha = pRGI->GetMaxValue();
        if (m_maxparamvalues.size() != m_nparam)
        {
            m_maxparamvalues.push_back(m_maxAllowedValueForAlpha);
            m_minparamvalues.push_back(pRGI->GetMinValue());
            if (m_pPostLike->isLinearParam(m_nparam-1))
            {
                m_maxlparamvalues.push_back(m_maxparamvalues[m_maxparamvalues.size()-1]);
                m_minlparamvalues.push_back(m_minparamvalues[m_minparamvalues.size()-1]);
            }
            else
            {
                m_maxlparamvalues.push_back(log(m_maxparamvalues[m_maxparamvalues.size()-1]));
                m_minlparamvalues.push_back(log(m_minparamvalues[m_minparamvalues.size()-1]));
            }
        }
    }

    if (m_minparamvalues.size() != m_nparam)
    {
        string msg = "Maximizer::SetLikelihood(), vector m_minparamvalues ";
        msg += "contains values for " + ToString(m_minparamvalues.size());
        msg += " parameters; it should contain values for " + ToString(m_nparam);
        msg += " parameters, which is the value found in m_nparam.";
        throw implementation_error(msg);
    }
}

//------------------------------------------------------------------------------------
// Find optimum using  Broyden-Fletcher-Goldfarb-Shanno mechanism
// (or customized steepest ascent if growth is present)
// returns log(likelihood)
// replaces param with the result param

bool Maximizer::Calculate(vector<double>& thisparam, double& lnL, string& message)
{
    message = "";

    copy(thisparam.begin(), thisparam.end(), m_param.begin());
    LogVec0(m_param, m_lparam); // store log(params), 0 for neg. params
    bool calc_succeeded = true;

    nIter = 0UL;
#ifdef TEST
    nMatrixResets = 0;
    nLambdaSmall = 0;
    n_dtg_zero = 0;
    nFunc = 0UL;
    nDFunc = 0UL;
#endif // TEST

    double loglikelihood = 0.0;

    //if (m_dataHasGrowth || m_dataHasGamma) {
    if (m_dataHasLinearParam || m_dataHasGamma)
    {
        //loglikelihood = CalculateByParts();  //An LS debug test thingy.
        try
        {
            calc_succeeded = CalculateSteepest(loglikelihood, message);
        }
        catch (const insufficient_variability_over_regions_error& e)
        {
            lnL = -DBL_BIG;
            copy(m_param.begin(), m_param.end(), thisparam.begin());
            message = maxstr::MAX_HIGH_ALPHA_0;
            return false;
        }

    }
    else
    {
        calc_succeeded = CalculateBroyden(NTRIALS, loglikelihood, message);
    }

    if (!calc_succeeded)
    {
        lnL = loglikelihood;
        return false;
    }

    copy(m_param.begin(), m_param.end(), thisparam.begin());
    lnL = loglikelihood;
    return true;

} // Calculate

//------------------------------------------------------------------------------------
// SetParam sets vector m_newlparam = m_lparam + lambda*direction.
// "direction" is m_direction for BFGS, m_gradient for steepest ascents.
// For parameters such as growth, which are treated linearly,
// applying this step to m_newlparam is identical to applying it to m_newparam.
// erynes Sept. 2004:  Put the calculation code here in the maximizer,
// removed it from the plforces and likelihood classes.

double Maximizer::SetParam(const double& lambda, const DoubleVec1d& direction)
{
    unsigned long int i;
    double biggestChange = 0.0;

    for (i = 0; i < m_nparam; i++)
    {
        if (m_isLinearParam[i])
        {
            m_newparam[i] = m_param[i] + lambda * direction[i];

            double change = m_newparam[i] - m_param[i];
            change = fabs(change);
            if(change > biggestChange) biggestChange = change;

            AdjustExtremeLinearParam(i);
            m_newlparam[i] = m_newparam[i];
        }
        else if (m_param[i] > 0.0) // param is treated logarithmically
        {
            m_newlparam[i] = m_lparam[i] + lambda * direction[i];

            double change = m_newlparam[i] - m_lparam[i];
            change = fabs(change);
            if(change > biggestChange) biggestChange = change;

            AdjustExtremeLogParam(i);
            m_newparam[i] = exp(m_newlparam[i]);
        }
    }
    return biggestChange;

} // Maximizer::SetParam

//------------------------------------------------------------------------------------

void Maximizer::CoutNewParams() const // used for debugging
{
    unsigned long int i;
    for (i = 0; i < m_nparam; i++)
    {
        cout << setw(10) << m_newparam[i] << " ";
    }
    cout << ":: ";
    for (i = 0; i < m_nparam; i++)
    {
        if (m_isLinearParam[i])
        {
            cout << setw(10) << "--" << " ";
        }
        else
        {
            cout << setw(10) << m_newlparam[i] << " ";
        }
    }
    cout << endl;
}

//------------------------------------------------------------------------------------

void Maximizer::CoutCurParams() const // used for debugging
{
    unsigned long int i;
    for (i = 0; i < m_nparam; i++)
    {
        cout << setw(10) << m_param[i] << " ";
    }
    cout << ":: ";
    for (i = 0; i < m_nparam; i++)
    {
        if (m_isLinearParam[i])
        {
            cout << setw(10) << "--" << " ";
        }
        else
        {
            cout << setw(10) << m_lparam[i] << " ";
        }
    }
    cout << endl;
}

//------------------------------------------------------------------------------------

void Maximizer::CoutByLinOrNoMult(double mult,const DoubleVec1d& d) const // used for debugging
{
    unsigned long int i;
    for (i = 0; i < d.size(); i++)
    {
        if (!m_isLinearParam[i])
        {
            cout << setw(10) << "--" << " ";
        }
        else
        {
            cout << setw(10) << mult * d[i] << " ";
        }
    }
    cout << ":: ";
    for (i = 0; i < d.size(); i++)
    {
        if (m_isLinearParam[i])
        {
            cout << setw(10) << "--" << " ";
        }
        else
        {
            cout << setw(10) << mult * d[i] << " ";
        }
    }
    cout << endl;
}

//------------------------------------------------------------------------------------
// A one-dimensional version of SetParam(), for adding a step to only one
// component of the parameter vector.  Added by erynes for use with
// CalculateSteepest().  Modified by erynes early Sept. '04 to allow for
// constrained parameters ("whichparam" will often be a one-element vector).

bool Maximizer::SetParam1d(const double & steplength,
                           const vector<unsigned long int> * pWhichparam)
{
    vector<unsigned long int>::const_iterator it;

    if (m_isLinearParam[(*pWhichparam)[0]])
    {
        for (it = pWhichparam->begin(); it != pWhichparam->end(); it++)
        {
            m_newparam[*it]  = m_param[*it] + steplength;
            if (m_newparam[*it] < m_minparamvalues[*it])
            {
#ifdef TEST
                cerr << "Parameter \"out of bounds\"--setting param " << *it
                     << " (and any parameters constrained to equal this) to "
                     << m_minparamvalues[*it] << " instead of "
                     << m_newparam[*it] << "." << endl;
#endif // TEST
                for (it = pWhichparam->begin();
                     it != pWhichparam->end(); it++)
                {
                    m_newparam[*it] = m_minparamvalues[*it];
                    m_newlparam[*it] = m_minlparamvalues[*it];
                }
                return false;
            }
            else if (m_newparam[*it] > m_maxparamvalues[*it])
            {
#ifdef TEST
                cerr << "Parameter \"out of bounds\"--setting param " << *it
                     << " (and any parameters constrained to equal this) to "
                     << m_maxparamvalues[*it] << " instead of "
                     << m_newparam[*it] << "." << endl;
#endif // TEST
                for (it = pWhichparam->begin();
                     it != pWhichparam->end(); it++)
                {
                    m_newparam[*it] = m_maxparamvalues[*it];
                    m_newlparam[*it] = m_maxlparamvalues[*it];
                }
                return false;
            }
            m_newlparam[*it] = m_newparam[*it];
        }
        return true;
    }

    // Else the parameter(s) is/are treated logarithmically.
    // No worries; we're guaranteed not to get linear/log mixing within this function.

    for (it = pWhichparam->begin(); it != pWhichparam->end(); it++)
    {
        m_newlparam[*it] = m_lparam[*it] + steplength;
        if (m_newlparam[*it] < m_minlparamvalues[*it])
        {
#ifdef TEST
            cerr << "Parameter \"out of bounds\"--setting param " << *it
                 << " (and any parameters constrained to equal this) to "
                 << m_minparamvalues[*it] << " instead of "
                 << exp(m_newlparam[*it]) << "." << endl;
#endif // TEST
            for (it = pWhichparam->begin();
                 it != pWhichparam->end(); it++)
            {
                m_newparam[*it] = m_minparamvalues[*it];
                m_newlparam[*it] = m_minlparamvalues[*it];
            }
            return false;
        }
        else if (m_newlparam[*it] > m_maxlparamvalues[*it])
        {
#ifdef TEST
            cerr << "Parameter \"out of bounds\"--setting param " << *it
                 << " (and any parameters constrained to equal this) to "
                 << m_maxparamvalues[*it] << " instead of "
                 << exp(m_newlparam[*it]) << "." << endl;
#endif // TEST
            for (it = pWhichparam->begin();
                 it != pWhichparam->end(); it++)
            {
                m_newparam[*it] = m_maxparamvalues[*it];
                m_newlparam[*it] = m_maxlparamvalues[*it];
            }
            return false;
        }
        m_newparam[*it] = exp(m_newlparam[*it]);
    }
    return true;

} // Maximizer::SetParam1d

//------------------------------------------------------------------------------------
// calculates the norm of the first derivative (or any other vector):
// sqrt(sum(x^2))

double Maximizer::Norm(const DoubleVec1d& g)
{
    return sqrt(inner_product(g.begin(), g.end(), g.begin(), 0.0));
} // Maximizer::Norm

//------------------------------------------------------------------------------------
// Debugging function.

double Maximizer::GetLastNorm()
{
    return m_lastnormg;
}

//------------------------------------------------------------------------------------
// Calculates the direction for the Broyden-Fletcher-Goldfarb-Shanno algorithm:
// direction = second  .  gradient
// erynes note:  This direction points to the maximum.
// Textbooks applying BFGS to minimization set direction = -1*second . gradient.

void Maximizer::CalcDirection ()
{
    DoubleVec1d::iterator dir;
    DoubleVec2d::iterator h;
    for (dir = m_direction.begin (), h = m_second.begin ();
         dir != m_direction.end (); dir++, h++)
        (*dir) =
            inner_product ((*h).begin (), (*h).end (), m_gradient.begin (), 0.0);
}

//------------------------------------------------------------------------------------

void Maximizer::ExplainParamChange(long index, double oldVal, double newVal, string) //kind unused.
{
#ifdef TEST
    cerr << "Parameter \"out of bounds\"--setting param " << index
         << " to " << newVal << " instead of "
         << oldVal << "." << endl;
#endif // TEST
}

//------------------------------------------------------------------------------------

void Maximizer::AdjustExtremeLinearParam(long index)
{

    if (m_newparam[index] < m_minparamvalues[index])
    {
        ExplainParamChange(index,m_newparam[index],m_minparamvalues[index],"min");
        m_newparam[index] = m_minparamvalues[index];
    }
    else if (m_newparam[index] > m_maxparamvalues[index])
    {
        ExplainParamChange(index,m_newparam[index],m_maxparamvalues[index],"max");
        m_newparam[index] = m_maxparamvalues[index];
    }

}

//------------------------------------------------------------------------------------

void Maximizer::AdjustExtremeLogParam(long index)
{

    if (m_newlparam[index] < m_minlparamvalues[index])
    {
        ExplainParamChange(index,exp(m_newlparam[index]),m_minparamvalues[index],"min");
        m_newlparam[index] = m_minlparamvalues[index];
    }
    else if (m_newlparam[index] > m_maxlparamvalues[index])
    {
        ExplainParamChange(index,exp(m_newlparam[index]),m_maxparamvalues[index],"max");
        m_newlparam[index] = m_maxlparamvalues[index];
    }

}

//------------------------------------------------------------------------------------
// CalcSecond updates approximative second derivative matrix.
// erynes note:  This is the BFGS algorithm; it must produce symmetric,
// positive-definite matricies.  It is guaranteed to do so, unless
// finite-precision effects (roundoff errors) become significant.  Symmetric,
// real-valued matricies are positive definite if and only if they have strictly
// positive eigenvalues.  (A positive-definite matrix can contain negative
// elements.)  The matrix produced here is identical to the one you'd produce if
// you were looking for a minimum, rather than a maximum.  This is fine--
// actually, this is great.  See comments for Calculate() and CalcDirection()
// if you are curious about the question of maximizing vs. minimizing.

void Maximizer::CalcSecond ()
{
    long int i, j, k;
    long int n = m_paramdelta.size ();
    double t, dtg;
    DoubleVec2d::iterator h;
    DoubleVec1d::iterator tt;

    // m_paramdelta^T . m_gradientdelta
    dtg = inner_product (m_paramdelta.begin (), m_paramdelta.end (),
                         m_gradientdelta.begin (), 0.0);
    if (dtg != 0)
    {
        dtg = 1.0 / dtg;
    }
    else
    {
#ifdef TEST
        n_dtg_zero++;
#endif // TEST
        ResetSecond();
        return;
    }

    // m_temp = m_gradientdelta^T . m_second
    for (tt = m_temp.begin (), h = m_second.begin ();
         tt != m_temp.end (); tt++, h++)
        (*tt) = inner_product (m_gradientdelta.begin (), m_gradientdelta.end (),
                               (*h).begin (), 0.0);
    // t = m_temp . m_gradientdelta
    t = inner_product (m_temp.begin (), m_temp.end (),
                       m_gradientdelta.begin (), 0.0);
    // t = (1 + (m_gradientdelta^T . m_second . m_gradientdelta) * dtg) * dtg
    t = (1.0 + t * dtg) * dtg;
    // m_paramdelta . m_gradientdelta^T . m_second
    for (i = 0; i < n; i++)
        for (j = 0; j < n; j++)
        {
            m_dd[i][j] = 0.0;
            for (k = 0; k < n; k++)
                m_dd[i][j] += m_paramdelta[i] * m_gradientdelta[k] * m_second[k][j];
        }

    for (i = 0; i < n; i++)
        for (j = 0; j < n; j++)
            m_second[i][j] += m_paramdelta[i] * m_paramdelta[j] * t
                - (m_dd[i][j] + m_temp[i] * m_paramdelta[j]) * dtg;

}

//------------------------------------------------------------------------------------

void Maximizer::ResetSecond ()
{
    unsigned long int i;
    unsigned long int j;
    unsigned long int nsecond = m_second.size (); // m_second is square
    for (i = 0; i < nsecond; ++i)
    {
        m_second[i][i] = 1.0;
        for (j = 0; j < i; ++j)
        {
            m_second[i][j] = 0.0;
            m_second[j][i] = 0.0;
        }
    }
#ifdef TEST
    nMatrixResets++;
#endif // TEST
}

//------------------------------------------------------------------------------------
// Helper function to help us determine whether we haven't bracketed a maximum.
// A local maximum (or minimum) of a differentiable (hence continuous) function
// is bounded by an increasing slope on one side and a decreasing slope on the
// other side.
// This function receives 2 slopes and returns true if they have the same sign.
// IMPORTANT NOTE #1.  The "new" slope is treated differently from the "old"
// slope.  If the new slope is sufficiently flat, false is returned.
// Parameter "tolerance" defines flatness.
// IMPORTANT NOTE #2.  "tolerance" is assumed to be positive.  We avoid calling
// fabs() for speed's sake.

inline bool SlopesHaveSameSign(const double& oldgrad, const double& newgrad,
                               const double& tolerance)
{
    if (newgrad > tolerance)
        return oldgrad > 0.0;
    if (newgrad < -tolerance)
        return oldgrad < 0.0;
    return false; // reached convergence on this component:
    // -tolerance <= newgrad <= +tolerance
}

//------------------------------------------------------------------------------------
// helper function--a simply-named wrapper for std::transform
// calculates differences between two arrays
// result = one - two

void CalcDelta (const DoubleVec1d & one,
                const DoubleVec1d & two, DoubleVec1d & result)
{
    transform (one.begin (), one.end (), two.begin (),
               result.begin (), minus < double >());
}

//------------------------------------------------------------------------------------

void Maximizer::SetMLEs(DoubleVec1d newMLEs)
{
    m_param = newMLEs;
    m_lparam = newMLEs;
    LogVec0 (m_param, m_lparam);
    for(unsigned long int i = 0; i < m_nparam; i++)
    {
        if (m_isLinearParam[i])
        {
            m_lparam[i] = m_param[i]; // possibly unnecessary...
        }
    }
}

//------------------------------------------------------------------------------------

bool Maximizer::CalculateSteepest(double& lnL, string& message)
// Find the maximum lnL using a customized version of the method of steepest
// ascents.  Use when growth is present; otherwise use CalculateBroyden().
// See comments within the function body for details on the customization.
//
// Implemented by erynes in early 2004.
//
// Returns false (failure) and lnL = -DBL_BIG if:
// 1. lnL cannot be calculated for the initial point Po
// 2. A derivative points toward a higher lnL, but none is found there
// 3. Along a coordinate axis, we bound the location of a peak, but fail
//    to find it within this region (this is related to (2))
// 4. We move many orders of magnitude along a coordinate axis
//    and observe no change in the sign of the slope
//
// Otherwise returns true (success) and a valid lnL value.
// Failure generally means there is something wrong with the trees;
// this can be pinpointed by nimbly stepping through the plforces code.
//
{
    unsigned long int i;
    double newlike(0.0), lambda(1.0), oldlike(0.0);

    if (!m_pPostLike->Calculate(m_param, m_lparam, oldlike))
    {
#ifdef TEST
        nFunc++;
        cerr << "Initial parameter vector received by CalculateSteepest() "
             << "is infinitely unlikely to reproduce the genealogies;"
             << endl << "params = (" << m_param[0];
        for (unsigned long int k = 1; k < m_nparam; k++)
            cerr << ", " << m_param[k];
        cerr << ").  Returning lnL = " << -DBL_BIG << "...." << endl;
#endif // TEST
        lnL = -DBL_BIG;
        return false; // m_param is infinitely unlikely to reproduce
        // the genealogies currently under consideration
    }

    // The first PostLike::Calculate call will always return oldlike == 0, because
    // log(Prob(G|Po)/Prob(G|Po)) = log(1) = 0.0  (Well, this is true for SinglePostLike.
    // It's not true when calculating likelihoods over multiple regions.)
    // Each call to DCalculate must be preceded by a call to Calculate with the same
    // parameter list, because Calculate sets up an intermediate variable needed by
    // DCalculate (see comment in likelihood.cpp).
    DCalculate(m_param, m_gradient); // result into m_gradient
#ifdef TEST
    nDFunc++;
#endif // TEST

    double normg = Norm(m_gradient), old_normg = DBL_BIG;
    bool calc_succeeded = true;

#ifdef TEST
    cerr << "Begin; gradient = (" << m_gradient[0];
    for (i = 1; i < m_gradient.size(); ++i)
        cerr << ", " << m_gradient[i];
    cerr << ")" << endl << "params = (" << m_param[0];
    for (i = 1; i < m_param.size(); ++i)
        cerr << ", " << m_param[i];
    cerr << ")" << endl << "normg = " << normg << ", and lnL = " << oldlike << endl;
#endif

    // Apply the standard, classic method of n-dimensional steepest ascents
    // while our steps take us to points that are successively higher and flatter.
    // Quit when we get less flat, or if our step size has gotten tiny.
    // This approach seems to efficiently get us onto the ridge that usually
    // appears in the likelihood surfaces of growing populations.
    // Once we're atop the ridge, a customized algorithm works better for us.

    while (normg > NORM_EPSILON && normg < old_normg
           && old_normg - normg > 0.1*NORM_EPSILON)
    {
        // Iterate over the first few ascent directions.
        lambda = 2.0; // reset; will be reduced to 1 before lambda gets used
        double thisChange(DBL_MAX);
        do {
            // "Line search" -- find a higher lnL along this ascent direction
            lambda /= 2.0;    // set the step length
            thisChange = SetParam(lambda, m_gradient); // probe this step in this direction
            calc_succeeded = m_pPostLike->Calculate (m_newparam, m_newlparam,
                                                     newlike);
#ifdef TEST
            nFunc++;
#endif // TEST
        } while ((!calc_succeeded || newlike <= oldlike ||
                  systemSpecificIsnan(newlike)) && lambda > LAMBDA_EPSILON
                 && thisChange > LAMBDA_EPSILON
            );

        if (lambda <= LAMBDA_EPSILON ||  thisChange <= LAMBDA_EPSILON)
        {
            // Something is wrong.  We started with a point, perhaps the initial
            // point Po that was passed to Calculate(), and successfully
            // calculated lnL there.  We calculated the gradient there; if there
            // was a problem with it, the problem might be manifesting itself
            // here.  Otherwise, the gradient pointed us toward a higher lnL, and
            // we probed steplengths along this direction of smaller and smaller
            // lengths until we wound up basically back at the starting point.
            // There was no higher lnL to be found, contrary to what the gradient
            // claimed.
            unsigned long int k;
            string msg = maxstr::MAX_BAD_LNL_0;
            msg += Pretty(oldlike) + maxstr::MAX_BAD_LNL_1;
            msg += Pretty(m_param[0]);
            for (k = 1; k < m_nparam; k++)
                msg += ", " + Pretty(m_param[k]);
            if (!calc_succeeded)
            {
                // Not only was there no higher lnL to be found--we were unable to
                // calculate lnL at a point in this guaranteed ascent direction,
                // extremely close to the point where we _did_ calculate lnL!
                msg += maxstr::MAX_BAD_LNL_2A1;
                msg += Pretty(lambda) + maxstr::MAX_BAD_LNL_2A2;
                message = msg;
                lnL = -DBL_BIG;
                return false;
            }
            msg += maxstr::MAX_BAD_LNL_2B;
            message = msg;
#ifdef TEST
            cerr << "Maximizer:  Initial loop, lambda = " << lambda
                 << ", giving up with normg = " << normg << ", lnL = " << oldlike
                 << ", after " << nIter << " iterations." << endl
                 << "            gradient = (" << m_gradient[0];
            for (i = 1; i < m_nparam; i++)
                cerr << ", " << m_gradient[i];
            cerr << ")" << endl;
#endif
            lnL = -DBL_BIG;
            return false;
        }

        // accept the step that we just tested
        m_param = m_newparam;
        m_lparam = m_newlparam;
        DCalculate(m_param, m_gradient);
        // The call to PostLike::Calculate, which needs to precede every call to
        // DCalculate, was performed at beginning of the while loop, above.
        oldlike = newlike;
        old_normg = normg;
        normg = Norm(m_gradient);
#ifdef TEST
        nDFunc++;
#endif
        nIter++;
    } // end of standard method-of-steepest-ascents loop

    if (normg <= NORM_EPSILON)
    {
        // Found the maximum, according to our convergence criterion.
#ifdef TEST
        cerr << "Finished before entering custom code; gradient = ("
             << m_gradient[0];
        for (i = 1; i < m_gradient.size(); ++i)
            cerr << ", " << m_gradient[i];
        cerr << ")" << endl << "params = (" << m_param[0];
        for (i = 1; i < m_param.size(); ++i)
            cerr << ", " << m_param[i];
        cerr << ")" << endl << "|g| = " << normg << ", and lnL = " << oldlike
             << ", after " << nIter << " iterations, " << nFunc
             << " lnL function evaluations, and " << nDFunc
             << " derivative evaluations." << endl << endl;
#endif
        m_lastnormg = normg;
        lnL = oldlike;
        return true;
    }

#ifdef TEST
    cerr << "Beginning one-at-a-time, custom code.  So far we have:" << endl;
    cerr << nIter << " iterations, |g| = " << normg << ", lnL = " << oldlike
         << ", nFunc = " << nFunc << ", nDFunc = " << nDFunc << endl
         << "     gradient = (" << m_gradient[0];
    for (i = 1; i < m_nparam; i++)
        cerr << ", " << m_gradient[i];
    cerr << ")," << endl << "     params = (" << m_param[0];
    for (i = 1; i < m_nparam; i++)
        cerr << ", " << m_param[i];
    cerr << ")." << endl;
#endif

    // Custom algorithm.  Should AVOID this when growth is absent.
    // Probe parameters one at a time; allow for artificially large steps because
    // growth tends to yield likelihood surfaces that are very long, narrow ridges
    // that are very gradually increasing (and non-quadratically so) on top.

    const long int UNINITIALIZED = -99L;
    const long int FIXED = -2L;
    const long int UNCONSTRAINED = -1L;
    const unsigned long int DONE = 1UL;
    // Explanation of the following vectors:
    // origGradGuide is a copy of the gradient guide with the values it held
    //   upon entry into this function, possibly set by the profiler;
    //   it does not change during the lifetime of this function call.
    // tempGradGuide changes with each iteration; it suppresses all but one
    //   gradient component calculation by PostLike.
    // compositeGradGuide is an "informed" composite of origGradGuide and the
    //   constraints; it doesn't change during the lifetime of this function call.
    // currentUnconstrainedParam and whichparam serve merely to simplify the code,
    //   so that the logic for unconstrained and constrained parameters can be
    //   written once in a uniform manner.
    vector<ParamStatus> origGradGuide(m_pPostLike->m_working_pstatusguides);
    assert(origGradGuide.size() == m_nparam);
    vector<ParamStatus> tempGradGuide(m_nparam, ParamStatus(pstat_constant));
    vector<long int> compositeGradGuide(m_nparam, UNINITIALIZED);
    vector<unsigned long int> currentUnconstrainedParam(1, 0UL);
    vector<unsigned long int>  *pWhichparam = &currentUnconstrainedParam;
    double numNonfixedParams(0.0); // determines convergence criterion

    // Set up the compositeGradGuide (see comment at its declaration).  We use it
    // to efficiently reset the gradient guide over and over while we calculate
    // derivatives for one independent parameter (or set of constrained
    // parameters) at a time.

    for (unsigned long int j = 0; j < m_nparam; j++)
    {
        if (UNINITIALIZED != compositeGradGuide[j])
            continue; // we've already updated this element
        if (!origGradGuide[j].Varies())
            compositeGradGuide[j] = FIXED;
        else if (!origGradGuide[j].Grouped())
        {
            numNonfixedParams += 1.0;
            compositeGradGuide[j] = UNCONSTRAINED;
        }
        else // constrained
        {
            bool found_j = false;
            unsigned long int k;
            for (k = 0; k < m_constraints.size(); k++)
            {
                // Loop over the constraint vectors to find which parameters
                // are constrained to equal one another--
                // e.g., a vector in position 0 containing 4 and 6,
                // followed by a vector in position 1 containing 5 and 9,
                // means that parameter 4 is constrained to equal parameter 6
                // and parameter 5 is constrained to equal parameter 9.
                // (m_constraints[0] holds 4, 6; m_constraints[1] holds 5, 9.)
                // In this case, if "F" means fixed and "U" means "unconstrained,"
                // then our compositeGradGuide vector might end up looking
                // like this:  U, U, U, F, 0, 1, 0, F, U, 1, U, F.
                // (This could be a 3-population run in which M12 = M21,
                // M13 = M31, and Mii = 0 for i = 1, 2, 3.)
                // Each of the constraint vectors has been sorted
                // in ascending order.  Also, no parameter index appears twice.
                if (j == m_constraints[k][0])
                {
                    compositeGradGuide[j] = k;
                    for (unsigned long int m = 1; m < m_constraints[k].size(); m++)
                        compositeGradGuide[m_constraints[k][m]] = k;
                    found_j = true;
                    break; // found it, look no further; retain "k"
                }
            }
            if (!found_j)
            {
                const ParamVector paramvec(true);  // read-only copy
                string paramname = paramvec[j].GetName();
                string msg = "Error:  Thee maximizer received a gradient guide ";
                msg += "indicating that " + paramname + " should be ";
                msg += "constrained, but this parameter was not found in the ";
                msg += "maximizer\'s lookup table of constraints.  This is ";
                msg += "a bug in LAMARC you shouldn't be able to reach; sorry.";
                throw implementation_error(msg);
            }
            else
                numNonfixedParams += 1.0 * m_constraints[k].size();
        }
    } // end of loop to set up compositeGradGuide

    double gradient_i(0.0); // ith component of the gradient
    vector<unsigned long int> grad_calculated(m_nparam, 0UL); // "to-do list"
    const double GRAD_COMPONENT_EPSILON = NORM_EPSILON /
        sqrt(numNonfixedParams); // convergence criterion for each component
    // Note: numNonfixedParams > 0, else we would have calculated a gradient
    // vector that's identically 0 at the top of this function, and returned.

#ifdef TEST
    cerr << "GRAD_COMPONENT_EPSILON = " << GRAD_COMPONENT_EPSILON << "." << endl;
#endif // TEST

    i = 0;

    long int previousComponentSearched = FLAGLONG;
    long int numConsecutiveTimesThisComponentHasBeenSearched = 0;
    const long int maxNumConsecutiveComponentSearchesAllowed = 4;

    while (normg > NORM_EPSILON && nIter < 300)
    {
        if (m_constrained && 0 == i)
            fill(grad_calculated.begin(), grad_calculated.end(), 0UL);
        // new cycle, so reset the "checklist" of what's been done

        if (FIXED == compositeGradGuide[i]                      // fixed; continue
            || fabs(m_gradient[i]) <= GRAD_COMPONENT_EPSILON    //  flat; continue
            || grad_calculated[i] == DONE) // already done in this cycle; continue
        {
            i++;
            if (i == m_nparam)
                i = 0; // loop back to the beginning
            continue;
        }

        if (UNCONSTRAINED == compositeGradGuide[i])
        {
            currentUnconstrainedParam[0] = i;
            tempGradGuide[i] = ParamStatus(pstat_unconstrained);
            pWhichparam = &currentUnconstrainedParam;
            // For example, if i == 2, and we use "0" to denote pstat_constant
            // and "U" to denote pstat_unconstrained, then tempGradGuide
            // will now look like this:  0 0 U 0 0 0 0 0 ...
            // and whichparam will look like this:  2.
        }

        else // constrained--we already determined that "i" is not fixed
        {
            vector<unsigned long int>::const_iterator it;
            for (it = m_constraints[compositeGradGuide[i]].begin();
                 it != m_constraints[compositeGradGuide[i]].end(); it++)
            {
                tempGradGuide[*it] = ParamStatus(pstat_identical);
                grad_calculated[*it] = DONE; // mark each one as "done"
            }
            pWhichparam = &(m_constraints[compositeGradGuide[i]]);
            // For example, if i == 4, and parameters 4 and 6 are constrained
            // to be equal, then if we use "0" to denote pstat_constant
            // and "C" to denote pstat_constrained, then tempGradGuide
            // will now look like this:  0 0 0 0 C 0 C 0 0 0 ...
            // and whichparam will look like this:  4 6.
        }

        // Are we "stuck?"
        if (static_cast<long int>(i) != previousComponentSearched)
            numConsecutiveTimesThisComponentHasBeenSearched = 0;
        else
            if (numConsecutiveTimesThisComponentHasBeenSearched > maxNumConsecutiveComponentSearchesAllowed)
                break; // maximizer warning will ensue

        vector<unsigned long int>::const_iterator it;
        // Temporarily restrict gradient calculations to this one component.
        SetGradGuide(tempGradGuide);

        // Find the local maximum along this direction.
        if (!BracketTheMaximumAndFindIt(pWhichparam, GRAD_COMPONENT_EPSILON,
                                        oldlike, newlike, message))
        {
            // Failure.  Error message already set.
            if (string::npos != message.find(maxstr::MAX_NO_UPPER_BOUND_0) && m_nparam - 1 == i)
            {
                if (m_dataHasGamma && m_newparam[i] >= m_maxAllowedValueForAlpha)
                {
                    SetGradGuide(origGradGuide); // restore to original values
                    throw insufficient_variability_over_regions_error(m_newparam[i],
                                                                      m_maxAllowedValueForAlpha);
                }
            }

            SetGradGuide(origGradGuide); // restore to its original values
            lnL = newlike; // -DBL_BIG, or something else for "stairway to heaven" surface
            return lnL > -DBL_BIG ? true : false; // per lpsmith request
        }

        // Update our variables.
        for (it = pWhichparam->begin(); it != pWhichparam->end(); it++)
        {
            m_param[*it] = m_newparam[*it];
            m_lparam[*it] = m_newlparam[*it];
        }
        oldlike = newlike;

        // Calculate the full gradient to see whether we've converged upon a flat
        // peak.  Recall that adjusting one component can affect another gradient
        // component.  Since we've just calculated the ith gradient component, we
        // avoid needlessly recalculating it here.  "i" could be a single,
        // unconstrained parameter, or a set of parameters constrained to
        // equal each other.
        SetGradGuide(origGradGuide); // restore to get full gradient
        gradient_i = m_gradient[i]; // store what we've just calculated
        // Suppress recalculation of "i" by DCalculate.
        for (it = pWhichparam->begin(); it != pWhichparam->end(); it++)
            m_pPostLike->m_working_pstatusguides[*it] = ParamStatus(pstat_constant);
        DCalculate(m_param, m_gradient); // full gradient except "i"
        for (it = pWhichparam->begin(); it != pWhichparam->end(); it++)
        {
            m_gradient[*it] = gradient_i; // restore component "i"
            // to get the full gradient
            // While we're at it, reset the one-at-a-time gradient guide
            // to "all constant" in preparation for the next iteration.
            // That is, undo the last operation we made onto tempGradGuide.
            tempGradGuide[*it] = ParamStatus(pstat_constant);
        }
        old_normg = normg;
        normg = Norm(m_gradient);

        // Increment i to the next component.
        previousComponentSearched = i; // store this, to determine if we're "stuck"
        numConsecutiveTimesThisComponentHasBeenSearched++;
        i++;
        if (i == m_nparam)
        {
            if (m_dataHasGamma && m_param[i-1] > m_maxAllowedValueForAlpha)
            {
                SetGradGuide(origGradGuide); // restore to original values
                throw insufficient_variability_over_regions_error(m_param[i-1],
                                                                  m_maxAllowedValueForAlpha);
            }
            i = 0; // loop back to the beginning of the vector
        }

        nIter++;
#ifdef TEST
        cerr << "Iter " << nIter << ", |g| = " << normg << ", lnL = " << oldlike
             << ", nFunc = " << nFunc << ", nDFunc = " << nDFunc << endl
             << "     gradient = (" << m_gradient[0];
        for (unsigned long int k = 1; k < m_nparam; k++)
            cerr << ", " << m_gradient[k];
        cerr << ")," << endl << "     params = (" << m_param[0];
        for (unsigned long int k = 1; k < m_nparam; k++)
            cerr << ", " << m_param[k];
        cerr << ")." << endl;
#endif

    }

#ifdef TEST
    cerr << "Finished; |g| = " << normg << " and lnL = " << oldlike
         << ", after " << nIter << " iterations, " << nFunc
         << " lnL function evaluations, and " << nDFunc
         << " derivative evaluations." << endl << endl;
#endif

    if (normg > NORM_EPSILON)
    {
        string msg = maxstr::MAX_NO_CONVERGENCE_0
            + maxstr::MAX_NO_CONVERGENCE_1 + ToString(nIter)
            + maxstr::MAX_NO_CONVERGENCE_2
            + maxstr::MAX_NO_CONVERGENCE_3  + Pretty(normg)
            + maxstr::MAX_NO_CONVERGENCE_4;
        message = msg;
    }

    SetGradGuide(origGradGuide); // restore to original values
    m_lastnormg = normg;
    lnL = oldlike;
    return true;
} // CalculateSteepest

//------------------------------------------------------------------------------------
// Attempt to bracket the maximum for one component alone, then search the
// bracketed region for this maximum.  "whichparam" is either a one-element
// vector holding the parameter number of an unconstrained parameter, or a
// vector holding the parameter numbers of a set of parameters that are
// constrained to equal one another.  Note that in the latter case,
// m_gradient[2] == m_gradient[1] == m_gradient[0]
// and likewise for vectors m_newparam and m_param.

bool Maximizer::BracketTheMaximumAndFindIt(const vector<unsigned long int> * pWhichparam,
                                           const double epsilon, double oldlike,
                                           double & newlike, string & message)
{
    double lambda(1.0), orig_gradient_i(0.0);
    vector<unsigned long int>::const_iterator it;
    bool calc_succeeded(true);
    const unsigned long int i = (*pWhichparam)[0]; // This is either the index
    // of an unconstrained parameter,
    // or the index of the first of a
    // set of parameters constrained
    // to equal one another.

    SetParam1d(lambda * m_gradient[i], pWhichparam);
    orig_gradient_i = m_gradient[i]; // "ith" component of gradient
    calc_succeeded = m_pPostLike->Calculate(m_newparam, m_newlparam, newlike);
#ifdef TEST
    nFunc++;
#endif // TEST
    if (calc_succeeded)
    {
        if (newlike >= oldlike)
        {
            // We took the full Newton step (lambda == 1) along the direction of
            // increase, and we found a higher lnL.  Does the slope here point to
            // a yet-higher lnL farther along this direction, or have we now
            // bracketed the maximum along the direction of this component?
            DCalculate(m_newparam, m_gradient); // grad guide avoids
            // unnecessary calculations
#ifdef TEST
            nDFunc++;
#endif
            if (SlopesHaveSameSign(orig_gradient_i, m_gradient[i], epsilon))
            {
                // m_gradient[i] did not change sign.  Hence a maximum lies ahead
                // in this direction.  Probe artificially large steps of
                // lambda = 10, 100, etc., until we bracket the maximum.
                bool slopes_have_same_sign = true;
                unsigned long int numLongPushes = 0;

                while (numLongPushes < MAX_NUM_LONG_PUSHES &&
                       calc_succeeded &&
                       newlike >= oldlike &&
                       slopes_have_same_sign)
                {
                    // Accept this step, since it has a higher lnL,
                    // and a yet-higher lnL lies ahead of it.
                    for (it = pWhichparam->begin(); it != pWhichparam->end(); it++)
                    {
                        m_param[*it] = m_newparam[*it];
                        m_lparam[*it] = m_newlparam[*it];
                    }
                    oldlike = newlike;
                    // Do NOT update orig_gradient_i.  Need consistency.

                    // Take a new step along this direction.
                    if (m_isLinearParam[i])
                    {
                        lambda *= 10.0;
                        if (!SetParam1d(lambda*orig_gradient_i, pWhichparam))
                            numLongPushes = MAX_NUM_LONG_PUSHES; // parameter boundary reached
                    }
                    else // Multiply the param by 10 or 0.1.
                        if (!SetParam1d(LOG10*sign(orig_gradient_i), pWhichparam))
                            numLongPushes = MAX_NUM_LONG_PUSHES; // parameter boundary reached

                    numLongPushes++; // count the number of these steps

                    calc_succeeded = m_pPostLike->Calculate(m_newparam, m_newlparam, newlike);
#ifdef TEST
                    nFunc++;
#endif // TEST
                    if (calc_succeeded)
                    {
                        if (newlike >= oldlike)
                        {
                            DCalculate(m_newparam, m_gradient); // one-dimensional
#ifdef TEST
                            nDFunc++;
#endif
                            slopes_have_same_sign =
                                SlopesHaveSameSign(orig_gradient_i, m_gradient[i],
                                                   epsilon);
                        }
                        // else we bracketed the maximum by finding a point
                        // with a worse lnL
                    }
                    // else we bracketed the maximum by finding a point where
                    // the surface is undefined (a flat "plane" at -infinity)
                }

                // Did we bracket the maximum,
                // or give up due to iterating suspiciously many times?
                if (numLongPushes >= MAX_NUM_LONG_PUSHES
                    && calc_succeeded && newlike >= oldlike
                    && slopes_have_same_sign)
                {
                    const ParamVector paramvec(true);  // read-only copy
                    string paramname = paramvec[i].GetName();
                    string msg = maxstr::MAX_NO_UPPER_BOUND_0
                        + paramname + maxstr::MAX_NO_UPPER_BOUND_1
                        + Pretty(m_newparam[i]) + maxstr::MAX_NO_UPPER_BOUND_2;
                    message = msg;
                    //newlike = +DBL_BIG; // erynes changed 17-Mar-06 per lpsmith request
                    return false;
                }
                // Else we bracketed the maximum, after making "numLongPushes"
                // long pushes along the current direction.
            }
            // Else if lambda == 1, we bracketed the maximum by taking a step of
            // lambda == 1 along this direction and detecting a sign-flip in the
            // slope there, or we happened to land on the exact maximum with this
            // step.  Otherwise, we bracketed the maximum by reaching a point
            // where (1) lnL is undefined, (2) lnL has been observed to decrease,
            // or (3) the slope has flipped sign.  In cases (1) and (2), we don't
            // know the slope at this new point, and we don't need to; for all 3
            // numbered cases we know we need to revserse direction to find the
            // maximum.  m_param and oldlike correspond to the "original" edge of
            // the bracket, and m_newparam and newlike correspond to the "current"
            // edge of the bracket.
        }
        // Else we bracketed the maximum, because taking a step of lambda = 1
        // along this direction led us to a lower lnL.  We don't know the slope
        // at this point, and we don't need to; we know we need to reverse
        // direction to find the maximum.
    }
    // Else we bracketed the maximum, because taking a step of lambda = 1
    // along this direction led us to a region in which the parameters are
    // infinitely unlikely to reproduce any of the genealogies.

    // At this point, we've converged for this component (unlikely), or we've
    // bracketed a local maximum for the component under consideration.  Now we'll
    // find the local maximum within this bracketed region.

    // Note to debuggers/code-reviewers:
    // Because we allow for the existence of multiple local extrema within this
    // bracketed region, there are cases in which variable m_gradient[i] may
    // happen to be out-of-sync and hence meaningless at this point in program
    // execution.  What's certain is that orig_gradient_i and oldlike correspond
    // to the "far" point of the bracket (the point that's "behind" us).  The
    // point upon which we're sitting corresponds to either
    // (!calc_succeeded || newlike < oldlike), or
    // (calc_succeeded && newlike >= oldlike && !slopes_have_same_sign).
    // In all cases, our next step will be "backwards" toward the point to which
    // orig_gradient_i corresponds.  And once we reach a point for which
    // newlike >= oldlike, the gradient will be valid, and it will point us
    // toward a maximum.
    //
    // Note that in the case of calc_succeeded == false, we can theoretically
    // expect either of the following cases for the lnL curve:
    //
    //
    //     __   |XXXXXXXXXXXXX                |XXXXXXXXXXXXX
    //    /  \  |XXXXXXXXXXXXX      or       /|XXXXXXXXXXXXX
    //   /    \ |X undefined X              / |X undefined X
    //  /      \|XXXXXXXXXXXXX             /  |XXXXXXXXXXXXX
    //
    //
    // In the first case, the curve has a standard peak.  In the second case,
    // the "peak" is the highest point outside the undefined region.

    // Now we search the bracket for the maximum.  We examine the midpoint of the
    // bracket, reduce the bracket length by 1/2, examine the midpoint of that,
    // etc., until we find the maximum.  (Note:  If our bracket values are, say,
    // 0 and 100, and the maximum is at 98, it would take 6 iterations to reach
    // 98.4475.  Just an example.)

    // m_gradient holds the gradient at the better point, which
    // could be m_newparam (newlike >= oldlike) or m_param (newlike < oldlike).

    // In the code below, we occasionally swap m_param and m_newparam,
    // so that m_newparam (at least momentarily) always holds the point
    // with the higher log-likelihood.

    double bracket_length = fabs(m_newparam[i] - m_param[i]);

#ifdef TEST
    cerr << "     i = " << i << ", initial bracket length = "
         << bracket_length << ", from p = " << min(m_param[i], m_newparam[i])
         << " to " << max(m_param[i], m_newparam[i]) << endl;
#endif // TEST

    // This variable is explained in the comment paragraph below.
    const double WIGGLE_CRITERION(5.0e-06);

    // The convergence criterion for the bracket search is a bit tricky.
    // The main idea is:  "While we haven't found a flat slope, keep going."
    // This is the main loop condition, the comparison between the gradient
    // and epsilon.  Because we always choose the point with the higher lnL,
    // and from there step only to a point with a yet-higher lnL, we never
    // risk interpreting a local minimum as a local maximum (if it's even
    // possible for a local minimum to occur).  Also, note that this enables
    // us to perform the minimum possible number of derivative evaluations
    // while we search the bracket for the maximum.
    // We employ a hard cut-off to our search, to ensure we never go into an
    // infinite loop if the surface is exceptionally problematic.

    // We also attempt to allow for the rare phenomenon of "wiggles," i.e.,
    // anomalies with some trees that, collectively, yield a likelihood surface
    // that has a reasonably well-defined maximum, but has a jagged contour
    // at the microscopic level.  (Imagine tracing the fuzz on the surface of a
    // peach).  We can often(?) return a reasonably accurate MLE in this case,
    // even though the surface is very jittery in the lower decimal places.
    // The "wiggle-handling code" precedes the bracket search, because most
    // surfaces don't have wiggles, and hence incorporating this criterion into
    // the bracket-search loop condition would almost always terminate the loop
    // before reaching optimal precision on the MLEs.  The placement of this
    // wiggle-handling code means that, yes, some full bracket searches are
    // performed, but eventually we reach a state where the bracket we
    // calculate is merely the tiny distance between two nearby wiggles,
    // and this is where the wiggle-handling code kicks in and claims
    // that we've converged to the MLE.

    if (bracket_length/fabs(m_param[i]) < WIGGLE_CRITERION &&
        fabs((newlike - oldlike)/oldlike) < WIGGLE_CRITERION)
        // Don't bother searching this tiny bracket.  Force an immediate return.
        m_gradient[i] = 0.0;

    // Search the bracket, halving its size with each search.

    while (fabs(m_gradient[i]) > epsilon &&
           bracket_length > fabs(m_newparam[i])*1.0e-10)
    {
        if (fabs(m_newparam[i])*1.0e-10 < 100.0/DBL_BIG)
            break; // hard stop condition

        if (newlike < oldlike)
        {
            // Redefine m_newparam, etc., to refer to the point with the higher lnL.
            m_param.swap(m_newparam);
            m_lparam.swap(m_newlparam);
            swap(oldlike, newlike);
        }

        // Take a step, of length half of the bracket size.
        // Apply it to multiple parameters if any are contrained to be equal.

        // Pre-compute two quantities, for speed.
        double step = 0.5 * sign(m_gradient[i]) * bracket_length;
        double lognewparam = log(m_newparam[i] + step); // it's safe to log() within the bracket

        oldlike = newlike; // Store the lnL before we take a step.

        for (it = pWhichparam->begin(); it != pWhichparam->end(); it++)
        {
            // Store the better point before we take a step away from it.
            m_param[*it] = m_newparam[*it];
            m_lparam[*it] = m_newlparam[*it];

            m_newparam[*it] += step;
            if (m_isLinearParam[*it]) // they're all log or all linear
                m_newlparam[*it] = m_newparam[*it];
            else
                m_newlparam[*it] = lognewparam;
        }

        // Update the bracket size.
        bracket_length = fabs(m_newparam[i] - m_param[i]);

        calc_succeeded = m_pPostLike->Calculate(m_newparam, m_newlparam, newlike);
#ifdef TEST
        nFunc++;
#endif // TEST

        if (newlike >= oldlike)
        {
            DCalculate(m_newparam, m_gradient); // one-dimensional
#ifdef TEST
            nDFunc++;
#endif
        }
    }

    // This should never be true at this point in the code.
    if (!calc_succeeded)
    {
        const ParamVector paramvec(true);  // read-only copy
        string paramname = paramvec[i].GetName();
        string msg = maxstr::MAX_UNDEFINED_BORDER_0 + paramname
            + maxstr::MAX_UNDEFINED_BORDER_1 + Pretty(m_param[i])
            + maxstr::MAX_UNDEFINED_BORDER_2;
        message = msg;
#ifdef TEST
        cerr << "The parameter vector where lnL = -DBL_BIG is p = (" << m_newparam[0];
        unsigned long int k;
        for (k = 1; k < m_nparam; k++)
            cerr << ", " << m_newparam[k];
        cerr << ")." << endl;
#endif // TEST
        //      assert(0); // for debugging
        newlike = -DBL_BIG; // for consistency
        return false;
    }

    // Print a debug message if our bracket search failed egregiously.
    if (fabs(m_gradient[i]) > 100.0 * epsilon)
    {
        const ParamVector paramvec(true);  // read-only copy
        string paramname = paramvec[i].GetName();
        string msg = maxstr::MAX_FAILED_BRACKET_0 + paramname
            + maxstr::MAX_FAILED_BRACKET_1
            + (newlike > oldlike ? Pretty(m_newparam[i]) : Pretty(m_param[i]))
            + maxstr::MAX_FAILED_BRACKET_2 + Pretty(m_gradient[i]) + "."
            + maxstr::MAX_FAILED_BRACKET_3;
        message = msg;
#ifdef TEST
        cerr << "The full parameter vector is p = ("
             << (newlike > oldlike ? m_newparam[0] : m_param[0]);
        unsigned long int k;
        for (k = 1; k < m_nparam; k++)
            cerr << ", " << (newlike > oldlike ? m_newparam[k] : m_param[k]);
        cerr << "), where lnL = "
             << (newlike > oldlike ? newlike : oldlike) << "." << endl;
#endif // TEST
    }

    return true;
}

//------------------------------------------------------------------------------------
// Try to find the maximum by the method of Broyden/Fletcher/Goldfarb/Shanno.
// Does not work well in the presence of growth; try CalculateSteepest instead.

bool Maximizer::CalculateBroyden(unsigned long int maxtrials, double& lnL, string& message)
{
    // The oldlike will always start at 0, but we must still call Calculate()
    // because it precomputes an essential term used by DCalculate().
    double oldlike = 0.0;
    if (!m_pPostLike->Calculate(m_param, m_lparam, oldlike))
    {
        lnL = -DBL_BIG;
        return false; // unrecoverable; gradient can't be calculated from m_param.
    }
    DCalculate(m_param, m_gradient); // result into m_gradient

    double lambda(1.0), newlike(0.0);
    m_oldlparam = m_lparam;
    m_direction = m_gradient; // Point to maximum.
    m_oldgradient = m_gradient;
    double normg = Norm(m_gradient); // length of the gradient vector
    double normg20 = 0; // length the gradient vector had in the 20th,
    // 40th, 60th, etc. iteration (used for convergence)

    ResetSecond(); // initialize the approx. inverse second derivative matrix
#ifdef TEST
    nMatrixResets--; // discount this first "reset"
#endif // TEST
    bool calc_succeeded = true;
    unsigned long int count = 0; // number of iterations

    while (normg > NORM_EPSILON && count < maxtrials)
    {
        double thisChange(DBL_MAX);
        lambda = 2.0; // reset; will be reduced to 1 before lambda gets used
        do {
            // "Line search" -- find a higher lnL along this direction
            lambda /= 2.0;    // set the step length
            thisChange = SetParam(lambda, m_direction); // probe this step in this direction
            calc_succeeded = m_pPostLike->Calculate (m_newparam, m_newlparam,
                                                     newlike);
        } while ((!calc_succeeded || newlike <= oldlike ||
                  systemSpecificIsnan(newlike)) && lambda > LAMBDA_EPSILON
                 && thisChange > LAMBDA_EPSILON
            );

        // the eventloop call used to be here when we were supporting
        // Mac OS 9
        // eventloop();

        // Track each 20th normg. If we're creeping along with the tiniest of steps,
        // reset the approx. inverse Hessian and continue along this direction.
        if (count % 20 == 0)
        {
            if (fabs (normg - normg20) < NORM_EPSILON)
            {
                lambda = 0.0; // creeping; force a matrix reset below
            }
            normg20 = normg;
        }

        if (lambda <= LAMBDA_EPSILON || thisChange <= LAMBDA_EPSILON)
        {
            if (m_direction == m_gradient && lambda != 0.0)
            {
                // This is bad--it means there's a nonzero gradient (guaranteed ascent
                // direction) at a certain point, but if we take an infinitesimally tiny
                // step along this direction, we don't find a greater likelihood.
                unsigned long int k;
                string msg = maxstr::MAX_CLIFF_EDGE_0 + Pretty(oldlike)
                    + maxstr::MAX_CLIFF_EDGE_1 + Pretty(m_param[0]);
                for (k = 1; k < m_nparam; k++)
                    msg += ", " + Pretty(m_param[k]);
                msg += maxstr::MAX_CLIFF_EDGE_2;
                message = msg;
#ifdef TEST
                cerr << "Maximizer:  direction = gradient = (" << m_gradient[0];
                for (k = 1; k < m_nparam; k++)
                    cerr << ", " << m_gradient[k];
                cerr << "), |g| = " << Norm(m_gradient) << endl;
#endif
                lnL = -DBL_BIG;
                return false;
            }

            // we're creeping along with the tiniest of steps,
            // either in "line search" or in normg (lambda purposely set to 0 above)
#ifdef TEST
            nLambdaSmall++;
#endif // TEST
            ResetSecond();
            m_direction = m_gradient; // guaranteed ascent direction
            count++;
            continue;
        }

        // found a point that's closer to the maximum

        m_param = m_newparam;
        m_lparam = m_newlparam;
        oldlike = newlike;
        DCalculate (m_param, m_gradient);
        normg = Norm(m_gradient);
        CalcDelta(m_lparam, m_oldlparam, m_paramdelta);
        CalcDelta(m_gradient, m_oldgradient, m_gradientdelta);
        CalcSecond();
        CalcDirection();
        copy (m_gradient.begin(), m_gradient.end(), m_oldgradient.begin());
        copy (m_lparam.begin(), m_lparam.end(), m_oldlparam.begin());
        count++;
    }

    m_lastnormg = normg;
    lnL = oldlike;

    if (normg > NORM_EPSILON)
    {
        string msg = maxstr::MAX_NO_CONVERGENCE_0;
        if (count > 0)
        {
            msg += maxstr::MAX_NO_CONVERGENCE_1;
            msg += ToString(count) + maxstr::MAX_NO_CONVERGENCE_2;
        }
        msg += maxstr::MAX_NO_CONVERGENCE_3 + Pretty(normg)
            + maxstr::MAX_NO_CONVERGENCE_4;
        message = msg;
    }

#ifdef TEST
    cerr << "Finished, after " << count << " iterations of BFGS and "
         << nMatrixResets << " matrix resets." << endl
         << "|grad| = " << normg << ", grad = (" << m_gradient[0];
    unsigned long int i;
    for (i = 1; i < m_gradient.size(); i++)
        cerr << ", " << m_gradient[i];
    cerr << ")" << endl << "direction = (" << m_direction[0];
    for (i = 1; i < m_direction.size(); i++)
        cerr << ", " << m_direction[i];
    cerr << ")" << endl << "params = (" << m_param[0];
    for (i = 1; i < m_param.size(); i++)
        cerr << ", " << m_param[i];
    cerr << ")" << endl;
    if (count > 21)
        cerr << "normg20 = " << normg20 << " and ";
    cerr << "lambda = " << lambda << "; lnL = " << lnL << endl;
    if (nMatrixResets > 2)
        cerr << endl << nLambdaSmall << " matrix resets were due to halving back too far"
             << ", and " << n_dtg_zero << " resets were due to dtg == 0 (rounding error)"
             << " in BFGS.";
    cerr << endl << endl;
#endif // TEST

    return true;
} // Maximizer::CalculateBroyden

//------------------------------------------------------------------------------------
//CalculateByParts is meant to calculate the maximum of the function by
// varying the gradient guides and using repeated calls to CalculateBroyden.
// It was created when we saw that profiling could come up with better
// likelihoods than the so-called 'mles'.

bool Maximizer::CalculateByParts(double& lnL, string& message)
{
    cerr << "Initial params:  " << m_param[0];
    for (unsigned long int i = 1; i < m_param.size(); ++i)
        cerr << ", " << m_param[i];
    cerr << endl;
    //First, just run BFGS, but not as long as we would normally.
    double newlike = 0.0, oldlike = 0.0;
    bool calc_succeeded = CalculateBroyden(NTRIALS/10, newlike, message);
    if (!calc_succeeded)
        cerr << "Maximizer::CalculateByParts -- infinitely unlikely initial param vector.";
    else
    {
        oldlike = newlike*2; //Just to get a clearly-different likelihood.
        cerr << "First newlike = " << newlike << endl;
    }
    cerr << "Params = (" << m_param[0];
    for (unsigned long int i = 1; i < m_param.size(); ++i)
        cerr << ", " << m_param[i];
    cerr << ")" << endl;
    if (!calc_succeeded)
        return false; // unrecoverable; can't calculate gradient

    //Now, go through and hold each parameter constant while maximizing
    // everything else.
    //
    //If this approach doesn't work, we might want to hold each parameter
    // *type* constant (theta, mig, grow, etc.) instead.  Or even just thetas
    // and growth, since those seem to be the problem pairs.

    vector<ParamStatus> gradGuide(m_pPostLike->m_working_pstatusguides);

    long int ncount = 0;
    while (ncount++ < 20)
    {
        oldlike = newlike;
        for (unsigned long int paramnum = 0; paramnum < gradGuide.size(); ++paramnum)
        {
            ParamStatus mystatus = gradGuide[paramnum];
            if (mystatus.Inferred())
            {
                ProfileGuideFix(paramnum);
                // Try varying all but one of the parameters, as an experiment.
                calc_succeeded = CalculateBroyden(NTRIALS/20, newlike, message);
                // We're not necessarily interested in each likelihood;
                // we're interested in where the params move to.
                if (!calc_succeeded)
                {
                    // This is an unrecoverable error; we can no longer
                    // calculate the gradient, because Prob(G|P) = 0 for all G.
                    break;
                }
                ProfileGuideRestore(paramnum);
            }
        }

        if (!calc_succeeded)
        {
#ifdef TEST
            cerr << "Maximizer::CalculateByParts -- moved to an infinitely unlikely param vector."
                 << endl << "Param vector = (" << m_param[0];
            for (unsigned long int i = 1; i < m_param.size(); i++)
                cerr << ", " << m_param[i];
            cerr << ")" << endl;
#endif // TEST
            return false;
        }

        //Finally, do one more round of BFGS, with nothing fixed.
        calc_succeeded = CalculateBroyden(NTRIALS/20, newlike, message);

        //Exit if the likelihood has not changed that much.
        if ( fabs(newlike - oldlike) < fabs(newlike/100) || !calc_succeeded)
        {
            cerr << "Exiting CalculateByParts after " << ncount
                 << " loops with newlike = " << newlike << "." << endl
                 << "Params = " << m_param[0];
            for (unsigned long int i = 1; i < m_param.size(); ++i)
                cerr << ", " << m_param[i];
            cerr << endl;
            if (!calc_succeeded)
            {
#ifdef TEST
                cerr << "(Parameter vector is infinitely unlikely "
                     << "to reproduce the genealogies.)" << endl;
#endif // TEST
                return false;
            }
            lnL = newlike;
            return true;
        }
        cerr << "Newlike = " << newlike << endl << "Params = " << m_param[0];
        for (unsigned long int i = 1; i < m_param.size(); ++i)
            cerr << ", " << m_param[i];
        cerr << endl;
    }
    cerr << "Exiting CalculateByParts after " << ncount
         << " loops; the maximum allowed." << endl;
    lnL = newlike;
    return true;
}

//------------------------------------------------------------------------------------
// Maximizer::SetConstraints()
// Allows the user to constrain some parameters to equal one another.
// For example, she might have six migration rate matrix elements, and wish to
// constrain M12 = M21 = A and M13 = M31 = B.  In this case, she would be
// probing the maximum-likelihood estimates for four migration rates:
// M23, M32, A, and B (in addition to the populations' three thetas).
// The maximizer is supposed to have no knowledge of forces, that is, whether
// the parameter vector it receives contains thetas and recomination rates
// or migration rates or anything else.  And rightly so.  Hence, here it simply
// receives a vector of vectors specifying which parameters should be
// constrained to equal one another.  If we wish to forbid the user from
// constraining a recomination rate to equal a migration rate, or equivalent,
// then that must be done at a higher level in the code (e.g.,
// in the user interface).
// At the level of the maximizer, we must forbid one thing:  A parameter
// that we manipulate via its logarithmic value cannot be constrained to equal
// a parameter that we vary linearly to allow the latter to become negative.
// At present, this means growth parameters can be constrained to equal
// one another in any combination, but cannot be constrained to equal
// thetas, disease rates, etc. (this would be nonsensical anyway).
// This function returns false if the caller attempts such a constraint.
// It also returns false if the caller sends a parameter index that is
// greater than the largest parameter index previously known by the maximizer,
// or if a parameter index appears in multiple places in the input vector.
// Otherwise, it stores the constraints and returns true.
// If false is returned, the maximizer's constraint parameters remain unchanged.
//
// If an empty vector is received, false is returned.
//
// Added by erynes, 23 August 2004.

bool Maximizer::SetConstraints(const vector<vector<unsigned long int> > & constraints)
{
    if (constraints.empty())
        return false;

    vector<vector<unsigned long int> > oldconstraints = m_constraints; // save previous
    m_constraints.clear(); // clear current so we can use push_back()
    vector<vector<unsigned long int> >::const_iterator outer_it; // it. over vectors
    vector<unsigned long int>::const_iterator inner_it; // it. over one vector
    bool isLinearParam; // whether a vec's first param is treated linearly
    vector<unsigned long int> values_seen; // All values seen so far.
    // Shouldn't have duplicates.

    m_constraintratio.clear();

    for (outer_it = constraints.begin(); outer_it != constraints.end();
         outer_it++)
    {
        // First, validate the contents of each vector.

        if ((*outer_it).size() < 2)
        {
            // Empty constraint vector, or missing constraints
            // (constraining a lone parameter to equal itself
            // is meaningless)
          ExitFailure:
            m_constraints.clear();
            m_constraints = oldconstraints;
            m_constrained = false;
            return false;
        }

        isLinearParam = m_isLinearParam[(*outer_it)[0]];

        for (inner_it = (*outer_it).begin();
             inner_it != (*outer_it).end();
             ++inner_it)
        {
            if (*inner_it >= m_nparam)
            {
                // Attempt to constrain a nonexistent parameter.
                goto ExitFailure;
            }
            if (isLinearParam && !m_isLinearParam[*inner_it])
            {
                // Attempt to constrain a logarithmic parameter
                // to a linear parameter.
                goto ExitFailure;
            }
            if (!isLinearParam && m_isLinearParam[*inner_it])
            {
                // Attempt to constrain a linear parameter
                // to a logarithmic parameter.
                goto ExitFailure;
            }
            if (find(values_seen.begin(), values_seen.end(), *inner_it)
                != values_seen.end())
            {
                // A parameter identifier occurs more than once.
                goto ExitFailure;
            }
            values_seen.push_back(*inner_it);
        }

        // Determined this vector is valid, so we store it for later use.
        m_constraints.push_back(*outer_it);
        // Sort the vector we just stored, to make maximization simpler.
        sort(m_constraints[m_constraints.size() - 1].begin(),
             m_constraints[m_constraints.size() - 1].end());

        // handle multiplicative constraint
        double ratio(0.0);  // assume no multiplicative constraint
        // this group has multiplicative constraint
        // JDEBUG we are EVIL kludging this at the moment...
        if ( find(outer_it->begin(),outer_it->end(),0.0) != outer_it->end() )
        {
            ratio = 0.5/0.5;  // (1-pA)/pA, JDEBUG, hard set pA = 0.5;
            // MDEBUG this needs to be replaced by ratio of the
            // input Thetas for the selection case; we are not
            // prepared (in phase I) to do anything more general yet.
        }
        m_constraintratio.push_back(ratio);
    }

    m_constrained = true;
    return true;
}

//------------------------------------------------------------------------------------
// Maximizer::DCalculate()
// In the absence of constraints, simply returns PostLike::DCalculate().
// In the presence of constraints,
// it calculates the directional derivative along the direction x = y,
// or y = z, or x = z and p = d = q, etc., at the n-dimensional point "param."
// Stores the result in "gradient."
// The unconstrained components are unaffected; they get the same values
// they would in the absence of constraints.
// The constrained components each get the value of the dot product of the
// unconstrained gradient vector with the unit vector in the direction of the
// constraint.  Since our constraints are of the form x = y, which makes a
// 45-degree angle with the coordinate directions, these constraints are
// equivalent to setting a group of constrained gradient components to
// the average value of those components.
// If we ever want to allow constraints of the form x = const * y,
// that would be possible, but would require turning the averages into
// weighted averages, with the weights coming from the cosines and sines
// (or arccosines and arcsines) of the angle defined by the constant.
//
// Calls PostLike::DCalculate, and returns its return value.
//
// Added by erynes, 23 August 2004.

bool Maximizer::DCalculate(const DoubleVec1d& param,
                           DoubleVec1d& gradient)
{
    bool retval = m_pPostLike->DCalculate(param, gradient);
    if (!m_constrained || !retval)
        return retval;

    DoubleVec1d::size_type group;
    DoubleVec1d::size_type numElements;
    vector<unsigned long>::iterator inner_it;
    double avg_grad_component;

    assert(m_constraints.size() == m_constraintratio.size());
    for (group = 0; group < m_constraints.size(); ++group)
    {
        if (!(numElements = m_constraints[group].size()))
            continue; // this vector is empty

        if (m_constraintratio[group]) // do multiplicative constraint
        { // we assume that there are exactly two elements here
          // and that they are theta values
            assert(numElements == 2);
            double ratio(m_constraintratio[group]);
            double& th0(gradient[m_constraints[group][0]]);
            double& th1(gradient[m_constraints[group][1]]);
            double mult(1.0/(1.0+ratio*ratio) * (th0+ratio*th1));
            th0 = mult;
            th1 = ratio * mult;
        }
        else                          // do the average
        {
            avg_grad_component = 0.0;   // reset for next average

            for (inner_it = m_constraints[group].begin();
                 inner_it != m_constraints[group].end();
                 inner_it++)
                avg_grad_component += gradient[*inner_it];

            avg_grad_component /= numElements; // numElements > 0 verified above

            for (inner_it = m_constraints[group].begin();
                 inner_it != m_constraints[group].end();
                 inner_it++)
                gradient[*inner_it] = avg_grad_component;
        }
    }

    return true;
}

//------------------------------------------------------------------------------------

inline double sign(const double& x) { return x < 0.0 ? -1.0 : 1.0; };

//------------------------------------------------------------------------------------
// Set up the gradient guide for the given postlike object.
// Does NOT store or update anything in the maximizer object.

void Maximizer::GradientGuideSetup(const ParamVector &thisToDolist,
                                   PostLike* pThisPostLike)
{
    // defines behavior in the gradient calculations
    // Type              Label [this gets used in gradient()]
    // c = constant   -> 0.
    // * = maximize   -> 1.
    ParamVector::const_iterator doListItem;
    if (!pThisPostLike)
    {
        assert(0); // received a NULL postlike pointer
        return;
    }
    pThisPostLike->m_working_pstatusguides.clear();
    for (doListItem = thisToDolist.begin();
         doListItem != thisToDolist.end(); ++doListItem)
    {
        pThisPostLike->m_default_pstatusguides.push_back(doListItem->GetStatus().Status());
    }
    pThisPostLike->m_working_pstatusguides =
        pThisPostLike->m_default_pstatusguides;
    //m_validguides might be useful at some point, but not right now.
}

//------------------------------------------------------------------------------------
// Instruct DCalculate to treat this component as a constant.

void Maximizer::ProfileGuideFix(long int guide)
{
    if (guide < 0 ||
        guide >= static_cast<long int>(m_pPostLike->m_default_pstatusguides.size()))
    {
        string msg = "Internal error:  Maximizer::ProfileGuideFix() ";
        msg += "received an invalid parameter index (" + ToString(guide);
        msg += ").  Valid values for this run range from 0 to ";
        msg += ToString(m_pPostLike->m_default_pstatusguides.size());
        msg += ".";
        throw implementation_error(msg);
    }
    unsigned long k(0);
    vector<unsigned long>::const_iterator it;
    bool found_guide = false;
    string msg;
    ParamStatus mystatus = m_pPostLike->m_default_pstatusguides[guide];
    if (!mystatus.Inferred())
    {
        msg = "Warning:  Attempted to fix parameter " + Pretty(guide) + " in "
            + "ProfileGuideFix(), but this parameter is of type " + ToString(mystatus.Status());
        registry.GetRunReport().ReportDebug(msg);
    }
    if (mystatus.Grouped())
    {
        for (k = 0; k < m_constraints.size(); k++)
        {
            if (guide == static_cast<long>(m_constraints[k][0]))
            {
                found_guide = true;
                for (it = m_constraints[k].begin();
                     it != m_constraints[k].end();
                     it++)
                    m_pPostLike->m_working_pstatusguides[*it] = ParamStatus(pstat_constant);
            }
        }
        if (!found_guide)
        {
            // "guide" not found as the zeroth element of a constraint vector
            msg = "Warning!  ProfileGuideFix() was called on parameter "
                + Pretty(guide) + ", whose default type is pstat_head, but "
                + "this parameter was not found in the maximizer\'s lookup table, "
                + "which is indexed by joint parameters.";
            registry.GetRunReport().ReportDebug(msg);
            assert(0);
        }
    }
    else
    {
        m_pPostLike->m_working_pstatusguides[guide] = ParamStatus(pstat_constant);
    }
}

//------------------------------------------------------------------------------------

void Maximizer::ProfileGuideFixAll()
{
    for (unsigned long int pnum = 0;
         pnum < m_pPostLike->m_working_pstatusguides.size();
         pnum++)
    {
        ParamStatus mystatus = m_pPostLike->m_working_pstatusguides[pnum];
        if (mystatus.Varies()) m_pPostLike->m_working_pstatusguides[pnum] = ParamStatus(pstat_constant);
    }
}

//------------------------------------------------------------------------------------

void Maximizer::ProfileGuideRestore()
{
    m_pPostLike->m_working_pstatusguides = m_pPostLike->m_default_pstatusguides;
}

//------------------------------------------------------------------------------------
// Instruct DCalculate to calculate the gradient for this component.
// Useful for "undoing" a call to ProfileGuideFix().
// If "guide" refers to a constrained parameter, then the corresponding
// constrained parameters also get fixed here.

void Maximizer::ProfileGuideRestore(long int guide)
{
    if (guide < 0 ||
        guide >= static_cast<long int>(m_pPostLike->m_default_pstatusguides.size()))
    {
        string msg = "Internal error:  Maximizer::ProfileGuideRestore() ";
        msg += "received an invalid parameter index (" + ToString(guide);
        msg += ").  Valid values for this run range from 0 to ";
        msg += ToString(m_pPostLike->m_default_pstatusguides.size());
        msg += ".";
        throw implementation_error(msg);
    }
    unsigned long int k(0);
    vector<unsigned long int>::const_iterator it;
    bool found_guide = false;
    string msg;
    ParamStatus mystatus = m_pPostLike->m_default_pstatusguides[guide];
    if (mystatus.Inferred() && mystatus.Grouped())
    {
        // MFIX need to handle multiplicative?
        for (k = 0; k < m_constraints.size(); k++)
        {
            if (guide == static_cast<long int>(m_constraints[k][0]))
            {
                found_guide = true;
                for (it = m_constraints[k].begin();
                     it != m_constraints[k].end();
                     it++)
                    m_pPostLike->m_working_pstatusguides[*it] =
                        m_pPostLike->m_default_pstatusguides[guide];
            }
        }
        if (!found_guide)
        {
            // "guide" not found as the zeroth element of a constraint vector
            msg = "Warning!  ProfileGuideRestore() was called on parameter "
                + Pretty(guide) + ", whose default type is pstat_head, but "
                + "this parameter was not found in the maximizer\'s lookup table, "
                + "which is indexed by joint parameters.";
            registry.GetRunReport().ReportDebug(msg);
            throw implementation_error(msg);
        }
    }
    else
        if (mystatus.Inferred())
        {
            m_pPostLike->m_working_pstatusguides[guide] =
                m_pPostLike->m_default_pstatusguides[guide];
        }
        else
        {
            msg = "Warning!  ProfileGuideRestore() may only be called on parameters ";
            msg += "of type \"pstatus_unconstrained\" and \"pstatus_head.\"";
            msg += "Parameter " + Pretty(guide) + " is of neither type, but it was ";
            msg += "sent to ProfileGuideRestore.";
            registry.GetRunReport().ReportDebug(msg);
            throw implementation_error(msg);
        }
}

//------------------------------------------------------------------------------------

void Maximizer::AppendConstraintOnAlpha(ParamStatus alphaStatus)
{
    if (!m_dataHasGamma)
    {
        string msg = "Maximizer::AppendConstraintOnAlpha() was called, but a multi-region ";
        msg += "estimate with gamma-distributed mutation rates was not being performed ";
        msg += "when this function was called.";
        throw implementation_error(msg);
    }
    if (m_constraintsVectorHasSlotForAlpha)
    {
        m_pPostLike->m_working_pstatusguides.pop_back();
        m_pPostLike->m_default_pstatusguides.pop_back();
    }
    m_pPostLike->m_working_pstatusguides.push_back(alphaStatus);
    m_pPostLike->m_default_pstatusguides.push_back(alphaStatus);
    m_constraintsVectorHasSlotForAlpha = true;
}

//------------------------------------------------------------------------------------

inline double tolerance(double number)
{
    if (0.0 == number)
        return 0.0;

    bool numIsNegative = (number < 0.0);
    double delta = floor(log10(numIsNegative ? -1.0 * number : number));

    if (numIsNegative)
        delta += 1.0;

    if (delta <= 6.0)
    {
        if (delta >= 0.0 && !(numIsNegative && delta == 0.0))
            delta = 5.0 * pow(10.0, delta - 7.0);
        else if (delta >= -4.0)
            delta = (numIsNegative && delta != -4.0) ? 5.0e-06 : 5.0e-07;
        else if (delta >= -99.0 && !(numIsNegative && delta == -99.0))
            delta = 5.0 * pow(10.0, delta - 3.0);
        else // delta <= -100.0
            delta = 5.0 * pow(10.0, delta - 2.0);
    }
    else if (delta == 7.0)
        delta = 0.5;
    else if (delta <= 99.0 || (numIsNegative && delta == 100.0))
        delta = 5.0 * pow(10.0, delta - 3.0);
    else // delta >= 100.0
        delta = 5.0 * pow(10.0, delta - 2.0);

    return delta;
}

//____________________________________________________________________________________
