/*
 *       File name:  libcn/standalone-neurons.cc
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 *                   building on original work by Thomas Nowotny <tnowotny@ucsd.edu>
 * Initial version:  2009-04-08
 *
 *         Purpose:  standalone neurons (those not having state vars
 *                   on model's integration vector)
 *
 *         License:  GPL-2+
 */

#if HAVE_CONFIG_H && !defined(VERSION)
#  include "config.h"
#endif

#include <iostream>

#include "types.hh"
#include "model.hh"
#include "standalone-neurons.hh"



cnrun::C_StandaloneNeuron::
C_StandaloneNeuron (TUnitType type_, const string& label_,
                    double x, double y, double z,
                    CModel *M_, int s_mask)
      : C_BaseNeuron( type_, label_, x, y, z, M_, s_mask),
        C_StandaloneAttributes( __CNUDT[type_].vno)
{
        reset_vars();
        if ( M )
                M->include_unit( this);
}






// --------- Rhythm'n'Blues

const char* const cnrun::CN_ParamNames_NeuronHH_r[] = {
        "a, " CN_PU_FREQUENCY,
        "I₀, " CN_PU_CURRENT,
        "r in F(I) = a (I-I₀)^r",
        "Externally applied DC, " CN_PU_CURRENT,
};
const char* const cnrun::CN_ParamSyms_NeuronHH_r[] = {
        "a",
        "I0",
        "r",
        "Idc",
};
const double cnrun::CN_Params_NeuronHH_r[] = {
        0.185,            //   a,
        0.0439,           //   I0,
        0.564,            //   r in F(I) = a * (I-I0)^r
        0.                // Externally applied DC
};


const char* const cnrun::CN_VarNames_NeuronHH_r[] = {
        "Spiking rate, " CN_PU_FREQUENCY,
};
const char* const cnrun::CN_VarSyms_NeuronHH_r[] = {
        "F",
};
const double cnrun::CN_Vars_NeuronHH_r[] = {
         0.        // frequency F
};


double
cnrun::CNeuronHH_r::
F( vector<double>& x) const
{
        double subsq = Isyn(x) - P[_I0_] + P[_Idc_];
        if ( subsq <= 0. )
                return 0.;
        else {
                return P[_a_] * pow( subsq, P[_r_]);
        }
}

void
cnrun::CNeuronHH_r::
preadvance()
{
        double subsq = Isyn() - P[_I0_] + P[_Idc_];
//        printf( "%s->Isyn(x) = %g,\tsubsq = %g\n", _label, Isyn(), subsq);
        if ( subsq <= 0. )
                V_next[0] = 0;
        else
                V_next[0] = P[_a_] * pow( subsq, P[_r_]);
}










const char* const cnrun::CN_ParamNames_OscillatorPoissonDot[] = {
        "Rate λ, " CN_PU_RATE,
        "Resting potential, " CN_PU_POTENTIAL,
        "Potential when firing, " CN_PU_POTENTIAL,
};
const char* const cnrun::CN_ParamSyms_OscillatorPoissonDot[] = {
        "lambda",
        "Vrst",
        "Vfir",
};
const double cnrun::CN_Params_OscillatorPoissonDot[] = {
        0.02,        // firing rate Lambda [1/ms]=[10^3 Hz]
      -60.0,        // input neuron resting potential
       20.0,        // input neuron potential when firing
};

const char* const cnrun::CN_VarNames_OscillatorPoissonDot[] = {
        "Membrane potential, " CN_PU_POTENTIAL,
        "Spikes recently fired",
//        "Time"
};
const char* const cnrun::CN_VarSyms_OscillatorPoissonDot[] = {
        "E",
        "nspk",
//        "t"
};
const double cnrun::CN_Vars_OscillatorPoissonDot[] = {
        -60.,        // = Vrst, per initialization code found in ctor
          0,
//          0.
};



inline namespace {
#define _THIRTEEN_ 13
unsigned long __factorials[_THIRTEEN_] = {
        1,
        1, 2, 6, 24, 120,
        720, 5040, 40320, 362880L, 3628800L,
        39916800L, 479001600L
};

inline double
__attribute__ ((pure))
factorial( unsigned n)
{
        if ( n < _THIRTEEN_ )
                return __factorials[n];
        else {
                //cerr << n << "!" << endl;
                return __factorials[_THIRTEEN_-1] * factorial(n-_THIRTEEN_);
        }
}
}

