// Copyright (c) 2015-2021 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "variational_bayes_mixture_mixture_model.hpp"

#include <utility>
#include <cmath>
#include <iterator>
#include <algorithm>
#include <numeric>
#include <iostream>
#include <limits>

#include <boost/math/special_functions/digamma.hpp>

#include "utils/maths.hpp"

namespace octopus { namespace model {

VariationalBayesMixtureMixtureModel::VariationalBayesMixtureMixtureModel(Options options)
: options_ {std::move(options)}
{}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const GroupOptionalPriorArray& group_priors,
                                              const GroupConcentrationVector& group_concentrations,
                                              const MixtureConcentrationArray& mixture_concentrations,
                                              std::vector<LogProbabilityVector> seeds) const
{
    const auto group_log_priors = to_logs(group_priors);
    const auto expanded_log_likelihoods = expand(log_likelihoods);
    const auto evaluate_seed = [&] (auto&& seed) {
        return this->evaluate(genotype_log_priors, log_likelihoods, expanded_log_likelihoods, group_log_priors, group_concentrations, mixture_concentrations, std::move(seed)); };
    std::vector<PointInferences> seed_inferences(seeds.size());
    if (options_.parallel_execution) {
        parallel_transform(std::make_move_iterator(std::begin(seeds)), std::make_move_iterator(std::end(seeds)), std::begin(seed_inferences), evaluate_seed);
    } else {
        std::transform(std::make_move_iterator(std::begin(seeds)), std::make_move_iterator(std::end(seeds)), std::begin(seed_inferences), evaluate_seed);
    }
    Inferences result {};
    compute_evidence_weighted_latents(result.weighted_genotype_posteriors, result.weighted_group_responsibilities, seed_inferences);
    const static auto evidence_less = [] (const auto& lhs, const auto& rhs) { return lhs.approx_log_evidence < rhs.approx_log_evidence; };
    result.map = *std::max_element(std::begin(seed_inferences), std::end(seed_inferences), evidence_less);
    return result;
}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const GroupConcentrationVector& group_concentrations,
                                              const MixtureConcentrationArray& mixture_concentrations,
                                              std::vector<LogProbabilityVector> seeds) const
{
    const auto num_samples = log_likelihoods.size();
    const GroupOptionalPriorArray no_priors(num_samples);
    return evaluate(genotype_log_priors, log_likelihoods, no_priors, group_concentrations, mixture_concentrations, std::move(seeds));
}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const GroupOptionalPriorArray& group_priors,
                                              const double group_concentration,
                                              const std::vector<double>& mixture_concentrations,
                                              std::vector<LogProbabilityVector> seeds) const
{
    const auto num_samples = log_likelihoods.size();
    assert(mixture_concentrations.size() == num_samples);
    const auto num_groups = log_likelihoods.front().front().size();
    std::vector<unsigned> group_mixture_sizes {};
    group_mixture_sizes.reserve(num_groups);
    for (const auto& genotype : log_likelihoods.front().front()) {
        group_mixture_sizes.push_back(genotype.size());
    }
    const GroupConcentrationVector group_concentrations(num_groups, group_concentration);
    MixtureConcentrationArray group_mixture_concentrations {};
    group_mixture_concentrations.reserve(num_samples);
    for (auto sample_concentration : mixture_concentrations) {
        group_mixture_concentrations.emplace_back(num_groups, ComponentConcentrationVector(sample_concentration));
        unsigned t {0};
        for (auto& sample_group_sample_concentrations : group_mixture_concentrations.back()) {
            sample_group_sample_concentrations.assign(group_mixture_sizes[t++], sample_concentration);
        }
    }
    return evaluate(genotype_log_priors, log_likelihoods, group_priors, group_concentrations, group_mixture_concentrations, std::move(seeds));
}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const double group_concentration,
                                              const std::vector<double>& mixture_concentrations,
                                              std::vector<LogProbabilityVector> seeds) const
{
    const auto num_samples = log_likelihoods.size();
    const GroupOptionalPriorArray no_priors(num_samples);
    return evaluate(genotype_log_priors, log_likelihoods, no_priors, group_concentration, mixture_concentrations, std::move(seeds));
}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const GroupOptionalPriorArray& group_priors,
                                              const double group_concentration,
                                              const double mixture_concentration,
                                              std::vector<LogProbabilityVector> seeds) const
{
    const auto num_samples = log_likelihoods.size();
    const auto num_groups = log_likelihoods.front().front().size();
    std::vector<unsigned> group_mixture_sizes {};
    group_mixture_sizes.reserve(num_groups);
    for (const auto& genotype : log_likelihoods.front().front()) {
        group_mixture_sizes.push_back(genotype.size());
    }
    const GroupConcentrationVector group_concentrations(num_groups, group_concentration);
    MixtureConcentrationArray mixture_concentrations(num_samples, MixtureConcentrationVector(num_groups));
    for (auto& sample_concentrations : mixture_concentrations) {
        unsigned t {0};
        for (auto& sample_group_sample_concentrations : sample_concentrations) {
            sample_group_sample_concentrations.assign(group_mixture_sizes[t++], mixture_concentration);
        }
    }
    return evaluate(genotype_log_priors, log_likelihoods, group_priors, group_concentrations, mixture_concentrations, std::move(seeds));
}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const double group_concentration,
                                              const double mixture_concentration,
                                              std::vector<LogProbabilityVector> seeds) const
{
    const auto num_samples = log_likelihoods.size();
    const GroupOptionalPriorArray no_priors(num_samples);
    return evaluate(genotype_log_priors, log_likelihoods, no_priors, group_concentration, mixture_concentration, std::move(seeds));
}

// Private methods

VariationalBayesMixtureMixtureModel::GroupOptionalLogPriorVector
VariationalBayesMixtureMixtureModel::to_logs(const GroupOptionalPriorVector& prior) const
{
    GroupOptionalLogPriorVector result {};
    if (prior) {
        result = LogProbabilityVector(prior->size());
        const static auto to_log = [] (auto p) noexcept {
            const static auto min_log_prior = std::log(std::numeric_limits<LogProbability>::min());
            return p > 0 ? std::log(p) : min_log_prior;
        };
        std::transform(std::cbegin(*prior), std::cend(*prior), std::begin(*result), to_log);
    }
    return result;
}

VariationalBayesMixtureMixtureModel::GroupOptionalLogPriorArray
VariationalBayesMixtureMixtureModel::to_logs(const GroupOptionalPriorArray& priors) const
{
    GroupOptionalLogPriorArray result {};
    result.reserve(priors.size());
    std::transform(std::cbegin(priors), std::cend(priors), std::back_inserter(result),
                   [this] (const auto& sample_priors) { return to_logs(sample_priors); });
    return result;
}

VariationalBayesMixtureMixtureModel::ExpandedHaplotypeLikelihoodMatrix
VariationalBayesMixtureMixtureModel::expand(const HaplotypeLikelihoodMatrix& likelihoods) const
{
    const auto S = likelihoods.size();
    const auto G = likelihoods[0].size();
    const auto T = likelihoods[0][0].size();
    ExpandedHaplotypeLikelihoodMatrix result(S, ExpandedGroupLikelihoodVector(T));
    for (std::size_t s {0}; s < S; ++s) {
        const auto N = likelihoods[s][0][0][0].size();
        for (std::size_t t {0}; t < T; ++t) {
            const auto K = likelihoods[0][0][t].size();
            result[s][t].assign(K, ExpandedGenotype(N, ExpandedLikelihood(G)));
            for (std::size_t k {0}; k < K; ++k) {
                for (std::size_t g {0}; g < G; ++g) {
                    for (std::size_t n {0}; n < N; ++n) {
                        result[s][t][k][n][g] = likelihoods[s][g][t][k][n];
                    }
                }
            }
        }
    }
    return result;
}

namespace {

VariationalBayesMixtureMixtureModel::ProbabilityVector&
exp(const LogProbabilityVector& log_probabilities, ProbabilityVector& result) noexcept
{
    std::transform(std::cbegin(log_probabilities), std::cend(log_probabilities), std::begin(result),
                   [] (const auto lp) noexcept { return std::exp(lp); });
    return result;
}

auto exp(const VariationalBayesMixtureMixtureModel::LogProbabilityVector& log_probabilities)
{
    VariationalBayesMixtureMixtureModel::ProbabilityVector result(log_probabilities.size());
    return exp(log_probabilities, result);
}

template <typename T>
auto sum(const std::vector<T>& values) noexcept
{
    return std::accumulate(std::cbegin(values), std::cend(values), T {0});
}

auto sum(const VBReadLikelihoodArray& likelihoods) noexcept
{
    using T = VBReadLikelihoodArray::BaseType::value_type;
    return std::accumulate(std::cbegin(likelihoods), std::cend(likelihoods), T {0});
}

template <typename T1, typename T2>
auto inner_product(const T1& lhs, const T2& rhs) noexcept
{
    assert(std::distance(std::cbegin(lhs), std::cend(lhs)) == std::distance(std::cbegin(rhs), std::cend(rhs)));
    using T = typename T1::value_type;
    return std::inner_product(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), T {0});
}

