/**
 * @file 	integrator.c
 * @brief 	BS integration scheme.
 * @author 	Hanno Rein <hanno@hanno-rein.de>
 * @details	This file implements the Gragg-Bulirsch-Stoer integration scheme.  
 *          It is a reimplementation of the fortran code by E. Hairer and G. Wanner.
 *          The starting point was the JAVA implementation in hipparchus:
 *          https://github.com/Hipparchus-Math/hipparchus/blob/master/hipparchus-ode/src/main/java/org/hipparchus/ode/nonstiff/GraggBulirschStoerIntegrator.java
 *
 * @section 	LICENSE
 * Copyright (c) 2021 Hanno Rein
 *
 * This file is part of rebound.
 *
 * rebound is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * rebound is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with rebound.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004, Ernst Hairer
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 *  - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *  - 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.
 *
 * 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 REGENTS 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <string.h> // memset
#include <float.h> // for DBL_MAX
#include "rebound.h"
#include "gravity.h"
#include "integrator_bs.h"
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

#define DEBUG 0 // set to 1 to print out debug information (reason for step rejection)
    
// Default configuration parameter. 
// They are hard coded here because it
// is unlikely that these need to be changed by the user.
// static const int maxOrder = 18;// was 18 
static const int sequence_length = 9; // = maxOrder / 2; 
static const double stepControl1 = 0.65;
static const double stepControl2 = 0.94;
static const double stepControl3 = 0.02;
static const double stepControl4 = 4.0;
static const double orderControl1 = 0.8;
static const double orderControl2 = 0.9;
static const double stabilityReduction = 0.5;
static const int maxIter = 2; // maximal number of iterations for which checks are performed
static const int maxChecks = 1; // maximal number of checks for each iteration

void reb_integrator_bs_update_particles(struct reb_simulation* r, const double* y){
    if (r==NULL){
        reb_error(r, "Update particles called without valid simulation pointer.");
        return;
    }
    if (y==NULL){
        reb_error(r, "Update particles called without valid y pointer.");
        return;
    }
    for (int i=0; i<r->N; i++){
        struct reb_particle* const p = &(r->particles[i]);
        p->x  = y[i*6+0];
        p->y  = y[i*6+1];
        p->z  = y[i*6+2];
        p->vx = y[i*6+3];
        p->vy = y[i*6+4];
        p->vz = y[i*6+5];
    }
}


static int tryStep(struct reb_simulation* r, const int Ns, const int k, const int n, const double t0, const double step) {
    struct reb_ode** odes = r->odes;
    const double subStep  = step / n;
    double t = t0;
    int needs_nbody = r->ri_bs.user_ode_needs_nbody;

    // LeapFrog Method did not seem to be of any advantage 
    //    switch (method) {
    //        case 0: // LeapFrog
    //            {
    //                // first substep
    //                for (int s=0; s < Ns; s++){
    //                    double* y0 = odes[s].y;
    //                    double* y1 = odes[s].y1;
    //                    const int length = odes[s].length;
    //                    for (int i = 0; i < length; ++i) {
    //                        if (i%6<3){ // Drift
    //                            y1[i] = y0[i] + 0.5*subStep * y0[i+3];
    //                        }
    //                    }
    //                }
    //                t += 0.5*subStep;
    //                for (int s=0; s < Ns; s++){
    //                    odes[s].derivatives(&odes[s], odes[s].yDot, odes[s].y1, t);
    //                }
    //                for (int s=0; s < Ns; s++){
    //                    double* y0 = odes[s].y;
    //                    double* y1 = odes[s].y1;
    //                    double* yDot = odes[s].yDot;
    //                    const int length = odes[s].length;
    //                    for (int i = 0; i < length; ++i) {
    //                        if (i%6>2){ // Kick
    //                            y1[i] = y0[i] + subStep * yDot[i];
    //                        }
    //                    }
    //                }
    //
    //
    //                // other substeps
    //                for (int j = 1; j < n; ++j) {
    //                    t += subStep;
    //                    for (int s=0; s < Ns; s++){
    //                        double* y1 = odes[s].y1;
    //                        const int length = odes[s].length;
    //                        for (int i = 0; i < length; ++i) {
    //                            if (i%6<3){ // Drift
    //                                y1[i] = y1[i] + subStep * y1[i+3];
    //                            }
    //                        }
    //                    }
    //                    for (int s=0; s < Ns; s++){
    //                        odes[s].derivatives(&odes[s], odes[s].yDot, odes[s].y1, t);
    //                    }
    //                    for (int s=0; s < Ns; s++){
    //                        double* y1 = odes[s].y1;
    //                        double* yDot = odes[s].yDot;
    //                        const int length = odes[s].length;
    //                        for (int i = 0; i < length; ++i) {
    //                            if (i%6>2){ // Kick
    //                                y1[i] = y1[i] + subStep * yDot[i];
    //                            }
    //                        }
    //                    }
    //
    //                    // stability check
    //                    //if (performStabilityCheck && (j <= maxChecks) && (k < maxIter)) {
    //                    //    double initialNorm = 0.0;
    //                    //    for (int l = 0; l < length; ++l) {
    //                    //        const double ratio = y0Dot[l] / scale[l];
    //                    //        initialNorm += ratio * ratio;
    //                    //    }
    //                    //    double deltaNorm = 0.0;
    //                    //    for (int l = 0; l < length; ++l) {
    //                    //        const double ratio = (yDot[l] - y0Dot[l]) / scale[l];
    //                    //        deltaNorm += ratio * ratio;
    //                    //    }
    //                    //    //printf("iii   %e %e\n",initialNorm, deltaNorm);
    //                    //    if (deltaNorm > 4 * MAX(1.0e-15, initialNorm)) {
    //                    //        return 0;
    //                    //    }
    //                    //}
    //                }
    //
    //                // correction of the last substep (at t0 + step)
    //                for (int s=0; s < Ns; s++){
    //                    double* y1 = odes[s].y1;
    //                    const int length = odes[s].length;
    //                    for (int i = 0; i < length; ++i) {
    //                        if (i%6<3){ // Drift
    //                            y1[i] = y1[i] + 0.5 * subStep * y1[i+3];
    //                        }
    //                    }
    //                }
    //
    //                return 1;
    //            }
    
    
    // Modified Midpoint method
    // first substep
    t += subStep;
    for (int s=0; s < Ns; s++){
        double* y0 = odes[s]->y;
        double* y1 = odes[s]->y1;
        double* y0Dot = odes[s]->y0Dot;
        const int length = odes[s]->length;
        for (int i = 0; i < length; ++i) {
            y1[i] = y0[i] + subStep * y0Dot[i];
        }
    }

    // other substeps
    if (needs_nbody){
        reb_integrator_bs_update_particles(r, r->ri_bs.nbody_ode->y1);
    }
    for (int s=0; s < Ns; s++){
        odes[s]->derivatives(odes[s], odes[s]->yDot, odes[s]->y1, t);
    }
    for (int s=0; s < Ns; s++){
        double* y0 = odes[s]->y;
        double* yTmp = odes[s]->yTmp;
        const int length = odes[s]->length;
        for (int i = 0; i < length; ++i) {
            yTmp[i] = y0[i];
        }
    }

    for (int j = 1; j < n; ++j) {  // Note: iterating n substeps, not 2n substeps as in Eq. (9.13)
        t += subStep;
        for (int s=0; s < Ns; s++){
            double* y1 = odes[s]->y1;
            double* yDot = odes[s]->yDot;
            double* yTmp = odes[s]->yTmp;
            const int length = odes[s]->length;
            for (int i = 0; i < length; ++i) {
                const double middle = y1[i];
                y1[i]       = yTmp[i] + 2.* subStep * yDot[i];
                yTmp[i]       = middle;
            }
        }

        if (needs_nbody){
            reb_integrator_bs_update_particles(r, r->ri_bs.nbody_ode->y1);
        }
        for (int s=0; s < Ns; s++){
            odes[s]->derivatives(odes[s], odes[s]->yDot, odes[s]->y1, t);
        }

        // stability check
        if (j <= maxChecks && k < maxIter) {
            double initialNorm = 0.0;
            double deltaNorm = 0.0;
            for (int s=0; s < Ns; s++){
                double* yDot = odes[s]->yDot;
                double* y0Dot = odes[s]->y0Dot;
                double* scale = odes[s]->scale;
                const int length = odes[s]->length;
                for (int l = 0; l < length; ++l) {
                    const double ratio1 = y0Dot[l] / scale[l];
                    initialNorm += ratio1 * ratio1;
                    const double ratio2 = (yDot[l] - y0Dot[l]) / scale[l];
                    deltaNorm += ratio2 * ratio2;
                }
            }
            if (deltaNorm > 4 * MAX(1.0e-15, initialNorm)) {
                return 0;
            }
        }

    }

    // correction of the last substep (at t0 + step)
    for (int s=0; s < Ns; s++){
        double* y1 = odes[s]->y1;
        double* yTmp = odes[s]->yTmp;
        double* yDot = odes[s]->yDot;
        const int length = odes[s]->length;
        for (int i = 0; i < length; ++i) {
            y1[i] = 0.5 * (yTmp[i] + y1[i] + subStep * yDot[i]); // = 0.25*(y_(2n-1) + 2*y_n(2) + y_(2n+1))     Eq (9.13c)
        }
    }

    return 1;
}

static void extrapolate(const struct reb_ode* ode, double * const coeff, const int k) {
    double* const y1 = ode->y1;
    double* const C = ode->C;  // C and D values follow Numerical Recipes 
    double** const D =  ode->D;
    double const length = ode->length;
    for (int j = 0; j < k; ++j) {
        double xi = coeff[k-j-1];
        double xim1 = coeff[k];
        double facC = xi/(xi-xim1);
        double facD = xim1/(xi-xim1);
        for (int i = 0; i < length; ++i) {
            double CD = C[i] - D[k - j -1][i];
            C[i] = facC * CD; // Only need to keep one C value
            D[k - j - 1][i] = facD * CD; // Keep all D values for recursion
        }
    }
    for (int i = 0; i < length; ++i) {
        y1[i] = D[0][i];
    }
    for (int j = 1; j <= k; ++j) {
        for (int i = 0; i < length; ++i) {
        y1[i] += D[j][i];
        }
    }
}


static void nbody_derivatives(struct reb_ode* ode, double* const yDot, const double* const y, double const t){
    struct reb_simulation* const r = ode->r;
    if (r->t != t) { 
        // Not needed for first step. Accelerations already calculated. Just need to copy them
        reb_integrator_bs_update_particles(r, y);
        reb_update_acceleration(r);
        if (r->N_var){
            reb_calculate_acceleration_var(r);
        }
        // Calculate non-gravity accelerations. 
        if (r->additional_forces) r->additional_forces(r);
    }

    for (int i=0; i<r->N; i++){
        const struct reb_particle p = r->particles[i];
        yDot[i*6+0] = p.vx;
        yDot[i*6+1] = p.vy;
        yDot[i*6+2] = p.vz;
        yDot[i*6+3] = p.ax;
        yDot[i*6+4] = p.ay;
        yDot[i*6+5] = p.az;
    }
}


void reb_integrator_bs_part1(struct reb_simulation* r){
}

static void allocate_sequence_arrays(struct reb_simulation_integrator_bs* ri_bs){
    ri_bs->sequence        = malloc(sizeof(int)*sequence_length);
    ri_bs->costPerStep     = malloc(sizeof(int)*sequence_length);
    ri_bs->coeff           = malloc(sizeof(double)*sequence_length);
    ri_bs->costPerTimeUnit = malloc(sizeof(double)*sequence_length);
    ri_bs->optimalStep     = malloc(sizeof(double)*sequence_length);

    // step size sequence: 2, 6, 10, 14, ...  // only needed for dense output
     for (int k = 0; k < sequence_length; ++k) {
        ri_bs->sequence[k] = 4 * k + 2;
    }
    
    // step size sequence: 1,2,3,4,5 ...
    //for (int k = 0; k < sequence_length; ++k) {
    //    ri_bs->sequence[k] = 2*( k+1);
    //}

    // initialize the order selection cost array
    // (number of function calls for each column of the extrapolation table)
    ri_bs->costPerStep[0] = ri_bs->sequence[0] + 1;
    for (int k = 1; k < sequence_length; ++k) {
        ri_bs->costPerStep[k] = ri_bs->costPerStep[k - 1] + ri_bs->sequence[k];
    }
    ri_bs->costPerTimeUnit[0]       = 0;

    // initialize the extrapolation tables
    for (int j = 0; j < sequence_length; ++j) {
        double r = 1./((double) ri_bs->sequence[j]);
        ri_bs->coeff[j] = r*r;
    }
}

static void reb_integrator_bs_default_scale(struct reb_ode* ode, double* y1, double* y2, double relTol, double absTol){
    double* scale = ode->scale;
    int length = ode->length;
    for (int i = 0; i < length; i++) {
        scale[i] = absTol + relTol * MAX(fabs(y1[i]), fabs(y2[i]));
    }
}


int reb_integrator_bs_step(struct reb_simulation* r, double dt){
    // return 1 if step was successful
    //        0 if rejected 
    //
    struct reb_simulation_integrator_bs* ri_bs = &r->ri_bs;
    
    if (ri_bs->sequence==NULL){
        allocate_sequence_arrays(ri_bs);
    }

    double t = r->t;
    ri_bs->dt_proposed = dt; // In case of early fail

    // initial order selection
    if (ri_bs->targetIter == 0){
        const double tol    = ri_bs->eps_rel;
        const double log10R = log10(MAX(1.0e-10, tol));
        ri_bs->targetIter = MAX(1, MIN(sequence_length - 2, (int) floor(0.5 - 0.6 * log10R)));
    }

    double  maxError = DBL_MAX;

    int Ns = r->odes_N; // Number of ode sets
    struct reb_ode** odes = r->odes;
    double error;
    int reject = 0;

    // Check if ODEs have been set up correctly 
    for (int s=0; s < Ns; s++){
        if (!odes[s]->derivatives){
            reb_error(r,"A user-specified set of ODEs has not been provided with a derivatives function.");
            r->status = REB_EXIT_ERROR;
            return 0;
        }
    }
    
    for (int s=0; s < Ns; s++){
        if (odes[s]->getscale){
            odes[s]->getscale(odes[s], odes[s]->y, odes[s]->y); // initial scaling
        }else{
            reb_integrator_bs_default_scale(odes[s], odes[s]->y, odes[s]->y, ri_bs->eps_rel, ri_bs->eps_abs);
        }
    }

    // first evaluation, at the beginning of the step
    for (int s=0; s < Ns; s++){
        odes[s]->derivatives(odes[s], odes[s]->y0Dot, odes[s]->y, t);
    }

    const int forward = (dt >= 0.);

    // iterate over several substep sizes
    int k = -1;
    for (int loop = 1; loop; ) {

        ++k;
        
        // modified midpoint integration with the current substep
        if ( ! tryStep(r, Ns, k, ri_bs->sequence[k], t, dt)) {

            // the stability check failed, we reduce the global step
#if DEBUG
            printf("S");
#endif
            dt  = fabs(dt * stabilityReduction);
            reject = 1;
            loop   = 0;

        } else {
            for (int s=0; s < Ns; s++){
                const int length = odes[s]->length;
                for (int i = 0; i < length; ++i) {
                    double CD = odes[s]->y1[i];
                    odes[s]->C[i] = CD;
                    odes[s]->D[k][i] = CD;
                }
            }

            // the substep was computed successfully
            if (k > 0) {

                // extrapolate the state at the end of the step
                // using last iteration data
                for (int s=0; s < Ns; s++){
                    extrapolate(odes[s], ri_bs->coeff, k);
                    if (odes[s]->getscale){
                        odes[s]->getscale(odes[s], odes[s]->y, odes[s]->y1);
                    }else{
                        reb_integrator_bs_default_scale(odes[s], odes[s]->y, odes[s]->y, ri_bs->eps_rel, ri_bs->eps_abs);
                    }
                }

                // estimate the error at the end of the step.
                error = 0;
                //long int combined_length = 0;
                for (int s=0; s < Ns; s++){
                    const int length = odes[s]->length;
                    //combined_length += length;
                    double * C = odes[s]->C;
                    double * scale = odes[s]->scale;
                    for (int j = 0; j < length; ++j) {
                        const double e = C[j] / scale[j];
                        error = MAX(error, e * e);
                    }
                }
                // Note: Used to be: error = sqrt(error / combined_length). But for N-body applications it might be more consistent to use:
                error = sqrt(error);
                if (isnan(error)) {
                    reb_error(r, "NaN appearing during ODE integration.");
                    r->status = REB_EXIT_ERROR;
                    return 0;
                }

                if ((error > 1.0e25)){ // TODO: Think about what to do when error increases: || ((k > 1) && (error > maxError))) {
                    // error is too big, we reduce the global step
#if DEBUG
                    printf("R (error= %.5e)",error);
#endif
                    dt  = fabs(dt * stabilityReduction);
                    reject = 1;
                    loop   = 0;
                } else {

                    maxError = MAX(4 * error, 1.0);

                    // compute optimal stepsize for this order
                    const double exp = 1.0 / (2 * k + 1);
                    double fac = stepControl2 / pow(error / stepControl1, exp);
                    const double power = pow(stepControl3, exp);
                    fac = MAX(power / stepControl4, MIN(1. / power, fac));
                    ri_bs->optimalStep[k]     = fabs(dt * fac);
                    ri_bs->costPerTimeUnit[k] = ri_bs->costPerStep[k] / ri_bs->optimalStep[k];

                    // check convergence
                    switch (k - ri_bs->targetIter) {

                        case -1 : // one before target
                            if ((ri_bs->targetIter > 1) && ! ri_bs->previousRejected) {

                                // check if we can stop iterations now
                                if (error <= 1.0) {
                                    // convergence have been reached just before targetIter
                                    loop = 0;
                                } else {
                                    // estimate if there is a chance convergence will
                                    // be reached on next iteration, using the
                                    // asymptotic evolution of error
                                    const double ratio = ((double) ri_bs->sequence[ri_bs->targetIter] * ri_bs->sequence[ri_bs->targetIter + 1]) / (ri_bs->sequence[0] * ri_bs->sequence[0]);
                                    if (error > ratio * ratio) {
                                        // we don't expect to converge on next iteration
                                        // we reject the step immediately and reduce order
                                        reject = 1;
                                        loop   = 0;
                                        ri_bs->targetIter = k;
                                        if ((ri_bs->targetIter > 1) &&
                                                (ri_bs->costPerTimeUnit[ri_bs->targetIter - 1] <
                                                 orderControl1 * ri_bs->costPerTimeUnit[ri_bs->targetIter])) {
                                            ri_bs->targetIter -= 1;
                                        }
                                        dt = ri_bs->optimalStep[ri_bs->targetIter];
#if DEBUG
                                        printf("O");
#endif
                                    }
                                }
                            }
                            break;

                        case 0: // exactly on target
                            if (error <= 1.0) {
                                // convergence has been reached exactly at targetIter
                                loop = 0;
                            } else {
                                // estimate if there is a chance convergence will
                                // be reached on next iteration, using the
                                // asymptotic evolution of error
                                const double ratio = ((double) ri_bs->sequence[k + 1]) / ri_bs->sequence[0];
                                if (error > ratio * ratio) {
                                    // we don't expect to converge on next iteration
                                    // we reject the step immediately
#if DEBUG
                                    printf("o");
#endif
                                    reject = 1;
                                    loop = 0;
                                    if ((ri_bs->targetIter > 1) &&
                                            (ri_bs->costPerTimeUnit[ri_bs->targetIter - 1] <
                                             orderControl1 * ri_bs->costPerTimeUnit[ri_bs->targetIter])) {
                                        --ri_bs->targetIter;
                                    }
                                    dt = ri_bs->optimalStep[ri_bs->targetIter];
                                }
                            }
                            break;

                        case 1 : // one past target
                            if (error > 1.0) {
#if DEBUG
                                printf("e");
#endif
                                reject = 1;
                                if ((ri_bs->targetIter > 1) &&
                                        (ri_bs->costPerTimeUnit[ri_bs->targetIter - 1] <
                                         orderControl1 * ri_bs->costPerTimeUnit[ri_bs->targetIter])) {
                                    --ri_bs->targetIter;
                                }
                                dt = ri_bs->optimalStep[ri_bs->targetIter];
                            }
                            loop = 0;
                            break;

                        default :
                            if (ri_bs->firstOrLastStep && (error <= 1.0)) {
                                loop = 0;
                            }
                            break;

                    }
                }
            }
        }
    }


    if (! reject) {
#if DEBUG
        printf("."); 
#endif
        // Swap arrays
        for (int s=0; s < Ns; s++){
            double* y_tmp = odes[s]->y;
            odes[s]->y = odes[s]->y1; 
            odes[s]->y1 = y_tmp; 
        }

        int optimalIter;
        if (k == 1) {
            optimalIter = 2;
            if (ri_bs->previousRejected) {
                optimalIter = 1;
            }
        } else if (k <= ri_bs->targetIter) { // Converged before or on target
            optimalIter = k;
            if (ri_bs->costPerTimeUnit[k - 1] < orderControl1 * ri_bs->costPerTimeUnit[k]) {
                optimalIter = k - 1;
            } else if (ri_bs->costPerTimeUnit[k] < orderControl2 * ri_bs->costPerTimeUnit[k - 1]) {
                optimalIter = MIN(k + 1, sequence_length - 2);
            }
        } else {                            // converged after target
            optimalIter = k - 1;
            if ((k > 2) && (ri_bs->costPerTimeUnit[k - 2] < orderControl1 * ri_bs->costPerTimeUnit[k - 1])) {
                optimalIter = k - 2;
            }
            if (ri_bs->costPerTimeUnit[k] < orderControl2 * ri_bs->costPerTimeUnit[optimalIter]) {
                optimalIter = MIN(k, sequence_length - 2);
            }
        }

        if (ri_bs->previousRejected) {
            // after a rejected step neither order nor stepsize
            // should increase
            ri_bs->targetIter = MIN(optimalIter, k);
            dt = MIN(fabs(dt), ri_bs->optimalStep[ri_bs->targetIter]);
        } else {
            // stepsize control
            if (optimalIter <= k) {
                dt = ri_bs->optimalStep[optimalIter];
            } else {
                if ((k < ri_bs->targetIter) &&
                        (ri_bs->costPerTimeUnit[k] < orderControl2 * ri_bs->costPerTimeUnit[k - 1])) {
                    dt = ri_bs->optimalStep[k] * ri_bs->costPerStep[optimalIter + 1] / ri_bs->costPerStep[k];
                } else {
                    dt = ri_bs->optimalStep[k] * ri_bs->costPerStep[optimalIter] / ri_bs->costPerStep[k];
                }
            }

            ri_bs->targetIter = optimalIter;

        }
    }

    dt = fabs(dt);

    if (ri_bs->min_dt !=0.0 && dt < ri_bs->min_dt) {
        dt = ri_bs->min_dt;
        reb_warning(r,"Minimal stepsize reached during ODE integration.");
    }

    if (ri_bs->max_dt !=0.0 && dt > ri_bs->max_dt) {
        dt = ri_bs->max_dt;
        reb_warning(r,"Maximum stepsize reached during ODE integration.");
    }

    if (! forward) {
        dt = -dt;
    }
    ri_bs->dt_proposed = dt;

    if (reject) {
        ri_bs->previousRejected = 1;
    } else {
        ri_bs->previousRejected = 0;
        ri_bs->firstOrLastStep = 0;
    }
    return !reject;
}

struct reb_ode* reb_create_ode(struct reb_simulation* r, unsigned int length){
    struct reb_ode* ode = malloc(sizeof(struct reb_ode));
    
    memset(ode, 0, sizeof(struct reb_ode)); // not really necessaery

    if (r->odes_allocatedN <= r->odes_N){
        r->odes_allocatedN += 32;
        r->odes = realloc(r->odes,sizeof(struct reb_ode*)*r->odes_allocatedN);
    }
    
    r->odes[r->odes_N] = ode;
    r->odes_N += 1;


    ode->r = r; // weak reference
    ode->length = length;
    ode->needs_nbody = 1;
    ode->allocatedN = length;
    ode->getscale = NULL;
    ode->derivatives = NULL;
    ode->D   = malloc(sizeof(double*)*(sequence_length));
    for (int k = 0; k < sequence_length; ++k) {
        ode->D[k]   = malloc(sizeof(double)*length);
    }

    ode->C     = malloc(sizeof(double)*length);
    ode->y     = malloc(sizeof(double)*length);
    ode->y1    = malloc(sizeof(double)*length);
    ode->y0Dot = malloc(sizeof(double)*length);
    ode->yTmp  = malloc(sizeof(double)*length);
    ode->yDot  = malloc(sizeof(double)*length);

    ode->scale = malloc(sizeof(double)*length);

    r->ri_bs.firstOrLastStep = 1;

    return ode;
}

void reb_integrator_bs_part2(struct reb_simulation* r){
    struct reb_simulation_integrator_bs* ri_bs = &(r->ri_bs);
    
    int nbody_length = r->N*3*2;
    if (ri_bs->nbody_ode == NULL){ 
        ri_bs->nbody_ode = reb_create_ode(r, nbody_length);
        ri_bs->nbody_ode->derivatives = nbody_derivatives;
        ri_bs->nbody_ode->needs_nbody = 0; // No need to update unless there's another ode
        ri_bs->firstOrLastStep = 1;
    }
    
    for (int s=0; s < r->odes_N; s++){
        if (r->odes[s]->needs_nbody){
            ri_bs->user_ode_needs_nbody = 1;
        }
    }

    double* const y = ri_bs->nbody_ode->y;
    for (int i=0; i<r->N; i++){
        const struct reb_particle p = r->particles[i];
        y[i*6+0] = p.x;
        y[i*6+1] = p.y;
        y[i*6+2] = p.z;
        y[i*6+3] = p.vx;
        y[i*6+4] = p.vy;
        y[i*6+5] = p.vz;
    }

    int success = reb_integrator_bs_step(r, r->dt);
    if (success){
        r->t += r->dt;
        r->dt_last_done = r->dt;
    }
    r->dt = ri_bs->dt_proposed;

    reb_integrator_bs_update_particles(r, ri_bs->nbody_ode->y);
}

void reb_integrator_bs_synchronize(struct reb_simulation* r){
    // Do nothing.
}

void reb_free_ode(struct reb_ode* ode){
    // Free data array
    free(ode->y1);
    ode->y1 = NULL;
    free(ode->C);
    ode->C = NULL;
    free(ode->scale);
    ode->scale = NULL;
    
    if (ode->D){
        for (int k = 0; k < sequence_length; ++k) {
            ode->D[k] = NULL;
        }
        free(ode->D);
        ode->D = NULL;
    }
    free(ode->y0Dot);
    ode->y0Dot = NULL;
    free(ode->yTmp);
    ode->yTmp = NULL;
    free(ode->yDot);
    ode->yDot = NULL;
    
    struct reb_simulation* r = ode->r;
    if (r){ // only do this is ode is in a simulation
        struct reb_simulation_integrator_bs* ri_bs = &r->ri_bs;
        int shift = 0;
        for (int s=0; s < r->odes_N; s++){
            if (r->odes[s] == ode){
                r->odes_N--;
                shift = 1;
            }
            if (shift && s <= r->odes_N ){
                r->odes[s] = r->odes[s+1];
            }
        }
        if (ri_bs->nbody_ode == ode){
            ri_bs->nbody_ode = NULL;
        }
    }
    free(ode);
}



void reb_integrator_bs_reset(struct reb_simulation* r){
    struct reb_simulation_integrator_bs* ri_bs = &(r->ri_bs);
    
    // Delete nbody ode but not others
    if (ri_bs->nbody_ode){
        reb_free_ode(ri_bs->nbody_ode);
    }

    // Free sequence arrays
    free(ri_bs->sequence);
    ri_bs->sequence = NULL;
    
    free(ri_bs->coeff);
    ri_bs->coeff = NULL;
    free(ri_bs->costPerStep);
    ri_bs->costPerStep = NULL;
    free(ri_bs->costPerTimeUnit);
    ri_bs->costPerTimeUnit = NULL;
    free(ri_bs->optimalStep);
    ri_bs->optimalStep = NULL;
    
    
    // Default settings
    ri_bs->eps_abs          = 1e-8;
    ri_bs->eps_rel          = 1e-8;
    ri_bs->max_dt           = 0;
    ri_bs->min_dt           = 0; 
    ri_bs->firstOrLastStep  = 1;
    ri_bs->previousRejected = 0;
        
}
