/*
 * OpenPixi - Open Particle-In-Cell (PIC) Simulator
 * Copyright (C) 2012  OpenPixi.org
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.openpixi.pixi.physics.solver.relativistic;

import org.openpixi.pixi.physics.*;
import org.openpixi.pixi.physics.force.Force;
import org.openpixi.pixi.physics.particles.Particle;
import org.openpixi.pixi.physics.solver.Solver;

/**The calculation is due to Boris and the equations((7) - (10)) can be found here:
 * http://ptsg.eecs.berkeley.edu/publications/Verboncoeur2005IOP.pdf
 */
public class BorisRelativistic implements Solver{
	
	RelativisticVelocity relvelocity;
	
	public BorisRelativistic(double c)
	{
		relvelocity = new RelativisticVelocity(c);
	}
	
	/**
	 * Boris algorithm for implementing the electric and magnetic field.
	 * The damping is implemented with an linear error O(dt).
	 * Warning: the velocity is stored half a time step before of the position.
	 * @param p before the update: x(t), u(t-dt/2);
	 *                 after the update: x(t+dt), u(t+dt/2)
	 *                 u(t) is the relativistic momentum
	 */
	public void step(Particle p, Force f, double step) {

		double getPositionComponentofForceX = f.getPositionComponentofForceX(p);
		double getPositionComponentofForceY = f.getPositionComponentofForceY(p);
		double getBz = f.getBz(p);
		double getTangentVelocityComponentOfForceX = f.getTangentVelocityComponentOfForceX(p);
		double getTangentVelocityComponentOfForceY = f.getTangentVelocityComponentOfForceY(p);
		double getMass = p.getMass();
		
		// remember for complete()
		p.setPrevPositionComponentForceX(getPositionComponentofForceX);
		p.setPrevPositionComponentForceY(getPositionComponentofForceY);
		p.setPrevBz(getBz);
		p.setPrevTangentVelocityComponentOfForceX(getTangentVelocityComponentOfForceX);
		p.setPrevTangentVelocityComponentOfForceY(getTangentVelocityComponentOfForceY);
		
		//calculating u(t + dt / 2). Although getV() and setV() are used, the represent the relativistic momentum, i.e. v->u
		double uxminus = p.getVx() + getPositionComponentofForceX * step / (2.0 * getMass);
		
		double uyminus = p.getVy() + getPositionComponentofForceY * step / (2.0 * getMass);
		
		//gamma(t)
		double gamma = relvelocity.calculateGamma(uxminus, uyminus);
		
		double t_z = p.getCharge() * getBz * step / (2.0 * getMass * gamma);   //t vector
		
		double s_z = 2 * t_z / (1 + t_z * t_z);               //s vector
		
		double uxprime = uxminus + uyminus * t_z;
		double uyprime = uyminus - uxminus * t_z;
		
		double uxplus = uxminus + uyprime * s_z;
		double uyplus = uyminus - uxprime * s_z;
		
		p.setVx(uxplus + getPositionComponentofForceX * step / (2.0 * getMass) + getTangentVelocityComponentOfForceX * step / getMass);
		p.setVy(uyplus + getPositionComponentofForceY * step / (2.0 * getMass) + getTangentVelocityComponentOfForceY * step / getMass);
		
		//calculating gamma(t + dt / 2)
		gamma = relvelocity.calculateGamma(p);
		
		// x(t+dt) = u(t) + u(t+dt/2) * dt / gamma(t + dt / 2)
		p.setX(p.getX() + p.getVx() * step / gamma);
		p.setY(p.getY() + p.getVy() * step / gamma);
	}	
	
	/**
	 * prepare method for bringing the velocity in the desired half step
	 * @param p before the update: v(t);
	 *                 after the update: v(t-dt/2)
	 */
	public void prepare(Particle p, Force f, double dt)
	{	
		double getPositionComponentofForceX = f.getPositionComponentofForceX(p);
		double getPositionComponentofForceY = f.getPositionComponentofForceY(p);
		double getBz = f.getBz(p);
		double getTangentVelocityComponentOfForceX = f.getTangentVelocityComponentOfForceX(p);
		double getTangentVelocityComponentOfForceY = f.getTangentVelocityComponentOfForceY(p);
		double getMass = p.getMass();
		
		// remember for complete()
		p.setPrevPositionComponentForceX(getPositionComponentofForceX);
		p.setPrevPositionComponentForceY(getPositionComponentofForceY);
		p.setPrevBz(getBz);
		p.setPrevTangentVelocityComponentOfForceX(getTangentVelocityComponentOfForceX);
		p.setPrevTangentVelocityComponentOfForceY(getTangentVelocityComponentOfForceY);
		
		double step = - dt * 0.5;
		
		//calculating u(t + dt / 2). Although getV() and setV() are used, the represent the relativistic momentum, i.e. v->u
		double uxminus = p.getVx() + getPositionComponentofForceX * step / (2.0 * getMass) + getTangentVelocityComponentOfForceX * step / getMass;
		
		double uyminus = p.getVy() + getPositionComponentofForceY * step / (2.0 * getMass) + getTangentVelocityComponentOfForceY * step / getMass;
		
		//gamma(t)
		double gamma = relvelocity.calculateGamma(uxminus, uyminus);
		
		double t_z = p.getCharge() * getBz * step / (2.0 * getMass * gamma);   //t vector
		
		double s_z = 2 * t_z / (1 + t_z * t_z);               //s vector
		
		double uxprime = uxminus + uyminus * t_z;
		double uyprime = uyminus - uxminus * t_z;
		
		double uxplus = uxminus + uyprime * s_z;
		double uyplus = uyminus - uxprime * s_z;
		
		p.setVx(uxplus + getPositionComponentofForceX * step / (2.0 * getMass));
		p.setVy(uyplus + getPositionComponentofForceY * step / (2.0 * getMass));
	}

	/**
	 * complete method for bringing the velocity in the desired half step
	 * @param p before the update: v(t-dt/2);
	 *                 after the update: v(t)
	 */
	public void complete(Particle p, Force f, double dt)
	{
		double getPrevPositionComponentForceX = p.getPrevPositionComponentForceX();
		double getPrevPositionComponentForceY = p.getPrevPositionComponentForceY();
		double getMass = p.getMass();
		
		dt = dt * 0.5;
		double uxminus = p.getVx() + getPrevPositionComponentForceX * dt / (2.0 * getMass);
		
		double uyminus = p.getVy() + getPrevPositionComponentForceY * dt / (2.0 * getMass);
		
		//gamma(t)
		double gamma = relvelocity.calculateGamma(uxminus, uyminus);
		double t_z = p.getCharge() * f.getBz(p) * dt / (2.0 * getMass * gamma);   //t vector
		
		double s_z = 2 * t_z / (1 + t_z * t_z);               //s vector
		
		double uxprime = uxminus + uyminus * t_z;
		double uyprime = uyminus - uxminus * t_z;
		
		double uxplus = uxminus + uyprime * s_z;
		double uyplus = uyminus - uxprime * s_z;
		
		p.setVx(uxplus + getPrevPositionComponentForceX * dt / (2.0 * getMass) + p.getPrevTangentVelocityComponentOfForceX() * dt / getMass);
		p.setVy(uyplus + getPrevPositionComponentForceY * dt / (2.0 * getMass) + p.getPrevTangentVelocityComponentOfForceY() * dt / getMass);
	}
}
