/*    Copyright (c) 2010-2019, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 */

#include <boost/make_shared.hpp>
#include <boost/bind.hpp>

#include "Tudat/Astrodynamics/Aerodynamics/exponentialAtmosphere.h"
#include "Tudat/Astrodynamics/Aerodynamics/tabulatedAtmosphere.h"
#if USE_NRLMSISE00
#include "Tudat/Astrodynamics/Aerodynamics/nrlmsise00Atmosphere.h"
#include "Tudat/Astrodynamics/Aerodynamics/nrlmsise00InputFunctions.h"
#endif
#include "Tudat/InputOutput/basicInputOutput.h"
#include "Tudat/InputOutput/solarActivityData.h"
#include "Tudat/SimulationSetup/EnvironmentSetup/createAtmosphereModel.h"

namespace tudat
{

namespace simulation_setup
{

//! Function to create a wind model.
std::shared_ptr< aerodynamics::WindModel > createWindModel(
        const std::shared_ptr< WindModelSettings > windSettings,
        const std::string& body )
{
    std::shared_ptr< aerodynamics::WindModel > windModel;

    // Check wind model type and create requested model
    switch( windSettings->getWindModelType( ) )
    {
    case custom_wind_model:
    {
        // Check input consistency
        std::shared_ptr< CustomWindModelSettings > customWindModelSettings =
                std::dynamic_pointer_cast< CustomWindModelSettings >( windSettings );

        if( customWindModelSettings == nullptr )
        {
            throw std::runtime_error( "Error when making custom wind model for body " + body + ", input is incompatible" );
        }
        windModel = std::make_shared< aerodynamics::CustomWindModel >(
                    customWindModelSettings->getWindFunction( ) );
        break;
    }
    default:
        throw std::runtime_error( "Error when making wind model for body " + body + ", input type not recognized" );
    }

    return windModel;

}

//! Function to create an atmosphere model.
std::shared_ptr< aerodynamics::AtmosphereModel > createAtmosphereModel(
        const std::shared_ptr< AtmosphereSettings > atmosphereSettings,
        const std::string& body )
{
    using namespace tudat::aerodynamics;

    // Declare return object.
    std::shared_ptr< AtmosphereModel > atmosphereModel;

    // Check which type of atmosphere model is to be created.
    switch( atmosphereSettings->getAtmosphereType( ) )
    {
    case exponential_atmosphere:
    {
        // Check whether settings for atmosphere are consistent with its type.
        std::shared_ptr< ExponentialAtmosphereSettings > exponentialAtmosphereSettings =
                std::dynamic_pointer_cast< ExponentialAtmosphereSettings >( atmosphereSettings );

        if( exponentialAtmosphereSettings == nullptr )
        {
            throw std::runtime_error( "Error, expected exponential atmosphere settings for body " + body );
        }
        else
        {
            // Create and initialize exponential atmosphere model.
            std::shared_ptr< ExponentialAtmosphere > exponentialAtmosphereModel;
            if ( exponentialAtmosphereSettings->getBodyName( ) == undefined_body )
            {
                exponentialAtmosphereModel = std::make_shared< ExponentialAtmosphere >(
                            exponentialAtmosphereSettings->getDensityScaleHeight( ) ,
                            exponentialAtmosphereSettings->getConstantTemperature( ),
                            exponentialAtmosphereSettings->getDensityAtZeroAltitude( ),
                            exponentialAtmosphereSettings->getSpecificGasConstant( ),
                            exponentialAtmosphereSettings->getRatioOfSpecificHeats( ) );
            }
            else
            {
                exponentialAtmosphereModel = std::make_shared< ExponentialAtmosphere >(
                            exponentialAtmosphereSettings->getBodyName( ) );
            }
            atmosphereModel = exponentialAtmosphereModel;
        }
        break;
    }
    case custom_constant_temperature_atmosphere:
    {
        // Check whether settings for atmosphere are consistent with its type.
        std::shared_ptr< CustomConstantTemperatureAtmosphereSettings > customConstantTemperatureAtmosphereSettings =
                std::dynamic_pointer_cast< CustomConstantTemperatureAtmosphereSettings >( atmosphereSettings );
        if( customConstantTemperatureAtmosphereSettings == nullptr )
        {
            throw std::runtime_error( "Error, expected exponential atmosphere settings for body " + body );
        }
        else
        {
            // Create and initialize exponential atmosphere model.
            std::shared_ptr< CustomConstantTemperatureAtmosphere > customConstantTemperatureAtmosphereModel;
            if ( customConstantTemperatureAtmosphereSettings->getModelSpecificParameters( ).empty( ) )
            {
                customConstantTemperatureAtmosphereModel = std::make_shared< CustomConstantTemperatureAtmosphere >(
                            customConstantTemperatureAtmosphereSettings->getDensityFunction( ) ,
                            customConstantTemperatureAtmosphereSettings->getConstantTemperature( ),
                            customConstantTemperatureAtmosphereSettings->getSpecificGasConstant( ),
                            customConstantTemperatureAtmosphereSettings->getRatioOfSpecificHeats( ) );
            }
            else
            {
                customConstantTemperatureAtmosphereModel = std::make_shared< CustomConstantTemperatureAtmosphere >(
                            customConstantTemperatureAtmosphereSettings->getDensityFunctionType( ),
                            customConstantTemperatureAtmosphereSettings->getConstantTemperature( ),
                            customConstantTemperatureAtmosphereSettings->getSpecificGasConstant( ),
                            customConstantTemperatureAtmosphereSettings->getRatioOfSpecificHeats( ),
                            customConstantTemperatureAtmosphereSettings->getModelSpecificParameters( ) );
            }
            atmosphereModel = customConstantTemperatureAtmosphereModel;
        }
        break;
    }
    case tabulated_atmosphere:
    {
        // Check whether settings for atmosphere are consistent with its type
        std::shared_ptr< TabulatedAtmosphereSettings > tabulatedAtmosphereSettings =
                std::dynamic_pointer_cast< TabulatedAtmosphereSettings >( atmosphereSettings );
        if( tabulatedAtmosphereSettings == nullptr )
        {
            throw std::runtime_error( "Error, expected tabulated atmosphere settings for body " + body );
        }
        else
        {
            // Create and initialize tabulated atmosphere model.
            atmosphereModel = std::make_shared< TabulatedAtmosphere >(
                        tabulatedAtmosphereSettings->getAtmosphereFile( ),
                        tabulatedAtmosphereSettings->getIndependentVariables( ),
                        tabulatedAtmosphereSettings->getDependentVariables( ),
                        tabulatedAtmosphereSettings->getSpecificGasConstant( ),
                        tabulatedAtmosphereSettings->getRatioOfSpecificHeats( ),
                        tabulatedAtmosphereSettings->getBoundaryHandling( ),
                        tabulatedAtmosphereSettings->getDefaultExtrapolationValue( ) );
        }
        break;
    }
#if USE_NRLMSISE00
    case nrlmsise00:
    {
        std::string spaceWeatherFilePath;
        std::shared_ptr< NRLMSISE00AtmosphereSettings > nrlmsise00AtmosphereSettings =
                std::dynamic_pointer_cast< NRLMSISE00AtmosphereSettings >( atmosphereSettings );

        if( nrlmsise00AtmosphereSettings == nullptr )
        {
            // Use default space weather file stored in tudatBundle.
            spaceWeatherFilePath = input_output::getSpaceWeatherDataPath( ) + "sw19571001.txt";
        }
        else
        {
            // Use space weather file specified by user.
            spaceWeatherFilePath = nrlmsise00AtmosphereSettings->getSpaceWeatherFile( );
        }

        tudat::input_output::solar_activity::SolarActivityDataMap solarActivityData =
                tudat::input_output::solar_activity::readSolarActivityData( spaceWeatherFilePath ) ;

        // Create atmosphere model using NRLMISE00 input function
        std::function< tudat::aerodynamics::NRLMSISE00Input( double, double, double, double ) > inputFunction =
                std::bind( &tudat::aerodynamics::nrlmsiseInputFunction,
                           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
                           solarActivityData, false, TUDAT_NAN );
        atmosphereModel = std::make_shared< aerodynamics::NRLMSISE00Atmosphere >( inputFunction );
        break;
    }
#endif
    default:
        throw std::runtime_error( "Error, did not recognize atmosphere model settings type " +
                                  std::to_string( atmosphereSettings->getAtmosphereType( ) ) );
    }

    if( atmosphereSettings->getWindSettings( ) != nullptr )
    {
        atmosphereModel->setWindModel( createWindModel( atmosphereSettings->getWindSettings( ), body ) );
    }

    return atmosphereModel;
}

} // namespace simulation_setup

} // namespace tudat


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018, 2019.
// Modifications copyright (c) 2017-2019, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// Purpose:  Implementation of the aitoff (Aitoff) and wintri (Winkel Tripel)
//           projections.
// Author:   Gerald Evenden (1995)
//           Drazen Tutic, Lovro Gradiser (2015) - add inverse
//           Thomas Knudsen (2016) - revise/add regression tests
// Copyright (c) 1995, Gerald Evenden

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_AITOFF_HPP
#define BOOST_GEOMETRY_PROJECTIONS_AITOFF_HPP