template <typename T>
auto dirichlet_expectation_log(const std::vector<T>& concentrations)
{
    std::vector<T> result(concentrations.size());
    if (concentrations.size() > 1) {
        using boost::math::digamma;
        const auto digamma_alpha_0 = digamma(sum(concentrations));
        std::transform(std::cbegin(concentrations), std::cend(concentrations), std::begin(result),
                       [=] (auto alpha) { return digamma(alpha) - digamma_alpha_0; });
    }
    return result;
}

template <typename T>
T log_sum_exp(const std::vector<T>& logs)
{
    return maths::log_sum_exp(logs);
}

bool all_equal_sizes(const VariationalBayesMixtureMixtureModel::MixtureConcentrationVector& concentrations) noexcept
{
    const auto size_unequal = [] (const auto& lhs, const auto& rhs) { return lhs.size() != rhs.size(); };
    return std::adjacent_find(std::cbegin(concentrations), std::end(concentrations), size_unequal) == std::cend(concentrations);
}

} // namespace

VariationalBayesMixtureMixtureModel::PointInferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const ExpandedHaplotypeLikelihoodMatrix& expanded_log_likelihoods,
                                              const GroupOptionalLogPriorArray& group_log_priors,
                                              const GroupConcentrationVector& prior_group_concentrations,
                                              const MixtureConcentrationArray& prior_mixture_concentrations,
                                              LogProbabilityVector genotype_log_posteriors) const
{
    Latents latents {};
    latents.genotype_log_posteriors = std::move(genotype_log_posteriors);
    latents.genotype_posteriors = exp(latents.genotype_log_posteriors);
    latents.group_concentrations = prior_group_concentrations;
    latents.mixture_concentrations = prior_mixture_concentrations;
    latents.group_responsibilities = init_responsibilities(group_log_priors, prior_group_concentrations, prior_mixture_concentrations,
                                                           latents.genotype_posteriors, log_likelihoods);
    latents.component_responsibilities = init_responsibilities(prior_group_concentrations, prior_mixture_concentrations,
                                                               latents.genotype_posteriors, latents.group_responsibilities, expanded_log_likelihoods);
    auto prev_evidence = std::numeric_limits<double>::lowest();
    for (unsigned i {0}; i < options_.max_iterations; ++i) {
        update_genotype_log_posteriors(latents.genotype_log_posteriors, genotype_log_priors,
                                       latents.group_responsibilities, latents.component_responsibilities,
                                       log_likelihoods);
        exp(latents.genotype_log_posteriors, latents.genotype_posteriors);
        update_group_concentrations(latents.group_concentrations, prior_group_concentrations, latents.group_responsibilities);
        update_mixture_concentrations(latents.mixture_concentrations, prior_mixture_concentrations, latents.component_responsibilities);
        auto curr_evidence = calculate_evidence(prior_group_concentrations, latents.group_concentrations,
                                                prior_mixture_concentrations, latents.mixture_concentrations,
                                                genotype_log_priors, latents.genotype_log_posteriors, latents.genotype_posteriors,
                                                group_log_priors, latents.group_responsibilities, latents.component_responsibilities,
                                                log_likelihoods);
//        assert(curr_evidence + options_.epsilon >= prev_evidence);
        if (curr_evidence <= prev_evidence || (curr_evidence - prev_evidence) < options_.epsilon) {
            prev_evidence = curr_evidence;
            break;
        }
        prev_evidence = curr_evidence;
        update_responsibilities(latents.group_responsibilities, group_log_priors,latents.group_concentrations,
                                latents.mixture_concentrations, latents.genotype_posteriors,
                                latents.component_responsibilities, log_likelihoods);
        update_responsibilities(latents.component_responsibilities, latents.group_concentrations, latents.mixture_concentrations,
                                latents.genotype_posteriors, latents.group_responsibilities, expanded_log_likelihoods);
    }
    return {std::move(latents), prev_evidence};
}

namespace {

template <typename ForwardIterator>
std::size_t max_element_index(ForwardIterator first, ForwardIterator last)
{
    return std::distance(first, std::max_element(first, last));
}

template <typename Range>
std::size_t max_element_index(const Range& values)
{
    return max_element_index(std::cbegin(values), std::cend(values));
}

} // namespace

VariationalBayesMixtureMixtureModel::GroupResponsibilityVector
VariationalBayesMixtureMixtureModel::init_responsibilities(const GroupOptionalLogPriorArray& group_log_priors,
                                                           const GroupConcentrationVector& group_concentrations,
                                                           const MixtureConcentrationArray& mixture_concentrations,
                                                           const ProbabilityVector& genotype_priors,
                                                           const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto S = log_likelihoods.size();
    const auto T = group_concentrations.size();
    GroupResponsibilityVector result(S, Sigma(T));
    const auto G = genotype_priors.size();
    const auto ln_ex_psi = dirichlet_expectation_log(group_concentrations);
    std::size_t max_K {0};
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            max_K = std::max(max_K, mixture_concentrations[s][t].size());
        }
    }
    const auto max_genotype_prior_idx = max_element_index(genotype_priors);
    ComponentResponsibilityVector approx_taus(max_K);
    for (std::size_t s {0}; s < S; ++s) {
        const auto N = log_likelihoods[s][0][0][0].size(); // num reads
        for (auto& tau : approx_taus) tau.resize(N);
        for (std::size_t t {0}; t < T; ++t) {
            result[s][t] = ln_ex_psi[t];
            if (group_log_priors[s]) result[s][t] += (*group_log_priors[s])[t];
            const auto ln_ex_pi = dirichlet_expectation_log(mixture_concentrations[s][t]);
            const auto K = mixture_concentrations[s][t].size();
            // Approximate tau assuming p(t) = 1
            Tau approx_tau(K);
            for (std::size_t n {0}; n < N; ++n) {
                for (std::size_t k {0}; k < K; ++k) {
                    approx_tau[k] = ln_ex_pi[k] + log_likelihoods[s][max_genotype_prior_idx][t][k][n];
                }
                maths::normalise_exp(approx_tau);
                for (std::size_t k {0}; k < K; ++k) {
                    approx_taus[k][n] = approx_tau[k];
                }
            }
            for (std::size_t k {0}; k < K; ++k) {
                for (std::size_t g {0}; g < G; ++g) {
                    result[s][t] += genotype_priors[g] * inner_product(approx_taus[k], log_likelihoods[s][g][t][k]);
                }
            }
        }
        maths::normalise_exp(result[s]);
    }
    return result;
}

void
VariationalBayesMixtureMixtureModel::update_responsibilities(GroupResponsibilityVector& result,
                                                             const GroupOptionalLogPriorArray& group_log_priors,
                                                             const GroupConcentrationVector& group_concentrations,
                                                             const MixtureConcentrationArray& mixture_concentrations,
                                                             const ProbabilityVector& genotype_posteriors,
                                                             const ComponentResponsibilityMatrix& component_responsibilities,
                                                             const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto T = group_concentrations.size();
    const auto S = log_likelihoods.size();
    const auto G = genotype_posteriors.size();
    const auto ln_ex_psi = dirichlet_expectation_log(group_concentrations);
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            const auto max_K = component_responsibilities[s][t].size();
            result[s][t] = ln_ex_psi[t];
            if (group_log_priors[s]) result[s][t] += (*group_log_priors[s])[t];
            const auto ln_ex_pi = dirichlet_expectation_log(mixture_concentrations[s][t]);
            const auto K = ln_ex_pi.size();
            result[s][t] += sum(ln_ex_pi);
            for (std::size_t k {0}; k < max_K; ++k) {
                for (std::size_t g {0}; g < G; ++g) {
                    if (k < K) {
                        result[s][t] += genotype_posteriors[g] * inner_product(component_responsibilities[s][t][k], log_likelihoods[s][g][t][k]);
                    } else {
                        result[s][t] += genotype_posteriors[g] * inner_product(component_responsibilities[s][t][k], log_likelihoods[s][g][t][0]);
                    }
                }
            }
        }
        maths::normalise_exp(result[s]);
    }
}

VariationalBayesMixtureMixtureModel::ComponentResponsibilityMatrix
VariationalBayesMixtureMixtureModel::init_responsibilities(const GroupConcentrationVector& group_concentrations,
                                                           const MixtureConcentrationArray& mixture_concentrations,
                                                           const ProbabilityVector& genotype_priors,
                                                           const GroupResponsibilityVector& group_responsibilities,
                                                           const ExpandedHaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto S = log_likelihoods.size();
    const auto T = group_concentrations.size();
    std::size_t K {0};
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            K = std::max(K, mixture_concentrations[s][t].size());
        }
    }
    ComponentResponsibilityMatrix result(S, ComponentResponsibilityVectorArray(T, ComponentResponsibilityVector(K)));
    for (std::size_t s {0}; s < S; ++s) {
        const auto N = log_likelihoods[s][0][0].size();
        for (std::size_t t {0}; t < T; ++t) {
            for (std::size_t k {0}; k < K; ++k) {
                result[s][t][k].resize(N);
            }
        }
    }
    update_responsibilities(result, group_concentrations, mixture_concentrations,
                            genotype_priors, group_responsibilities, log_likelihoods);
    return result;
}