void
cnrun::COscillatorDotPoisson::
possibly_fire()
{
        double        lt = P[_lambda_] * M->dt(),
                dice = M->rng_sample(),
                probk = 0.;

        unsigned k;
        for ( k = 0; ; k++ ) {
                probk += exp( -lt) * pow( lt, (double)k) / factorial(k);
                if ( probk > dice ) {
                        nspikes() = k;
                        break;
                }
        }

        if ( k ) {
                _status |=  CN_NFIRING;
                var_value(0) = P[_Vfir_];
        } else {
                _status &= ~CN_NFIRING;
                var_value(0) = P[_Vrst_];
        }
}



void
cnrun::COscillatorDotPoisson::
do_detect_spike_or_whatever()
{
        unsigned n = n_spikes_in_last_dt();
        if ( n > 0 ) {
                for ( unsigned qc = 0; qc < n; qc++ )
                        _spikelogger_agent->spike_history.push_back( model_time());
                _spikelogger_agent->_status |= CN_KL_ISSPIKINGNOW;
                _spikelogger_agent->t_last_spike_start = _spikelogger_agent->t_last_spike_end = model_time();
        } else
                _spikelogger_agent->_status &= ~CN_KL_ISSPIKINGNOW;
}











const char* const cnrun::CN_ParamNames_OscillatorPoisson[] = {
        "Rate λ, " CN_PU_RATE,
        "Input neuron resting potential, " CN_PU_POTENTIAL,
        "Input neuron potential when firing, " CN_PU_POTENTIAL,
        "Spike time, " CN_PU_TIME,
        "Spike time + refractory period, " CN_PU_TIME,
};
const char* const cnrun::CN_ParamSyms_OscillatorPoisson[] = {
        "lambda",
        "trel",
        "trel+trfr",
        "Vrst",
        "Vfir",
};
const double cnrun::CN_Params_OscillatorPoisson[] = {
        0.02,        // firing rate Lambda [1/ms]=[10^3 Hz]
        0.0,        // spike time
        0.0,        // refractory period + spike time
      -60.0,        // input neuron resting potential
       20.0,        // input neuron potential when firing
};

const char* const cnrun::CN_VarNames_OscillatorPoisson[] = {
        "Membrane potential E, " CN_PU_POTENTIAL,
};
const char* const cnrun::CN_VarSyms_OscillatorPoisson[] = {
        "E",
};
const double cnrun::CN_Vars_OscillatorPoisson[] = {
        -60.,
};



void
cnrun::COscillatorPoisson::
possibly_fire()
{
        if ( _status & CN_NFIRING )
                if ( model_time() - _spikelogger_agent->t_last_spike_start > P[_trel_] ) {
                        (_status &= ~CN_NFIRING) |= CN_NREFRACT;
                        _spikelogger_agent->t_last_spike_end = model_time();
                }
        if ( _status & CN_NREFRACT )
                if ( model_time() - _spikelogger_agent->t_last_spike_start > P[_trelrfr_] )
                        _status &= ~CN_NREFRACT;

        if ( !(_status & (CN_NFIRING | CN_NREFRACT)) ) {
                double lt = P[_lambda_] * M->dt();
                if ( M->rng_sample() <= exp( -lt) * lt ) {
                        _status |= CN_NFIRING;
                        _spikelogger_agent->t_last_spike_start = model_time() /* + M->dt() */ ;
                }
        }

//        E() = next_state_E;
//        next_state_E = (_status & CN_NFIRING) ?P.n.Vfir :P.n.Vrst;
        var_value(0) = (_status & CN_NFIRING) ?P[_Vfir_] :P[_Vrst_];
//        if ( strcmp( label, "ORNa.1") == 0 ) cout << label << ": firing_started = " << t_firing_started << ", firing_ended = " << t_firing_ended << " E = " << E() << endl;
}


void
cnrun::COscillatorPoisson::
do_detect_spike_or_whatever()
{
        unsigned n = n_spikes_in_last_dt();
        if ( n > 0 ) {
                if ( !(_spikelogger_agent->_status & CN_KL_ISSPIKINGNOW) ) {
                        _spikelogger_agent->spike_history.push_back( model_time());
                        _spikelogger_agent->_status |= CN_KL_ISSPIKINGNOW;
                }
        } else
                if ( _spikelogger_agent->_status & CN_KL_ISSPIKINGNOW ) {
                        _spikelogger_agent->_status &= ~CN_KL_ISSPIKINGNOW;
                        _spikelogger_agent->t_last_spike_end = model_time();
                }
}





