# ifndef CPPAD_LOCAL_OP_ERF_OP_HPP
# define CPPAD_LOCAL_OP_ERF_OP_HPP

/* --------------------------------------------------------------------------
CppAD: C++ Algorithmic Differentiation: Copyright (C) 2003-21 Bradley M. Bell

CppAD is distributed under the terms of the
             Eclipse Public License Version 2.0.

This Source Code may also be made available under the following
Secondary License when the conditions for such availability set forth
in the Eclipse Public License, Version 2.0 are satisfied:
      GNU General Public License, Version 2.0 or later.
---------------------------------------------------------------------------- */

# include <cppad/local/op/mul_op.hpp>
# include <cppad/local/op/sub_op.hpp>
# include <cppad/local/op/exp_op.hpp>


namespace CppAD { namespace local { // BEGIN_CPPAD_LOCAL_NAMESPACE
/*!
\file erf_op.hpp
Forward and reverse mode calculations for z = erf(x) or erfc(x).
*/

/*!
Forward mode Taylor coefficient for result of op = ErfOp or ErfcOp.

The C++ source code corresponding to this operation is one of
\verbatim
    z = erf(x)
    z = erfc(x)
\endverbatim

\tparam Base
base type for the operator; i.e., this operation was recorded
using AD< Base > and computations by this routine are done using type Base.

\param op
must be either ErfOp or ErfcOp and indicates if this is
z = erf(x) or z = erfc(x).

\param p
lowest order of the Taylor coefficients that we are computing.

\param q
highest order of the Taylor coefficients that we are computing.

\param i_z
variable index corresponding to the last (primary) result for this operation;
i.e. the row index in taylor corresponding to z.
The auxillary results are called y_j have index i_z - j.

\param arg
arg[0]: is the variable index corresponding to x.
\n
arg[1]: is the parameter index corresponding to the value zero.
\n
arg[2]: is  the parameter index correspodning to the value 2 / sqrt(pi).

\param parameter
parameter[ arg[1] ] is the value zero,
and parameter[ arg[2] ] is the value 2 / sqrt(pi).

\param cap_order
maximum number of orders that will fit in the taylor array.

\param taylor
\b Input:
taylor [ size_t(arg[0]) * cap_order + k ]
for k = 0 , ... , q,
is the k-th order Taylor coefficient corresponding to x.
\n
\b Input:
taylor [ i_z * cap_order + k ]
for k = 0 , ... , p - 1,
is the k-th order Taylor coefficient corresponding to z.
\n
\b Input:
taylor [ ( i_z - j) * cap_order + k ]
for k = 0 , ... , p-1,
and j = 0 , ... , 4,
is the k-th order Taylor coefficient corresponding to the j-th result for z.
\n
\b Output:
taylor [ (i_z-j) * cap_order + k ],
for k = p , ... , q,
and j = 0 , ... , 4,
is the k-th order Taylor coefficient corresponding to the j-th result for z.

*/
template <class Base>
void forward_erf_op(
    OpCode        op          ,
    size_t        p           ,
    size_t        q           ,
    size_t        i_z         ,
    const addr_t* arg         ,
    const Base*   parameter   ,
    size_t        cap_order   ,
    Base*         taylor      )
{
    // check assumptions
    CPPAD_ASSERT_UNKNOWN( op == ErfOp || op == ErfcOp );
    CPPAD_ASSERT_UNKNOWN( NumArg(op) == 3 );
    CPPAD_ASSERT_UNKNOWN( NumRes(op) == 5 );
    CPPAD_ASSERT_UNKNOWN( q < cap_order );
    CPPAD_ASSERT_UNKNOWN( p <= q );
    CPPAD_ASSERT_UNKNOWN(
        size_t( std::numeric_limits<addr_t>::max() ) >= i_z + 2
    );

    // array used to pass parameter values for sub-operations
    addr_t addr[2];

    // convert from final result to first result
    i_z -= 4; // 4 = NumRes(ErfOp) - 1;

    // z_0 = x * x
    addr[0] = arg[0]; // x
    addr[1] = arg[0]; // x
    forward_mulvv_op(p, q, i_z+0, addr, parameter, cap_order, taylor);

    // z_1 = - x * x
    addr[0] = arg[1];           // zero
    addr[1] = addr_t( i_z );    // z_0
    forward_subpv_op(p, q, i_z+1, addr, parameter, cap_order, taylor);

    // z_2 = exp( - x * x )
    forward_exp_op(p, q, i_z+2, i_z+1, cap_order, taylor);

    // z_3 = (2 / sqrt(pi)) * exp( - x * x )
    addr[0] = arg[2];            // 2 / sqrt(pi)
    addr[1] = addr_t( i_z + 2 ); // z_2
    forward_mulpv_op(p, q, i_z+3, addr, parameter, cap_order, taylor);

    // pointers to taylor coefficients for x , z_3, and z_4
    Base* x    = taylor + size_t(arg[0]) * cap_order;
    Base* z_3  = taylor + (i_z+3) * cap_order;
    Base* z_4  = taylor + (i_z+4) * cap_order;

    // calculte z_4 coefficients
    if( p == 0 )
    {   // z4 (t) = erf[x(t)]
        if( op == ErfOp )
            z_4[0] = erf(x[0]);
        else
            z_4[0] = erfc(x[0]);
        p++;
    }
    // sign
    Base sign(1.0);
    if( op == ErfcOp )
        sign = Base(-1.0);
    //
    for(size_t j = p; j <= q; j++)
    {   // erf:  z_4' (t) =   erf'[x(t)] * x'(t) = z3(t) * x'(t)
        // erfc: z_4' (t) = - erf'[x(t)] * x'(t) = - z3(t) * x'(t)
        // z_4[1] + 2 * z_4[2] * t +  ... =
        // sign * (z_3[0] + z_3[1] * t +  ...) * (x[1] + 2 * x[2] * t +  ...)
        Base base_j = static_cast<Base>(double(j));
        z_4[j]      = static_cast<Base>(0);
        for(size_t k = 1; k <= j; k++)
            z_4[j] += sign * (Base(double(k)) / base_j) * x[k] * z_3[j-k];
    }
}

/*!
Zero order Forward mode Taylor coefficient for result of op = ErfOp or ErfcOp.

The C++ source code corresponding to this operation one of
\verbatim
    z = erf(x)
    z = erfc(x)
\endverbatim

\tparam Base
base type for the operator; i.e., this operation was recorded
using AD< Base > and computations by this routine are done using type Base.

\param op
must be either ErfOp or ErfcOp and indicates if this is
z = erf(x) or z = erfc(x).

\param i_z
variable index corresponding to the last (primary) result for this operation;
i.e. the row index in taylor corresponding to z.
The auxillary results are called y_j have index i_z - j.

\param arg
arg[0]: is the variable index corresponding to x.
\n
arg[1]: is the parameter index corresponding to the value zero.
\n
arg[2]: is  the parameter index correspodning to the value 2 / sqrt(pi).

\param parameter
parameter[ arg[1] ] is the value zero,
and parameter[ arg[2] ] is the value 2 / sqrt(pi).

\param cap_order
maximum number of orders that will fit in the taylor array.

\param taylor
\b Input:
taylor [ size_t(arg[0]) * cap_order + 0 ]
is the zero order Taylor coefficient corresponding to x.
\n
\b Input:
taylor [ i_z * cap_order + 0 ]
is the zero order Taylor coefficient corresponding to z.
\n
\b Output:
taylor [ (i_z-j) * cap_order + 0 ],
for j = 0 , ... , 4,
is the zero order Taylor coefficient for j-th result corresponding to z.

*/
template <class Base>
void forward_erf_op_0(
    OpCode        op          ,
    size_t        i_z         ,
    const addr_t* arg         ,
    const Base*   parameter   ,
    size_t        cap_order   ,
    Base*         taylor      )
{
    // check assumptions
    CPPAD_ASSERT_UNKNOWN( op == ErfOp || op == ErfcOp );
    CPPAD_ASSERT_UNKNOWN( NumArg(op) == 3 );
    CPPAD_ASSERT_UNKNOWN( NumRes(op) == 5 );
    CPPAD_ASSERT_UNKNOWN( 0 < cap_order );
    CPPAD_ASSERT_UNKNOWN(
        size_t( std::numeric_limits<addr_t>::max() ) >= i_z + 2
    );

    // array used to pass parameter values for sub-operations
    addr_t addr[2];

    // convert from final result to first result
    i_z -= 4; // 4 = NumRes(ErfOp) - 1;

    // z_0 = x * x
    addr[0] = arg[0]; // x
    addr[1] = arg[0]; // x
    forward_mulvv_op_0(i_z+0, addr, parameter, cap_order, taylor);

    // z_1 = - x * x
    addr[0] = arg[1];       // zero
    addr[1] = addr_t(i_z);  // z_0
    forward_subpv_op_0(i_z+1, addr, parameter, cap_order, taylor);

    // z_2 = exp( - x * x )
    forward_exp_op_0(i_z+2, i_z+1, cap_order, taylor);

    // z_3 = (2 / sqrt(pi)) * exp( - x * x )
    addr[0] = arg[2];          // 2 / sqrt(pi)
    addr[1] = addr_t(i_z + 2); // z_2
    forward_mulpv_op_0(i_z+3, addr, parameter, cap_order, taylor);

    // zero order Taylor coefficient for z_4
    Base* x    = taylor + size_t(arg[0]) * cap_order;
    Base* z_4  = taylor + (i_z + 4) * cap_order;
    if( op == ErfOp )
        z_4[0] = erf(x[0]);
    else
        z_4[0] = erfc(x[0]);
}
/*!
Forward mode Taylor coefficient for result of op = ErfOp or ErfcOp.

The C++ source code corresponding to this operation is one of
\verbatim
    z = erf(x)
    z = erfc(x)
\endverbatim

\tparam Base
base type for the operator; i.e., this operation was recorded
using AD< Base > and computations by this routine are done using type Base.

\param op
must be either ErfOp or ErfcOp and indicates if this is
z = erf(x) or z = erfc(x).

\param q
order of the Taylor coefficients that we are computing.

\param r
number of directions for the Taylor coefficients that we afre computing.

\param i_z
variable index corresponding to the last (primary) result for this operation;
i.e. the row index in taylor corresponding to z.
The auxillary results have index i_z - j for j = 0 , ... , 4
(and include z).

\param arg
arg[0]: is the variable index corresponding to x.
\n
arg[1]: is the parameter index corresponding to the value zero.
\n
arg[2]: is  the parameter index correspodning to the value 2 / sqrt(pi).

\param parameter
parameter[ arg[1] ] is the value zero,
and parameter[ arg[2] ] is the value 2 / sqrt(pi).

\param cap_order
maximum number of orders that will fit in the taylor array.

\par tpv
We use the notation
<code>tpv = (cap_order-1) * r + 1</code>
which is the number of Taylor coefficients per variable

\param taylor
\b Input: If x is a variable,
<code>taylor [ arg[0] * tpv + 0 ]</code>,
is the zero order Taylor coefficient for all directions and
<code>taylor [ arg[0] * tpv + (k-1)*r + ell + 1 ]</code>,
for k = 1 , ... , q,
ell = 0, ..., r-1,
is the k-th order Taylor coefficient
corresponding to x and the ell-th direction.
\n
\b Input:
taylor [ (i_z - j) * tpv + 0 ]
is the zero order Taylor coefficient for all directions and the
j-th result for z.
for k = 1 , ... , q-1,
ell = 0, ... , r-1,
<code>
taylor[ (i_z - j) * tpv + (k-1)*r + ell + 1]
</code>
is the Taylor coefficient for the k-th order, ell-th direction,
and j-th auzillary result.
\n
\b Output:
taylor [ (i_z-j) * tpv + (q-1)*r + ell + 1 ],
for ell = 0 , ... , r-1,
is the Taylor coefficient for the q-th order, ell-th direction,
and j-th auzillary result.

*/
template <class Base>
void forward_erf_op_dir(
    OpCode        op          ,
    size_t        q           ,
    size_t        r           ,
    size_t        i_z         ,
    const addr_t* arg         ,
    const Base*   parameter   ,
    size_t        cap_order   ,
    Base*         taylor      )
{
    // check assumptions
    CPPAD_ASSERT_UNKNOWN( op == ErfOp || op == ErfcOp );
    CPPAD_ASSERT_UNKNOWN( NumArg(op) == 3 );
    CPPAD_ASSERT_UNKNOWN( NumRes(op) == 5 );
    CPPAD_ASSERT_UNKNOWN( q < cap_order );
    CPPAD_ASSERT_UNKNOWN( 0 < q );
    CPPAD_ASSERT_UNKNOWN(
        size_t( std::numeric_limits<addr_t>::max() ) >= i_z + 2
    );

    // array used to pass parameter values for sub-operations
    addr_t addr[2];

    // convert from final result to first result
    i_z -= 4; // 4 = NumRes(ErfOp) - 1;

    // z_0 = x * x
    addr[0] = arg[0]; // x
    addr[1] = arg[0]; // x
    forward_mulvv_op_dir(q, r, i_z+0, addr, parameter, cap_order, taylor);

    // z_1 = - x * x
    addr[0] = arg[1];         // zero
    addr[1] = addr_t( i_z );  // z_0
    forward_subpv_op_dir(q, r, i_z+1, addr, parameter, cap_order, taylor);

    // z_2 = exp( - x * x )
    forward_exp_op_dir(q, r, i_z+2, i_z+1, cap_order, taylor);

    // z_3 = (2 / sqrt(pi)) * exp( - x * x )
    addr[0] = arg[2];            // 2 / sqrt(pi)
    addr[1] = addr_t( i_z + 2 ); // z_2
    forward_mulpv_op_dir(q, r, i_z+3, addr, parameter, cap_order, taylor);

    // pointers to taylor coefficients for x , z_3, and z_4
    size_t num_taylor_per_var = (cap_order - 1) * r + 1;
    Base* x    = taylor + size_t(arg[0]) * num_taylor_per_var;
    Base* z_3  = taylor + (i_z+3) * num_taylor_per_var;
    Base* z_4  = taylor + (i_z+4) * num_taylor_per_var;

    // sign
    Base sign(1.0);
    if( op == ErfcOp )
        sign = Base(-1.0);

    // erf:  z_4' (t) =   erf'[x(t)] * x'(t) = z3(t) * x'(t)
    // erfc: z_4' (t) = - erf'[x(t)] * x'(t) = z3(t) * x'(t)
    // z_4[1] + 2 * z_4[2] * t +  ... =
    // sign * (z_3[0] + z_3[1] * t +  ...) * (x[1] + 2 * x[2] * t +  ...)
    Base base_q = static_cast<Base>(double(q));
    for(size_t ell = 0; ell < r; ell++)
    {   // index in z_4 and x for q-th order term
        size_t m = (q-1)*r + ell + 1;
        // initialize q-th order term summation
        z_4[m] = sign * z_3[0] * x[m];
        for(size_t k = 1; k < q; k++)
        {   size_t x_index  = (k-1)*r + ell + 1;
            size_t z3_index = (q-k-1)*r + ell + 1;
            Base bk = Base(double(k));
            z_4[m] += sign * (bk / base_q) * x[x_index] * z_3[z3_index];
        }
    }
}

/*!
Compute reverse mode partial derivatives for result of op = ErfOp or ErfcOp.

The C++ source code corresponding to this operation is one of
\verbatim
    z = erf(x)
    z = erfc(x)
\endverbatim

\tparam Base
base type for the operator; i.e., this operation was recorded
using AD< Base > and computations by this routine are done using type Base.

\param op
must be either ErfOp or ErfcOp and indicates if this is
z = erf(x) or z = erfc(x).

\param d
highest order Taylor of the Taylor coefficients that we are computing
the partial derivatives with respect to.

\param i_z
variable index corresponding to the last (primary) result for this operation;
i.e. the row index in taylor corresponding to z.
The auxillary results are called y_j have index i_z - j.

\param arg
arg[0]: is the variable index corresponding to x.
\n
arg[1]: is the parameter index corresponding to the value zero.
\n
arg[2]: is  the parameter index correspodning to the value 2 / sqrt(pi).

\param parameter
parameter[ arg[1] ] is the value zero,
and parameter[ arg[2] ] is the value 2 / sqrt(pi).

\param cap_order
maximum number of orders that will fit in the taylor array.

\param taylor
\b Input:
taylor [ size_t(arg[0]) * cap_order + k ]
for k = 0 , ... , d,
is the k-th order Taylor coefficient corresponding to x.
\n
taylor [ (i_z - j) * cap_order + k ]
for k = 0 , ... , d,
and for j = 0 , ... , 4,
is the k-th order Taylor coefficient corresponding to the j-th result
for this operation.

\param nc_partial
number of columns in the matrix containing all the partial derivatives

\param partial
\b Input:
partial [ size_t(arg[0]) * nc_partial + k ]
for k = 0 , ... , d,
is the partial derivative of G( z , x , w , u , ... ) with respect to
the k-th order Taylor coefficient for x.
\n
\b Input:
partial [ (i_z - j) * nc_partial + k ]
for k = 0 , ... , d,
and for j = 0 , ... , 4,
is the partial derivative of G( z , x , w , u , ... ) with respect to
the k-th order Taylor coefficient for the j-th result of this operation.
\n
\b Output:
partial [ size_t(arg[0]) * nc_partial + k ]
for k = 0 , ... , d,
is the partial derivative of H( x , w , u , ... ) with respect to
the k-th order Taylor coefficient for x.
\n
\b Output:
partial [ (i_z-j) * nc_partial + k ]
for k = 0 , ... , d,
and for j = 0 , ... , 4,
may be used as work space; i.e., may change in an unspecified manner.

*/
template <class Base>
void reverse_erf_op(
    OpCode        op          ,
    size_t        d           ,
    size_t        i_z         ,
    const addr_t* arg         ,
    const Base*   parameter   ,
    size_t        cap_order   ,
    const Base*   taylor      ,
    size_t        nc_partial  ,
    Base*         partial     )
{
    // check assumptions
    CPPAD_ASSERT_UNKNOWN( op == ErfOp || op == ErfcOp );
    CPPAD_ASSERT_UNKNOWN( NumArg(op) == 3 );
    CPPAD_ASSERT_UNKNOWN( NumRes(op) == 5 );
    CPPAD_ASSERT_UNKNOWN( d < cap_order );
    CPPAD_ASSERT_UNKNOWN(
        size_t( std::numeric_limits<addr_t>::max() ) >= i_z + 2
    );

    // array used to pass parameter values for sub-operations
    addr_t addr[2];

    // If pz is zero, make sure this operation has no effect
    // (zero times infinity or nan would be non-zero).
    Base* pz  = partial + i_z * nc_partial;
    bool skip(true);
    for(size_t i_d = 0; i_d <= d; i_d++)
        skip &= IdenticalZero(pz[i_d]);
    if( skip )
        return;

    // convert from final result to first result
    i_z -= 4; // 4 = NumRes(ErfOp) - 1;

    // Taylor coefficients and partials corresponding to x
    const Base* x  = taylor  + size_t(arg[0]) * cap_order;
    Base* px       = partial + size_t(arg[0]) * nc_partial;

    // Taylor coefficients and partials corresponding to z_3
    const Base* z_3  = taylor  + (i_z+3) * cap_order;
    Base* pz_3       = partial + (i_z+3) * nc_partial;

    // Taylor coefficients and partials corresponding to z_4
    Base* pz_4 = partial + (i_z+4) * nc_partial;

    // sign
    Base sign(1.0);
    if( op == ErfcOp )
        sign = Base(-1.0);

    // Reverse z_4
    size_t j = d;
    while(j)
    {   pz_4[j] /= Base(double(j));
        for(size_t k = 1; k <= j; k++)
        {   px[k]     += sign * azmul(pz_4[j], z_3[j-k]) * Base(double(k));
            pz_3[j-k] += sign * azmul(pz_4[j], x[k]) * Base(double(k));
        }
        j--;
    }
    px[0] += sign * azmul(pz_4[0], z_3[0]);

    // z_3 = (2 / sqrt(pi)) * exp( - x * x )
    addr[0] = arg[2];            // 2 / sqrt(pi)
    addr[1] = addr_t( i_z + 2 ); // z_2
    reverse_mulpv_op(
        d, i_z+3, addr, parameter, cap_order, taylor, nc_partial, partial
    );

    // z_2 = exp( - x * x )
    reverse_exp_op(
        d, i_z+2, i_z+1, cap_order, taylor, nc_partial, partial
    );

    // z_1 = - x * x
    addr[0] = arg[1];           // zero
    addr[1] = addr_t( i_z );    // z_0
    reverse_subpv_op(
        d, i_z+1, addr, parameter, cap_order, taylor, nc_partial, partial
    );

    // z_0 = x * x
    addr[0] = arg[0]; // x
    addr[1] = arg[0]; // x
    reverse_mulvv_op(
        d, i_z+0, addr, parameter, cap_order, taylor, nc_partial, partial
    );

}


} } // END_CPPAD_LOCAL_NAMESPACE
# endif // CPPAD_ERF_OP_INCLUDED