#include <boost/core/ignore_unused.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>

#include <boost/geometry/util/math.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace aitoff
    {
            enum mode_type {
                mode_aitoff = 0,
                mode_winkel_tripel = 1
            };

            template <typename T>
            struct par_aitoff
            {
                T    cosphi1;
                mode_type mode;
            };

            template <typename T, typename Parameters>
            struct base_aitoff_spheroid
            {
                par_aitoff<T> m_proj_parm;

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(Parameters const& , T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    T c, d;

                    if((d = acos(cos(lp_lat) * cos(c = 0.5 * lp_lon)))) {/* basic Aitoff */
                        xy_x = 2. * d * cos(lp_lat) * sin(c) * (xy_y = 1. / sin(d));
                        xy_y *= d * sin(lp_lat);
                    } else
                        xy_x = xy_y = 0.;
                    if (this->m_proj_parm.mode == mode_winkel_tripel) { /* Winkel Tripel */
                        xy_x = (xy_x + lp_lon * this->m_proj_parm.cosphi1) * 0.5;
                        xy_y = (xy_y + lp_lat) * 0.5;
                    }
                }
                /***********************************************************************************
                *
                * Inverse functions added by Drazen Tutic and Lovro Gradiser based on paper:
                *
                * I.Özbug Biklirici and Cengizhan Ipbüker. A General Algorithm for the Inverse
                * Transformation of Map Projections Using Jacobian Matrices. In Proceedings of the
                * Third International Symposium Mathematical & Computational Applications,
                * pages 175{182, Turkey, September 2002.
                *
                * Expected accuracy is defined by epsilon = 1e-12. Should be appropriate for
                * most applications of Aitoff and Winkel Tripel projections.
                *
                * Longitudes of 180W and 180E can be mixed in solution obtained.
                *
                * Inverse for Aitoff projection in poles is undefined, longitude value of 0 is assumed.
                *
                * Contact : dtutic@geof.hr
                * Date: 2015-02-16
                *
                ************************************************************************************/

                // INVERSE(s_inverse)  sphere
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(Parameters const& , T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    static const T pi = detail::pi<T>();
                    static const T two_pi = detail::two_pi<T>();
                    static const T epsilon = 1e-12;

                    int iter, max_iter = 10, round = 0, max_round = 20;
                    T D, C, f1, f2, f1p, f1l, f2p, f2l, dp, dl, sl, sp, cp, cl, x, y;

                    if ((fabs(xy_x) < epsilon) && (fabs(xy_y) < epsilon )) {
                        lp_lat = 0.; lp_lon = 0.;
                        return;
                    }

                    /* intial values for Newton-Raphson method */
                    lp_lat = xy_y; lp_lon = xy_x;
                    do {
                        iter = 0;
                        do {
                            sl = sin(lp_lon * 0.5); cl = cos(lp_lon * 0.5);
                            sp = sin(lp_lat); cp = cos(lp_lat);
                            D = cp * cl;
                            C = 1. - D * D;
                            D = acos(D) / math::pow(C, T(1.5));
                            f1 = 2. * D * C * cp * sl;
                            f2 = D * C * sp;
                            f1p = 2.* (sl * cl * sp * cp / C - D * sp * sl);
                            f1l = cp * cp * sl * sl / C + D * cp * cl * sp * sp;
                            f2p = sp * sp * cl / C + D * sl * sl * cp;
                            f2l = 0.5 * (sp * cp * sl / C - D * sp * cp * cp * sl * cl);
                            if (this->m_proj_parm.mode == mode_winkel_tripel) { /* Winkel Tripel */
                                f1 = 0.5 * (f1 + lp_lon * this->m_proj_parm.cosphi1);
                                f2 = 0.5 * (f2 + lp_lat);
                                f1p *= 0.5;
                                f1l = 0.5 * (f1l + this->m_proj_parm.cosphi1);
                                f2p = 0.5 * (f2p + 1.);
                                f2l *= 0.5;
                            }
                            f1 -= xy_x; f2 -= xy_y;
                            dl = (f2 * f1p - f1 * f2p) / (dp = f1p * f2l - f2p * f1l);
                            dp = (f1 * f2l - f2 * f1l) / dp;
                            dl = fmod(dl, pi); /* set to interval [-M_PI, M_PI] */
                            lp_lat -= dp;    lp_lon -= dl;
                        } while ((fabs(dp) > epsilon || fabs(dl) > epsilon) && (iter++ < max_iter));
                        if (lp_lat > two_pi) lp_lat -= 2.*(lp_lat-two_pi); /* correct if symmetrical solution for Aitoff */
                        if (lp_lat < -two_pi) lp_lat -= 2.*(lp_lat+two_pi); /* correct if symmetrical solution for Aitoff */
                        if ((fabs(fabs(lp_lat) - two_pi) < epsilon) && (!this->m_proj_parm.mode)) lp_lon = 0.; /* if pole in Aitoff, return longitude of 0 */

                        /* calculate x,y coordinates with solution obtained */
                        if((D = acos(cos(lp_lat) * cos(C = 0.5 * lp_lon))) != 0.0) {/* Aitoff */
                            x = 2. * D * cos(lp_lat) * sin(C) * (y = 1. / sin(D));
                            y *= D * sin(lp_lat);
                        } else
                            x = y = 0.;
                        if (this->m_proj_parm.mode == mode_winkel_tripel) { /* Winkel Tripel */
                            x = (x + lp_lon * this->m_proj_parm.cosphi1) * 0.5;
                            y = (y + lp_lat) * 0.5;
                        }
                    /* if too far from given values of x,y, repeat with better approximation of phi,lam */
                    } while (((fabs(xy_x-x) > epsilon) || (fabs(xy_y-y) > epsilon)) && (round++ < max_round));

                    if (iter == max_iter && round == max_round)
                    {
                        BOOST_THROW_EXCEPTION( projection_exception(error_non_convergent) );
                        //fprintf(stderr, "Warning: Accuracy of 1e-12 not reached. Last increments: dlat=%e and dlon=%e\n", dp, dl);
                    }
                }

                static inline std::string get_name()
                {
                    return "aitoff_spheroid";
                }

            };

            template <typename Parameters>
            inline void setup(Parameters& par)
            {
                par.es = 0.;
            }

            // Aitoff
            template <typename Parameters, typename T>
            inline void setup_aitoff(Parameters& par, par_aitoff<T>& proj_parm)
            {
                proj_parm.mode = mode_aitoff;
                setup(par);
            }

            // Winkel Tripel
            template <typename Params, typename Parameters, typename T>
            inline void setup_wintri(Params& params, Parameters& par, par_aitoff<T>& proj_parm)
            {
                static const T two_div_pi = detail::two_div_pi<T>();

                T phi1;

                proj_parm.mode = mode_winkel_tripel;
                if (pj_param_r<srs::spar::lat_1>(params, "lat_1", srs::dpar::lat_1, phi1)) {
                    if ((proj_parm.cosphi1 = cos(phi1)) == 0.)
                        BOOST_THROW_EXCEPTION( projection_exception(error_lat_larger_than_90) );
                } else /* 50d28' or phi1=acos(2/pi) */
                    proj_parm.cosphi1 = two_div_pi;
                setup(par);
            }

    }} // namespace detail::aitoff
    #endif // doxygen

    /*!
        \brief Aitoff projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Miscellaneous
         - Spheroid
        \par Example
        \image html ex_aitoff.gif
    */
    template <typename T, typename Parameters>
    struct aitoff_spheroid : public detail::aitoff::base_aitoff_spheroid<T, Parameters>
    {
        template <typename Params>
        inline aitoff_spheroid(Params const& , Parameters & par)
        {
            detail::aitoff::setup_aitoff(par, this->m_proj_parm);
        }
    };

    /*!
        \brief Winkel Tripel projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Miscellaneous
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel (degrees)
        \par Example
        \image html ex_wintri.gif
    */
    template <typename T, typename Parameters>
    struct wintri_spheroid : public detail::aitoff::base_aitoff_spheroid<T, Parameters>
    {
        template <typename Params>
        inline wintri_spheroid(Params const& params, Parameters & par)
        {
            detail::aitoff::setup_wintri(params, par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION_FI(srs::spar::proj_aitoff, aitoff_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION_FI(srs::spar::proj_wintri, wintri_spheroid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(aitoff_entry, aitoff_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(wintri_entry, wintri_spheroid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(aitoff_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(aitoff, aitoff_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(wintri, wintri_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_AITOFF_HPP


//---------------------------------------------------------------------------//
//!
//! \file   MonteCarlo_CompleteDopplerBroadenedPhotonEnergyDistribution.hpp
//! \author Alex Robinson
//! \brief  The complete Doppler broadened photon energy distribution decl.
//!
//---------------------------------------------------------------------------//

#ifndef MONTE_CARLO_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_HPP
#define MONTE_CARLO_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_HPP

// Boost Includes
#include <boost/scoped_ptr.hpp>

// FRENSIE Includes
#include <Teuchos_Array.hpp>

// FRENSIE Includes
#include "MonteCarlo_DopplerBroadenedPhotonEnergyDistribution.hpp"
#include "MonteCarlo_SubshellType.hpp"
#include "Utility_TabularOneDDistribution.hpp"

namespace MonteCarlo{

//! The complete (all subshells) Doppler broadened photon energy dist. class
class CompleteDopplerBroadenedPhotonEnergyDistribution : public DopplerBroadenedPhotonEnergyDistribution
{

public:

  //! Constructor
  CompleteDopplerBroadenedPhotonEnergyDistribution(
		     const Teuchos::Array<double>& endf_subshell_occupancies,
		     const Teuchos::Array<SubshellType>& endf_subshell_order );

  //! Destructor
  virtual ~CompleteDopplerBroadenedPhotonEnergyDistribution()
  { /* ... */ }

  //! Evaluate the subshell distribution
  virtual double evaluateSubshell( const double incoming_energy,
				   const double outgoing_energy,
				   const double scattering_angle_cosine,
				   const SubshellType subshell ) const = 0;

  //! Evaluate the PDF
  virtual double evaluateSubshellPDF( const double incoming_energy,
				      const double outgoing_energy,
				      const double scattering_angle_cosine,
				      const SubshellType subshell ) const = 0;

  //! Evaluate the integrated cross section (b/mu)
  virtual double evaluateSubshellIntegratedCrossSection(
				          const double incoming_energy,
					  const double scattering_angle_cosine,
					  const SubshellType subshell,
					  const double precision ) const = 0;

protected:

  // Sample an ENDF subshell
  void sampleENDFInteractionSubshell( SubshellType& shell_of_interaction,
				      unsigned& shell_index ) const;

private:

  // The ENDF subshell interaction probabilities
  boost::scoped_ptr<const Utility::TabularOneDDistribution>
  d_endf_subshell_occupancy_distribution;

  // The ENDF subshell order
  Teuchos::Array<SubshellType> d_endf_subshell_order;
};

} // end MonteCarlo namespace

#endif // end MONTE_CARLO_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_HPP

//---------------------------------------------------------------------------//
// end MonteCarlo_CompleteDopplerBroadenedPhotonEnergyDistribution.hpp
//---------------------------------------------------------------------------//


#include <iostream>
#include <vector>
#include <cassert>
#include <cstring>
#include <limits>
#include <mpi.h>
//#include <Eigen/Eigenvalues>

enum KernelType {CubicSpline = 0, WendlandC2 = 1, WendlandC4 = 2};

#include "particle_simulator.hpp"
#include "hdr_time.hpp"
#include "hdr_run.hpp"
#ifdef USE_INTRINSICS
#include "vector_x86.hpp"
#endif
#include "hdr_dimension.hpp"
#include "hdr_kernel.hpp"
#include "hdr_sph.hpp"
#include "hdr_hgas.hpp"
#include "hdr_bhns.hpp"

class SPHAnalysis : public HelmholtzGas {
public:
    SPHAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->uene  = 0.;
        this->alph  = 0.;
        this->alphu = 0.;
        this->ksr   = 0.;
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            this->cmps[k] = 0.;
        }
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf", &this->id, &this->istar, &this->mass);      //  3
        fscanf(fp, "%lf%lf%lf", &this->pos[0], &this->pos[1], &this->pos[2]); //  6
        fscanf(fp, "%lf%lf%lf", &this->vel[0], &this->vel[1], &this->vel[2]); //  9
        fscanf(fp, "%lf%lf%lf", &this->acc[0], &this->acc[1], &this->acc[2]); // 12
        fscanf(fp, "%lf%lf%lf", &this->uene, &this->alph, &this->alphu);      // 15
        fscanf(fp, "%lf%lf%6d", &this->dens, &this->ksr,  &this->np);         // 18
        fscanf(fp, "%lf%lf%lf", &this->vsnd, &this->pres, &this->temp);       // 21
        fscanf(fp, "%lf%lf%lf", &this->divv, &this->rotv, &this->bswt);       // 24
        fscanf(fp, "%lf%lf%lf", &this->pot,  &this->abar, &this->zbar);       // 27
        fscanf(fp, "%lf",       &this->enuc);                                 // 28
        fscanf(fp, "%lf%lf%lf", &this->vsmx, &this->udot, &this->dnuc);       // 31
        for(PS::S32 k = 0; k < NuclearReaction::NumberOfNucleon; k++) {       // 32 -- 44
            fscanf(fp, "%lf", &this->cmps[k]);
        }
        fscanf(fp, "%lf", &this->pot3);
        fscanf(fp, "%lf%lf%lf", &this->tempmax[0], &this->tempmax[1], &this->tempmax[2]);
        fscanf(fp, "%lf", &this->entr);
    }

    void writeAscii(FILE *fp) const {
        fprintf(fp, "%6d %2d %+e", this->id, this->istar, this->mass);
        fprintf(fp, " %+e %+e %+e", this->pos[0], this->pos[1], this->pos[2]);
        fprintf(fp, " %+e %+e %+e", this->vel[0], this->vel[1], this->vel[2]);
        fprintf(fp, " %+e %+e %+e", this->acc[0], this->acc[1], this->acc[2]);
        fprintf(fp, " %+e %+e %+e", this->uene, this->alph, this->alphu);
        fprintf(fp, " %+e %+e %6d", this->dens, this->ksr,  this->np);
        fprintf(fp, " %+e %+e %+e", this->vsnd, this->pres, this->temp);
        fprintf(fp, " %+e %+e %+e", this->divv, this->rotv, this->bswt);
        fprintf(fp, " %+e %+e %+e", this->pot, this->abar, this->zbar);
        fprintf(fp, " %+e",         this->enuc);
        fprintf(fp, " %+e %+e %+e", this->vsmx, this->udot, this->dnuc);
        for(PS::S32 k = 0; k < NuclearReaction::NumberOfNucleon; k++) {
            fprintf(fp, " %+.3e", this->cmps[k]);
        }
        fprintf(fp, " %+e", this->pot3);
        fprintf(fp, " %+e %+e %+e", this->tempmax[0], this->tempmax[1], this->tempmax[2]);
        fprintf(fp, " %+e", this->entr);
        fprintf(fp, "\n");
    }

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }
};

class BlackHoleNeutronStarAnalysis : public BlackHoleNeutronStar {
public:
    BlackHoleNeutronStarAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->acc   = 0.;
        this->eps   = 0.;
        this->pot   = 0.;
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf",
               &this->id, &this->istar, &this->mass,
               &this->pos[0], &this->pos[1], &this->pos[2],
               &this->vel[0], &this->vel[1], &this->vel[2],
               &this->acc[0], &this->acc[1], &this->acc[2],
               &this->eps, &this->pot);
    }

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }
};

template <class Tsph,
          class Tbhns>
void projectOnOrbitalPlane(char * ofile,
                           Tsph  & sph,
                           Tbhns & bhns,
                           PS::F64vec xmin,
                           PS::F64    wdth,
                           PS::S64    nmax) {

    PS::F64 dx    = wdth / (PS::F64)nmax;
    PS::F64 dxinv = 1. / dx;

#if 1
    PS::S64 **ptcl;
    PS::F64 **dens;
    PS::F64 **temp;
    PS::F64 **divv;
    ptcl = (PS::S64 **) malloc(sizeof(PS::S64 *) * nmax);
    dens = (PS::F64 **) malloc(sizeof(PS::F64 *) * nmax);
    temp = (PS::F64 **) malloc(sizeof(PS::F64 *) * nmax);
    divv = (PS::F64 **) malloc(sizeof(PS::F64 *) * nmax);
    for(PS::S64 i = 0; i < nmax; i++) {
        ptcl[i] = (PS::S64 *) malloc(sizeof(PS::S64) * nmax);
        dens[i] = (PS::F64 *) malloc(sizeof(PS::F64) * nmax);
        temp[i] = (PS::F64 *) malloc(sizeof(PS::F64) * nmax);
        divv[i] = (PS::F64 *) malloc(sizeof(PS::F64) * nmax);
    }
#else
    const PS::S64 nmaxmax = 512;
    assert(nmaxmax > nmax);
    static PS::S64 ptcl[nmaxmax][nmaxmax];
    static PS::F64 dens[nmaxmax][nmaxmax];
    static PS::F64 temp[nmaxmax][nmaxmax];
    static PS::F64 divv[nmaxmax][nmaxmax];
#endif

    for(PS::S64 i = 0; i < nmax; i++) {
        for(PS::S64 j = 0; j < nmax; j++) {
            ptcl[i][j] = 0;
            dens[i][j] = 0.;
            temp[i][j] = 0.;
            divv[i][j] = 0.;
        }
    }

    for(PS::S64 i = 0; i < sph.getNumberOfParticleLocal(); i++) {
        //PS::F64vec px = sph[i].pos - bhns[0].pos;
        PS::F64vec px = sph[i].pos;
        if(fabs(px[2]) > fabs(sph[i].ksr)) {
            continue;
        }
        PS::S64 nxi = (PS::S64)((px[0] - xmin[0]) * dxinv);
        if(nxi < 0 || nmax <= nxi) {
            continue;
        }
        PS::S64 nyi = (PS::S64)((px[1] - xmin[1]) * dxinv);
        if(nyi < 0 || nmax <= nyi) {
            continue;
        }
        ptcl[nxi][nyi] += 1;
        dens[nxi][nyi] += sph[i].dens;
        temp[nxi][nyi] += sph[i].temp;
        divv[nxi][nyi] += sph[i].divv;
    }

    for(PS::S64 i = 0; i < nmax; i++) {
        for(PS::S64 j = 0; j < nmax; j++) {
            ptcl[i][j] = PS::Comm::getSum(ptcl[i][j]);
            dens[i][j] = PS::Comm::getSum(dens[i][j]);
            temp[i][j] = PS::Comm::getSum(temp[i][j]);
            divv[i][j] = PS::Comm::getSum(divv[i][j]);;
        }
    }

    for(PS::S64 i = 0; i < nmax; i++) {
        for(PS::S64 j = 0; j < nmax; j++) {
            PS::F64 pinv = ((ptcl[i][j] != 0) ? (1. / ptcl[i][j]) : 0.);
            dens[i][j] *= pinv;
            temp[i][j] *= pinv;
            divv[i][j] *= pinv;
        }
    }

    if(PS::Comm::getRank() == 0) {
        FILE * fp = fopen(ofile, "w");
        for(PS::S64 i = 0; i < nmax; i++) {
            for(PS::S64 j = 0; j < nmax; j++) {
                if(ptcl[i][j] == 0) {
                    continue;
                }
                PS::F64 px = xmin[0] + dx * (PS::F64)i;
                PS::F64 py = xmin[1] + dx * (PS::F64)j;
                fprintf(fp, "%+e %+e %+e %+e %+e %8lld\n",
                        px, py,
                        dens[i][j], temp[i][j], divv[i][j],
                        ptcl[i][j]);
            }
        }
        fclose(fp);
    }

}

int main(int argc, char ** argv) {
    MPI_Init(&argc, &argv);

    PS::ParticleSystem<SPHAnalysis> sph;
    sph.initialize();
    sph.createParticle(0);
    sph.setNumberOfParticleLocal(0);
    PS::ParticleSystem<BlackHoleNeutronStarAnalysis> bhns;
    bhns.initialize();
    bhns.createParticle(0);
    bhns.setNumberOfParticleLocal(0);

    char idir[1024], otype[1024];
    PS::S32 fflag, nfile;
    PS::S64 ibgn, iend;
    PS::F64vec xmin;
    PS::F64    wdth;
    PS::S64    nnxx;
    FILE * fp = fopen(argv[1], "r");
    fscanf(fp, "%s", idir);
    fscanf(fp, "%s", otype);
    fscanf(fp, "%d %d", &fflag, &nfile);
    fscanf(fp, "%lld%lld", &ibgn, &iend);
    fscanf(fp, "%lf%lf%lf", &xmin[0], &xmin[1], &xmin[2]);
    fscanf(fp, "%lf%lld", &wdth, &nnxx);
    fclose(fp);

    for(PS::S64 itime = ibgn; itime <= iend; itime++) {
        if(fflag == 0) {
            /*
            char sfile[1024], bfile[1024];
            sprintf(sfile, "%s/sph_t%04d.dat",  idir, itime);
            sprintf(bfile, "%s/bhns_t%04d.dat", idir, itime);
            fp = fopen(sfile, "r");
            assert(fp);
            sph.readParticleAscii(sfile);
            fclose(fp);
            fp = fopen(bfile, "r");
            if(fp == NULL) {
                sprintf(bfile, "%s/origin.dat", idir);
                fp = fopen(bfile, "r");
            }
            bhns.readParticleAscii(bfile);
            fclose(fp);
            */
        } else {
            SPHAnalysis tmpsph;
            PS::S64 nrank = PS::Comm::getNumberOfProc();
            PS::S64 irank = PS::Comm::getRank();
            PS::S64 ihead = nfile *  irank      / nrank;
            PS::S64 itail = nfile * (irank + 1) / nrank;
            PS::S64 ntot = 0;
            for(PS::S64 ifile = ihead; ifile < itail; ifile++) {
                char sfile[1024];
                sprintf(sfile, "%s/sph_t%04d_p%06d_i%06d.dat",  idir, itime, nfile, ifile);
                fp = fopen(sfile, "r");
                assert(fp);
                PS::S64 ntmp = 0;
                for(PS::S32 c; (c = getc(fp)) != EOF; ntmp += ('\n' == c ? 1 : 0)) {
                    ;
                }
                fclose(fp);
                sph.setNumberOfParticleLocal(ntot+ntmp);
                fp = fopen(sfile, "r");
                for(PS::S64 i = 0; i < ntmp; i++) {
                    sph[ntot+i].readAscii(fp);
                }
                fclose(fp);
                ntot += ntmp;
            }
        }

        char ofile[1024];
        sprintf(ofile, "%s_t%04d.dat", otype, itime);
        projectOnOrbitalPlane(ofile, sph, bhns, xmin, wdth, nnxx);
    }

    MPI_Finalize();

    return 0;
}


//  Copyright (c) 2021 DNV AS
//
//  Distributed under the Boost Software License, Version 1.0.
//  See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt
#include "Units\Reflection\ReflectQuantities.h"

#include "Units\Quantity.h"
#include "Units\Length.h"
#include "Units\Time.h"
#include "Units\Force.h"
#include "Units\Angle.h"
#include "Units\Mass.h"
#include "Units\Area.h"
#include "Units\Volume.h"
#include "Units\Curvature.h"
#include "Units\Energy.h"
#include "Units\TempDiff.h"
#include "Units\Acceleration.h"
#include "Units\ForcePerUnitArea.h"
#include "Units\ForcePerUnitLength.h"
#include "Units\Velocity.h"
#include "Units\MassDensity.h"
#include "Units\Runtime\DynamicQuantity.h"
#include "Units\ForAllDimensions.h"

#include "Reflection\Classes\Class.h"
#include "Reflection\TypeConversions\UserConversion.h"

#include "Units\Reflection\Details\ReflectAcceleration.h"
#include "Units\Reflection\Details\ReflectAngle.h"
#include "Units\Reflection\Details\ReflectArea.h"
#include "Units\Reflection\Details\ReflectAxialStiffness.h"
#include "Units\Reflection\Details\ReflectBendingStiffness.h"
#include "Units\Reflection\Details\ReflectCoupledDamping.h"
#include "Units\Reflection\Details\ReflectCoupledMass.h"
#include "Units\Reflection\Details\ReflectCoupledQuadraticDamping.h"
#include "Units\Reflection\Details\ReflectCoupledQuadraticDampingForce.h"
#include "Units\Reflection\Details\ReflectCoupledQuadraticDampingMoment.h"
#include "Units\Reflection\Details\ReflectCoupledStiffness.h"
#include "Units\Reflection\Details\ReflectCurvature.h"
#include "Units\Reflection\Details\ReflectCurvatureResponseSpectrum.h"
#include "Units\Reflection\Details\ReflectDampingPerUnitLength.h"
#include "Units\Reflection\Details\ReflectDisplacementResponseSpectrum.h"
#include "Units\Reflection\Details\ReflectEnergy.h"
#include "Units\Reflection\Details\ReflectEnergyDensity.h"
#include "Units\Reflection\Details\ReflectFirstMomentOfArea.h"
#include "Units\Reflection\Details\ReflectForce.h"
#include "Units\Reflection\Details\ReflectForceEnergyDensity.h"
#include "Units\Reflection\Details\ReflectForcePerUnitArea.h"
#include "Units\Reflection\Details\ReflectForcePerUnitAreaPerAngle.h"
#include "Units\Reflection\Details\ReflectForcePerUnitLength.h"
#include "Units\Reflection\Details\ReflectForcePerUnitVolume.h"
#include "Units\Reflection\Details\ReflectForceResponseSpectrum.h"
#include "Units\Reflection\Details\ReflectFrequency.h"
#include "Units\Reflection\Details\ReflectKinematicViscosity.h"
#include "Units\Reflection\Details\ReflectLength.h"
#include "Units\Reflection\Details\ReflectMass.h"
#include "Units\Reflection\Details\ReflectMassDensity.h"
#include "Units\Reflection\Details\ReflectMassMomentOfInertia.h"
#include "Units\Reflection\Details\ReflectMassPerUnitArea.h"
#include "Units\Reflection\Details\ReflectMassPerUnitLength.h"
#include "Units\Reflection\Details\ReflectMomentOfForce.h"
#include "Units\Reflection\Details\ReflectMomentOfForceEnergyDensity.h"
#include "Units\Reflection\Details\ReflectMomentResponseSpectrum.h"
#include "Units\Reflection\Details\ReflectPressure.h"
#include "Units\Reflection\Details\ReflectQuadraticDampingPerUnitLength.h"
#include "Units\Reflection\Details\ReflectRotationalAcceleration.h"
#include "Units\Reflection\Details\ReflectRotationalDamping.h"
#include "Units\Reflection\Details\ReflectRotationalFlexibility.h"
#include "Units\Reflection\Details\ReflectRotationalQuadraticDamping.h"
#include "Units\Reflection\Details\ReflectRotationalResponseSpectrum.h"
#include "Units\Reflection\Details\ReflectRotationalStiffness.h"
#include "Units\Reflection\Details\ReflectRotationalStiffnessPerUnitLength.h"
#include "Units\Reflection\Details\ReflectRotationalVelocity.h"
#include "Units\Reflection\Details\ReflectRotationPerLength.h"
#include "Units\Reflection\Details\ReflectSecondMomentOfArea.h"
#include "Units\Reflection\Details\ReflectSectionModulus.h"
#include "Units\Reflection\Details\ReflectSpecificHeat.h"
#include "Units\Reflection\Details\ReflectStress.h"
#include "Units\Reflection\Details\ReflectTempDiff.h"
#include "Units\Reflection\Details\ReflectTempGradient.h"
#include "Units\Reflection\Details\ReflectThermalExpansionCoeff.h"
#include "Units\Reflection\Details\ReflectTime.h"
#include "Units\Reflection\Details\ReflectTorsionalStiffness.h"
#include "Units\Reflection\Details\ReflectTranslationalDamping.h"
#include "Units\Reflection\Details\ReflectTranslationalFlexibility.h"
#include "Units\Reflection\Details\ReflectTranslationalQuadraticDamping.h"
#include "Units\Reflection\Details\ReflectTranslationalStiffness.h"
#include "Units\Reflection\Details\ReflectTranslationalStiffnessPerUnitLength.h"
#include "Units\Reflection\Details\ReflectVelocity.h"
#include "Units\Reflection\Details\ReflectVelocityResponseSpectrum.h"
#include "Units\Reflection\Details\ReflectVolume.h"
#include "Units\Reflection\ReflectCommon.h"
#include "Units\Reflection\ReflectOperators.h"
#include "Units\Runtime\UnitParser.h"
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include "Reflection\Attributes\UndefinedAttribute.h"

using namespace DNVS::MoFa::Reflection::Variants;
using namespace DNVS::MoFa::Reflection::Operators;
using namespace DNVS::MoFa::Reflection::TypeLibraries;
using namespace DNVS::MoFa::Reflection::Types;
using namespace DNVS::MoFa::Reflection::TypeConversions;
using namespace DNVS::MoFa::Reflection::Classes;

namespace DNVS {namespace MoFa {namespace Units { namespace Reflection {
    void ReflectQuantities(TypeLibraryPointer typeLibrary)
    {
        ReflectAcceleration(typeLibrary);
        ReflectAngle(typeLibrary);
        ReflectArea(typeLibrary);
        ReflectAxialStiffness(typeLibrary);
        ReflectBendingStiffness(typeLibrary);
        ReflectCoupledDamping(typeLibrary);
        ReflectCoupledMass(typeLibrary);
        ReflectCoupledQuadraticDamping(typeLibrary);
        ReflectCoupledQuadraticDampingForce(typeLibrary);
        ReflectCoupledQuadraticDampingMoment(typeLibrary);
        ReflectCoupledStiffness(typeLibrary);
        ReflectCurvature(typeLibrary);
        ReflectCurvatureResponseSpectrum(typeLibrary);
        ReflectDampingPerUnitLength(typeLibrary);
        ReflectDisplacementResponseSpectrum(typeLibrary);
        ReflectEnergy(typeLibrary);
        ReflectEnergyDensity(typeLibrary);
        ReflectFirstMomentOfArea(typeLibrary);
        ReflectForce(typeLibrary);
        ReflectForceEnergyDensity(typeLibrary);
        ReflectForcePerUnitArea(typeLibrary);
        ReflectForcePerUnitAreaPerAngle(typeLibrary);
        ReflectForcePerUnitLength(typeLibrary);
        ReflectForcePerUnitVolume(typeLibrary);
        ReflectForceResponseSpectrum(typeLibrary);
        ReflectFrequency(typeLibrary);
        ReflectKinematicViscosity(typeLibrary);
        ReflectLength(typeLibrary);
        ReflectMass(typeLibrary);
        ReflectMassDensity(typeLibrary);
        ReflectMassMomentOfInertia(typeLibrary);
        ReflectMassPerUnitArea(typeLibrary);
        ReflectMassPerUnitLength(typeLibrary);
        ReflectMomentOfForce(typeLibrary);
        ReflectMomentOfForceEnergyDensity(typeLibrary);
        ReflectMomentResponseSpectrum(typeLibrary);
        ReflectPressure(typeLibrary);
        ReflectQuadraticDampingPerUnitLength(typeLibrary);
        ReflectRotationalAcceleration(typeLibrary);
        ReflectRotationalDamping(typeLibrary);
        ReflectRotationalFlexibility(typeLibrary);
        ReflectRotationalQuadraticDamping(typeLibrary);
        ReflectRotationalResponseSpectrum(typeLibrary);
        ReflectRotationalStiffness(typeLibrary);
        ReflectRotationalStiffnessPerUnitLength(typeLibrary);
        ReflectRotationalVelocity(typeLibrary);
        ReflectRotationPerLength(typeLibrary);
        ReflectSecondMomentOfArea(typeLibrary);
        ReflectSectionModulus(typeLibrary);
        ReflectSpecificHeat(typeLibrary);
        ReflectStress(typeLibrary);
        ReflectTempDiff(typeLibrary);
        ReflectTempGradient(typeLibrary);
        ReflectThermalExpansionCoeff(typeLibrary);
        ReflectTime(typeLibrary);
        ReflectTorsionalStiffness(typeLibrary);
        ReflectTranslationalDamping(typeLibrary);
        ReflectTranslationalFlexibility(typeLibrary);
        ReflectTranslationalQuadraticDamping(typeLibrary);
        ReflectTranslationalStiffness(typeLibrary);
        ReflectTranslationalStiffnessPerUnitLength(typeLibrary);
        ReflectVelocity(typeLibrary);
        ReflectVelocityResponseSpectrum(typeLibrary);
        ReflectVolume(typeLibrary);

        ReflectCommon(typeLibrary);

        ReflectAllOperators<Quantity<Dimension<0, 0, 0, 0, 2> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<-2, 0, 0, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<2, -2, 0, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<2, 0, 1, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<0, 0, 2, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<2, 0, 2, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<0, 1, 1, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<-1, 0, 1, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<0, 2, 1, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<0, 0, 0, 0, 0> > >(typeLibrary);
        ReflectAllOperators<Quantity<Dimension<-3, 0, 1, 0, 0> > >(typeLibrary);

        AddUserConversion<Quantity<NoDimension>, double>(typeLibrary->GetConversionGraph(), ConversionType::StandardConversion, &Quantity<NoDimension>::operator double);
        AddConstructorConversion<double, Quantity<NoDimension>>(typeLibrary->GetConversionGraph(), ConversionType::StandardConversion);
    }

    struct ConverterFromDynamicQuantityToStaticQuantity
    {
    public:
        ConverterFromDynamicQuantityToStaticQuantity(const Variant& dynamicQuantity)
            :   m_dynamicQuantity(InternalVariantService::UnreflectUnchecked<Runtime::DynamicQuantity>(dynamicQuantity))
            ,   m_staticQuantity(dynamicQuantity)
        {
        }
        template<typename DimensionT>
        struct StaticQuantityComputer
        {
            typedef Quantity<DimensionT> Type;
        };
        template<>
        struct StaticQuantityComputer<Dimension<0, 0, 0, 0, 0>>
        {
            typedef double Type;
        };
        template<typename DimensionT>
        void Apply()
        {
            typedef typename StaticQuantityComputer<DimensionT>::Type StaticQuantity;
            if(Runtime::DynamicDimension(DimensionT()) == m_dynamicQuantity.GetSimplifiedUnit().GetDimension())
                m_staticQuantity = VariantService::ReflectType<StaticQuantity>(StaticQuantity(m_dynamicQuantity.GetNeutralValue()));
        }
        Variant GetStaticQuantity() const
        {
            return m_staticQuantity;
        }
    private:
        Variant m_staticQuantity;
        Runtime::DynamicQuantity m_dynamicQuantity;
    };

    struct FallbackUnitConverter : public IConversion
    {
        virtual Variant Convert(const Variant& other)
        {
            ConverterFromDynamicQuantityToStaticQuantity converter(other);
            ForAllUsedDimensions(converter);
            converter.Apply<Dimension<0, 0, 0, 0, 0>>();
            return converter.GetStaticQuantity();
        }
        virtual void IntrusiveConvert( Variant& variable )
        {
            ConverterFromDynamicQuantityToStaticQuantity converter(variable);
            ForAllUsedDimensions(converter);
            converter.Apply<Dimension<0, 0, 0, 0, 0>>();
            variable = converter.GetStaticQuantity();
        }
    };
    Runtime::DynamicQuantity DivideDynamicQuantities(const Runtime::DynamicQuantity& lhs, const Runtime::DynamicQuantity& rhs)
    {
        Runtime::DynamicQuantity result = lhs / rhs;
        result.TrySimplifyUnit();
        return result;
    }
    Runtime::DynamicQuantity MultiplyDynamicQuantities(const Runtime::DynamicQuantity& lhs, const Runtime::DynamicQuantity& rhs)
    {
        Runtime::DynamicQuantity result = lhs * rhs;
        result.TrySimplifyUnit();
        return result;
    }
    class StringToQuantityConversion : public IConversion
    {
    public:
        // creates copy of variable, converts it and returns converted copy
        virtual Variant Convert(const Variant& variable)
        {
            const std::string& quantity = InternalVariantService::UnreflectUnchecked<const std::string&>(variable);
            using boost::spirit::qi::double_;
            using boost::spirit::qi::char_;
            using boost::spirit::qi::phrase_parse;
            using boost::spirit::qi::_1;
            using boost::spirit::qi::space;

            double value;
            std::vector<char> unitVect;
            if (phrase_parse(quantity.begin(), quantity.end(), double_[boost::phoenix::ref(value) = _1] >> (*char_)[boost::phoenix::ref(unitVect) = _1], space))
            {
                if (unitVect.empty())
                    return VariantService::Reflect(std::move(value));
                else
                {
                    std::string unit(&unitVect.front(), unitVect.size());
                    return VariantService::Reflect(Runtime::DynamicQuantity(value, unit));
                }
            }
            else
                throw std::runtime_error("Unable to convert '" + quantity + "' to a quantity");
        }

        // converts variable itself (it is overwritten)
        virtual void IntrusiveConvert(Variant& variable) {
            variable = Convert(variable);
        }
    };

    void ReflectDynamicQuantities(TypeLibraryPointer typeLibrary)
    {
        Class<Runtime::DynamicQuantity> cls(typeLibrary, "DynamicQuantity");
        cls.AddAttribute<UndefinedAttribute>([](const Runtime::DynamicQuantity& q) {return !!_isnan(q.GetValue()); });
        cls.Constructor<double>();
        cls.Operator(This.Const + This.Const);
        cls.Operator(This.Const - This.Const);
        cls.Operator(This.Const * This.Const, &MultiplyDynamicQuantities);
        cls.Operator(This.Const / This.Const, &DivideDynamicQuantities);
        cls.Operator(This.Const / double());
        cls.Operator(double() / This.Const);
        cls.Operator(This.Const * double());
        cls.Operator(double() * This.Const);
        cls.Operator(-This.Const);
        cls.Operator( + This.Const);
        cls.Operator(This.Const < This.Const);
        cls.Operator(This.Const <= This.Const);
        cls.Operator(This.Const > This.Const);
        cls.Operator(This.Const >= This.Const);
        cls.Operator(This.Const == This.Const);
        cls.Operator(This.Const != This.Const);

        typeLibrary->GetConversionGraph()->AddConversion(
            TypeId<Runtime::DynamicQuantity>(),
            TypeId<void>(),
            ConversionType::FallbackConversion,
            ConversionPointer(new FallbackUnitConverter)
            );
        typeLibrary->GetConversionGraph()->AddConversion(
            TypeId<const std::string>(),
            TypeId<void>(),
            ConversionType::FallbackConversion,
            ConversionPointer(new StringToQuantityConversion)
        );
    }
}}}}