namespace {

auto inner_product(const VariationalBayesMixtureMixtureModel::ProbabilityVector& genotype_posteriors,
                   const VariationalBayesMixtureMixtureModel::GenotypeCombinationLikelihoodVector& log_likelihoods,
                   const std::size_t t, const std::size_t k, const std::size_t n) noexcept
{
    ProbabilityVector::value_type result {0};
    const auto G = genotype_posteriors.size();
    for (std::size_t g {0}; g < G; ++g) {
        result += genotype_posteriors[g] * log_likelihoods[g][t][k][n];
    }
    return result;
}

} // namespace

void
VariationalBayesMixtureMixtureModel::update_responsibilities(ComponentResponsibilityMatrix& result,
                                                             const GroupConcentrationVector& group_concentrations,
                                                             const MixtureConcentrationArray& mixture_concentrations,
                                                             const ProbabilityVector& genotype_posteriors,
                                                             const GroupResponsibilityVector& group_responsibilities,
                                                             const ExpandedHaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto T = group_concentrations.size();
    const auto S = log_likelihoods.size();
    const auto max_K = result[0][0].size();
    std::vector<float> float_genotype_posteriors {std::cbegin(genotype_posteriors), std::cend(genotype_posteriors)};
    for (std::size_t s {0}; s < S; ++s) {
        const auto N = log_likelihoods[s][0][0].size();
        for (std::size_t t {0}; t < T; ++t) {
            const auto ln_exp_pi = dirichlet_expectation_log(mixture_concentrations[s][t]);
            const auto K = ln_exp_pi.size();
            for (std::size_t k {0}; k < max_K; ++k) {
                for (std::size_t n {0}; n < N; ++n) {
                    if (t == 0) result[s][t][k][n] = 0;
                    if (k < K) {
                        //result[s][t][k][n] += ln_exp_pi[k] + inner_product(genotype_posteriors, log_likelihoods[s], t, k, n);
                        result[s][t][k][n] += ln_exp_pi[k] + inner_product(float_genotype_posteriors, log_likelihoods[s][t][k][n]);
                    } else {
                        result[s][t][k][n] = options_.null_log_probability;
                    }
                }
            }
            std::vector<double> ln_rho(max_K);
            for (std::size_t n {0}; n < N; ++n) {
                for (std::size_t k {0}; k < max_K; ++k) {
                    ln_rho[k] = result[s][t][k][n];
                }
                const auto ln_rho_norm = log_sum_exp(ln_rho);
                for (std::size_t k {0}; k < max_K; ++k) {
                    result[s][t][k][n] = std::exp(ln_rho[k] - ln_rho_norm);
                }
            }
        }
    }
}

void
VariationalBayesMixtureMixtureModel::update_genotype_log_posteriors(LogProbabilityVector& result,
                                                                    const LogProbabilityVector& genotype_log_priors,
                                                                    const GroupResponsibilityVector& group_responsibilities,
                                                                    const ComponentResponsibilityMatrix& component_responsibilities,
                                                                    const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    for (std::size_t g {0}; g < genotype_log_priors.size(); ++g) {
        result[g] = genotype_log_priors[g] + marginalise(group_responsibilities, component_responsibilities, log_likelihoods, g);
    }
    maths::normalise_logs(result);
}

VariationalBayesMixtureMixtureModel::LogProbability
VariationalBayesMixtureMixtureModel::marginalise(const GroupResponsibilityVector& group_responsibilities,
                                                 const ComponentResponsibilityMatrix& component_responsibilities,
                                                 const HaplotypeLikelihoodMatrix& log_likelihoods,
                                                 const std::size_t g) const noexcept
{
    const auto T = group_responsibilities.front().size();
    const auto S = component_responsibilities.size();
    LogProbability result {0};
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            result += group_responsibilities[s][t] * marginalise(component_responsibilities[s][t], log_likelihoods[s][g][t]);
        }
    }
    return result;
}

VariationalBayesMixtureMixtureModel::LogProbability
VariationalBayesMixtureMixtureModel::marginalise(const ComponentResponsibilityVector& responsibilities,
                                                 const HaplotypeLikelihoodVector& log_likelihoods) const noexcept
{
    const auto K = log_likelihoods.size();
    const auto max_K = responsibilities.size();
    assert(K <= max_K);
    LogProbability result {0};
    for (std::size_t k {0}; k < max_K; ++k) {
        if (k < K) {
            result += inner_product(responsibilities[k], log_likelihoods[k]);
        } else {
            result += inner_product(responsibilities[k], log_likelihoods[0]);
        }
    }
    return result;
}

void
VariationalBayesMixtureMixtureModel::update_group_concentrations(GroupConcentrationVector& result,
                                                                 const GroupConcentrationVector& prior_group_concentrations,
                                                                 const GroupResponsibilityVector& group_responsibilities) const
{
    assert(result.size() == prior_group_concentrations.size());
    const auto T = prior_group_concentrations.size();
    const auto S = group_responsibilities.size();
    for (std::size_t t {0}; t < T; ++t) {
        result[t] = prior_group_concentrations[t];
        for (std::size_t s {0}; s < S; ++s) {
            result[t] += group_responsibilities[s][t];
        }
    }
}

void
VariationalBayesMixtureMixtureModel::update_mixture_concentrations(MixtureConcentrationArray& result,
                                                                   const MixtureConcentrationArray& prior_mixture_concentrations,
                                                                   const ComponentResponsibilityMatrix& component_responsibilities) const
{
    const auto S = result.size();
    const auto T = result.front().size();
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            const auto K = prior_mixture_concentrations[s][t].size();
            for (std::size_t k {0}; k < K; ++k) {
                result[s][t][k] = prior_mixture_concentrations[s][t][k] + sum(component_responsibilities[s][t][k]);
            }
        }
    }
}

namespace {

template <typename T>
auto shannon_entropy(const std::vector<T>& probabilities) noexcept
{
    return -std::accumulate(std::cbegin(probabilities), std::cend(probabilities), T {0},
                            [] (const auto curr, const auto p) noexcept { return curr + (p > 0 ? p * std::log(p) : 0.0); });
}

template <typename T>
auto shannon_entropy(const std::vector<std::vector<T>>& probabilities) noexcept
{
    return std::accumulate(std::cbegin(probabilities), std::cend(probabilities), T {0},
                           [] (const auto curr, const auto& ps) noexcept { return curr + shannon_entropy(ps); });
}

} // namespace

double
VariationalBayesMixtureMixtureModel::calculate_evidence(const GroupConcentrationVector& prior_group_concentrations,
                                                        const GroupConcentrationVector& posterior_group_concentrations,
                                                        const MixtureConcentrationArray& prior_mixture_concentrations,
                                                        const MixtureConcentrationArray& posterior_mixture_concentrations,
                                                        const LogProbabilityVector& genotype_log_priors,
                                                        const LogProbabilityVector& genotype_log_posteriors,
                                                        const ProbabilityVector& genotype_posteriors,
                                                        const GroupOptionalPriorArray& group_log_priors,
                                                        const GroupResponsibilityVector& group_responsibilities,
                                                        const ComponentResponsibilityMatrix& component_responsibilities,
                                                        const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto G = genotype_log_priors.size();
    const auto S = prior_mixture_concentrations.size();
    const auto T = group_responsibilities.front().size();
    double result {0};
    for (std::size_t g {0}; g < G; ++g) {
        auto w = genotype_log_priors[g] - genotype_log_posteriors[g];
        for (std::size_t s {0}; s < S; ++s) {
            double ss {0};
            for (std::size_t t {0}; t < T; ++t) {
                ss += group_responsibilities[s][t] * marginalise(component_responsibilities[s][t], log_likelihoods[s][g][t]);
            }
            w += ss;
        }
        result += genotype_posteriors[g] * w;
    }
    for (std::size_t s {0}; s < S; ++s) {
        result += shannon_entropy(group_responsibilities[s]);
        for (std::size_t t {0}; t < T; ++t) {
            result += group_responsibilities[s][t] * shannon_entropy(component_responsibilities[s][t]);
            result += group_responsibilities[s][t] * (maths::log_beta(posterior_mixture_concentrations[s][t]) - maths::log_beta(prior_mixture_concentrations[s][t]));
        }
        if (group_log_priors[s]) {
            auto sigma_expected_ln_norm = maths::log_each_copy(group_responsibilities[s]);
            for (std::size_t t {0}; t < T; ++t) {
                sigma_expected_ln_norm[t] += (*group_log_priors[s])[t];
            }
            result += maths::log_sum_exp(sigma_expected_ln_norm);
        }
    }
    result += maths::log_beta(posterior_group_concentrations) - maths::log_beta(prior_group_concentrations);
    return result;
}

namespace {

template <typename Range>
void check_normalisation(Range& probabilities) noexcept
{
    const auto mass = std::accumulate(std::cbegin(probabilities), std::cend(probabilities), 0.0);
    if (mass > 1.0) for (auto& p : probabilities) p /= mass;
}

} // namespace