// Map neurons require descrete time

const double cnrun::CN_Params_NeuronMap[] = {
        60.0,                // 0 - Vspike: spike Amplitude factor
         3.0002440,        // 1 - alpha: "steepness / size" parameter
        -2.4663490,        // 3 - gamma: "shift / excitation" parameter
         2.64,                // 2 - beta: input sensitivity
         0.,
// Old comment by TN: beta chosen such that Isyn= 10 "nA" is the threshold for spiking
};
const char* const cnrun::CN_ParamNames_NeuronMap[] = {
        "Spike amplitude factor, " CN_PU_POTENTIAL,
        "\"Steepness / size\" parameter α",
        "\"Shift / excitation\" parameter γ",
        "Input sensitivity β, " CN_PU_RESISTANCE,
        "External DC, " CN_PU_CURRENT,
};
const char* const cnrun::CN_ParamSyms_NeuronMap[] = {
        "Vspike",
        "alpha",
        "gamma",
        "beta",
        "Idc"
};

const double cnrun::CN_Vars_NeuronMap[] = {
      -50,        // E
};
const char* const cnrun::CN_VarNames_NeuronMap[] = {
        "Membrane potential E, " CN_PU_POTENTIAL
};
const char* const cnrun::CN_VarSyms_NeuronMap[] = {
        "E",
};


cnrun::CNeuronMap::
CNeuronMap (const string& inlabel, double x, double y, double z, CModel *inM, int s_mask)
      : C_StandaloneConductanceBasedNeuron( NT_MAP, inlabel, x, y, z, inM, s_mask)
{
        if ( inM ) {
                if ( isfinite( inM->_discrete_dt) && inM->_discrete_dt != fixed_dt )
                        throw "Inappropriate discrete dt";

                inM -> _discrete_dt = fixed_dt;
        }
}



void
cnrun::CNeuronMap::
preadvance()
{
        double Vspxaxb = P[_Vspike_] * (P[_alpha_] + P[_gamma_]);
        V_next[0] =
                ( E() <= 0. )
                  ? P[_Vspike_] * ( P[_alpha_] * P[_Vspike_] / (P[_Vspike_] - E() - P[_beta_] * (Isyn() + P[_Idc_]))
                                   + P[_gamma_] )
                  : ( E() <= Vspxaxb && _E_prev <= 0.)
                    ? Vspxaxb
                    : -P[_Vspike_];

        _E_prev = E();
}







// ----- Pulse

const char* const cnrun::CN_ParamNames_NeuronDotPulse[] = {
        "Frequency f, " CN_PU_FREQUENCY,
        "Resting potential Vrst, " CN_PU_VOLTAGE,
        "Firing potential Vfir, " CN_PU_VOLTAGE,
};
const char* const cnrun::CN_ParamSyms_NeuronDotPulse[] = {
        "f",
        "Vrst",
        "Vfir",
};
const double cnrun::CN_Params_NeuronDotPulse[] = {
         10,
        -60,
         20
};

const char* const cnrun::CN_VarNames_NeuronDotPulse[] = {
        "Membrane potential E, " CN_PU_VOLTAGE,
        "Spikes recently fired",
};
const char* const cnrun::CN_VarSyms_NeuronDotPulse[] = {
        "E",
        "nspk",
};
const double cnrun::CN_Vars_NeuronDotPulse[] = {
        -60.,        // E
         0
};


void
cnrun::CNeuronDotPulse::
possibly_fire()
{
        enum TParametersNeuronDotPulse { _f_, _Vrst_, _Vfir_ };

        spikes_fired_in_last_dt() = floor( (model_time() + M->dt()) * P[_f_]/1000)
                                  - floor(  model_time()            * P[_f_]/1000);

        if ( spikes_fired_in_last_dt() ) {
                _status |=  CN_NFIRING;
                var_value(0) = P[_Vfir_];
        } else {
                _status &= ~CN_NFIRING;
                var_value(0) = P[_Vrst_];
        }

}

void
cnrun::CNeuronDotPulse::
param_changed_hook()
{
        if ( P[_f_] < 0 ) {
                M->vp( 0, stderr, "DotPulse oscillator \"%s\" got a negative parameter f: capping at 0\n", _label);
                P[_f_] = 0.;
        }
}

// Local Variables:
// Mode: c++
// indent-tabs-mode: nil
// tab-width: 8
// c-basic-offset: 8
// End:
