/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
|  Phycas: Python software for phylogenetic analysis                          |
|  Copyright (C) 2006 Mark T. Holder, Paul O. Lewis and David L. Swofford     |
|                                                                             |
|  This program is free software; you can redistribute it and/or modify       |
|  it under the terms of the GNU General Public License as published by       |
|  the Free Software Foundation; either version 2 of the License, or          |
|  (at your option) any later version.                                        |
|                                                                             |
|  This program is distributed in the hope that it will be useful,            |
|  but WITHOUT ANY WARRANTY; without even the implied warranty of             |
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              |
|  GNU General Public License for more details.                               |
|                                                                             |
|  You should have received a copy of the GNU General Public License along    |
|  with this program; if not, write to the Free Software Foundation, Inc.,    |
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.                |
\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

#include <fstream>
#include <boost/format.hpp>

//#include "phycas/force_include.h"
#include "probability_distribution.hpp"
#include "model.hpp"
#include "tree_likelihood.hpp"
#include "xlikelihood.hpp"
#include "mcmc_chain_manager.hpp"
#include "bush_move.hpp"
#include "basic_tree.hpp"
#include "tree_manip.hpp"

//#define KEEP_BRANCHES_LEGAL
//#define MAX_LEGAL_BRLEN 285

using namespace phycas;

/*----------------------------------------------------------------------------------------------------------------------
|	The constructor sets `num_taxa' and `num_nodes_in_fully_resolved_tree' to zero, `edgelen_mean' to 1.0, `is_move' to
|   true, creates the `topo_prior_calculator', and finally calls BushMove::reset to re-initialize all other variables.
*/
BushMove::BushMove()
  : MCMCUpdater()
	{
	is_move = true;
	edgelen_mean = 1.0;

	num_taxa = 0;
	num_nodes_in_fully_resolved_tree = 0;

	reset();
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Destructor does not need to do anything.
*/
BushMove::~BushMove()
    {
    //std::cerr << "BushMove dying..." << std::endl;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Returns true because this class is capable of computing the tree topology prior.
*/
bool BushMove::computesTopologyPrior() const
    {
    return true;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Calls proposeNewState(), then decides whether to accept or reject the proposed new state, calling accept() or
|	revert(), whichever is appropriate.
*/
bool BushMove::update()
	{
	// The only case in which is_fixed is true occurs when the user decides to fix the edge lengths.
	// A proposed BushMove cannot be accepted without changing edge lengths, so it is best to bail out now.
	if (is_fixed)
		return false;

	if (num_taxa == 0)
		{
		throw XLikelihood("Must call finalize() before calling update() for BushMove");
		}

    // chain_mgr is a weak_ptr: calling lock() creates a shared_ptr which ensures that the ChainManager
    // object pointed to does not go away before we are done using it
	ChainManagerShPtr p = chain_mgr.lock();
	PHYCAS_ASSERT(p);
    JointPriorManagerShPtr jpm = p->getJointPriorManager();

	double prev_ln_like = p->getLastLnLike();
	double prev_ln_prior = jpm->getLogJointPrior();

	proposeNewState();

	curr_ln_like = (heating_power > 0.0 ? likelihood->calcLnL(tree) : 0.0);
	curr_ln_prior = jpm->getLogJointPrior();

	//likelihood->startTreeViewer(tree, str(boost::format("Bush move PROPOSED (%s)") % (add_edge_move_proposed ? "add edge" : "delete edge")));

    double prev_posterior = 0.0;
	double curr_posterior = 0.0;

    if (is_standard_heating)
        {
        prev_posterior = heating_power*(prev_ln_like + prev_ln_prior);
	    curr_posterior = heating_power*(curr_ln_like + curr_ln_prior);
        PHYCAS_ASSERT(!use_ref_dist);   // Bush move not ready for generalized steppingstone yet
        }
    else
        {
        prev_posterior = heating_power*prev_ln_like + prev_ln_prior;
	    curr_posterior = heating_power*curr_ln_like + curr_ln_prior;
        }

	double ln_accept_ratio = curr_posterior - prev_posterior + ln_hastings + ln_jacobian;

    bool accepted = (ln_accept_ratio >= 0.0 || std::log(rng->Uniform()) <= ln_accept_ratio);
    if (save_debug_info)
        {
    	if (add_edge_move_proposed)
            {
            debug_info = str(boost::format("Bush: add (%s)") % (accepted ? "accepted" : "rejected"));
            }
        else
            {
            debug_info = str(boost::format("Bush: del (%s)") % (accepted ? "accepted" : "rejected"));
            }
        }

	if (accepted)
		{
		p->setLastLnLike(curr_ln_like);
		accept();
		}
	else
		{
		curr_ln_like = p->getLastLnLike();
		revert();
		}

    return accepted;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Proposes either an add-edge move (addition of an edge to a polytomy) or a delete-edge move (deletion of an existing
|	edge) with equal probability. If tree is currently fully-resolved, proposes only a delete-edge move. If tree is
|	currently the star tree, proposes only an add-edge move.
*/
void BushMove::proposeNewState()
	{
	unsigned nnodes = tree->GetNNodes();
	const bool fully_resolved_before = (num_nodes_in_fully_resolved_tree == nnodes);
	const bool star_tree_before = (nnodes == num_taxa + 1);
	add_edge_move_proposed = (!fully_resolved_before) && (star_tree_before || rng->Uniform() < 0.5);

	refreshPolytomies();
	num_polytomies = (unsigned)polytomies.size();

	// One internal edge leads to the tip node serving as the root, but that one doesn't count
	const unsigned num_internal_edges_before = tree->GetNInternals() - 1;

	TreeNode * nd = NULL;
	if (add_edge_move_proposed)
		{
		// Choose a polytomy at random to split
		//
		PHYCAS_ASSERT(num_polytomies > 0);
		unsigned i = rng->SampleUInt(num_polytomies);
		nd = polytomies[i];
		proposeAddEdgeMove(nd);

		// Compute the Jacobian by working backwards to obtain the uniform used to sample new_edgelen (done in
		// proposeAddEdgeMove function). Remember that U = 1 - e^{-theta*v} where v is the new edge length,
		// and theta = 1/edgelen_mean is the hazard parameter of the exponential distribution used for
		// sampling new edge lengths. log(1 - U) = -theta*v = -v/edgelen_mean. The Jacobian for this move
		// can be obtained using the Green (2003) approach. The new edge length v that exists after the
		// add-edge move corresponds with the uniform random deviate U before the add-edge move. The
		// Jacobian is the matrix of the derivatives of all post-move quantities with respect to all
		// pre-move quantities. In this case, there is only one post-move quantity (v) and only one pre-move
		// quantity (U), so the Jacobian is a 1x1 matrix consisting only of the derivative of v with respect
		// to U. The quantity v can be written as -edgelen_mean*log(1 - U), so the partial derivative is
        // simply edgelen_mean/(1 - U). The log of the jacobian is thus:
		//
		//      ln_jacobian = log(edgelen_mean) - log(1 - U) = log(edgelen_mean) + v/edgelen_mean
		//
		ln_jacobian = log(edgelen_mean) + new_edgelen/edgelen_mean;

		// The Hastings ratio is the probability of the reverse move divided by probability of the forward move.
		// The add-edge move involves the following 4 steps:
		//   1) choosing a polytomy to break (the probability of this is the inverse of the number of polytomies)
		//   2) choosing the number of spokes to move over to the new node
		//   3) selecting exactly which spokes to move
		//   4) choosing U ~ Uniform(0,1) to determine the new edge length (probability density is 1).
		// The probability of steps 2 and 3 are easier to compute jointly because of some convenient cancellation:
		// For step 2, assuming x spokes out of n total are moved, the probability is (see explanation in
		// documentation for BushMove::computePolytomyDistribution):
		//
		//              {n choose x} + {n choose (n-x)}
		// Pr(step 2) = -------------------------------
		//                       2^n - 2*(n+1)
		//
		// If x is exactly half of n, the numerator will have only the one term (n choose x), whereas the
		// denominator will remain the same. For example, let the tree be the 4-taxon star tree (A,B,C,D),
		// so n = 4, and let 2 nodes (A and C) be moved, then
		//
		//              {4 choose 2}       6
		// Pr(step 2) = ------------- = ------- = 1
		//              2^4 - 2*(4+1)   16 - 10
		//
		// For step 3, the probability is simply 1 over the number of distinct trees in which an n-spoke
		// polytomy has been divided into a node with x+1 spokes and another with n - x + 1 spokes:
		//
		//                              2
		// Pr(step 3) = -------------------------------
		//               {n choose x} + {n choose n-x}
		//
		// Again, if x is exactly half of n, the denominator will have only the one term {n choose x}. For
		// example, continuing the example above with n = 4 and x = 2, and the tree after the add-edge
		// move is (B, D, (A, C))
		//
		//                   2        2     1
		// Pr(step 3) = ---------- = --- = ---
		//              4 choose 2    6     3
		//
		// The 6 in the denominator corresponds with these possibilities, and the 2 in the numerator are
		// indicated by asterisks:
		//
		//  A     C   A     B   A     B   B     A   B     A   C     A
		//   \___/     \___/     \___/     \___/     \___/     \___/
		//   /   \     / * \     /   \     /   \     / * \     /   \  <not a backslash>
		//  B     D   C     D   C     D   C     D   D     C   D     B
		//
		//
		// Fortunately, the hard part of both calculations cancels, and the probability of steps 2 and 3 is:
		//
		//                                2                1
		// Pr(step 2 and step 3) = -------------- = ---------------
		//                           2^n - 2(n+1)   2^(n-1) - n - 1
		//
		// A different way to obtain Pr(step 2 and step 3) is to imagine placing nodes randomly in two piles,
		// pile 1 and pile 2. After the first node is placed, arbitrarily establishing pile 1, there are
		// n - 1 nodes left. For each one let the probability it is placed in pile 1 be 0.5, with the
		// probability of placing the node in pile 2 being 0.5 also. The probability of any one outcome is
		// thus 2^{n-1}, but some outcomes are not acceptable. The n outcomes in which one taxon is separated
		// from all other taxa are not acceptable because they are indistinguishable from the orignal polytomy.
		// In addition, the outcome in which all taxa end up in pile 1 is unacceptable for the same reason.
		// Thus, there are n + 1 unacceptable outcomes, and these must be subtracted from the 2^{n-1} possible
		// outcomes when determining the denominator of the probability.
		//
		// The probability of proposing an add-edge move is thus:
		//
		//                          1                   1             1                     1
		// Pr(add-edge move) = -------------- X ---------------- X ------- = ----------------------------------
		//                     num_polytomies    2^(n-1) - n - 1   (1 - 0)   num_polytomies * [2^(n-1) - n - 1]
		//
		// The delete-edge move is simpler, involving only the choice of the edge that must be deleted from the tree to
		// reverse the add-edge move (probability is inverse of the number of edges in the post-add-edge-move tree).
		//
		// Thus, the Hastings ratio for an add-edge move is:
		//
		//                                                                1
		//                                                  -----------------------------
		//  Pr(delete-edge of proposed add-edge move)       num_internal_edges_before + 1
		//  ----------------------------------------- = -------------------------------------
		//      Pr(proposed add-edge move)                                1
		//                                              -------------------------------------
		//                                               num_polytomies * [2^(n-1) - n - 1]
		//
		//                                                num_polytomies * [2^(n-1) - n - 1]
		//                                            =  -------------------------------------
		//                                                   num_internal_edges_before + 1
		//
		// If proposed state is the fully-resolved tree, or if the reverse move would generate a fully-resolved tree,
		// then the Hastings ratio must account for the fact that one of the moves is only attempted half the time
		// whereas the other move is the only move possible. If the proposal generates a fully-resolved tree,
		// multiply the Hastings ratio as calculated above by 2.0. If the proposal takes us away from a fully-resolved
		// tree, then divide the Hastings ratio as calculated above by 2.0.

		// nspokes is n_p in Lewis, Holder and Holsinger (2005)
		double nspokes = (double)polytomy_size;

		// Compute the log of the Hastings ratio
		ln_hastings = log((double)num_polytomies);
		ln_hastings += log(pow(2.0, nspokes - 1.0) - nspokes - 1.0);
		ln_hastings -= log((double)num_internal_edges_before + 1.0);

		// Now multiply by the value of the quantity labeled gamma_b in the paper
		nnodes = tree->GetNNodes();
		const bool fully_resolved_after = (nnodes == num_nodes_in_fully_resolved_tree);
		if (star_tree_before && !fully_resolved_after)
			ln_hastings -= log(2.0);
		else if (fully_resolved_after && !star_tree_before)
			ln_hastings += log(2.0);
		//@POL the pow will be problematic for large polytomies
		}
	else
		{
		// Choose an internal node at random (but not the only child of the root node)
		// and delete its edge to create a polytomy (or a bigger polytomy if there is
		// already a polytomy)
		//
		unsigned num_internals = nnodes - num_taxa - 1;
		PHYCAS_ASSERT(num_internals > 0);
		PHYCAS_ASSERT(num_internals < num_taxa);
		unsigned i = rng->SampleUInt(num_internals);

		for (nd = tree->GetFirstPreorder(); nd != NULL; nd = nd->GetNextPreorder())
			{
			if (nd->IsTip() || nd->GetParent()->IsTipRoot())
				continue;

			if (i == 0)
				break;
			else
				--i;
			}
        //nd->SelectNode();
		//likelihood->startTreeViewer(tree, "Delete edge move");
		proposeDeleteEdgeMove(nd);

		// The Jacobian is the matrix of the derivatives of all post-move quantities with respect to all
		// pre-move quantities. In this case, there is only one pre-move quantity (v) and only one post-move
		// quantity (U), so the Jacobian is a 1x1 matrix consisting only of the derivative of U with respect
		// to v. U = 1 - exp{-theta*v}, so the Jacobian for this delete-edge move is theta*exp(-theta*v),
		// where v is the length of the edge being deleted and theta = 1/edgelen_mean is the hazard parameter
		// of the exponential distribution used for sampling new edge lengths. Thus,
		//
		// ln_jacobian = log[theta*exp(-theta*v)]
		//             = log[exp(-v/edgelen_mean)/edgelen_mean]
		//             = -(v/edgelen_mean) - log(edgelen_mean)
		//
		// Note that v = orig_edgelen; orig_edgelen was set in ProposeDeleteEdgeMove.
		ln_jacobian = -orig_edgelen/edgelen_mean - log(edgelen_mean);

		// Hastings ratio is inverse of that for the add-edge move (see extensive notes above), but be
		// careful to use the number of polytomies in tree *after* a delete-edge move and the number of internal
		// edges *before* a delete-edge move in the formula. Here, n is the number of spokes in the polytomy
		// created by the delete-edge move. Both polytomy_size and num_polytomies are correctly computed by
		// ProposeDeleteEdgeMove.
		//
		//  Pr(add-edge move that reverts proposed delete-edge move)         num_internal_edges_before
		//  -------------------------------------------------------- = -----------------------------------------
		//                Pr(proposed delete-edge move)                 num_polytomies_after * [2^(n-1) - n - 1]
		//
		// If the proposed state is the star tree, then the Hastings ratio must account for the fact that
		// the forward move is only attempted half the time whereas the reverse move is the only move possible
		// for the star tree. Thus, if the number of nodes in the tree after the proposal is 1 more than the
		// number of taxa, the Hastings ratio as computed above must be multiplied by 2.0. If the proposal takes
		// us away from the star tree, then the Hastings ratio must be divided by 2.0.
		//
		double nspokes = (double)polytomy_size;

		// Compute the log of the Hastings ratio
		ln_hastings = log((double)num_internal_edges_before);
		ln_hastings -= log((double)num_polytomies);
		ln_hastings -= log(pow(2.0, nspokes - 1.0) - nspokes - 1.0);

		// Now multiply by the value of the quantity labeled gamma_d in the paper
		nnodes = tree->GetNNodes();
		const bool star_tree_after = (nnodes == num_taxa + 1);
		if (fully_resolved_before && !star_tree_after)
			{
			ln_hastings -= log(2.0);
			}
		else if (star_tree_after && !fully_resolved_before)
			{
			ln_hastings += log(2.0);
			}
		//@POL the pow will be problematic for large polytomies
		}

	ChainManagerShPtr p = chain_mgr.lock();
	PHYCAS_ASSERT(p);
    JointPriorManagerShPtr jpm = p->getJointPriorManager();
    jpm->skipNextDebugCheck();
    jpm->allEdgeLensModified(tree);
    jpm->topologyModified("tree_topology", tree);
	}

/*--------------------------------------------------------------------------------------------------------------------------
|	Sets `num_taxa' to the number of tips in `tree', computes the polytomy distribution, and sets the number of taxa for the
|	`topo_prior_calculator' object to `num_taxa'.
*/
void BushMove::finalize()
	{
	num_taxa = tree->GetNTips();
	num_nodes_in_fully_resolved_tree = 2*(num_taxa - 1);    // ASSUMES UNROOTED TREE

    // 1   2 3   4    1   2   3   4    Fully-resolved rooted trees have 2*num_taxa nodes
    //  \ /   \ /      \   \   \ /     if the root node (fake tip) is included, regardless
    //   5     6        \   \   5      of symmetry.
    //    \   /          \   \ /
    //     \ /            \   6
    //      7              \ /
    //      |               7
    //      8               |
    //                      8

	computePolytomyDistribution(num_taxa);
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Called if the proposed move is rejected. Causes tree to be returned to its state just prior to proposing the move.
*/
void BushMove::revert()
	{
	MCMCUpdater::revert();
	if (add_edge_move_proposed)
		{
		// An add-edge move was previously proposed, which means a new internal node (orig_lchild) and its edge were created.
		// orig_lchild's parent is known as orig_par. The children of orig_lchild represent a subset of the original children
		// of orig_par. To reverse the add-edge move, we need only transfer all children of orig_lchild to orig_par, then store
		// orig_lchild for use in some later add-edge move proposal

		// Must discard CLAs before moving children, otherwise orig_lchild will look like a tip node
		// which causes invalidateBothEndsDiscardCache to attempt to access its TipData structure
		likelihood->invalidateBothEndsDiscardCache(orig_lchild); //@POL should not be any cached CLAs here

		// Transfer all children of orig_lchild to orig_par
		while (orig_lchild->GetLeftChild() != NULL)
			{
			TreeNode * s = orig_lchild->GetLeftChild();
			tree_manipulator.DetachSubtree(s);
			tree_manipulator.InsertSubtree(s, orig_par, TreeManip::kOnRight);
			}

		// Now delete orig_lchild
		//
		tree_manipulator.DetachSubtree(orig_lchild);
		tree->StoreInternalNode(orig_lchild);

		likelihood->useAsLikelihoodRoot(orig_par);
		likelihood->restoreFromCacheAwayFromNode(*orig_par);

		//likelihood->startTreeViewer(tree, "Add edge move REVERTED");

		tree->InvalidateNodeCounts();
		}
	else
		{
		// A delete-edge move was proposed, which means an internal node and its edge have been deleted. This deleted
		// node's parent is known as orig_par. The children of the deleted node have been added as children of orig_par,
		// with the first being identified now as orig_lchild and the last in the series now pointed to by orig_rchild.
		// To revert the delete-edge move, create a new child of orig_par having edgelen equal to orig_edgelen, then transfer
		// the nodes from orig_lchild to orig_rchild (following rSib pointers, and including both orig_lchild and
		// orig_rchild) to the new node.
		//
		TreeNode * u = tree->GetNewNode();
		u->SetEdgeLen(orig_edgelen);
		tree_manipulator.InsertSubtree(u, orig_par, TreeManip::kOnRight);

		TreeNode * nd = orig_lchild;
		for (;;)
			{
			TreeNode * s = nd;
			PHYCAS_ASSERT(s != NULL);
			nd = nd->GetRightSib();
			tree_manipulator.SibToChild(u, s, TreeManip::kOnRight);
			if (s == orig_rchild)
				break;
			}

		likelihood->useAsLikelihoodRoot(orig_par);
		likelihood->restoreFromCacheAwayFromNode(*orig_lchild);
		likelihood->restoreFromCacheParentalOnly(orig_lchild);

		//orig_par->UnselectNode();
		//likelihood->startTreeViewer(tree, "Delete edge move REVERTED");

		tree->InvalidateNodeCounts();
		}

	reset();

	ChainManagerShPtr p = chain_mgr.lock();
	PHYCAS_ASSERT(p);
    JointPriorManagerShPtr jpm = p->getJointPriorManager();
    jpm->skipNextDebugCheck();
    jpm->allEdgeLensModified(tree);
    jpm->topologyModified("tree_topology", tree);
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Clears the `polytomies' vector, then walks the tree adding nodes that represent polytomies (more than two children)
|	to the `polytomies' vector.
*/
void BushMove::refreshPolytomies()
	{
	//@POL should keep polytomies list up to date rather than building anew
	polytomies.clear();
	for (TreeNode *nd = tree->GetFirstPreorder(); nd != NULL; nd = nd->GetNextPreorder())
		{
		if (nd->IsTipRoot())
			continue;

		unsigned s = nd->CountChildren();
		if (s > 2)
			{
			polytomies.push_back(nd);
			}
		}
	}

/*--------------------------------------------------------------------------------------------------------------------------
|	Determines distribution of x given nspokes, where x is the number spokes assigned to the newly created node in an
|	add-edge move. The number of ways of choosing x spokes to move out of nspokes total is {nspokes \choose x}. We are not
|	interested in the values x = 0, x = 1, x = nspokes - 1, and x = n because these lead either to a non-move (x = 0 and
|	x = nspokes) or to a tree that has an invalid structure (x = 1 and x = nspokes - 1). Thus, the total number of possible
|	trees considered is {nspokes \choose 2} + {nspokes \choose 3} + ... + {n \choose nspokes - 2} =
|	2^nspokes - 2*(nspokes + 1). The 2^nspokes comes from the fact that 2^nspokes is the sum of all binomial coefficients
|	for a sample size of nspokes. The subtracted term 2(nspokes + 1) comes from the fact that the first and last binomial
|	coefficients - {nspokes \choose 0} and {nspokes \choose nspokes} - are always 1 and the second and penultimate binomial
|	coefficients - {nspokes \choose 1} and {nspokes \choose nspokes - 1} - always equal nspokes. Thus, if one wishes to
|	choose randomly from all possible ways of splitting the polytomy into two groups of spokes, select x with probability:
|>
|	                    (nspokes \choose x}
|	  Pr(X = x) = -----------------------------, x = 2, 3, ..., nspokes - 2
|	                2^nspokes - 2*(nspokes + 1)
|>
*/
const VecPolytomyDistr & BushMove::computePolytomyDistribution(unsigned nspokes)
	{
	PHYCAS_ASSERT(nspokes > 2);
	std::pair<PolytomyDistrMap::const_iterator, bool> retval;
	PolytomyDistrMap::const_iterator i = poly_prob.find(nspokes);
	if (i == poly_prob.end())
		{
		// There is no existing probability distribution vector corresponding to nspokes
		// Need to calcuate it and insert into poly_prob map.
		double ln_nfact = cdf.LnGamma((double)(nspokes + 1));
		double denom = exp((double)nspokes * log(2.0)) - 2.0*nspokes - 2.0; //@POL may need to factor if n large
		double ln_denom = log(denom);
		VecPolytomyDistr v;
		for (unsigned x = 2; x <= nspokes - 2; ++x)
			{
			double ln_numer = ln_nfact - cdf.LnGamma((double)(x + 1)) - cdf.LnGamma((double)(nspokes - x + 1));
			double prob_x = exp(ln_numer - ln_denom);
			v.push_back(prob_x);
			}
		retval = poly_prob.insert(PolytomyDistrMap::value_type(nspokes, v));
		PHYCAS_ASSERT(retval.second == true);
		i = retval.first;
		}
	return (i->second);
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Split up the polytomy at `u' by creating a new internal node v and a new edge connecting u with v. Node u is saved
|	as `orig_par' and node v is saved as `orig_lchild' in case we need to revert the proposed move.
*/
void BushMove::proposeAddEdgeMove(TreeNode * u)
	{
	PHYCAS_ASSERT(u != NULL);
	polytomy_size = 1 + u->CountChildren();

	const std::vector<double> & prob_n = computePolytomyDistribution(polytomy_size);

	// Select number of spokes to move over to new node
	unsigned x;
	double p = rng->Uniform();
	double cum = 0.0;
	for (unsigned k = 0; k <= polytomy_size - 4; ++k)
		{
		x = k + 2;
		double prob_k_given_n = prob_n[k];
		cum += prob_k_given_n;
		if (p < cum)
			break;
		}
	PHYCAS_ASSERT(x < polytomy_size - 1);

	// Create the new node that will receive the x randomly-chosen spokes
	new_edgelen = edgelen_dist->Sample();
	TreeNode * v = tree->GetNewNode();
	v->SetEdgeLen(new_edgelen);
	v->SetNodeNum(tree->GetNNodes());
	likelihood->prepareInternalNodeForLikelihood(v);
	tree_manipulator.InsertSubtree(v, u, TreeManip::kOnLeft);

	// Save u and v. If revert is necessary, all of orig_lchild's nodes will be returned
	// to orig_par, and orig_lchild will be deleted.
	//
	orig_par = u;
	orig_lchild = v;

	// After the move, either v or u should have x spokes and the other node polytomy_size - x spokes (u and v will
	// each have 1 additional connector spoke).Choose x spokes randomly out of the polytomy_size available.
	// If u->par is included, let u retain the x spokes and move polytomy_size - x spokes to v. Otherwise, move the
	// x spokes to v leaving polytomy_size - x spokes behind.
	//
	std::vector<TreeNode *> uspokes;
	uspokes.push_back(u->GetParent());
	for (TreeNode *uchild = u->GetLeftChild(); uchild != NULL; uchild = uchild->GetRightSib())
		{
		if (uchild != v)
			uspokes.push_back(uchild);
		}
	PHYCAS_ASSERT(uspokes.size() == polytomy_size);

	bool reverse_polarity = false;
	std::vector<TreeNode *> vspokes;
	typedef std::vector<TreeNode *>::iterator::difference_type vec_it_diff;
	for (unsigned k = 0; k < x; ++k)
		{
		unsigned num_u_spokes = (unsigned)uspokes.size();
		PHYCAS_ASSERT(num_u_spokes > 0);
		unsigned j = rng->SampleUInt(num_u_spokes);
		TreeNode * s = uspokes[j];
		if (s == u->GetParent())
			reverse_polarity = true;
		vspokes.push_back(s);
		uspokes.erase(uspokes.begin() + (vec_it_diff) j);
		}
	PHYCAS_ASSERT(uspokes.size() + vspokes.size() == polytomy_size);

	if (reverse_polarity)
		{
		// transfer nodes in uspokes to v
		//
		std::vector<TreeNode *>::iterator s;
		for (s = uspokes.begin(); s != uspokes.end(); ++s)
			{
			tree_manipulator.DetachSubtree(*s);
			tree_manipulator.InsertSubtree(*s, v, TreeManip::kOnRight);
			}
		}
	else
		{
		// transfer nodes in vspokes to v
		//
		std::vector<TreeNode *>::iterator s;
		for (s = vspokes.begin(); s != vspokes.end(); ++s)
			{
			tree_manipulator.DetachSubtree(*s);
			tree_manipulator.InsertSubtree(*s, v, TreeManip::kOnRight);
			}
		}

	//orig_lchild->SelectNode();

    //v->SelectNode();
    //likelihood->startTreeViewer(tree, "proposeAddEdgeMove");

	likelihood->useAsLikelihoodRoot(orig_lchild);
	likelihood->invalidateAwayFromNode(*orig_lchild);
	likelihood->invalidateBothEnds(orig_lchild);	//@POL really just need invalidateParentalOnly function

	tree->InvalidateNodeCounts();
	}

/*--------------------------------------------------------------------------------------------------------------------------
|	Delete the edge associated with `u' to create a polytomy (or a bigger polytomy if `u->par' was already a polytomy).
|	The supplied node u should not be the only child of the root node.
|>
|	      b       c
|	       \     /
|	        \   /
|	         \ /
|	  a       u		   a   b   c
|	   \     /		    \  |  /
|	    \   /		     \ | /
|	     \ /		      \|/
|         v                v
|	     /			      /
|
|	    Before           After
|>
|	Returns the number of polytomies in the tree after the proposed delete-edge move. The return value will be incorrect if
|	the polytomies vector is not up-to-date.
*/
void BushMove::proposeDeleteEdgeMove(TreeNode * u)
	{
	// Node u will be stored, so get rid of any CLAs
	//@POL should not discard cache here in case move is reverted - right now we're discarding because
	// tree_manipulator.DeleteLeaf stores node u, and we do not want any CLAs to travel to node storage
	// and come back to haunt us later. Better solution would be to keep u around until we know the move
	// will be accepted, at which point the CLAs can definitely be discarded
	likelihood->invalidateBothEndsDiscardCache(u);

    bool u_is_internal = u->IsInternal();

    // Save nd's edge length in case we need to revert
	//
	orig_edgelen = u->GetEdgeLen();

	// This operation should not leave the root node (which is a tip) with more than
	// one child, so check to make sure that the supplied node is not the root nor a
	// child of root
	//
	orig_par = u->GetParent();
	PHYCAS_ASSERT(orig_par != NULL);
	PHYCAS_ASSERT(!orig_par->IsTipRoot());

	num_polytomies = (unsigned)polytomies.size();

	// Compute size of polytomy after the delete-edge move, a quantity that is needed for computing the Hastings ratio.
	// Note that one of v's children (i.e. u) is deleted but this is made up for by considering v->par, which is
	// also a spoke that counts.
	//
	unsigned u_children = u->CountChildren();
	unsigned v_children = orig_par->CountChildren();
	polytomy_size = v_children + u_children;

	bool u_polytomy_before = (u_children > 2);
	bool v_polytomy_before = (v_children > 2);
	if (u_polytomy_before && v_polytomy_before)
		{
		// No. polytomies will decrease by one as a result of this delete-edge move
		//
		--num_polytomies;
		}
	else if (!u_polytomy_before && !v_polytomy_before)
		{
		// No. polytomies will increase by one as a result of this delete-edge move
		//
		++num_polytomies;
		}

	// Make all of u's children left siblings (i.e. children of u->par)
	//
	orig_lchild = u->GetLeftChild();
	while (u->GetLeftChild() != NULL)
		{
		orig_rchild = u->GetLeftChild();
		tree_manipulator.DetachSubtree(orig_rchild);
		tree_manipulator.InsertSubtree(orig_rchild, orig_par, TreeManip::kOnRight);
		}

	tree_manipulator.DeleteLeaf(u, u_is_internal);

	//orig_par->SelectNode();

	likelihood->useAsLikelihoodRoot(orig_par);
	likelihood->invalidateAwayFromNode(*orig_par);

	tree->InvalidateNodeCounts();
	}

/*--------------------------------------------------------------------------------------------------------------------------
|	Setup `edgelen_dist' data member.
*/
void BushMove::setEdgeLenDistMean(
  double mean)
	{
	PHYCAS_ASSERT(mean > 0.0);
	if (rng)
		{
		edgelen_mean = mean;
		edgelen_dist = ExponentialDistributionShPtr(new phycas::ExponentialDistribution(1.0/edgelen_mean));
		edgelen_dist->SetMeanAndVariance(edgelen_mean, edgelen_mean);
		edgelen_dist->SetLot(rng.get());  //@POL should just be able to pass in the shared_ptr rather than a raw pointer
		}
	else
		{
		throw XLikelihood("Must set the pseudorandom number generator before calling setEdgeLenDistMean");
		}
	}

/*--------------------------------------------------------------------------------------------------------------------------
|	Called if the move is accepted.
*/
void BushMove::accept()
	{
	MCMCUpdater::accept();
	if (add_edge_move_proposed)
		{
		// Keeping added edge, where orig_par was original polytomous node and orig_lchild was the added node
		likelihood->useAsLikelihoodRoot(orig_lchild);
		likelihood->discardCacheAwayFromNode(*orig_lchild);
		likelihood->discardCacheBothEnds(orig_lchild);

		//likelihood->startTreeViewer(tree, "Add edge move ACCEPTED");
		}
	else
		{
		// Keeping edge deletion, orig_par is the new polytomous node
		likelihood->useAsLikelihoodRoot(orig_par);
		likelihood->discardCacheAwayFromNode(*orig_par);

		//likelihood->startTreeViewer(tree, "Delete edge move ACCEPTED");
		}

	reset();
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns current value of `add_edge_move_proposed' data member, which is true if the last move proposed was an
|	add-edge move and false if last move proposed was a delete-edge move.
*/
bool BushMove::addEdgeMoveProposed() const
	{
	return add_edge_move_proposed;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Re-initializes all data members that are recomputed each time ProposeNewState is called.
*/
void BushMove::reset()
	{
	// Workspace used for computing edge length prior
	// Should always be length one.
	//if (one_edgelen.empty())
    //    one_edgelen.push_back(0.0);

	polytomies.clear();

	add_edge_move_proposed	= false;
	orig_edgelen			= 0.0;
	orig_lchild				= NULL;
	orig_rchild				= NULL;
	orig_par				= NULL;
	ln_jacobian				= 0.0;
	ln_hastings				= 0.0;
	new_edgelen				= 0.0;
	polytomy_size			= 0;
	num_polytomies			= 0;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the value of `ln_hastings', which is the natural log of the Hastings ratio for this move and which is
|	computed in both BushMove::ProposeAddEdgeMove and BushMove::ProposeDeleteEdgeMove.
*/
double BushMove::getLnHastingsRatio() const
	{
	return ln_hastings;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the value of `ln_jacobian', which is the natural log of the Jacobian for this move and which is computed in
|	both BushMove::ProposeAddEdgeMove and BushMove::ProposeDeleteEdgeMove.
*/
double BushMove::getLnJacobian() const
	{
	return ln_jacobian;
	}


/* This software and supporting documentation are distributed by
 *     Institut Federatif de Recherche 49
 *     CEA/NeuroSpin, Batiment 145,
 *     91191 Gif-sur-Yvette cedex
 *     France
 *
 * This software is governed by the CeCILL-B license under
 * French law and abiding by the rules of distribution of free software.
 * You can  use, modify and/or redistribute the software under the
 * terms of the CeCILL-B license as circulated by CEA, CNRS
 * and INRIA at the following URL "http://www.cecill.info".
 *
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 *
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-B license and that you accept its terms.
 */

// activate deprecation warning
#ifdef AIMSDATA_CLASS_NO_DEPREC_WARNING
#undef AIMSDATA_CLASS_NO_DEPREC_WARNING
#endif

#include <aims/sparsematrix/sparseMatrix.h>
#include <aims/sparsematrix/sparseMatrixItemR.h>
#include <aims/sparsematrix/sparseMatrixItemW.h>
#include <cartobase/object/object_d.h>
#include <fstream>
#include <boost/numeric/ublas/matrix.hpp>

using namespace aims;
using namespace carto;

aims::SparseMatrix::SparseMatrix( int32_t size1, int32_t size2 )
  : _matrix( size1, size2, 0 ), _header( Object::value( PropertySet() ) )
{
}

aims::SparseMatrix::SparseMatrix( const aims::SparseMatrix& other )
  : RCObject(),
    _matrix( other._matrix ), _header( Object::value( PropertySet() ) )
{
  *_header = *other.header();
}

aims::SparseMatrix::~SparseMatrix()
{
}

aims::SparseMatrix&
    aims::SparseMatrix::operator=( const aims::SparseMatrix& other )
{

  try
  {

    _matrix = other._matrix;
    *_header = *other.header();

    return *this;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string("aims::SparseMatrix& "
        "aims::SparseMatrix::operator=( "
        "const aims::SparseMatrix& other )") + e.what() );
  }

}

void aims::SparseMatrix::reallocate( int32_t size1, int32_t size2 )
{

  try
  {

    // do not preservce because not implemented in boost !
    _matrix.resize( size1, size2, false );

  }

  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::reallocate( "
        "int32_t size1, int32_t size2 )" ) + e.what() );
  }

}

aims::SparseMatrix::iterator1 aims::SparseMatrix::begin1()
{

  try
  {

    return _matrix.begin1();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::iterator1 "
        "aims::SparseMatrix::begin1()" ) + e.what() );
  }

}

aims::SparseMatrix::iterator1 aims::SparseMatrix::end1()
{

  try
  {

    return _matrix.end1();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::iterator1 "
        "aims::SparseMatrix::end1()" ) + e.what() );
  }

}

aims::SparseMatrix::const_iterator1 aims::SparseMatrix::begin1() const
{

  try
  {

    return _matrix.begin1();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::iterator1 "
        "aims::SparseMatrix::begin1() const" ) + e.what() );
  }

}

aims::SparseMatrix::const_iterator1 aims::SparseMatrix::end1() const
{

  try
  {

    return _matrix.end1();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::const_iterator1 "
        "aims::SparseMatrix::end1() const" ) + e.what() );
  }

}

aims::SparseMatrix::iterator2 aims::SparseMatrix::begin2()
{

  try
  {

    return _matrix.begin2();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::iterator2 "
        "aims::SparseMatrix::begin2()" ) + e.what() );
  }

}

aims::SparseMatrix::iterator2 aims::SparseMatrix::end2()
{

  try
  {

    return _matrix.end2();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::iterator2 "
        "aims::SparseMatrix::end2()" ) + e.what() );
  }

}

aims::SparseMatrix::const_iterator2 aims::SparseMatrix::begin2() const
{

  try
  {

    return _matrix.begin2();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::iterator2 "
        "aims::SparseMatrix::begin2() const" ) + e.what() );
  }

}

aims::SparseMatrix::const_iterator2 aims::SparseMatrix::end2() const
{

  try
  {

    return _matrix.end2();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix::const_iterator2 "
        "aims::SparseMatrix::end2() const" ) + e.what() );
  }

}

int32_t aims::SparseMatrix::getSize1() const
{

  try
  {

    return ( int32_t )_matrix.size1();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "int32_t "
        "aims::SparseMatrix::getSize1() const" ) + e.what() );
  }

}

int32_t aims::SparseMatrix::getSize2() const
{

  try
  {

    return ( int32_t )_matrix.size2();

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "int32_t "
        "aims::SparseMatrix::getSize2() const" ) + e.what() );
  }

}

int32_t aims::SparseMatrix::getNonZeroElementCount() const
{

  try
  {

#if BOOST_VERSION < 103300
    boost_sparse_matrix::size_type filled = 0;
    boost_sparse_matrix::const_iterator2 itv2, etv2;
    for (boost_sparse_matrix::const_iterator1 itv = _matrix.begin1 (); itv != _matrix.end1 (); ++ itv)
      for( itv2=itv.begin(), etv2=itv.end(); itv2!=etv2; ++itv2 )
        ++filled;
    return filled;

#else
    return ( int32_t )_matrix.nnz();
#endif

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "int32_t aims::SparseMatrix::getNonZeroElementCount() "
        "const" ) + e.what() );
  }

}

bool aims::SparseMatrix::hasElement( int32_t i, int32_t j ) const
{

  try
  {

#if BOOST_VERSION < 103300
    return _matrix.at_element( i, j ) != 0;
#else
    return _matrix.find_element( i, j );
#endif

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "bool "
        "aims::SparseMatrix::hasElement( "
        "int32_t i, int32_t j ) const" ) + e.what() );
  }

}

aims::SparseMatrix::const_reference
    aims::SparseMatrix::operator()( int32_t i, int32_t j ) const
{

  try
  {

    return _matrix( i, j );

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "const double& "
        "aims::SparseMatrix::operator()( "
        "int32_t i, int32_t j ) const" ) +  e.what() );
  }

}

aims::SparseMatrix::reference
    aims::SparseMatrix::operator()( int32_t i, int32_t j )
{

  try
  {

    return _matrix( i, j );

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "double& aims::SparseMatrix::operator()( "
        "int32_t i, int32_t j )" ) + e.what() );
  }

}

bool aims::SparseMatrix::isSquare() const
{

  try
  {

    return ( _matrix.size1() == _matrix.size2() );

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "bool "
        "aims::SparseMatrix::isSquare() const" ) + e.what() );
  }

}

void aims::SparseMatrix::setZero()
{

  try
  {
    _matrix.clear();
  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::setZero()" ) + e.what() );
  }

}

void aims::SparseMatrix::setIdentity()
{

  try
  {

    _matrix.clear();

    int32_t size = getSize1();

    int32_t s = 0;
    for ( s = 0; s < size; s++ )
    {

        _matrix( s, s ) = 1.0;

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::setIdentity()" ) + e.what() );
  }

}

void aims::SparseMatrix::setRow( int32_t s1, const std::vector<double>& row )
{

  try
  {

    if ( int32_t(row.size()) != getSize2() )
    {

      throw std::runtime_error( "incoherent size(s)" );

    }
    int32_t s2;
    for ( s2 = 0; s2 < getSize2(); s2++ )
    {

      const double & element = row[s2];
      if( element == 0 )
        erase_element( s1, s2 );
      else
        ( *this )( s1, s2 ) = element;

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::setRow( int32_t s1, "
        "const std::vector<double>& row )" ) + e.what() );
  }

}

void aims::SparseMatrix::setColumn( int32_t s2, const std::vector<double>& column )
{

  try
  {

    if ( int32_t(column.size()) != getSize1() )
    {

      throw std::runtime_error( "incoherent size(s)" );

    }
    int32_t s1;
    for ( s1 = 0; s1 < getSize1(); s1++ )
    {

      ( *this )( s1, s2 ) = column[ s1 ];

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::setColumn( "
        "int32_t s2, const std::vector<double>& column )" ) + e.what() );
  }

}

void aims::SparseMatrix::fill( const double& value )
{

  try
  {

    int32_t size1 = getSize1();
    int32_t size2 = getSize2();

    int32_t s1 = 0;
    int32_t s2 = 0;
    for ( s1 = 0; s1 < size1; s1++ )
    {

      for ( s2 = 0; s2 < size2; s2++ )
      {

        _matrix( s1, s2 ) = value;

      }

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::fill( "
        "const double& value )" ) + e.what() );
  }

}

void aims::SparseMatrix::fill( int32_t offset1,
                               int32_t offset2,
                               int32_t size1,
                               int32_t size2,
                               const double& value )
{

  try
  {

    if ( ( offset1 < 0 ) ||
           ( offset2 < 0 ) ||
           ( offset1 + size1 > getSize1() ) ||
           ( offset2 + size2 > getSize2() ) )

    {

      throw std::runtime_error( "incompatible offset(s)/size(s)" );

    }
    int32_t s1, s2;
    for ( s1 = 0; s1 < size1; s1++ )
    {

      for ( s2 = 0; s2 < size2; s2++ )
      {

        ( *this )( offset1 + s1, offset2 + s2 ) = value;

      }

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void aims::SparseMatrix::fill( "
        "int32_t offset1, "
        "int32_t offset2, "
        "int32_t size1, "
        "int32_t size2, "
        "const double& value )" ) + e.what() );
  }

}

void aims::SparseMatrix::fill( int32_t offset1,
                               int32_t offset2,
                               const aims::SparseMatrix& other,
                               int32_t size1,
                               int32_t size2 )
{

  try
  {

    if ( ( size1 < 0 ) || ( size2 < 0 ) )
    {

      throw std::runtime_error( "size1 and size2 must be nul or positive" );

    }

    int32_t targetSize1 = ( size1 ? size1 : other.getSize1() );
    int32_t targetSize2 = ( size2 ? size2 : other.getSize2() );

    if ( ( offset1 < 0 ) ||
           ( offset2 < 0 ) ||
           ( offset1 + targetSize1 > getSize1() ) ||
           ( offset2 + targetSize2 > getSize2() ) )
    {

      throw std::runtime_error( "incompatible offset(s)/other matrix size" );

    }
    int32_t s1, s2;
    for ( s1 = 0; s1 < targetSize1; s1++ )
    {

      for ( s2 = 0; s2 < targetSize2; s2++ )
      {

        if ( hasElement( s1, s2 ) )
        {

          ( *this )( offset1 + s1, offset2 + s2 ) = other( s1, s2 );

        }

      }

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void aims::SparseMatrix::fill( "
        "int32_t offset1, "
        "int32_t offset2, "
        "const aims::SparseMatrix& other, "
        "int32_t size1, "
        "int32_t size2 )" ) + e.what() );
  }

}

void aims::SparseMatrix::setDiagonal( const double& value )
{

  try
  {

    _matrix.clear();
    int32_t s;
    int32_t maximumS = std::min( getSize1(), getSize2() );
    for ( s = 0; s < maximumS; s++ )
    {

      _matrix( s, s ) = value;

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::setDiagonal( "
        "const double& value )" ) + e.what() );
  }

}

void aims::SparseMatrix::transpose()
{

  try
  {

    aims::SparseMatrix tmp( *this );
    reallocate( tmp.getSize2(), tmp.getSize1() );

    for ( int32_t s1 = 0; s1 < getSize1(); s1++ )
    {

      for ( int32_t s2 = 0; s2 < getSize2(); s2++ )
      {

        if ( tmp.hasElement( s2, s1 ) )
        {

          ( *this )( s1, s2 ) = tmp( s2, s1 );

        }

      }

    }

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::transpose()" ) +  e.what() );
  }

}

double aims::SparseMatrix::getTrace() const
{

  try
  {

    if ( !isSquare() )
    {

      throw std::runtime_error( "not a square matrix" );

    }

    double trace = 0.0;
    int32_t s;
    for ( s = 0; s < getSize1(); s++ )
    {

      trace += ( *this )( s, s );

    }

    return trace;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "double "
        "aims::SparseMatrix::getTrace() const" ) + e.what() );
  }

}

aims::SparseMatrix aims::SparseMatrix::getTransposition() const
{

  try
  {

    aims::SparseMatrix matrix( *this );
    matrix.transpose();
    return matrix;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "aims::SparseMatrix::getTransposition() const" ) + e.what() );
  }

}

aims::SparseMatrix
    aims::SparseMatrix::getComposition( const aims::SparseMatrix& other ) const
{

  try
  {

    if ( getSize2() != other.getSize1() )
    {

      std::runtime_error( "the row count for other matrix is not equal to "
          "the column count for this matrix" );
    }

    aims::SparseMatrix composition( getSize1(), other.getSize2() );

    for ( int32_t s1 = 0; s1 < composition.getSize1(); s1++ )
    {

      for ( int32_t s2 = 0; s2 < composition.getSize2(); s2++ )
      {

        double value = 0.0;
        bool nonZero = false;
        for ( int32_t k = 0; k < getSize2(); k++ )
        {

          if ( this->hasElement( s1, k ) && other.hasElement( k, s2 ) )
          {

            value += ( *this )( s1, k ) * other( k, s2 );
            nonZero = true;

          }

        }

        if ( nonZero )
        {

          composition( s1, s2 ) = value;

        }

      }

    }
    return composition;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "aims::SparseMatrix::getComposition( "
        "const aims::SparseMatrix& other ) const" ) + e.what() );
  }

}

std::vector<double>
    aims::SparseMatrix::getComposition( const std::vector<double>& other ) const
{

  try
  {

    if ( getSize2() != int32_t(other.size()) )
    {

      std::runtime_error( "the vector size is not equal to "
          "the column count for this matrix" );
    }

    std::vector<double> composition( getSize1() );

    for ( int32_t s = 0; s < ( int32_t )composition.size(); s++ )
    {

      composition[ s ] = 0.0;
      for ( int32_t k = 0; k < getSize2(); k++ )
      {

        composition[ s ] += ( *this )( s, k ) * other[ k ];

      }

    }
    return composition;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::Vector "
        "aims::SparseMatrix::getComposition( "
        "const aims::Vector& other ) const" ) + e.what() );
  }

}

std::vector<double> aims::SparseMatrix::toVector() const
{

  try
  {

    std::vector<double> result;
    if ( getSize1() == 1 )
    {

      result.resize( getSize2() );
      for ( int32_t s = 0; s < ( int32_t )result.size(); s++ )
      {

        result[ s ] = ( *this )( 0, s );

      }

    }
    else if ( getSize2() == 1 )
    {

      result.resize( getSize1() );
      for ( int32_t s = 0; s < ( int32_t )result.size(); s++ )
      {

        result[ s ] = ( *this )( s, 0 );

      }

    }
    else
    {

      std::runtime_error( "none of the size is equal to 1" );

    }
    return result;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "std::vector "
        "aims::SparseMatrix::toVector() const" ) + e.what() );
  }

}

std::vector<double> aims::SparseMatrix::getRow( int32_t i ) const
{

  try
  {

    std::vector<double> row( getSize2(), 0. );
    boost_sparse_matrix::const_iterator1
      ir = _matrix.find1( 0, i, 0 );
    if( ir != _matrix.end1() && ir.index1() == static_cast<size_t>(i) )
    {
      boost_sparse_matrix::const_iterator2
        ic, ec = ir.end();
      long n = 0;
      for( ic=ir.begin(); ic!=ec; ++ic, ++n )
        row[ic.index2()] = *ic;
    }
    return row;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "std::vector "
        "aims::SparseMatrix::getRow( "
        "int32_t i ) const" ) + e.what() );
  }

}

std::vector<double> aims::SparseMatrix::getColumn( int32_t j ) const
{

  try
  {

    std::vector<double> column( getSize1() );
    for ( int32_t s = 0; s < getSize1(); s++ )
    {

      column[ s ] = ( *this )( s, j );

    }
    return column;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "std::vector "
        "aims::SparseMatrix::getColumn( "
        "int32_t j ) const" ) + e.what() );
  }

}

// #ifdef SPARSEVECTOR_H
// til::SparseVector<double> aims::SparseMatrix::getSparseRow( int32_t i ) const
// {
//
//   try
//   {
//
//     til::SparseVector<double> row( getSize2() );
//     boost_sparse_matrix::const_iterator1
//       ir = _matrix.find1( 0, i, 0 );
//     if( ir != _matrix.end1() && ir.index1() == i )
//     {
//       boost_sparse_matrix::const_iterator2
//         ic, ec = ir.end();
//       long n = 0;
//       for( ic=ir.begin(); ic!=ec; ++ic, ++n )
//         row[ic.index2()] = *ic;
//     }
//     return row;
//
//   }
//   catch( std::exception & e )
//   {
//     throw std::runtime_error( std::string( "til::SparseVector<double> "
//         "aims::SparseMatrix::getSparseRow( "
//         "int32_t i ) const" ) + e.what() );
//   }
//
// }
//
//
// til::SparseVector<double> aims::SparseMatrix::getSparseColumn( int32_t j ) const
// {
//
//   try
//   {
//
//     til::SparseVector<double> column( getSize1() );
//     for ( int32_t s = 0; s < getSize1(); s++ )
//     {
//
//       column[ s ] = ( *this )( s, j );
//
//     }
//     return column;
//
//   }
//   catch( std::exception & e )
//   {
//     throw std::runtime_error( std::string( "til::SparseVector<double> "
//         "aims::SparseMatrix::getSparseColumn( "
//         "int32_t j ) const" ) + e.what() );
//   }
//
// }
// #endif

void aims::SparseMatrix::erase_element( int32_t i, int32_t j )

{

  try
  {

#if BOOST_VERSION < 103300
    _matrix.erase( i, j );
#else
    _matrix.erase_element( i, j );
#endif

  }
  catch (std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::erase_element"
        "( int32_t i, int32_t j )" ) + e.what() );
  }

}

void aims::SparseMatrix::read( const std::string& filename,
                               const std::string & openmode,
                               bool bswap )
{

  try
  {

    std::ifstream is( filename.c_str() );

    aims::DefaultItemReader<aims::SparseMatrix>
        defaultItemR;
    aims::ItemReader<aims::SparseMatrix>*
        sparseMatrixItemR = defaultItemR.reader( openmode, bswap );

    sparseMatrixItemR->read( is, *this );
    is.close();

    delete sparseMatrixItemR;

  }
  catch (std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::read( const std::string& filename, "
        "const std::string & openmode, "
        "bool bswap )" ) + e.what() );
  }

}

void aims::SparseMatrix::write( const std::string& filename,
                                const std::string & openmode,
                                bool bswap ) const
{

  try
  {

    std::ofstream os( filename.c_str() );

    aims::DefaultItemWriter<aims::SparseMatrix>
        defaultItemW;
    aims::ItemWriter<aims::SparseMatrix>*
        sparseMatrixItemW = defaultItemW.writer( openmode, bswap );

    sparseMatrixItemW->write( os, *this );
    os.close();

    delete sparseMatrixItemW;

  }
  catch (std::exception & e )
  {
    throw std::runtime_error( std::string( "void "
        "aims::SparseMatrix::write( const std::string& filename, "
        "const std::string & openmode, "
        "bool bswap )" )+  e.what() );
  }

}

//
// unary operators + & -
//

// + SparseMatrix
aims::SparseMatrix
    operator + ( const aims::SparseMatrix& thing )
{

  try
  {

    return thing;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix  "
        "operator + ( const aims::SparseMatrix& thing )" ) +  e.what() );
  }

}

// - SparseMatrix
aims::SparseMatrix
    aims::operator - ( const aims::SparseMatrix& thing )
{

  try
  {

    aims::SparseMatrix result( thing );
    result *= -1;

    return result;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "operator - ( const aims::SparseMatrix& thing )" ) + e.what() );
  }

}

aims::SparseMatrix &
    aims::SparseMatrix::operator += ( const aims::SparseMatrix& thing )
{

  try
  {

    if ( ( thing.getSize1() != getSize1() ) ||
           ( thing.getSize2() != getSize2() ) )
    {

      throw std::runtime_error( "incompatible matrix size(s)" );

    }

    _matrix += thing._matrix;

    return *this;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "operator + ( const aims::SparseMatrix& thing1, "
        "const aims::SparseMatrix& thing2 )" ) + e.what() );
  }

}

aims::SparseMatrix &
    aims::SparseMatrix::operator -= ( const aims::SparseMatrix& thing )
{

  try
  {

    if ( ( thing.getSize1() != getSize1() ) ||
           ( thing.getSize2() != getSize2() ) )
    {

      throw std::runtime_error( "incompatible matrix size(s)" );

    }

    _matrix -= thing._matrix;

    return *this;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "operator + ( const aims::SparseMatrix& thing1, "
        "const aims::SparseMatrix& thing2 )" ) + e.what() );
  }

}

aims::SparseMatrix &
    aims::SparseMatrix::operator *= ( double x )
{

  _matrix *= x;

  return *this;
}

aims::SparseMatrix &
    aims::SparseMatrix::operator /= ( double x )
{

  _matrix /= x;

  return *this;
}

//
// binary operator +
//

// SparseMatrix + SparseMatrix
aims::SparseMatrix
    aims::operator + ( const aims::SparseMatrix& thing1,
                 const aims::SparseMatrix& thing2 )
{

  try
  {

    if ( ( thing1.getSize1() != thing2.getSize1() ) ||
           ( thing1.getSize2() != thing2.getSize2() ) )
    {

      throw std::runtime_error( "incompatible matrix size(s)" );

    }

    aims::SparseMatrix result( thing1 );
    result += thing2;

    return result;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "operator + ( const aims::SparseMatrix& thing1, "
        "const aims::SparseMatrix& thing2 )" ) + e.what() );
  }

}

//
// binary operator -
//

// SparseMatrix - SparseMatrix
aims::SparseMatrix
    aims::operator - ( const aims::SparseMatrix& thing1,
                 const aims::SparseMatrix& thing2 )
{

  try
  {

    if ( ( thing1.getSize1() != thing2.getSize1() ) ||
           ( thing2.getSize1() != thing2.getSize2() ) )
    {

      throw std::runtime_error( "incompatible matrix size(s)" );

    }

    aims::SparseMatrix result( thing1 );
    result -= thing2;

    return result;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "operator - ( const aims::SparseMatrix& thing1, "
        "const aims::SparseMatrix& thing2 )" ) + e.what() );
  }

}

//
// binary operator *
//

// SparseMatrix * SparseMatrix
aims::SparseMatrix
    aims::operator * ( const aims::SparseMatrix& thing1,
                 const aims::SparseMatrix& thing2 )
{

  try
  {

    return thing1.getComposition( thing2 );

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "operator * ( const aims::SparseMatrix& thing1, "
        "const aims::SparseMatrix& thing2 )" ) + e.what() );
  }

}

// SparseMatrix * std::vector<double>
std::vector<double>
    aims::operator * ( const aims::SparseMatrix& thing1,
                 const std::vector<double>& thing2 )
{

  try
  {

    return thing1.getComposition( thing2 );

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "std::vector<double> "
        "operator * ( const aims::SparseMatrix& thing1, "
        "const std::vector<double>& thing2 )" ) + e.what() );
  }

}

// SparseMatrix * double
aims::SparseMatrix
    aims::operator * ( const aims::SparseMatrix& thing1,
                 const double& thing2 )
{

  try
  {

    aims::SparseMatrix result( thing1 );
    result *= thing2;

    return result;

  }
  catch( std::exception & e )
  {
    throw std::runtime_error( std::string( "aims::SparseMatrix "
        "operator * ( const aims::SparseMatrix& thing1, "
        "const double& thing2 )" ) + e.what() );
  }

}

//
// binary operator /
//

// SparseMatrix / double
aims::SparseMatrix
    aims::operator / ( const aims::SparseMatrix& thing1,
                 const double& thing2 )
{

  try
  {

    aims::SparseMatrix result( thing1 );
    result /= thing2;

    return result;

  }
  catch( std::exception & e )
  {

    throw std::runtime_error(
      std::string( "aims::SparseMatrix "
                   "operator / ( const aims::SparseMatrix& thing1, "
                   "const double& thing2 ) : " ) + e.what() );
  }

}

//
// standard operator(s)
//

std::ostream& std::operator << ( std::ostream& os,
                                 const aims::SparseMatrix& thing )
{

  for ( int32_t s1 = 0; s1 < thing.getSize1(); s1++ )
  {

    for ( int32_t s2 = 0; s2 < thing.getSize2(); s2++ )
    {

      if ( thing.hasElement( s1, s2 ) )
      {

        os << thing( s1, s2 ) << " ";

      }
      else
      {

        os << "- ";

      }

    }
    if ( s1 != thing.getSize1() - 1 )
    {

      os << std::endl;

    }

  }
  return os;

}

bool std::operator == ( const aims::SparseMatrix& thing1,
                        const aims::SparseMatrix& thing2 )
{
  if( thing1.getSize1() != thing2.getSize1()
    || thing1.getSize2() != thing2.getSize2() )
    return false;

  aims::SparseMatrix::const_iterator1 il1, el1 = thing1.end1(),
    il2, el2 = thing2.end1();
  aims::SparseMatrix::const_iterator2 ic1, ec1, ic2, ec2;

  for( il1=thing1.begin1(), il2=thing2.begin1();
      il1!=el1 && il2!=el2 && il1.index1() == il2.index1(); ++il1, ++il2 )
  {
    for( ic1=il1.begin(), ec1=il1.end(), ic2=il2.begin(), ec2=il2.end();
        ic1!=ec1 && ic2!=ec2 && ic1.index2() == ic2.index2(); ++ic1, ++ic2 )
      if( *ic1 != *ic2 )
        return false;
    if( ic1 != ec1 || ic2 != ec2 )
      return false;
  }
  if( il1 != el1 || il2 != el2 )
    return false;
  return true;
}

void SparseMatrix::setHeader( Object ph )
{
  _header = ph;
}

namespace carto
{

INSTANTIATE_GENERIC_OBJECT_TYPE( aims::SparseMatrix )

}


/// \file   kelly_bettor.cpp
///
/// \brief
///
/// \authors    maarten
/// \date       2020-11-06
/// \copyright  Copyright 2017-2020 The Institute for New Economic Thinking,
///             Oxford Martin School, University of Oxford
///
///             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.
///
///             You may obtain instructions to fulfill the attribution
///             requirements in CITATION.cff
///
#include "kelly_bettor.hpp"

#include <utility>

#include <boost/accumulators/statistics/mean.hpp>

#include <esl/data/log.hpp>
using economics::finance::securities_lending_contract;

using namespace esl;
using namespace esl::economics;
using namespace esl::economics::markets;

kelly_bettor::kelly_bettor(const identity<fund> &i, const jurisdiction &j)
: agent(i)
, owner<cash>(i)
, owner<stock>(i)
, fund(i, j)
, past_price(100'00, currencies::USD)
{
    estimates(0.);
}

time_point kelly_bettor::invest(std::shared_ptr<walras::quote_message> message, simulation::time_interval interval, std::seed_seq &seed)
{
    if(message->received < interval.lower){
        return interval.lower;
    }

    demand.clear();
    for(auto [p,q]: message->proposed){
        demand.emplace(p->identifier, 1.0);
    }

    auto nav_ =  net_asset_value(interval);
    LOG(trace) << describe() << " " << identifier << " inventory " <<  inventory << std::endl;
    double update_value_ = 0.;

    if(interval.lower <= 100){
        return interval.lower;
    }

    auto m = this->template create_message<kelly_bettor_ddsf>(
        message->sender, interval.lower, (*this), message->sender,
        interval.lower, interval.lower,nav_,demand);

    price price_ = price::approximate(100.00, currencies::USD);
    double dividend_rate = 0.;
    for(auto [property_, quote_] : message->proposed) {

        price_ = std::get<price>(quote_.type);

        auto i = market_data.dividend_per_share.find(*property_);
        // it is assumed all assets are quoted with a price
        if(i != market_data.dividend_per_share.end()) {
            // in this simplified model, we assume there is one dividend payment per period, and therefore the dividend yield is in USD/day

            auto payment_ = double(i->second);
            auto shares_outstanding_ = market_data.shares_outstanding.find(property_->identifier)->second;
            dividend_rate = (payment_ / shares_outstanding_) ;
            update_value_ = 0.01 * std::min(interval.lower, 100ul) * (
                                (double(price_) / double(past_price)) - 1 + dividend_rate/ double(price_));
            past_price = price_;
        }
    }
    m->estimates = estimates;
    m->time = interval.lower;

    for(auto [p,q]: inventory){
        auto cast_ = std::dynamic_pointer_cast<stock>(p);
        if(cast_){
            if(0 == q.amount){
                continue;
            }
            m->supply.emplace(p->identifier, std::make_tuple(q, quantity(0)));
        }else{
            auto cast2_ = std::dynamic_pointer_cast<securities_lending_contract>(p);
            if(cast2_){
                if(0 == q.amount){
                    continue;
                }
                if(m->supply.end() != m->supply.find(cast2_->security)){
                    std::get<1>( m->supply.find(cast2_->security)->second ) = q;
                }else{
                    m->supply.emplace(cast2_->security, std::make_tuple(quantity(0), q));
                }
            }
        }
    }

    for(auto [p,q]: owner<stock>::properties.items){

    }
    for(auto [p,q]: owner<securities_lending_contract>::properties.items){

    }

    m->past_price = past_price;
    m->dividend_rate = dividend_rate;
    estimates(update_value_);
    return interval.lower;
}

std::string kelly_bettor::describe() const
{
    return "kelly bettor";
}

///
///
///
kelly_bettor_ddsf::kelly_bettor_ddsf(
    const identity<agent> &sender,
    const identity<agent> &recipient,
    simulation::time_point sent, simulation::time_point received,
    price net_asset_value,
    std::map<identity<law::property>, double> variates
)
    : differentiable_order_message(sender, recipient, sent, received)
    , variates(std::move(variates))
    , net_asset_value(net_asset_value)
{

}

std::map<identity<law::property>, esl::variable>
kelly_bettor_ddsf::excess_demand(
    const std::map<identity<law::property>,
        std::tuple<economics::markets::quote, variable>> &quotes)
const
{
    std::map<esl::identity<property>, esl::variable> result_;
    auto scale_ = double(net_asset_value) / quotes.size();

    for(auto &[k, v] : quotes) {
        const auto &[quote_, variable_] = v;
        const auto quoted_price_ = double(std::get<price>(quote_.type));
        auto i = variates.find(k);
        if(variates.end() != i) {
            auto value_ = i->second;
            auto j = supply.find(k);
            if(supply.end() == j){
                result_.emplace(k,  scale_ * value_ * value_);
            }else{
                auto supply_long_  = double(std::get<0>(j->second));
                auto supply_short_ = double(std::get<1>(j->second));

                double ease_in_ = 0.01 * std::min(time, 100ul);
                ease_in_ *= ease_in_;

                auto m = boost::accumulators::mean(estimates);
                m = std::max(-0.5, std::min(0.5, std::pow(1. + ease_in_ * m, 252.)-1));

                double x = quoted_price_ * adept::value(variable_);
                auto update_value_ = ease_in_ * ((x / double(past_price)) - 1 + dividend_rate/ x);

                const_cast<decltype(estimates) &>( estimates)(update_value_);

                auto sigma = std::sqrt(boost::accumulators::variance(estimates));
                sigma = std::max(0.001, sigma);
                sigma *= std::sqrt(252);

                // fractional kelly
                double c = ease_in_;
                double r = 0.01;  //   annual
//
//                std::cout << std::setprecision(6) << "price=" << x
//                          << " signal " << signal
//                          << " mu " << mu
//                          << " sigma " << sigma
//                          << " d " << dividend_rate
//                          << " t " << time << std::endl;

                auto lambda_ = 0.9 * 2;
                result_.emplace(k, scale_ * (lambda_ / (1. + adept::exp( - (c * ((m*time + (ease_in_ * ((quoted_price_ * variable_) / double(past_price) - 1 + dividend_rate/ (quoted_price_ * variable_))))/(time+1) - r) / (sigma*sigma)) )) - lambda_/2 + 0.5)
                                       - (supply_long_ - supply_short_) * (quoted_price_ * variable_)
                );
            }
        }
    }
    return result_;
}


/*!
 * @file swp_surfvol_primary.cpp
 * @author Matthew Celnik (msc37)
 *
  Author(s):      Matthew Celnik (msc37)
  Project:        sweepc (population balance solver)
  Sourceforge:    http://sourceforge.net/projects/mopssuite

  Copyright (C) 2008 Matthew S Celnik.

  File purpose:
    Implementation of the SurfVolPrimary class declared in the
    swp_surfvol_primary.h header file.
    @brief Implementation of particle described by its volume and surface area.

  Licence:
    This file is part of "sweepc".

    sweepc is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  Contact:
    Dr Markus Kraft
    Dept of Chemical Engineering
    University of Cambridge
    New Museums Site
    Pembroke Street
    Cambridge
    CB2 3RA
    UK

    Email:       mk306@cam.ac.uk
    Website:     http://como.cheng.cam.ac.uk
*/

#include "swp_primary.h"
#include "swp_surfvol_primary.h"
#include "swp_aggmodel_type.h"
#include "swp_model_factory.h"

#include <stdexcept>
#include <boost/random/poisson_distribution.hpp>

using namespace Sweep;
using namespace Sweep::AggModels;

using namespace std;

// CONSTRUCTORS AND DESTRUCTORS.

// Default constructor (protected).
SurfVolPrimary::SurfVolPrimary(void)
: m_sphsurf(0.0)
{
}

// Initialising constructor.
SurfVolPrimary::SurfVolPrimary(double time, const Sweep::ParticleModel &model)
: Primary(time, model), m_sphsurf(0.0)
{
}

// Copy constructor.
SurfVolPrimary::SurfVolPrimary(const SurfVolPrimary &copy)
{
    *this = copy;
}

// Stream-reading constructor.
SurfVolPrimary::SurfVolPrimary(std::istream &in, const Sweep::ParticleModel &model)
{
    Deserialize(in, model);
}

// Default destructor.
SurfVolPrimary::~SurfVolPrimary()
{
    releaseMem();
}

// OPERATOR OVERLOADS.

// Assignment operator (Primary RHS).
SurfVolPrimary &SurfVolPrimary::operator=(const Primary &rhs)
{
    // Now check the type of the RHS to see if additional data
    // copying is required.
    if (rhs.AggID() == Spherical_ID) {
        Primary::operator=(rhs);
        // The RHS is a spherical primary, therefore the
        // surface area is equal to the equiv. sphere
        // surface.  We must assign this separately because the
        // copyTo() implementation for a spherical primary is not
        // aware of this variable.
        m_sphsurf = rhs.SurfaceArea();
    } else {
        // Attempt to cast the primary to a SurfVolPrimary.  This will
        // throw an exception if the cast fails.
        operator=(dynamic_cast<const SurfVolPrimary&>(rhs));
    }

    return *this;
}

// Assignment operator (SurfVolPrimary RHS).
SurfVolPrimary &SurfVolPrimary::operator=(const SurfVolPrimary &rhs)
{
    // First copy everything for spherical primaries.
    Primary::operator=(rhs);

    // Now set surface-volume specific variables.
    m_sphsurf = rhs.m_sphsurf;

    return *this;
}

// AGGREGATION MODEL.

// Returns the aggregation model which this primary describes.
AggModels::AggModelType SurfVolPrimary::AggID(void) const {return AggModels::SurfVol_ID;}

// BASIC DERIVED PARTICLE PROPERTIES.

// Calculates the derived properties from the unique properties.  This
// function is broadly similar to the version in the spherical Primary
// class except that it uses the surface-volume model.  Therefore the
// surface area is not altered and the collision diameter is calculated
// using the arithmetic mean function.
//
// Mobility diameter:
// This calculation is based on the work of Rogak et al., 1993 Aer. Sci.
// Tech. 18:25-47, who give the calculation of dmob in the FM, SF and
// transition regime.
void SurfVolPrimary::UpdateCache(void)
{
    // Store the correct surface area.
    double s = m_surf;

    // Pretend that the primary is spherical and set the cache
    // accordingly.
    Primary::UpdateCache();

    // The surface area is now set to that of a sphere. Set
    // correct surface area (minimum is spherical surface area).
    m_sphsurf = m_surf;
    m_surf = max(s, m_sphsurf);

    // Calculate diameters.
    m_dcol = (m_diam + sqrt(m_surf / PI)) * 0.5;

    // Calculate mobility diameter
    m_dmob = PP_Diameter();
    if (false) {
        // SF regime mobility diameter
        m_dmob *= 0.9 * sqrt(m_pmodel->GetFractDim() / (m_pmodel->GetFractDim() + 2));
        m_dmob *= pow(PP_Count(), (1.0/m_pmodel->GetFractDim()));
    } else {
        // FM regime mobility diameter
        m_dmob *= sqrt(0.802*(PP_Count()-1) + 1);
    }

    if (m_dmob < m_diam) m_dmob = m_diam;
}

// Returns the equivalent spherical particle surface area.
double SurfVolPrimary::SphSurfaceArea(void) const {return m_sphsurf;}

// Returns the number of primary particles if the aggregate is assumed
// to consist of mono-sized primaries.
unsigned int SurfVolPrimary::PP_Count(void) const
{
    // Note the minimum number of primary particles must be 1.
    return max(1u, (unsigned int)((m_surf * m_surf * m_surf) /
                                  (36.0 * PI * m_vol * m_vol)));
}

// Returns the primary particle diameter if the aggregate is assumed
// to consist of mono-sized primaries.
double SurfVolPrimary::PP_Diameter(void) const
{
    // This should always be <= equiv. sphere diameter.
    return 6.0 * m_vol / m_surf;
}

// OPERATIONS.

// Adjusts the primary with the given composition and
// tracker values changes n times.  If the particle cannot be adjust
// n times, then this function returns the number of times
// it was adjusted.
unsigned int SurfVolPrimary::Adjust(const fvector &dcomp, const fvector &dvalues, rng_type &rng,
                                    unsigned int n)
{
    // Store initial surface and volume
    double surfOld = m_surf;
    double volOld  = m_vol;

    // Adjust the particle assuming that it is spherical.
    n = Primary::Adjust(dcomp, dvalues, rng, n);

    // Calculate change in volume.
    double dvol = 0.0;
    for (unsigned int i=0; i!=dcomp.size(); ++i) {
        dvol += dcomp[i] * m_pmodel->Components(i)->MolWt() /
                m_pmodel->Components(i)->Density();
    }
    dvol *= (double)n / NA;

    // Calculate change in surface area.
    double invRadius = 0.0;
    if (dvol > 0.0) {
        // Inverse growth radius.
        invRadius = sqrt(4.0 * PI / surfOld);
    } else {
        // Inverse oxidation radius.
        invRadius = surfOld / (3.0 * volOld);
    }

    // Save new surface area.
    double s = surfOld + (2.0 * dvol * invRadius);

    // Set correct surface area, which was incorrectly set by
    // Primary::Adjust.
    m_sphsurf = m_surf;
    m_surf    = max(s, m_sphsurf);

    // This has a knock-on affect of changing the collision diameter.
    // Note, we can avoid recalling UpdateCache() here, because only
    // a couple of cached values will have changed.
    m_dcol = (m_diam + sqrt(m_surf / PI)) * 0.5;
    m_dmob = m_dcol;

    return n;
}

/*!
 * Combines this primary with another.
 *
 * \param[in]       rhs         Particle to add to current instance
 * \param[in,out]   rng         Random number generator
 *
 * \return      Reference to the current instance after rhs has been added
 */
SurfVolPrimary &SurfVolPrimary::Coagulate(const Primary &rhs, rng_type &rng)

{
    // Store the resultant surface area.
    double s = m_surf + rhs.SurfaceArea();

    // Perform the coagulation.
    Primary::Coagulate(rhs, rng);

    // The spherical particle Coagulate() routine has set the
    // surface area incorrectly.  We now replace the surface area
    // to the correct point-contact value.
    m_sphsurf = m_surf;
    m_surf    = max(m_sphsurf, s);

    // This has a knock-on affect of changing the collision diameter.
    // Note, we can avoid recalling UpdateCache() here, because only
    // a couple of cached values will have changed.
    m_dcol = (m_diam + sqrt(m_surf / PI)) * 0.5;
    m_dmob = m_dcol;

    return *this;
}

/*!
 * Combines this primary with another.
 *
 * \param[in]       rhs         Particle to add to current instance
 * \param[in,out]   rng         Random number generator
 *
 * \return      Reference to the current instance after rhs has been added
 */
SurfVolPrimary &SurfVolPrimary::Fragment(const Primary &rhs, rng_type &rng)

{
    // Store the resultant surface area.
    double s = m_surf + rhs.SurfaceArea();

    // Perform the coagulation.
    Primary::Fragment(rhs, rng);

    // The spherical particle Coagulate() routine has set the
    // surface area incorrectly.  We now replace the surface area
    // to the correct point-contact value.
    m_sphsurf = m_surf;
    m_surf    = max(m_sphsurf, s);

    // This has a knock-on affect of changing the collision diameter.
    // Note, we can avoid recalling UpdateCache() here, because only
    // a couple of cached values will have changed.
    m_dcol = (m_diam + sqrt(m_surf / PI)) * 0.5;
    m_dmob = m_dcol;

    return *this;
}

// This routine sinters the Primary for the given length of
// time using the provided sintering model.
void SurfVolPrimary::Sinter(double dt, Cell &sys,
                            const Processes::SinteringModel &model,
                            rng_type &rng,
                            double wt)
{
    // Perform a first order integration method to sinter
    // the primary for the given time.

    // Declare time step variables.
    double t1=0.0, delt=0.0, tstop=dt;
    double r=0.0;

    // Define the maximum allowed change in surface
    // area in one internal time step (10% spherical surface).
    double dAmax = 0.1 * m_sphsurf;

    // The scale parameter discretises the delta-S when using
    // the Poisson distribution.  This allows a smoother change
    // (smaller scale = higher precision).
    double scale = 0.01;

    // Perform integration loop.
    while (t1 < tstop) {
        // Calculate sintering rate.
        r = model.Rate(m_time+t1, sys, *this);

        if(r > 0) {
            // Calculate next time-step end point so that the
            // surface area changes by no more than dAmax.
            delt = dAmax / max(r, 1.0e-300);

            // Approximate sintering by a poisson process.  Calculate
            // number of poisson events.
            double mean;
            if (tstop > (t1+delt)) {
                // A sub-step, we have changed surface by dAmax, on average
                mean = 1.0 / scale;
            } else {
                // Step until end.  Calculate degree of sintering explicitly.
                mean = r * (tstop - t1) / (scale*dAmax);
            }
            boost::random::poisson_distribution<unsigned, double> repeatDistribution(mean);
            const unsigned n = repeatDistribution(rng);

            // Adjust the surface area.
            if (n > 0) {
                m_surf -= n * scale * dAmax;
                // Check that primary is not completely sintered.
                if (m_surf <= m_sphsurf) {
                    m_surf = m_sphsurf;
                    break;
                }
            }

            // Set t1 for next time step.
            t1 += delt;
        }
        else {
            // No sintering is happening.
            // This may need refining so that a step in which nothing is happening
            // cannot be too long.
            break;
        }
    }
}

// READ/WRITE/COPY.

// Returns a copy of the model data.
SurfVolPrimary *const SurfVolPrimary::Clone(void) const
{
    return new SurfVolPrimary(*this);
}

// Writes the object to a binary stream.
void SurfVolPrimary::Serialize(std::ostream &out) const
{
    if (out.good()) {
        // Output the version ID (=0 at the moment).
        const unsigned int version = 0;
        out.write((char*)&version, sizeof(version));

        // Output base class.
        Primary::Serialize(out);

        // Write spherical surface area.
        double val = (double)m_sphsurf;
        out.write((char*)&val, sizeof(val));
    } else {
        throw invalid_argument("Output stream not ready "
                               "(Sweep, SurfVolPrimary::Serialize).");
    }
}

// Reads the object from a binary stream.
void SurfVolPrimary::Deserialize(std::istream &in, const Sweep::ParticleModel &model)
{
    if (in.good()) {
        // Read the output version.  Currently there is only one
        // output version, so we don't do anything with this variable.
        // Still needs to be read though.
        unsigned int version = 0;
        in.read(reinterpret_cast<char*>(&version), sizeof(version));

        double val = 0.0;

        switch (version) {
            case 0:
                // Read base class.
                Primary::Deserialize(in, model);

                // Read spherical surface area.
                in.read(reinterpret_cast<char*>(&val), sizeof(val));
                m_sphsurf = (double)val;

                break;
            default:
                throw runtime_error("Serialized version number is invalid "
                                    "(Sweep, SurfVolPrimary::Deserialize).");
        }
    } else {
        throw invalid_argument("Input stream not ready "
                               "(Sweep, SurfVolPrimary::Deserialize).");
    }
}