void
VariationalBayesMixtureMixtureModel::compute_evidence_weighted_latents(ProbabilityVector& genotype_posteriors,
                                                                       GroupResponsibilityVector& group_responsibilities,
                                                                       const std::vector<PointInferences>& latents) const
{
    assert(!latents.empty());
    const auto num_genotypes = latents.front().latents.genotype_posteriors.size();
    std::vector<std::size_t> map_genotypes(num_genotypes, latents.size());
    for (std::size_t i {0}; i < latents.size(); ++i) {
        const auto map_genotype_idx = max_element_index(latents[i].latents.genotype_posteriors);
        if (map_genotypes[map_genotype_idx] == latents.size()
         || latents[i].approx_log_evidence > latents[map_genotypes[map_genotype_idx]].approx_log_evidence) {
            map_genotypes[map_genotype_idx] = i;
        }
    }
    std::vector<std::size_t> modes {};
    modes.reserve(latents.size());
    for (std::size_t g {0}; g < num_genotypes; ++g) {
        if (map_genotypes[g] < latents.size()) {
            modes.push_back(map_genotypes[g]);
        }
    }
    std::vector<double> mode_weights(modes.size());
    std::transform(std::cbegin(modes), std::cend(modes), std::begin(mode_weights),
                   [&] (auto mode) { return latents[mode].approx_log_evidence; });
    maths::normalise_exp(mode_weights);
    genotype_posteriors.resize(num_genotypes);
    const auto num_samples = latents.front().latents.group_responsibilities.size();
    const auto num_groups = latents.front().latents.group_responsibilities.front().size();
    group_responsibilities.resize(num_samples, Sigma(num_groups));
    for (std::size_t i {0}; i < modes.size(); ++i) {
        const auto mode_weight = mode_weights[i];
        const auto& mode_genotype_posteriors = latents[modes[i]].latents.genotype_posteriors;
        std::transform(std::cbegin(mode_genotype_posteriors), std::cend(mode_genotype_posteriors),
                       std::cbegin(genotype_posteriors), std::begin(genotype_posteriors),
                       [mode_weight] (auto seed_posterior, auto curr_posterior) {
                           return curr_posterior + mode_weight * seed_posterior;
                       });
        const auto& mode_group_responsabilities = latents[modes[i]].latents.group_responsibilities;
        for (std::size_t s {0}; s < num_samples; ++s) {
            for (std::size_t t {0}; t < num_groups; ++t) {
                group_responsibilities[s][t] += mode_weight * mode_group_responsabilities[s][t];
            }
        }
    }
    check_normalisation(genotype_posteriors);
    for (auto& responsibilities : group_responsibilities)  check_normalisation(responsibilities);
}

void VariationalBayesMixtureMixtureModel::print_concentrations(const GroupConcentrationVector& concentrations) const
{
    for (std::size_t t {0}; t < concentrations.size(); ++t) {
        std::cout << "t: " << t << " = " << concentrations[t] << std::endl;
    }
}

void VariationalBayesMixtureMixtureModel::print_concentrations(const MixtureConcentrationArray& concentrations) const
{
    for (std::size_t s {0}; s < concentrations.size(); ++s) {
        for (std::size_t t {0}; t < concentrations[s].size(); ++t) {
            for (std::size_t k {0}; k < concentrations[s][t].size(); ++k) {
                std::cout << "s: " << s << " t: " << t << " k: " << k << " = " << concentrations[s][t][k] << std::endl;
            }
        }
    }
}

void VariationalBayesMixtureMixtureModel::print(const ProbabilityVector& probabilities) const
{
    for (std::size_t g {0}; g < probabilities.size(); ++g) {
        std::cout << "g: " << g << " = " << probabilities[g] << std::endl;
    }
}

void VariationalBayesMixtureMixtureModel::print(const GroupResponsibilityVector& responsibilities) const
{
    for (std::size_t s {0}; s < responsibilities.size(); ++s) {
        for (std::size_t t {0}; t < responsibilities[s].size(); ++t) {
            std::cout << "s: " << s << " t: " << t << " = " << responsibilities[s][t] << std::endl;
        }
    }
}

void VariationalBayesMixtureMixtureModel::print(const ComponentResponsibilityMatrix& responsibilities) const
{
    for (std::size_t s {0}; s < responsibilities.size(); ++s) {
        for (std::size_t t {0}; t < responsibilities[s].size(); ++t) {
            for (std::size_t k {0}; k < responsibilities[s].size(); ++k) {
                for (std::size_t n {0}; n < responsibilities[s][k].size(); ++n) {
                    std::cout << "s: " << s << " t: " << t <<  " k: " << k << " n: " << n << " = " << responsibilities[s][t][k][n] << std::endl;
                }
            }
        }
    }
}

void VariationalBayesMixtureMixtureModel::print(const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    for (std::size_t s {0}; s < log_likelihoods.size(); ++s) {
        for (std::size_t g {0}; g < log_likelihoods[s].size(); ++g) {
            for (std::size_t t {0}; t < log_likelihoods[s][g].size(); ++t) {
                for (std::size_t k {0}; k < log_likelihoods[s][g][t].size(); ++k) {
                    for (std::size_t n {0}; n < log_likelihoods[s][g][t][k].size(); ++n) {
                        std::cout << "s: " << s <<  " g: " << g << " t: " << t << " k: " << k
                                  << " n: " << n << " = " << log_likelihoods[s][g][t][k][n] << std::endl;
                    }
                }
            }
        }
    }
}

} // namespace model
} // namespace octopus


// BSD 3-Clause License
//
// Copyright (c) 2022, Map IV, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <height_converter/gsigeo.hpp>

#include <iostream>
#include <fstream>
#include <sstream>
#include <cmath>

#include <boost/algorithm/string.hpp>

namespace height_converter
{

std::vector<std::string> split(std::string input, char delimiter)
{
  std::istringstream stream(input);
  std::string field;
  std::vector<std::string> result;

  while (std::getline(stream, field, delimiter))
  {
    if (field != std::string(" ") && !field.empty() && field != "\n" && field != "\r")
      result.push_back(field);
  }
  return result;
}

GSIGEO2011::GSIGEO2011()
{
}

GSIGEO2011::~GSIGEO2011()
{
}

void GSIGEO2011::loadGeoidMap(const std::string& geoid_file)
{
  std::ifstream ifs(geoid_file);
  if (!ifs)
  {
    std::cerr << "Error: Cannot open Geoid data file: " << geoid_file << std::endl;
    exit(2);
  }

  geoid_map_.reserve(row_size_);
  for (int i = 0; i < row_size_; i++)
  {
    geoid_map_[i].reserve(column_size_);
  }

  std::string curr_line;
  // Skip header line
  std::getline(ifs, curr_line);

  int i = 0, j = 0;

  while (std::getline(ifs, curr_line))
  {
    std::vector<std::string> str_vec = split(curr_line, ' ');

    for (int k = 0; k < str_vec.size(); k++)
    {
      geoid_map_[i][j] = std::stod(str_vec[k]);
      j++;

    }
    if (j == column_size_)
    {
      j = 0;
      i++;
    }
  }
  is_geoid_loaded_ = true;
}

double GSIGEO2011::getGeoid(const double& lat, const double& lon)
{
  if (!is_geoid_loaded_)
  {
    std::cerr << "Error: Geoid map is not loaded" << std::endl;
    exit(1);
  }
  const double lat_min = 20;
  const double lon_min = 120;
  const double d_lat = 1.0 / 60.0;
  const double d_lon = 1.5 / 60.0;

  const int i_lat = std::floor((lat - lat_min) / d_lat);
  const int i_lon = std::floor((lon - lon_min) / d_lon);
  const int j_lat = i_lat + 1;
  const int j_lon = i_lon + 1;

  // const double t = (((lat - lat_min) / d_lat) - i_lat) / d_lat;
  // const double u = (((lon - lon_min) / d_lon) - i_lon) / d_lon;
  const double t = (lat - (lat_min + i_lat * d_lat)) / d_lat;
  const double u = (lon - (lon_min + i_lon * d_lon)) / d_lon;

  if (i_lat < 0 || i_lat >= row_size_ - 1 || i_lon < 0 || i_lon >= column_size_ - 1)
  {
    std::cerr << "Error: latitude/longitude is out of range (20~50, 120~150)" << std::endl;
    std::cerr << (lat, lon) << std::endl;
    exit(1);
  }

  if (geoid_map_[i_lat][i_lon] == 999 || geoid_map_[i_lat][j_lon] == 999 || geoid_map_[j_lat][i_lon] == 999 || geoid_map_[j_lat][j_lon] == 999)
  {
    std::cerr << "Error: Not supported area" << std::endl;
    exit(1);
  }

  double geoid = (1-t)*(1-u)*geoid_map_[i_lat][i_lon] + (1-t)*u*geoid_map_[i_lat][j_lon] + t*(1-u)*geoid_map_[j_lat][i_lon] + t*u*geoid_map_[j_lat][j_lon];

  return geoid;
}
}   // namespace height_converter


#include <algorithm>
#include <boost/optional.hpp>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <functional>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#define REP(i, n) for(int i = 0, i##_MACRO = (n); i < i##_MACRO; i++)
#define RANGE(i, a, b) for(int i = (a), i##_MACRO = (b); i < i##_MACRO; i++)
#define EACH(e, a) for(auto&& e : a)
#define ALL(a) std::begin(a), std::end(a)
#define RALL(a) std::rbegin(a), std::rend(a)
#define FILL(a, n) memset((a), n, sizeof(a))
#define FILLZ(a) FILL(a, 0)
#define INT(x) (static_cast<int>(x))

using namespace std;

using ll = long long;
using VI = vector<int>;
using VI2D = vector<vector<int>>;

constexpr int INF = 2e9;
constexpr double EPS = 1e-10;
constexpr double PI = acos(-1.0);

constexpr int dx[] = {-1, 0, 1, 0};
constexpr int dy[] = {0, -1, 0, 1};

template <typename T>
constexpr int sign(T x) {
	return x < 0 ? -1 : x > 0 ? 1 : 0;
}

template <>
constexpr int sign(double x) {
	return x < -EPS ? -1 : x > EPS ? 1 : 0;
}

template <typename T, typename U>
constexpr void chmax(T& m, U x) {
	m = max(m, x);
}

template <typename T, typename U>
constexpr void chmin(T& m, U x) {
	m = min(m, x);
}

template <typename T>
constexpr T square(T x) {
	return x * x;
}

// UnionFind(disjoint set)
class UnionFind {
	std::vector<size_t> parents;
	std::vector<size_t> rank;
	std::vector<size_t> tree_size;

	public:
	UnionFind(size_t size) : parents(size), rank(size, 0), tree_size(size, 1) {
		std::iota(this->parents.begin(), this->parents.end(), 0);
	}

	// 併合
	bool merge(size_t a, size_t b) {
		size_t ar = this->root(a);
		size_t br = this->root(b);
		if(ar == br) {
			return false;
		}
		if(this->rank[ar] < this->rank[br]) {
			std::swap(ar, br);
		}
		if(this->rank[ar] == this->rank[br]) {
			this->rank[ar]++;
		}
		this->tree_size[ar] = this->tree_size[br] =
			this->tree_size[ar] + this->tree_size[br];
		return this->parents[br] = ar;
	}
	bool unite(size_t a, size_t b) { return this->merge(a, b); }

	// 同集合か判定
	bool is_same(size_t a, size_t b) { return this->root(a) == this->root(b); }
	bool is_union(size_t a, size_t b) { return this->is_same(a, b); }
	size_t size(size_t n) {
		this->root(n);
		return this->tree_size[n];
	}

	private:
	size_t root(int n) {
		if(this->parents[n] == n) {
			return n;
		}

		size_t r = this->root(this->parents[n]);
		this->parents[n] = r;
		this->tree_size[n] = this->tree_size[r];
		return r;
	}
};

int main() {
	int n, m, k;
	cin >> n >> m >> k;
	VI a(m);
	VI b(m);
	VI2D friends(n);
	REP(i, m) {
		cin >> a[i] >> b[i];
		friends[a[i] - 1].push_back(b[i] - 1);
		friends[b[i] - 1].push_back(a[i] - 1);
	}
	VI c(k);
	VI d(k);
	VI2D blocks(n);
	REP(i, k) {
		cin >> c[i] >> d[i];
		blocks[c[i] - 1].push_back(d[i] - 1);
		blocks[d[i] - 1].push_back(c[i] - 1);
	}
	UnionFind uf(n);
	REP(i, m) { uf.merge(a[i] - 1, b[i] - 1); }
	REP(i, n) {
		int size = uf.size(i) - 1;
		EACH(e, friends[i]) {
			if(uf.is_same(i, e)) {
				size--;
			}
		}
		EACH(e, blocks[i]) {
			if(uf.is_same(i, e)) {
				size--;
			}
		}
		cout << size << ' ';
	}
	cout << endl;
	return 0;
}


#pragma once

#include <algorithm>
#include <vector>
#include <Eigen/Dense>
#include "genetic_code.hpp"
#include "xlorad.hpp"

namespace lorad {

    class QMatrix {

        public:
            typedef std::vector<double>             freq_xchg_t;
            typedef std::shared_ptr<freq_xchg_t>    freq_xchg_ptr_t;
            typedef double                          omega_t;
            typedef std::shared_ptr<omega_t>        omega_ptr_t;
            typedef boost::shared_ptr<QMatrix>      SharedPtr;

                                                    QMatrix();
            virtual                                 ~QMatrix();

            virtual void                            clear() = 0;

            virtual void                            setEqualStateFreqs(freq_xchg_ptr_t freq_ptr) = 0;
            virtual void                            setStateFreqsSharedPtr(freq_xchg_ptr_t freq_ptr) = 0;
            virtual void                            setStateFreqs(freq_xchg_t & freq) = 0;
            virtual freq_xchg_ptr_t                 getStateFreqsSharedPtr() = 0;
            virtual const double *                  getStateFreqs() const = 0;
            void                                    fixStateFreqs(bool is_fixed);
            bool                                    isFixedStateFreqs() const;

            virtual void                            setEqualExchangeabilities(freq_xchg_ptr_t xchg_ptr) = 0;
            virtual void                            setExchangeabilitiesSharedPtr(freq_xchg_ptr_t xchg) = 0;
            virtual void                            setExchangeabilities(freq_xchg_t & xchg) = 0;
            virtual freq_xchg_ptr_t                 getExchangeabilitiesSharedPtr() = 0;
            virtual const double *                  getExchangeabilities() const = 0;
            void                                    fixExchangeabilities(bool is_fixed);
            bool                                    isFixedExchangeabilities() const;

            virtual void                            setOmegaSharedPtr(omega_ptr_t omega) = 0;
            virtual void                            setOmega(omega_t omega) = 0;
            virtual omega_ptr_t                     getOmegaSharedPtr() = 0;
            virtual double                          getOmega() const = 0;
            void                                    fixOmega(bool is_fixed);
            bool                                    isFixedOmega() const;

#if defined(POLGSS)
            virtual void                            setStateFreqRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t freq_params_ptr) = 0;
            virtual std::vector<double>             getStateFreqRefDistParamsVect() const = 0;
            virtual void                            setExchangeabilityRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t xchg_params_ptr) = 0;
            virtual std::vector<double>             getExchangeabilityRefDistParamsVect() const = 0;
#endif

            virtual const double *                  getEigenvectors() const = 0;
            virtual const double *                  getInverseEigenvectors() const = 0;
            virtual const double *                  getEigenvalues() const = 0;

            void                                    setActive(bool activate);

        protected:

            virtual void                            recalcRateMatrix() = 0;
            void                                    normalizeFreqsOrExchangeabilities(freq_xchg_ptr_t v);

            bool                                    _is_active;
            bool                                    _state_freqs_fixed;
            bool                                    _exchangeabilities_fixed;
            bool                                    _omega_fixed;

#if defined(POLGSS)
            freq_xchg_ptr_t                         _state_freq_refdist;
            freq_xchg_ptr_t                         _exchangeability_refdist;
#endif
    };

    inline QMatrix::QMatrix() {
    }

    inline QMatrix::~QMatrix() {
    }

    inline void QMatrix::setActive(bool activate) {
        _is_active = activate;
        recalcRateMatrix();
    }

    inline void QMatrix::clear() {
        _is_active = false;
        _state_freqs_fixed = false;
        _exchangeabilities_fixed = false;
        _omega_fixed = false;
    }

    inline void QMatrix::fixStateFreqs(bool is_fixed) {
        _state_freqs_fixed = is_fixed;
    }

    inline void QMatrix::fixExchangeabilities(bool is_fixed) {
        _exchangeabilities_fixed = is_fixed;
    }

    inline void QMatrix::fixOmega(bool is_fixed) {
        _omega_fixed = is_fixed;
    }

    inline bool QMatrix::isFixedStateFreqs() const {
        return _state_freqs_fixed;
    }

    inline bool QMatrix::isFixedExchangeabilities() const {
        return _exchangeabilities_fixed;
    }

    inline bool QMatrix::isFixedOmega() const {
        return _omega_fixed;
    }

    inline void QMatrix::normalizeFreqsOrExchangeabilities(QMatrix::freq_xchg_ptr_t v) {
        // Be sure elements of v sum to 1.0 and assert that they are all positive
        double sum_v = std::accumulate(v->begin(), v->end(), 0.0);
        for (auto & x : *v) {
            assert(x > 0.0);
            x /= sum_v;
        }
    }

    class QMatrixNucleotide : public QMatrix {

        public:
            typedef Eigen::Matrix<double, 4, 4, Eigen::RowMajor>    eigenMatrix4d_t;
            typedef Eigen::Vector4d                                 eigenVector4d_t;

                                        QMatrixNucleotide();
                                        ~QMatrixNucleotide();

            void                        clear();

            void                        setEqualStateFreqs(freq_xchg_ptr_t freq_ptr);
            void                        setStateFreqsSharedPtr(freq_xchg_ptr_t freq_ptr);
            void                        setStateFreqs(freq_xchg_t & freqs);
            freq_xchg_ptr_t             getStateFreqsSharedPtr();
            const double *              getStateFreqs() const;

            void                        setEqualExchangeabilities(freq_xchg_ptr_t xchg_ptr);
            void                        setExchangeabilitiesSharedPtr(freq_xchg_ptr_t xchg_ptr);
            void                        setExchangeabilities(freq_xchg_t & xchg);
            freq_xchg_ptr_t             getExchangeabilitiesSharedPtr();
            const double *              getExchangeabilities() const;

            void                        setOmegaSharedPtr(omega_ptr_t omega_ptr);
            void                        setOmega(omega_t omega);
            omega_ptr_t                 getOmegaSharedPtr();
            double                      getOmega() const;

#if defined(POLGSS)
            void                        setStateFreqRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t freq_params_ptr);
            std::vector<double>         getStateFreqRefDistParamsVect() const;
            void                        setExchangeabilityRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t xchg_params_ptr);
            std::vector<double>         getExchangeabilityRefDistParamsVect() const;
#endif

            const double *              getEigenvectors() const;
            const double *              getInverseEigenvectors() const;
            const double *              getEigenvalues() const;

            EIGEN_MAKE_ALIGNED_OPERATOR_NEW

        protected:

            virtual void                recalcRateMatrix();

        private:

            // workspaces for computing eigenvectors/eigenvalues
            eigenMatrix4d_t             _sqrtPi;
            eigenMatrix4d_t             _sqrtPiInv;
            eigenMatrix4d_t             _Q;
            eigenMatrix4d_t             _eigenvectors;
            eigenMatrix4d_t             _inverse_eigenvectors;
            eigenVector4d_t             _eigenvalues;

            freq_xchg_ptr_t             _state_freqs;
            freq_xchg_ptr_t             _exchangeabilities;
    };

    inline QMatrixNucleotide::QMatrixNucleotide() {
        clear();
    }

    inline QMatrixNucleotide::~QMatrixNucleotide() {
    }

    inline void QMatrixNucleotide::clear() {
        QMatrix::clear();

        QMatrix::freq_xchg_t xchg = {1,1,1,1,1,1};
        _exchangeabilities = std::make_shared<QMatrix::freq_xchg_t>(xchg);

        QMatrix::freq_xchg_t freq_vect = {0.25, 0.25, 0.25, 0.25};
        _state_freqs = std::make_shared<QMatrix::freq_xchg_t>(freq_vect);

#if defined(POLGSS)
        QMatrix::freq_xchg_t freq_param_vect = {1.0, 1.0, 1.0, 1.0};
        _state_freq_refdist = std::make_shared<QMatrix::freq_xchg_t>(freq_param_vect);

        QMatrix::freq_xchg_t xchg_param_vect = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
        _exchangeability_refdist = std::make_shared<QMatrix::freq_xchg_t>(xchg_param_vect);
#endif

        recalcRateMatrix();
    }

    inline QMatrix::freq_xchg_ptr_t QMatrixNucleotide::getExchangeabilitiesSharedPtr() {
        return _exchangeabilities;
    }

    inline QMatrix::freq_xchg_ptr_t QMatrixNucleotide::getStateFreqsSharedPtr() {
        return _state_freqs;
    }

    inline QMatrix::omega_ptr_t QMatrixNucleotide::getOmegaSharedPtr() {
        assert(false);
        return nullptr;
    }

    inline const double * QMatrixNucleotide::getEigenvectors() const {
        return _eigenvectors.data();
    }

    inline const double * QMatrixNucleotide::getInverseEigenvectors() const {
        return _inverse_eigenvectors.data();
    }

    inline const double * QMatrixNucleotide::getEigenvalues() const {
        return _eigenvalues.data();
    }

    inline const double * QMatrixNucleotide::getExchangeabilities() const {
        return &(*_exchangeabilities)[0];
    }

    inline const double * QMatrixNucleotide::getStateFreqs() const {
        return &(*_state_freqs)[0];
    }

    inline double QMatrixNucleotide::getOmega() const {
        assert(false);
        return 0.0;
    }

    inline void QMatrixNucleotide::setEqualExchangeabilities(QMatrix::freq_xchg_ptr_t xchg_ptr) {
        _exchangeabilities = xchg_ptr;
        _exchangeabilities->assign(6, 1.0/6.0);
        recalcRateMatrix();
    }

    inline void QMatrixNucleotide::setExchangeabilitiesSharedPtr(QMatrix::freq_xchg_ptr_t xchg_ptr) {
        if (xchg_ptr->size() != 6)
            throw XLorad(boost::format("Expecting 6 exchangeabilities and got %d: perhaps you meant to specify a subset data type other than nucleotide") % xchg_ptr->size());
        _exchangeabilities = xchg_ptr;
        normalizeFreqsOrExchangeabilities(_exchangeabilities);
        recalcRateMatrix();
    }

    inline void QMatrixNucleotide::setExchangeabilities(QMatrix::freq_xchg_t & xchg) {
        if (xchg.size() != 6)
            throw XLorad(boost::format("Expecting 6 exchangeabilities and got %d: perhaps you meant to specify a subset data type other than nucleotide") % xchg.size());
        std::copy(xchg.begin(), xchg.end(), _exchangeabilities->begin());
        recalcRateMatrix();
    }

    inline void QMatrixNucleotide::setEqualStateFreqs(QMatrix::freq_xchg_ptr_t freq_ptr) {
        _state_freqs = freq_ptr;
        _state_freqs->assign(4, 0.25);
        recalcRateMatrix();
    }

    inline void QMatrixNucleotide::setStateFreqsSharedPtr(QMatrix::freq_xchg_ptr_t freq_ptr) {
        if (freq_ptr->size() != 4)
            throw XLorad(boost::format("Expecting 4 state frequencies and got %d: perhaps you meant to specify a subset data type other than nucleotide") % freq_ptr->size());
        double sum_of_freqs = std::accumulate(freq_ptr->begin(), freq_ptr->end(), 0.0);
        if (std::fabs(sum_of_freqs - 1.0) > 0.001)
            throw XLorad(boost::format("Expecting sum of 4 state frequencies to be 1, but instead got %g") % sum_of_freqs);
        _state_freqs = freq_ptr;
        recalcRateMatrix();
    }

    inline void QMatrixNucleotide::setStateFreqs(QMatrix::freq_xchg_t & freqs) {
        if (freqs.size() != 4)
            throw XLorad(boost::format("Expecting 4 state frequencies and got %d: perhaps you meant to specify a subset data type other than nucleotide") % freqs.size());
        std::copy(freqs.begin(), freqs.end(), _state_freqs->begin());
        recalcRateMatrix();
    }

    inline void QMatrixNucleotide::setOmegaSharedPtr(QMatrix::omega_ptr_t omega_ptr) {
        assert(false);
    }

    inline void QMatrixNucleotide::setOmega(QMatrix::omega_t omega) {
        assert(false);
    }

#if defined(POLGSS)
    inline void QMatrixNucleotide::setStateFreqRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t freq_params_ptr) {
        if (freq_params_ptr->size() != 4)
            throw XLorad(boost::format("Expecting 4 state frequency reference distribution parameters and got %d: perhaps you meant to specify a subset data type other than nucleotide") % freq_params_ptr->size());
        _state_freq_refdist = freq_params_ptr;
    }

    inline std::vector<double> QMatrixNucleotide::getStateFreqRefDistParamsVect() const {
        return std::vector<double>(_state_freq_refdist->begin(), _state_freq_refdist->end());
    }

    inline void QMatrixNucleotide::setExchangeabilityRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t xchg_params_ptr) {
        if (xchg_params_ptr->size() != 6)
            throw XLorad(boost::format("Expecting 6 exchangeability reference distribution parameters and got %d: perhaps you meant to specify a subset data type other than nucleotide") % xchg_params_ptr->size());
        _exchangeability_refdist = xchg_params_ptr;
    }

    inline std::vector<double> QMatrixNucleotide::getExchangeabilityRefDistParamsVect() const {
        return std::vector<double>(_exchangeability_refdist->begin(), _exchangeability_refdist->end());
    }
#endif

    inline void QMatrixNucleotide::recalcRateMatrix() {
        // Must have assigned both _state_freqs and _exchangeabilities to recalculate rate matrix
        if (!_is_active || !(_state_freqs && _exchangeabilities))
            return;

        double piA = (*_state_freqs)[0];
        double piC = (*_state_freqs)[1];
        double piG = (*_state_freqs)[2];
        double piT = (*_state_freqs)[3];

        Eigen::Map<const Eigen::Array4d> tmp(_state_freqs->data());
        _sqrtPi = tmp.sqrt().matrix().asDiagonal();
        _sqrtPiInv = _sqrtPi.inverse();

        assert(_exchangeabilities->size() == 6);
        double rAC = (*_exchangeabilities)[0];
        double rAG = (*_exchangeabilities)[1];
        double rAT = (*_exchangeabilities)[2];
        double rCG = (*_exchangeabilities)[3];
        double rCT = (*_exchangeabilities)[4];
        double rGT = (*_exchangeabilities)[5];

        double inverse_scaling_factor = piA*(rAC*piC + rAG*piG + rAT*piT) + piC*(rAC*piA + rCG*piG + rCT*piT) + piG*(rAG*piA + rCG*piC + rGT*piT) + piT*(rAT*piA + rCT*piC + rGT*piG);
        double scaling_factor = 1.0/inverse_scaling_factor;

        _Q(0,0) = -scaling_factor*(rAC*piC + rAG*piG + rAT*piT);
        _Q(0,1) = scaling_factor*rAC*piC;
        _Q(0,2) = scaling_factor*rAG*piG;
        _Q(0,3) = scaling_factor*rAT*piT;

        _Q(1,0) = scaling_factor*rAC*piA;
        _Q(1,1) = -scaling_factor*(rAC*piA + rCG*piG + rCT*piT);
        _Q(1,2) = scaling_factor*rCG*piG;
        _Q(1,3) = scaling_factor*rCT*piT;

        _Q(2,0) = scaling_factor*rAG*piA;
        _Q(2,1) = scaling_factor*rCG*piC;
        _Q(2,2) = -scaling_factor*(rAG*piA + rCG*piC + rGT*piT);
        _Q(2,3) = scaling_factor*rGT*piT;

        _Q(3,0) = scaling_factor*rAT*piA;
        _Q(3,1) = scaling_factor*rCT*piC;
        _Q(3,2) = scaling_factor*rGT*piG;
        _Q(3,3) = -scaling_factor*(rAT*piA + rCT*piC + rGT*piG);

        // S is a symmetric matrix
        eigenMatrix4d_t S = eigenMatrix4d_t(_sqrtPi*_Q*_sqrtPiInv);

        // Can use efficient eigensystem solver because S is symmetric
        Eigen::SelfAdjointEigenSolver<Eigen::Matrix4d> solver(S);
        if (solver.info() != Eigen::Success) {
            throw XLorad("Error in the calculation of eigenvectors and eigenvalues of the GTR rate matrix");
        }

        _eigenvectors           = _sqrtPiInv*solver.eigenvectors();
        _inverse_eigenvectors   = solver.eigenvectors().transpose()*_sqrtPi;
        _eigenvalues            = solver.eigenvalues();
    }

    class QMatrixCodon : public QMatrix {

        public:
            typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>      eigenMatrixXd_t;
            typedef Eigen::VectorXd                                                             eigenVectorXd_t;

                                        QMatrixCodon(GeneticCode::SharedPtr gcode);
                                        ~QMatrixCodon();

            void                        clear();

            void                        setEqualStateFreqs(freq_xchg_ptr_t freq_ptr);
            void                        setStateFreqsSharedPtr(freq_xchg_ptr_t freq_ptr);
            void                        setStateFreqs(freq_xchg_t & freqs);
            freq_xchg_ptr_t             getStateFreqsSharedPtr();
            const double *              getStateFreqs() const;

            void                        setEqualExchangeabilities(freq_xchg_ptr_t xchg_ptr);
            void                        setExchangeabilitiesSharedPtr(freq_xchg_ptr_t xchg_ptr);
            void                        setExchangeabilities(freq_xchg_t & xchg);
            freq_xchg_ptr_t             getExchangeabilitiesSharedPtr();
            const double *              getExchangeabilities() const;

            void                        setOmegaSharedPtr(omega_ptr_t omega_ptr);
            void                        setOmega(omega_t omega);
            omega_ptr_t                 getOmegaSharedPtr();
            double                      getOmega() const;

#if defined(POLGSS)
            void                        setStateFreqRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t freq_params_ptr);
            std::vector<double>         getStateFreqRefDistParamsVect() const;
            void                        setExchangeabilityRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t xchg_params_ptr);
            std::vector<double>         getExchangeabilityRefDistParamsVect() const;
#endif

            const double *              getEigenvectors() const;
            const double *              getInverseEigenvectors() const;
            const double *              getEigenvalues() const;

        protected:

            virtual void                recalcRateMatrix();

        private:

            // workspaces for computing eigenvectors/eigenvalues
            eigenMatrixXd_t             _sqrtPi;
            eigenMatrixXd_t             _sqrtPiInv;
            eigenMatrixXd_t             _Q;
            eigenMatrixXd_t             _eigenvectors;
            eigenMatrixXd_t             _inverse_eigenvectors;
            eigenVectorXd_t             _eigenvalues;

            freq_xchg_ptr_t             _state_freqs;
            omega_ptr_t                 _omega;

            std::vector<std::string>    _codons;
            std::vector<unsigned>       _amino_acids;

            GeneticCode::SharedPtr      _genetic_code;
    };

    inline QMatrixCodon::QMatrixCodon(GeneticCode::SharedPtr gcode) {
        assert(gcode);
        _genetic_code = gcode;
        clear();
    }

    inline QMatrixCodon::~QMatrixCodon() {
    }

    inline void QMatrixCodon::clear() {
        QMatrix::clear();

        unsigned nstates = _genetic_code->getNumNonStopCodons();
        _genetic_code->copyCodons(_codons);
        _genetic_code->copyAminoAcids(_amino_acids);

        QMatrix::omega_t omega = 0.1;
        _omega = std::make_shared<QMatrix::omega_t>(omega);

        QMatrix::freq_xchg_t freq_vect(nstates, 1./nstates);
        _state_freqs = std::make_shared<QMatrix::freq_xchg_t>(freq_vect);

        _sqrtPi.resize(nstates, nstates);
        _sqrtPiInv.resize(nstates, nstates);
        _Q.resize(nstates, nstates);
        _eigenvectors.resize(nstates, nstates);
        _inverse_eigenvectors.resize(nstates, nstates);
        _eigenvalues.resize(nstates);

        recalcRateMatrix();
    }

    inline QMatrix::freq_xchg_ptr_t QMatrixCodon::getExchangeabilitiesSharedPtr() {
        assert(false);
        return nullptr;
    }

    inline QMatrix::freq_xchg_ptr_t QMatrixCodon::getStateFreqsSharedPtr() {
        return _state_freqs;
    }

    inline QMatrix::omega_ptr_t QMatrixCodon::getOmegaSharedPtr() {
        return _omega;
    }

    inline const double * QMatrixCodon::getEigenvectors() const {
        return _eigenvectors.data();
    }

    inline const double * QMatrixCodon::getInverseEigenvectors() const {
        return _inverse_eigenvectors.data();
    }

    inline const double * QMatrixCodon::getEigenvalues() const {
        return _eigenvalues.data();
    }

    inline const double * QMatrixCodon::getExchangeabilities() const {
        assert(false);
        return 0;
    }

    inline const double * QMatrixCodon::getStateFreqs() const {
        return &(*_state_freqs)[0];
    }

    inline double QMatrixCodon::getOmega() const {
        return *_omega;
    }

    inline void QMatrixCodon::setEqualExchangeabilities(QMatrix::freq_xchg_ptr_t xchg_ptr) {
        assert(false);
    }

    inline void QMatrixCodon::setExchangeabilitiesSharedPtr(QMatrix::freq_xchg_ptr_t xchg_ptr) {
        assert(false);
    }

    inline void QMatrixCodon::setExchangeabilities(QMatrix::freq_xchg_t & xchg) {
        assert(false);
    }

    inline void QMatrixCodon::setEqualStateFreqs(QMatrix::freq_xchg_ptr_t freq_ptr) {
        _state_freqs = freq_ptr;
        unsigned nstates = _genetic_code->getNumNonStopCodons();
        _state_freqs->assign(nstates, 1./nstates);
        recalcRateMatrix();
    }

    inline void QMatrixCodon::setStateFreqsSharedPtr(QMatrix::freq_xchg_ptr_t freq_ptr) {
        unsigned nstates = _genetic_code->getNumNonStopCodons();
        if (freq_ptr->size() != nstates)
            throw XLorad(boost::format("Expecting %d state frequencies and got %d: perhaps you meant to specify a subset data type other than codon") % nstates % freq_ptr->size());
        double sum_of_freqs = std::accumulate(freq_ptr->begin(), freq_ptr->end(), 0.0);
        if (std::fabs(sum_of_freqs - 1.0) > 0.001)
            throw XLorad(boost::format("Expecting sum of codon frequencies to be 1, but instead got %g") % sum_of_freqs);
        _state_freqs = freq_ptr;
        normalizeFreqsOrExchangeabilities(_state_freqs);
        recalcRateMatrix();
    }

    inline void QMatrixCodon::setStateFreqs(QMatrix::freq_xchg_t & freqs) {
        unsigned nstates = _genetic_code->getNumNonStopCodons();
        if (freqs.size() != nstates)
            throw XLorad(boost::format("Expecting %d state frequencies and got %d: perhaps you meant to specify a subset data type other than codon") % nstates % freqs.size());
        std::copy(freqs.begin(), freqs.end(), _state_freqs->begin());
        recalcRateMatrix();
    }

    inline void QMatrixCodon::setOmegaSharedPtr(QMatrix::omega_ptr_t omega_ptr) {
        _omega = omega_ptr;
        recalcRateMatrix();
    }

    inline void QMatrixCodon::setOmega(QMatrix::omega_t omega) {
        *_omega = omega;
        recalcRateMatrix();
    }

    inline void QMatrixCodon::recalcRateMatrix() {
        // Must have assigned both _state_freqs and _omega to recalculate rate matrix
        if (!_is_active || !(_state_freqs && _omega))
            return;

        unsigned nstates = _genetic_code->getNumNonStopCodons();
        assert(_state_freqs->size() == nstates);
        const double * pi = getStateFreqs();
        double omega = getOmega();

        Eigen::Map<const Eigen::ArrayXd> tmp(_state_freqs->data(), nstates);
        _sqrtPi = tmp.sqrt().matrix().asDiagonal();
        _sqrtPiInv = _sqrtPi.inverse();

        // Calculate (unscaled) instantaneous rate matrix
        _Q = Eigen::MatrixXd::Zero(nstates,nstates);

        for (unsigned i = 0; i < nstates-1; i++) {
            for (unsigned j = i+1; j < nstates; j++) {
                unsigned diffs = 0;
                if (_codons[i][0] != _codons[j][0])
                    diffs++;
                if (_codons[i][1] != _codons[j][1])
                    diffs++;
                if (_codons[i][2] != _codons[j][2])
                    diffs++;
                if (diffs == 1) {
                    bool synonymous = _amino_acids[i] == _amino_acids[j];
                    _Q(i,j) = (synonymous ? 1.0 : omega)*pi[j];
                    _Q(j,i) = (synonymous ? 1.0 : omega)*pi[i];
                    _Q(i,i) -= _Q(i,j);
                    _Q(j,j) -= _Q(j,i);
                }
            }
        }

        double average_rate = 0.0;
        for (unsigned i = 0; i < nstates; i++)
            average_rate -= pi[i]*_Q(i,i);
        double scaling_factor = 3.0/average_rate;

        _Q *= scaling_factor;

        // S is a symmetric matrix
        eigenMatrixXd_t S = eigenMatrixXd_t(_sqrtPi*_Q*_sqrtPiInv);

        // Can use efficient eigensystem solver because S is symmetric
        Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver(S);
        if (solver.info() != Eigen::Success) {
            throw XLorad("Error in the calculation of eigenvectors and eigenvalues of the codon model rate matrix");
        }

        _eigenvectors           = _sqrtPiInv*solver.eigenvectors();
        _inverse_eigenvectors   = solver.eigenvectors().transpose()*_sqrtPi;
        _eigenvalues            = solver.eigenvalues();
    }

#if defined(POLGSS)
    inline void QMatrixCodon::setStateFreqRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t freq_params_ptr) {
        if (freq_params_ptr->size() != 61)
            throw XLorad(boost::format("Expecting 61 state frequency reference distribution parameters and got %d: perhaps you meant to specify a subset data type other than codon") % freq_params_ptr->size());
        _state_freq_refdist = freq_params_ptr;
    }

    inline std::vector<double> QMatrixCodon::getStateFreqRefDistParamsVect() const {
        return std::vector<double>(_state_freq_refdist->begin(), _state_freq_refdist->end());
    }

    inline void QMatrixCodon::setExchangeabilityRefDistParamsSharedPtr(QMatrix::freq_xchg_ptr_t xchg_params_ptr) {
        throw XLorad("Not expecting exchangeability reference distribution to be specified for a codon model");
    }

    inline std::vector<double> QMatrixCodon::getExchangeabilityRefDistParamsVect() const {
        throw XLorad("Not expecting to copy exchangeability reference distribution parameters for a codon model");
        return std::vector<double>();
    }
#endif

} // namespace lorad


// External libraries
#include <iostream>
#include <stdio.h>
#include <boost/property_tree/ptree.hpp>

// Local libraries
#include <inout/ioutil.h>
#include <fparameters/parameters.h>
#include <fparameters/SpaceIterator.h>

// Local headers
#include "read.h"
#include "messages.h"
#include "modelParameters.h"
#include "State.h"
#include "write.h"
#include "comptonScattMatrix.h"
#include "thermalProcesses.h"
#include "adafFunctions.h"
#include "globalVariables.h"
#include "flareProcesses.h"
#include "redshiftFunction.h"
#include "thermalDistribution.h"
#include "NTtimescales.h"
#include "NTinjection.h"
#include "injectionNeutrons.h"
#include "distributionNeutrons.h"
#include "NTdistribution.h"
#include "NTradiation.h"
#include "absorption.h"
#include "secondariesProcesses.h"
#include "jetEmission.h"

using namespace std;

int main()
{
	string folder{ prepareOutputfolder() };
	try {
		GlobalConfig = readConfig();
		prepareGlobalCfg();

		State model(GlobalConfig.get_child("model"));
		redshiftFactor(model);
		createH5file("h5prueba.h5", model);
	}
	catch (std::runtime_error& e)
	{
		std::cout << "ERROR: " << e.what() << std::endl;
	}
	return 0;
}

/*	string folder{ prepareOutputfolder() };
	try {
		GlobalConfig = readConfig();
		prepareGlobalCfg();

		State model(GlobalConfig.get_child("model"));
		writeFields(model);
		redshiftFactor(model);

		if (calculateThermal) {
			if (calculateComptonScatt)
				comptonScattMatrix(model);
			else
				comptonScattMatrixRead(model);
			thermalRadiation(model, "lumThermal.dat");
		}
		else
			readEandRParamSpace("photonDensity", model.photon.distribution, 0, 0);

		//writeEandRParamSpace("photonDensity_z",model.photon.injection,0,0);

		if (calculateJetEmission)
			jetProcesses(model,"lumJet.txt");

		if (calculateFlare)
			flareProcesses(model);

//***********nonthermal particles**************
		if (calculateNonThermal) {

			if (calculateLosses) {
				if (calculateNTelectrons)
					nonThermalTimescales(model.ntElectron, model, "electronCoolingTimes.dat");
				if (calculateNTprotons)
					nonThermalTimescales(model.ntProton, model, "protonCoolingTimes.dat");
			}

			if (calculateNTdistributions) {

				if (calculateNTelectrons) {
					injection(model.ntElectron, model);
					if (accMethod == 0)
						//distributionSpatialDiffusion(model.ntElectron, model);
						distributionMultiZone(model.ntElectron, model);
						//distributionSpatialDiffusionSteady(model.ntElectron, model);
					else
						distributionSpatialDiffusion(model.ntElectron, model);
				}
				else {
					readEandRParamSpace("electronInjection_vol", model.ntElectron.injection, 0, 1);
					readEandRParamSpace("electronDistribution_vol", model.ntElectron.distribution, 0, 1);
				}

				if (calculateNTprotons) {
					if (accMethod == 0)
						injection(model.ntProton,model);
					else
						injection(model.ntProton,model);
						//injectionFokkerPlanckOneZone(model.ntProton,model);
					if (accMethod == 0)
						distributionSpatialDiffusionSteady(model.ntProton, model);
						//distributionMultiZoneRadial(model.ntProton, model);
					else
						distributionSpatialDiffusionSteady(model.ntProton, model);
						//distributionFokkerPlanckCompleteSteadyState(model.ntProton, model);
						//distributionFokkerPlanckSpatialDiffusionTimeDependent(model.ntProton, model);
						//distributionFokkerPlanckRadial(model.ntProton,model);
				}
				else {
					readEandRParamSpace("protonInjection_vol", model.ntProton.injection,0,1);
					readEandRParamSpace("protonDistribution_vol", model.ntProton.distribution,0,1);
				}
			}
			else {
				if (calculateNTelectrons) {
					readEandRParamSpace("electronInjection_vol",model.ntElectron.injection,0,1);
					readEandRParamSpace("electronDistribution_vol",model.ntElectron.distribution,0,1);
				}
				if (calculateNTprotons) {
					readEandRParamSpace("protonInjection_vol",model.ntProton.injection,0,1);
					readEandRParamSpace("protonDistribution_vol",model.ntProton.distribution,0,1);
				}
			}

			if (calculateNonThermalLum)
				nonThermalRadiation(model, "lumNonThermal.dat");
			else {
				readEandRParamSpace("photonDensity", model.photon.distribution, 0, 0);
				readEandRParamSpace("NTphotonDensity", model.ntPhoton.distribution, 0, 0);
				readEandRParamSpace("NTphotonDensity", model.ntPhoton.injection, 0, 0);
				readEandRParamSpace("opticalDepth_gg", model.tau_gg, 0, 0);
			}

			if (calculateSecondaries)
				secondariesProcesses(model);
			else {
				readEandRParamSpace("muonDistribution", model.ntMuon.distribution, 0, 0);
				readEandRParamSpace("pionDistribution", model.ntChargedPion.distribution, 0, 0);
			}

			// NEUTRINO TRANSPORT
			if (calculateNeutrinos)
				injectionNeutrino(model.neutrino, model);

			// NEUTRON TRANPOSRT
			if (calculateNeutronInj) {
				injectionNeutrons(model);
				radiativeLossesNeutron(model.ntNeutron,model,"neutronTimescales.dat");
				if (calculateNeutronDis)
					distributionNeutronsAGN(model);
				if (calculateJetDecay)
					jetNeutronDecay(model);
			}
		}
	}
	catch (std::runtime_error& e)
	{
		std::cout << "ERROR: " << e.what() << std::endl;
	}
	return 0;
}*/