diff --git a/src/Algorithms/ParallelCyclotronTracker.cpp b/src/Algorithms/ParallelCyclotronTracker.cpp
index fa08fd61c67b904d392087a68e204f3fe744cee3..6b1c9979d3dc4f4c683742a93b55d1605968a705 100644
--- a/src/Algorithms/ParallelCyclotronTracker.cpp
+++ b/src/Algorithms/ParallelCyclotronTracker.cpp
@@ -40,6 +40,10 @@
 #include "AbsBeamline/Monitor.h"
 #include "AbsBeamline/Multipole.h"
 #include "AbsBeamline/MultipoleT.h"
+#include "AbsBeamline/MultipoleTBase.h"
+#include "AbsBeamline/MultipoleTStraight.h"
+#include "AbsBeamline/MultipoleTCurvedConstRadius.h"
+#include "AbsBeamline/MultipoleTCurvedVarRadius.h"
 #include "AbsBeamline/Probe.h"
 #include "AbsBeamline/RBend.h"
 #include "AbsBeamline/RFCavity.h"
@@ -745,7 +749,6 @@ void ParallelCyclotronTracker::visitMonitor(const Monitor &corr) {
     //   applyDrift(flip_s * corr.getElementLength());
 }
 
-
 /**
  *
  *
@@ -772,6 +775,54 @@ void ParallelCyclotronTracker::visitMultipoleT(const MultipoleT &multT) {
     myElements.push_back(dynamic_cast<MultipoleT *>(multT.clone()));
 }
 
+/**
+ *
+ *
+ * @param multTstraight
+ */
+void ParallelCyclotronTracker::visitMultipoleTStraight(const MultipoleTStraight &multTstraight) {
+    *gmsg << "Adding MultipoleTStraight" << endl;
+    if (opalRing_m != NULL) {
+        opalRing_m->appendElement(multTstraight);
+    } else {
+        throw OpalException("ParallelCyclotronTracker::visitMultipoleTStraight",
+                            "Need to define a RINGDEFINITION to use MultipoleTStraight element");
+    }
+    myElements.push_back(dynamic_cast<MultipoleTStraight *>(multTstraight.clone()));
+}
+
+/**
+ *
+ *
+ * @param multTccurv
+ */
+void ParallelCyclotronTracker::visitMultipoleTCurvedConstRadius(const MultipoleTCurvedConstRadius &multTccurv) {
+    *gmsg << "Adding MultipoleTCurvedConstRadius" << endl;
+    if (opalRing_m != NULL) {
+        opalRing_m->appendElement(multTccurv);
+    } else {
+        throw OpalException("ParallelCyclotronTracker::visitMultipoleTCurvedConstRadius",
+                            "Need to define a RINGDEFINITION to use MultipoleTCurvedConstRadius element");
+    }
+    myElements.push_back(dynamic_cast<MultipoleTCurvedConstRadius *>(multTccurv.clone()));
+}
+
+/**
+ *
+ *
+ * @param multTvcurv
+ */
+void ParallelCyclotronTracker::visitMultipoleTCurvedVarRadius(const MultipoleTCurvedVarRadius &multTvcurv) {
+    *gmsg << "Adding MultipoleTCurvedVarRadius" << endl;
+    if (opalRing_m != NULL) {
+        opalRing_m->appendElement(multTvcurv);
+    } else {
+        throw OpalException("ParallelCyclotronTracker::visitMultipoleTCurvedVarRadius",
+                            "Need to define a RINGDEFINITION to use MultipoleTCurvedVarRadius element");
+    }
+    myElements.push_back(dynamic_cast<MultipoleTCurvedVarRadius *>(multTvcurv.clone()));
+}
+
 /**
  *
  *
@@ -3589,4 +3640,4 @@ void ParallelCyclotronTracker::injectBunch_m(bool& flagTransition) {
         // After this, numBunch_m is wrong but not needed anymore...
         numBunch_m--;
     }
-}
\ No newline at end of file
+}
diff --git a/src/Algorithms/ParallelCyclotronTracker.h b/src/Algorithms/ParallelCyclotronTracker.h
index 3bdf02cefe86e094428d7cf136f9021f1f96d27f..64dfcffa118510158a861afbb0ac027e25765dae 100644
--- a/src/Algorithms/ParallelCyclotronTracker.h
+++ b/src/Algorithms/ParallelCyclotronTracker.h
@@ -134,6 +134,15 @@ public:
 
     /// Apply the algorithm to a MultipoleT
     virtual void visitMultipoleT (const MultipoleT &);
+
+    /// Apply the algorithm to a MultipoleTStraight
+    virtual void visitMultipoleTStraight (const MultipoleTStraight &);
+
+    /// Apply the algorithm to a MultipoleTCurvedConstRadius
+    virtual void visitMultipoleTCurvedConstRadius (const MultipoleTCurvedConstRadius &);
+
+    /// Apply the algorithm to a MultipoleTCurvedVarRadius
+    virtual void visitMultipoleTCurvedVarRadius (const MultipoleTCurvedVarRadius &);
     
     /// Apply the algorithm to a Offset.
     virtual void visitOffset(const Offset &);
diff --git a/src/Algorithms/ParallelTTracker.h b/src/Algorithms/ParallelTTracker.h
index d47cd2a248b640ed75e221c5e2a06e5492747768..55159418c08b16404ed55c49a6e73d2769f16087 100644
--- a/src/Algorithms/ParallelTTracker.h
+++ b/src/Algorithms/ParallelTTracker.h
@@ -42,6 +42,7 @@
 #include "AbsBeamline/Marker.h"
 #include "AbsBeamline/Monitor.h"
 #include "AbsBeamline/Multipole.h"
+#include "AbsBeamline/MultipoleT.h"
 #include "AbsBeamline/Probe.h"
 #include "AbsBeamline/RBend.h"
 #include "AbsBeamline/RBend3D.h"
@@ -136,6 +137,9 @@ public:
     /// Apply the algorithm to a Multipole.
     virtual void visitMultipole(const Multipole &);
 
+    /// Apply the algorithm to a MultipoleT.
+    virtual void visitMultipoleT(const MultipoleT &);
+
     /// Apply the algorithm to a Probe.
     virtual void visitProbe(const Probe &);
 
@@ -381,6 +385,10 @@ inline void ParallelTTracker::visitMultipole(const Multipole &mult) {
     itsOpalBeamline_m.visit(mult, *this, itsBunch_m);
 }
 
+inline void ParallelTTracker::visitMultipoleT(const MultipoleT &mult) {
+    itsOpalBeamline_m.visit(mult, *this, itsBunch_m);
+}
+
 inline void ParallelTTracker::visitProbe(const Probe &prob) {
     itsOpalBeamline_m.visit(prob, *this, itsBunch_m);
 }
@@ -562,4 +570,4 @@ void ParallelTTracker::kickParticlesDKS() {
 }
 #endif
 
-#endif // OPAL_ParallelTTracker_HH
\ No newline at end of file
+#endif // OPAL_ParallelTTracker_HH
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6f3c7a0ea70004055ed773f901925d7ee205a280..ee98c6cee26251c61c81a4edd883991bec46e745 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -193,3 +193,7 @@ install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/src")
 # cmake-tab-width: 4
 # indent-tabs-mode:nil
 # End:
+
+#Turn on gcov
+#set(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
+#set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)
diff --git a/src/Classic/AbsBeamline/BeamlineVisitor.h b/src/Classic/AbsBeamline/BeamlineVisitor.h
index 15c19cc95d8348f87ff1d03da2d089320e4c5113..ec62d053785933d257184a02f39ec6c8c43fdbad 100644
--- a/src/Classic/AbsBeamline/BeamlineVisitor.h
+++ b/src/Classic/AbsBeamline/BeamlineVisitor.h
@@ -47,6 +47,9 @@ class Marker;
 class Monitor;
 class Multipole;
 class MultipoleT;
+class MultipoleTStraight;
+class MultipoleTCurvedConstRadius;
+class MultipoleTCurvedVarRadius;
 class Offset;
 class Patch;
 class Probe;
@@ -150,9 +153,18 @@ public:
     /// Apply the algorithm to a multipole.
     virtual void visitMultipole(const Multipole &) = 0;
 
-    /// Apply the algorithm to an arbitrary straight Multipole.
+    /// Apply the algorithm to an arbitrary Multipole.
     virtual void visitMultipoleT(const MultipoleT &) = 0;
 
+    /// Apply the algorithm to an arbitrary straight Multipole.
+    virtual void visitMultipoleTStraight(const MultipoleTStraight &) = 0;
+
+    /// Apply the algorithm to an arbitrary curved Multipole of constant radius.
+    virtual void visitMultipoleTCurvedConstRadius(const MultipoleTCurvedConstRadius &) = 0;
+
+    /// Apply the algorithm to an arbitrary curved Multipole of variable radius.
+    virtual void visitMultipoleTCurvedVarRadius(const MultipoleTCurvedVarRadius &) = 0;
+
     /// Apply the algorithm to a patch.
     virtual void visitPatch(const Patch &) = 0;
 
@@ -252,4 +264,4 @@ void BeamlineVisitor::visitRBend3D(const RBend3D &) {
 
 }
 
-#endif // CLASSIC_BeamlineVisitor_HH
\ No newline at end of file
+#endif // CLASSIC_BeamlineVisitor_HH
diff --git a/src/Classic/AbsBeamline/CMakeLists.txt b/src/Classic/AbsBeamline/CMakeLists.txt
index 2e8dab69e68e481423d58cff3ffb92046f3e00c0..124b17cfbdb041935ced0cbe3a61775faf4ec00a 100644
--- a/src/Classic/AbsBeamline/CMakeLists.txt
+++ b/src/Classic/AbsBeamline/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_subdirectory(EndFieldModel)
+add_subdirectory(MultipoleTFunctions)
 
 set (_SRCS
   AlignWrapper.cpp
@@ -24,6 +25,10 @@ set (_SRCS
   Monitor.cpp
   Multipole.cpp
   MultipoleT.cpp
+  MultipoleTBase.cpp
+  MultipoleTStraight.cpp
+  MultipoleTCurvedConstRadius.cpp
+  MultipoleTCurvedVarRadius.cpp
   Offset.cpp
   ParallelPlate.cpp
   Patch.cpp
@@ -75,6 +80,10 @@ set (HDRS
     Monitor.h
     Multipole.h
     MultipoleT.h
+    MultipoleTBase.h
+    MultipoleTStraight.h
+    MultipoleTCurvedConstRadius.h
+    MultipoleTCurvedVarRadius.h
     Offset.h
     ParallelPlate.h
     Patch.h
@@ -98,4 +107,8 @@ set (HDRS
     VariableRFCavity.h
 )
 
-install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/AbsBeamline")
\ No newline at end of file
+install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/AbsBeamline")
+
+#Turn on gcov
+#set(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
+#set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)
diff --git a/src/Classic/AbsBeamline/MultipoleT.cpp b/src/Classic/AbsBeamline/MultipoleT.cpp
index a9abf93a28465956b28ca79afa10d8e49c193bf1..40b13996b27825a136e3c3164b647fc0701a0d29 100644
--- a/src/Classic/AbsBeamline/MultipoleT.cpp
+++ b/src/Classic/AbsBeamline/MultipoleT.cpp
@@ -1,5 +1,6 @@
 /*
  *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
  *  All rights reserved.
  *  Redistribution and use in source and binary forms, with or without
  *  modification, are permitted provided that the following conditions are met:
@@ -29,6 +30,10 @@
 #include "Algorithms/PartBunch.h"
 #include "MultipoleT.h"
 #include <gsl/gsl_math.h>
+#include "MultipoleTFunctions/RecursionRelation.h"
+#include "MultipoleTFunctions/RecursionRelationTwo.h"
+#include "MultipoleTFunctions/tanhDeriv.h"
+#include "MultipoleTFunctions/CoordinateTransform.h"
 
 using namespace endfieldmodel;
 
@@ -37,14 +42,15 @@ MultipoleT::MultipoleT(const std::string &name):
     fringeField_l(endfieldmodel::Tanh()),
     fringeField_r(endfieldmodel::Tanh()),
     maxOrder_m(5),
+    maxOrderX_m(10),
     transMaxOrder_m(0),
     planarArcGeometry_m(1., 1.),
-    varStep_m(0.1),
     length_m(1.0),
     angle_m(0.0),
     entranceAngle_m(0.0),
     rotation_m(0.0),
     variableRadius_m(false),
+    boundingBoxLength_m(0.0),
     verticalApert_m(0.5),
     horizApert_m(0.5) {
 }
@@ -54,15 +60,18 @@ MultipoleT::MultipoleT(const MultipoleT &right):
     fringeField_l(right.fringeField_l),
     fringeField_r(right.fringeField_r),
     maxOrder_m(right.maxOrder_m),
+    maxOrderX_m(right.maxOrderX_m),
+    recursion_VarRadius_m(right.recursion_VarRadius_m),
+    recursion_ConstRadius_m(right.recursion_ConstRadius_m),
     transMaxOrder_m(right.transMaxOrder_m),
     transProfile_m(right.transProfile_m),
     planarArcGeometry_m(right.planarArcGeometry_m),
-    varStep_m(right.varStep_m),
     length_m(right.length_m),
     angle_m(right.angle_m),
     entranceAngle_m(right.entranceAngle_m),
     rotation_m(right.rotation_m),
     variableRadius_m(right.variableRadius_m),
+    boundingBoxLength_m(right.boundingBoxLength_m),
     verticalApert_m(right.verticalApert_m),
     horizApert_m(right.horizApert_m),
     dummy() {
@@ -85,15 +94,34 @@ bool MultipoleT::apply(const Vector_t &R, const Vector_t &P,
                const double &t,Vector_t &E, Vector_t &B) {
     /** Rotate coordinates around the central axis of the magnet */
     Vector_t R_prime = rotateFrame(R);
-    /** If magnet is not straight go to coords along the magnet */
-    if(angle_m != 0.0) {
-        Vector_t X = R_prime;
-        R_prime = transformCoords(X);
-    }
+    /** If magnet is not straight go to local Frenet-Serret coordinates */
+    R_prime[2] *= -1; //OPAL uses different sign convention...
+    Vector_t X = R_prime;
+    R_prime = transformCoords(X);
     if (insideAperture(R_prime)) {
-        B[0] = getBx(R_prime);
+        /** Transform B-field from local to lab coordinates */
+        double theta;
+        double prefactor = (length_m / angle_m) * (tanh((fringeField_l.getX0())
+                           / fringeField_l.getLambda()) +
+                           tanh((fringeField_r.getX0())
+                           / fringeField_r.getLambda()));
+        if (angle_m == 0.0) {
+            theta = 0.0;
+        } else if (!variableRadius_m) {
+            theta = R_prime[2] * angle_m / length_m;
+        } else if (variableRadius_m) {
+            theta = fringeField_l.getLambda() * log(cosh((R_prime[2] +
+                    fringeField_l.getX0()) / fringeField_l.getLambda())) -
+                    fringeField_r.getLambda() * log(cosh((R_prime[2] -
+                    fringeField_r.getX0()) / fringeField_r.getLambda()));
+            theta /= prefactor;
+        }
+        double Bx = getBx(R_prime);
+        double Bs = getBs(R_prime);
+        B[0] = Bx * cos(theta) - Bs * sin(theta);
+        B[2] = Bx * sin(theta) + Bs * cos(theta);
         B[1] = getBz(R_prime);
-        B[2] = getBs(R_prime);
+        B[2] *= -1; //OPAL uses different sign convention
         return false;
     } else {
         for(int i = 0; i < 3; i++) {
@@ -110,9 +138,9 @@ bool MultipoleT::apply(const size_t &i, const double &t,
 
 Vector_t MultipoleT::rotateFrame(const Vector_t &R) {
   Vector_t R_prime(3), R_pprime(3);
-    /** Apply two 2D rotation matrices to coords vector
-      * -> rotate around central axis => skew fields
-      * -> rotate azymuthaly => entrance angle
+    /** Apply two 2D rotation matrices to coordinate vector
+      * Rotate around central axis => skew fields
+      * Rotate azymuthaly => entrance angle
       */
     // 1st rotation
     R_prime[0] = R[0] * cos(rotation_m) + R[1] * sin(rotation_m);
@@ -131,7 +159,7 @@ Vector_t MultipoleT::rotateFrame(const Vector_t &R) {
 Vector_t MultipoleT::rotateFrameInverse(Vector_t &B) {
     /** This function represents the inverse of the rotation 
       * around the central axis performed by rotateFrame() method
-      * -> used to rotate B field back to global coord system
+      * Used to rotate B field back to global coordinate system
       */
     Vector_t B_prime(3);
     B_prime[0] = B[0] * cos(rotation_m) + B[1] * sin(rotation_m);
@@ -141,7 +169,7 @@ Vector_t MultipoleT::rotateFrameInverse(Vector_t &B) {
 }
 
 bool MultipoleT::insideAperture(const Vector_t &R) {
-    if (abs(R[1]) <= verticalApert_m / 2. && abs(R[0]) <= horizApert_m / 2.) {
+    if (std::abs(R[1]) <= verticalApert_m / 2. && std::abs(R[0]) <= horizApert_m / 2.) {
         return true;
     }
     else {
@@ -150,53 +178,60 @@ bool MultipoleT::insideAperture(const Vector_t &R) {
 }
 
 Vector_t MultipoleT::transformCoords(const Vector_t &R) {
-    Vector_t X(3), Y(3);
-    // if radius not variable
-    if (not variableRadius_m) {
+    Vector_t X(3);
+    // If radius is constant
+    if (!variableRadius_m) {
         double radius = length_m / angle_m;
-        // Transform from Cartesian to Frenet-Seret along the magnet
+        // Transform from Cartesian to Frenet-Serret along the magnet
         double alpha = atan(R[2] / (R[0] + radius ));
-        if (alpha != 0.0) {
-                X[0] = R[2] / sin(alpha) - radius;
+        if (alpha != 0.0 && angle_m != 0.0) {
+            X[0] = R[2] / sin(alpha) - radius;
             X[1] = R[1];
-            X[2] = radius * alpha;
+            X[2] = radius * alpha;// + boundingBoxLength_m;
         } else {
-                X = R;
+            X[0] = R[0];
+            X[1] = R[1];
+            X[2] = R[2];// + boundingBoxLength_m;
         }
     } else {
-        // if radius is variable need to transform coordinates at each
-        // point along the trajectory
-        double deltaAlpha, S = 0.0, localRadius;
-        double stepSize = varStep_m; // mm -> has a big effect on tracking time
-        if (abs(R[2]) <= stepSize) {
-            return R; // no transformation around origin
+        // If radius is variable
+        coordinatetransform::CoordinateTransform t(R[0], R[1], R[2],
+                                                   fringeField_l.getX0(),
+                                                   fringeField_l.getLambda(),
+                                                   fringeField_r.getLambda(),
+                                                   (length_m / angle_m));
+        std::vector<double> r = t.getTransformation();
+        X[0] = r[0];
+        X[1] = r[1];
+        X[2] = r[2];
+    }
+    return X;
+}
+
+void MultipoleT::setMaxOrder(std::size_t maxOrder) {
+    if (variableRadius_m && angle_m != 0.0) {
+        std::size_t N = recursion_VarRadius_m.size();
+        while (maxOrder >= N) {
+            polynomial::RecursionRelationTwo r(N, 2 * (N + maxOrderX_m + 1));
+            r.resizeX(transMaxOrder_m);
+            r.truncate(maxOrderX_m);
+            recursion_VarRadius_m.push_back(r);
+            N = recursion_VarRadius_m.size();
         }
-        Y = R;
-        Vector_t temp;
-        while (abs(Y[2]) > stepSize and getRadius(S) != -1) {
-            localRadius = getRadius(S);
-            deltaAlpha = stepSize / localRadius;
-            if (R[2] < 0) {
-                deltaAlpha *= - 1.; // rotate in the other direction
-            }
-            temp = Y;
-            // translation
-            temp[0] += localRadius * (1 - cos(deltaAlpha));
-            temp[2] -= localRadius * sin(deltaAlpha);
-            // + rotation along the ideal trajectory
-            Y[2] = temp[2] * cos(deltaAlpha) - temp[0] * sin(deltaAlpha);
-            Y[0] = temp[2] * sin(deltaAlpha) + temp[0] * cos(deltaAlpha);  
-            S += localRadius * deltaAlpha;
-            // until we reach the actual point from Cartesian coordinates
+    } else if (!variableRadius_m && angle_m != 0.0) {
+        std::size_t N = recursion_ConstRadius_m.size();
+        while (maxOrder >= N) {
+            polynomial::RecursionRelation r(N, 2 * (N + maxOrderX_m + 1));
+            r.resizeX(transMaxOrder_m);
+            r.truncate(maxOrderX_m);
+            recursion_ConstRadius_m.push_back(r);
+            N = recursion_ConstRadius_m.size();
         }
-        X[0] = Y[0];
-        X[1] = Y[1];
-        X[2] = S;
     }
-    return X;
+    maxOrder_m = maxOrder; 
 }
 
-void MultipoleT::setTransProfile(unsigned int n, double dTn) {
+void MultipoleT::setTransProfile(std::size_t n, double dTn) {
     if (n > transMaxOrder_m) {
       transMaxOrder_m = n;
       transProfile_m.resize(n+1, 0.0);
@@ -218,14 +253,14 @@ bool MultipoleT::setFringeField(double s0, double lambda_l, double lambda_r) {
 
 double MultipoleT::getBz(const Vector_t &R) {
     /** Returns the vertical field component
-      * -> sum_n  f_n * z^(2n) / (2n)!
+      * sum_n  f_n * z^(2n) / (2n)!
       */
     double Bz = 0.0;
     if (angle_m == 0.0) {
-        // Straight geometry -> use corresponding field expansion 
-        for(unsigned int n = 0; n <= maxOrder_m; n++) {
+        // Straight geometry -> Use corresponding field expansion directly
+        for(std::size_t n = 0; n <= maxOrder_m; n++) {
             double f_n = 0.0;
-            for(unsigned int i = 0; i <= n; i++)  {
+            for(std::size_t i = 0; i <= n; i++)  {
                 f_n += gsl_sf_choose(n, i) * getTransDeriv(2 * i, R[0]) * 
                        getFringeDeriv(2 * n - 2 * i, R[2]);
             }
@@ -234,12 +269,12 @@ double MultipoleT::getBz(const Vector_t &R) {
         }
     } else {
         if (variableRadius_m == true and getFringeDeriv(0, R[2]) < 1.0e-12) {
-            // return 0 if end of fringe field is reached
-            // this is to avoid functions being called at infinite radius
+            // Return 0 if end of fringe field is reached
+            // This is to avoid functions being called at infinite radius
             return 0.0; 
         }
-        // Curved geometry -> use corresponding field expansion
-        for(unsigned int n = 0; n <= maxOrder_m; n++) {
+        // Curved geometry -> Use full machinery of differential operators
+        for(std::size_t n = 0; n <= maxOrder_m; n++) {
             double f_n = getFn(n, R[0], R[2]);
             Bz += gsl_sf_pow_int(R[1], 2 * n) / gsl_sf_fact(2 * n) * f_n;
         }
@@ -249,14 +284,14 @@ double MultipoleT::getBz(const Vector_t &R) {
 
 double MultipoleT::getBx(const Vector_t &R) {
     /** Returns the radial component of the field
-      * -> sum_n z^(2n+1) / (2n+1)! * \partial_x f_n 
+      * sum_n z^(2n+1) / (2n+1)! * \partial_x f_n 
       */
     double Bx = 0.0;
     if (angle_m == 0.0) {
-        // Straight geometry -> use corresponding field expansion
-        for(unsigned int n = 0; n <= maxOrder_m; n++) {
+        // Straight geometry -> Use corresponding field expansion directly
+        for(std::size_t n = 0; n <= maxOrder_m; n++) {
             double f_n = 0.0;
-            for(unsigned int i = 0; i <= n; i++) {
+            for(std::size_t i = 0; i <= n; i++) {
                 f_n += gsl_sf_choose(n, i) * getTransDeriv(2 * i + 1, R[0]) * 
                    getFringeDeriv(2 * n - 2 * i, R[2]);
             }
@@ -266,15 +301,15 @@ double MultipoleT::getBx(const Vector_t &R) {
         }
     } else {
         if (variableRadius_m == true and getFringeDeriv(0, R[2]) < 1.0e-12) {
-            // return 0 if end of fringe field is reached
-            // this is to avoid functions being called at infinite radius
+            // Return 0 if end of fringe field is reached
+            // This is to avoid functions being called at infinite radius
             return 0.0; 
         }
-        // Curved geometry -> use corresponding field expansion
-        for(unsigned int n = 0; n <= maxOrder_m; n++) {
+        // Curved geometry -> Use full machinery of differential operators
+        for(std::size_t n = 0; n <= maxOrder_m; n++) {
             double partialX_fn = getFnDerivX(n, R[0], R[2]);
-            Bx += gsl_sf_pow_int(R[1], 2 * n + 1) / gsl_sf_fact(2 * n + 1); 
-            Bx *= partialX_fn;
+            Bx += partialX_fn * gsl_sf_pow_int(R[1], 2 * n + 1)
+                  / gsl_sf_fact(2 * n + 1); 
         }
     }
     return Bx;
@@ -282,14 +317,14 @@ double MultipoleT::getBx(const Vector_t &R) {
 
 double MultipoleT::getBs(const Vector_t &R) {
     /** Returns the component of the field along the central axis 
-      * -> 1/h_s * sum_n z^(2n+1) / (2n+1)! \partial_s f_n
+      * 1/h_s * sum_n z^(2n+1) / (2n+1)! \partial_s f_n
       */
     double Bs = 0.0;
     if (angle_m == 0.0) {
-        // Straight geometry -> use corresponding field expansion 
-        for(unsigned int n = 0; n <= maxOrder_m; n++) {
+        // Straight geometry -> Use corresponding field expansion directly
+        for(std::size_t n = 0; n <= maxOrder_m; n++) {
             double f_n = 0.0;
-            for(unsigned int i = 0; i <= n; i++) {
+            for(std::size_t i = 0; i <= n; i++) {
                 f_n += gsl_sf_choose(n, i) * getTransDeriv(2 * i, R[0]) *
                        getFringeDeriv(2 * n - 2 * i + 1, R[2]);
             }
@@ -299,15 +334,15 @@ double MultipoleT::getBs(const Vector_t &R) {
         }
     } else {
         if (variableRadius_m == true and getFringeDeriv(0, R[2]) < 1.0e-12) {
-            // return 0 if end of fringe field is reached
-            // this is to avoid functions being called at infinite radius
+            // Return 0 if end of fringe field is reached
+            // This is to avoid functions being called at infinite radius
             return 0.0; 
         }
-        // Curved geometry -> use corresponding field expansion
-        for(unsigned int n = 0; n <= maxOrder_m; n++) {
+        // Curved geometry -> Use full machinery of differential operators
+        for(std::size_t n = 0; n <= maxOrder_m; n++) {
             double partialS_fn = getFnDerivS(n, R[0], R[2]);
-            Bs += gsl_sf_pow_int(R[1], 2 * n + 1) / gsl_sf_fact(2 * n + 1);
-            Bs *= partialS_fn;
+            Bs += partialS_fn * gsl_sf_pow_int(R[1], 2 * n + 1)
+                  / gsl_sf_fact(2 * n + 1);
         }
         Bs /= getScaleFactor(R[0], R[2]);
     }
@@ -315,14 +350,19 @@ double MultipoleT::getBs(const Vector_t &R) {
 }
 
 double MultipoleT::getFringeDeriv(int n, double s) {
-    // Wraps around the getTanh method of endfield 
-    double temp;
-    temp = fringeField_l.getTanh(s, n) - fringeField_r.getNegTanh(s, n);
-    temp /= 2;
-    return temp;
+    if (n <= 10) {
+        return (fringeField_l.getTanh(s, n) - fringeField_r.getNegTanh(s, n))
+                / 2;
+    } else {
+        return tanhderiv::integrate(s,
+                                    fringeField_l.Tanh::getX0(),
+                                    fringeField_l.Tanh::getLambda(),
+                                    fringeField_r.Tanh::getLambda(),
+                                    n);
+    }
 }
 
-double MultipoleT::getTransDeriv(unsigned int n, double x) {
+double MultipoleT::getTransDeriv(std::size_t n, double x) {
     /** Sets a vector of the coefficients in the polynomial expansion
       * of transverse profile; shifts them to the left and multiply by
       * corresponding power each time to take derivative once; 
@@ -332,20 +372,20 @@ double MultipoleT::getTransDeriv(unsigned int n, double x) {
     std::vector<double> temp = transProfile_m;
     if (n <= transMaxOrder_m) {
         if (n != 0) {
-            for(unsigned int i = 1; i <= n; i++) {
-                for(unsigned int j = 0; j <= transMaxOrder_m; j++) {
+            for(std::size_t i = 1; i <= n; i++) {
+                for(std::size_t j = 0; j <= transMaxOrder_m; j++) {
                     if (j <= transMaxOrder_m - i) {
-                        // move terms to the left and multiply by power
+                        // Move terms to the left and multiply by power
                         temp[j] = temp[j + 1] * (j + 1);
                         } else {
-                        // put 0 at the end for missing higher powers
+                        // Put 0 at the end for missing higher powers
                         temp[j] = 0.0;
                     }
                 }
             }
         }
         // Now use the vector to calculate value of the function
-        for(unsigned int k = 0; k <= transMaxOrder_m; k++) {
+        for(std::size_t k = 0; k <= transMaxOrder_m; k++) {
             func += temp[k] * gsl_sf_pow_int(x, k);
         }
     }
@@ -386,185 +426,115 @@ std::vector<double> MultipoleT::getFringeLength() const {
 }
 
 double MultipoleT::getRadius(double s) {
-    /** The radius is calculated to be inverse proportional to the field on
-      * the refrence trajectory (central axis of magnet)
-      * @f$ \rho (s) = \rho(0) * S(0) / S(s) @f$
-      */
     double centralRadius = length_m / angle_m;
-    // at the centre of the magnet
-    double propCoeff = centralRadius * getFringeDeriv(0, 0);
-    // move to current position on central axis
+    if (!variableRadius_m) {
+        return centralRadius;
+    }
     if (getFringeDeriv(0, s) != 0.0) {
-       return propCoeff / getFringeDeriv(0, s);
+       return centralRadius * getFringeDeriv(0, 0) / getFringeDeriv(0, s);
     } else {
-       return -1; // return -1 if radius is infinite 
+       return -1; // Return -1 if radius is infinite 
     }
 }
 
-double MultipoleT::getRadiusFirstDeriv(double s) {
-    /** @return The derivative of the function which describes the radius
-      * as a function of s; 
-      */
-    return  -1.0 * getRadius(s) * getFringeDeriv(1, s) / getFringeDeriv(0, s);
-}
-
-double MultipoleT::getRadiusSecDeriv(double s) {
-    /** @return The second derivative of radius function
-      */
-    double deriv = 0.0;
-    deriv += gsl_pow_2(getFringeDeriv(1, s)) / getFringeDeriv(0, s);
-    deriv -= getFringeDeriv(2, s);
-    deriv *= getRadius(s);
-    return deriv;
-}
-
 double MultipoleT::getScaleFactor(double x, double s) {
-    /** @return The scale factor h_s = [(1 + x / rho)^2 + (d rho / ds)^2]^0.5
-      * rho -> radius
-      * used in field expansion, comes from Laplacian when curvature is variable
-      * if radius is fixed returns h_s = 1 + x / rho
-      */
-    if (not variableRadius_m) {
-        double rho = length_m / angle_m;
-        return 1 + x / rho;
+    if (!variableRadius_m) {
+        return 1 + x * angle_m/ length_m;
     } else {
-        double temp = 0.0;
-        temp += gsl_pow_2(getRadiusFirstDeriv(s));
-        temp += gsl_pow_2(1 + x / getRadius(s));
-        return std::pow(temp, 0.5);
+        return 1 + x / getRadius(s);
     }
 }
 
-double MultipoleT::getScaleFactorDerivX(double x, double s) {
-    /** Helper function to bunch terms together
-      * -> returns the partial deriv of the scale factor wrt. x
-      * -> partial h_s / partial x#include "Algorithms/PartBunch.h"
-      * -> = (1 + x / rho) / (rho * h_s ^ 0.5)
-      */
-    double temp = 0.0;
-    temp += (1 + x / getRadius(s)) / getRadius(s);
-    temp /= std::pow(getScaleFactor(x, s), 0.5);
-    return temp;
-}
-
-double MultipoleT::getScaleFactorDerivS(double x, double s) {
-    /** Helper function to bunch terms together
-      * -> returns partial h_s / partial s
-      * -> = (h_s)^(-1/2) * partial rho / partial s *
-      * -> * [-(1+x/rho)*x/rho^2 + partial^2 rho / partial^x]
-      */ 
-    double temp = 0.0;
-    temp -= (1 + x /getRadius(s)) * x / gsl_pow_2(getRadius(s));
-    temp += getRadiusSecDeriv(s);
-    temp *= getRadiusFirstDeriv(s) / std::pow(getScaleFactor(x, s), -0.5);
-    return temp;
-}
-
-double MultipoleT::getFnDerivX(unsigned int n, double x, double s) {
-    /** Returns the partial derivative of f_n(x, s) wrt x
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
+double MultipoleT::getFnDerivX(std::size_t n, double x, double s) {
     if (n == 0) {
       return getTransDeriv(1, x) * getFringeDeriv(0, s);
     }
     double deriv = 0.0;
     double stepSize = 1e-3;
-    deriv += getFn(n, x - 2. * stepSize, s) - 8. * getFn(n, x - stepSize, s);
-    deriv += 8. * getFn(n, x + stepSize, s) - getFn(n, x + 2. * stepSize, s);
-    deriv /= 12.0 * stepSize;
+    deriv += 1. * getFn(n, x - 2. * stepSize, s);
+    deriv += -8. * getFn(n, x - stepSize, s);
+    deriv += 8. * getFn(n, x + stepSize, s);
+    deriv += -1. * getFn(n, x + 2. * stepSize, s);
+    deriv /= 12 * stepSize;
     return deriv;
 }
 
-double MultipoleT::getFnDerivS(unsigned int n, double x, double s) {
-    /** Returns the partial derivative of f_n(x, s) wrt s
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
+double MultipoleT::getFnDerivS(std::size_t n, double x, double s) {
     if (n == 0) {
       return getTransDeriv(0, x) * getFringeDeriv(1, s);
     }
     double deriv = 0.0;
     double stepSize = 1e-3;
-    deriv += getFn(n, x, s - 2. * stepSize) - 8. * getFn(n, x, s - stepSize);
-    deriv += 8. * getFn(n, x, s + stepSize) - getFn(n, x, s + 2. * stepSize);
-    deriv /= 12.0 * stepSize;
-    return deriv;
-}
-
-double MultipoleT::getFnSecDerivX(unsigned int n, double x, double s) {
-    /** Returns the second partial derivative of f_n(x, s) wrt x
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
-    if (n == 0) {
-      return getTransDeriv(2, x) * getFringeDeriv(0, s);
-    }
-    double deriv = 0.0;
-    double stepSize = 1e-3;
-    deriv += getFn(n, x + 2. * stepSize, s) + getFn(n, x - 2. * stepSize, s);
-    deriv -= getFn(n, x + stepSize, s) + getFn(n, x - stepSize, s);
-    deriv /= 3. * gsl_pow_2(stepSize);
-    return deriv;
-}
-
-double MultipoleT::getFnSecDerivS(unsigned int n, double x, double s) {
-    /** Returns the second partial derivative of f_n(x, s) wrt s
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
-    if (n == 0) {
-      return getTransDeriv(0, x) * getFringeDeriv(2, s);
-    }
-    double deriv = 0.0;
-    double stepSize = 1e-3;
-    deriv += getFn(n, x, s + 2. * stepSize) + getFn(n, x, s - 2. * stepSize);
-    deriv -= getFn(n, x, s + stepSize) + getFn(n, x, s - stepSize);
-    deriv /= 3. * gsl_pow_2(stepSize);
+    deriv += 1. * getFn(n, x, s - 2. * stepSize);
+    deriv += -8. * getFn(n, x, s - stepSize);
+    deriv += 8. * getFn(n, x, s + stepSize);
+    deriv += -1. * getFn(n, x, s + 2. * stepSize);
+    deriv /= 12 * stepSize;
     return deriv;
 }
 
-double MultipoleT::getFn(unsigned int n, double x, double s) {
-    /** Returns the function f_n(x, s) for curved geometry
-      * -> based on recursion
-      */
+double MultipoleT::getFn(std::size_t n, double x, double s) {
     if (n == 0) {
         return getTransDeriv(0, x) * getFringeDeriv(0, s);
     }
-    // If radius is constant use corresponding recursion
-    if (not variableRadius_m) {
+    if (!variableRadius_m) {
         double rho = length_m / angle_m;
-        double h_s = 1 + x / rho;
-        double temp = 0.0;
-        temp += getFnDerivX(n - 1, x, s) / (h_s * rho);
-        temp += getFnSecDerivX(n - 1, x, s);
-        temp += getFnSecDerivS(n - 1, x, s) / gsl_pow_2(h_s);
-        temp *= -1.0;
-        return temp;
+        double func = 0.0;
+        
+        for (std::size_t j = 0;
+             j <= recursion_ConstRadius_m.at(n).getMaxSDerivatives();
+             j++) {
+            double FringeDerivj = getFringeDeriv(2 * j, s);
+            for (std::size_t i = 0;
+                 i <= recursion_ConstRadius_m.at(n).getMaxXDerivatives();
+                 i++) {
+                if (recursion_ConstRadius_m.at(n).isPolynomialZero(i, j)) {
+                    continue;
+                }
+                func += (recursion_ConstRadius_m.at(n)
+                        .evaluatePolynomial(x / rho, i, j) *
+                        getTransDeriv(i, x) * FringeDerivj) /
+                        gsl_sf_pow_int(rho, 2 * n - i - 2 * j);
+            }
+        }
+        func *= gsl_sf_pow_int(-1.0, n);
+        return func;
     } else {
-        // If radius is variable use corresponding recursion 
-        double temp = 0.0;
-        double h_s = getScaleFactor(x, s);
-        temp += getScaleFactorDerivX(x, s) * getFnDerivX(n - 1, x, s);
-        temp -= getScaleFactorDerivS(x, s) * getFnDerivS(n - 1, x, s) / 
-                gsl_pow_2(h_s);
-        temp += getFnSecDerivX(n - 1, x, s) * h_s;
-        temp += getFnSecDerivS(n - 1, x, s) / h_s;
-        temp *= -1.0 / h_s; 
-        return temp;
+        double rho = length_m / angle_m;
+        double S_0 = getFringeDeriv(0, 0);
+        double y = getFringeDeriv(0, s) / (S_0 * rho);
+        double func = 0.0;
+        std::vector<double> fringeDerivatives;
+        for (std::size_t j = 0;
+             j <= recursion_VarRadius_m.at(n).getMaxSDerivatives();
+             j++) {
+            fringeDerivatives.push_back(getFringeDeriv(j, s) / (S_0 * rho));
+        }
+        for (std::size_t i = 0;
+             i <= recursion_VarRadius_m.at(n).getMaxXDerivatives();
+             i++) {
+            double temp = 0.0;
+            for (std::size_t j = 0;
+                 j <= recursion_VarRadius_m.at(n).getMaxSDerivatives();
+                 j++) {
+                temp += recursion_VarRadius_m.at(n)
+                        .evaluatePolynomial(x, y, i, j, fringeDerivatives)
+                        * fringeDerivatives.at(j);
+            }
+            func += temp * getTransDeriv(i, x);
+        }
+        func *= gsl_sf_pow_int(-1.0, n) * S_0 * rho;
+        return func;
     }
-}    
-        
-
+}
+ 
 inline
 void MultipoleT::initialise(PartBunchBase<double, 3>* bunch, 
                 double &startField, 
                 double &endField) {
     RefPartBunch_m = bunch;
+    planarArcGeometry_m.setElementLength(2 * boundingBoxLength_m);
+    planarArcGeometry_m.setCurvature(angle_m / length_m);
 }
 
 inline
diff --git a/src/Classic/AbsBeamline/MultipoleT.h b/src/Classic/AbsBeamline/MultipoleT.h
index 1b4075243b9748030206411b7232b08786febca2..f24b3c7acfdb1fcb7daa7870272f51a8373e5eda 100644
--- a/src/Classic/AbsBeamline/MultipoleT.h
+++ b/src/Classic/AbsBeamline/MultipoleT.h
@@ -1,5 +1,6 @@
 /*
  *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
  *  All rights reserved.
  *  Redistribution and use in source and binary forms, with or without
  *  modification, are permitted provided that the following conditions are met:
@@ -37,7 +38,7 @@
   * ---------------------------------------------------------------------
   *
   * Class category: AbsBeamline \n
-  * $Author: Titus Dascalu, Chris Rogers
+  * $Author: Titus Dascalu, Martin Duy Tat, Chris Rogers
   *
   * ---------------------------------------------------------------------
   *
@@ -80,354 +81,273 @@
 #include "Algorithms/Vektor.h"
 #include "AbsBeamline/Component.h"
 #include "AbsBeamline/BeamlineVisitor.h"
+#include "AbsBeamline/MultipoleTFunctions/RecursionRelation.h"
+#include "AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.h"
 #include "gsl/gsl_sf.h"
 #include <vector>
 
 class MultipoleT: public Component {
- public: 
-    /** Constructor: &name -> user-defined name */
+public: 
+    /** Constructor
+     *  \param name -> User-defined name
+     */
     explicit MultipoleT(const std::string &name);
-    
     /** Copy constructor */
     MultipoleT(const MultipoleT &right);
-
     /** Destructor */ 
     ~MultipoleT();
-
     /** Inheritable copy constructor */
     ElementBase* clone() const;
-
-    /** Return a dummy (0.) field value */
+    /** Return a dummy field value */
     EMField &getField();
-
-    /** Return a dummy (0.) field value */
+    /** Return a dummy field value */
     const EMField &getField() const;
-
     /** Not implemented */
     void getDimensions(double &zBegin, double &zEnd) const;
-
     /** Calculate the field at some arbitrary position
-     *
-     *  \param R -> position in the local coordinate system of the multipole
-     *  \param P -> not used
-     *  \param t -> time at which the field is to be calculated
-     *  \param E -> calculated electric field - always 0 (no E-field)
-     *  \param B -> calculated magnetic field
-     *  \returns true if particle is outside the field map, else false
+     *  If particle is outside field map true is returned,
+     *  otherwise false is returned
+     *  \param R -> Position in the lab coordinate system of the multipole
+     *  \param P -> Not used
+     *  \param t -> Time at which the field is to be calculated
+     *  \param E -> Calculated electric field - always 0 (no E-field)
+     *  \param B -> Calculated magnetic field
      */
     bool apply(const Vector_t &R, const Vector_t &P, const double &t,
                Vector_t &E, Vector_t &B);
-
     /** Calculate the field at the position of the ith particle
-     *
-     *  \param i -> index of the particle event; field is calculated at this
-     *         position
-     *  \param t -> time at which the field is to be calculated
-     *  \param E -> calculated electric field - always 0 (no E-field)
-     *  \param B -> calculated magnetic field
-     *  \returns true if particle is outside the field map
+     *  \param i -> Index of the particle event; field is calculated at this
+     *  position
+     *  If particle is outside field map true is returned,
+     *  otherwise false is returned
+     *  \param t -> Time at which the field is to be calculated
+     *  \param E -> Calculated electric field - always 0 (no E-field)
+     *  \param B -> Calculated magnetic field
      */
     bool apply(const size_t &i, const double &t, Vector_t &E, Vector_t &B);
-
-     /** Initialise the MultipoleT
-      *
-      *  \param -> bunch the global bunch object
-      *  \param -> startField not used
-      *  \param -> endField not used
-      */
-    void initialise(PartBunchBase<double, 3>*, double &startField, double &endField);
-
-     /** Finalise the MultipoleT - sets bunch to NULL */
+    /** Initialise the MultipoleT
+     *  \param bunch -> Bunch the global bunch object
+     *  \param startField -> Not used
+     *  \param endField -> Not used
+     */
+    void initialise(PartBunchBase<double, 3>*,
+                    double &startField,
+                    double &endField);
+    /** Finalise the MultipoleT - sets bunch to NULL */
     void finalise();
-
-     /** Return true if dipole component not zero */
+    /** Return true if dipole component not zero */
     bool bends() const;
-
     /** Return the cell geometry */
     PlanarArcGeometry& getGeometry();
-
     /** Return the cell geometry */
     const PlanarArcGeometry& getGeometry() const;
-
     /** Accept a beamline visitor */
     void accept(BeamlineVisitor& visitor) const;
-    
     /** Get the dipole constant B_0 */
     double getDipoleConstant() const;
-
     /** Set the dipole constant B_0 */
     void setDipoleConstant(double B0);
-    
-     /** Get the number of terms used in calculation of field components
-       * max power of z in Bz is 2 * maxOrder
-       */
-    unsigned int getMaxOrder() const;
-
-     /** Set the number of terms used in calculation of field components
-       * max power of z in Bz is 2 * maxOrder
-       */
-    void setMaxOrder(unsigned int maxOrder);
-
-    /** Get the maximum order in the given transverse profile
-      * -> highest power in given mid-plane field expansion
-      */
-    unsigned int getTransMaxOrder() const;
-
+    /** Get the number of terms used in calculation of field components */
+    std::size_t getMaxOrder() const;
+    /** Set the number of terms used in calculation of field components \n
+     *  Maximum power of z in Bz is 2 * maxOrder_m \n
+     *  \param maxOrder -> Number of terms in expansion in z
+     */
+    void setMaxOrder(std::size_t maxOrder);
+    /** Get highest power of x in polynomial expansions */
+    std::size_t getMaxXOrder() const;
+    /** Set the number of terms used in polynomial expansions
+     *  \param maxXOrder -> Number of terms in expansion in z
+     */
+    void setMaxXOrder(std::size_t maxXOrder);
+    /** Get the maximum order in the given transverse profile */
+    std::size_t getTransMaxOrder() const;
     /** Set the maximum order in the given transverse profile
-      * -> highest power in given mid-plane field expansion 
-      */
-    void setTransMaxOrder(unsigned int transMaxOrder);
-    
+     *  \param transMaxOrder -> Highest power of x in field expansion 
+     */
+    void setTransMaxOrder(std::size_t transMaxOrder);
     /** Set transverse profile T(x)
-      *
       * T(x) = B_0 + B1 x + B2 x^2 + B3 x^3 + ...
-      * n -> the order of the term (d^n/dx^n) to be set
-      * dTn -> value of n-th derivative
+      * \param n -> Order of the term (d^n/dx^n) to be set
+      * \param Bn -> Value of transverse profile coefficient
       */
-    void setTransProfile(unsigned int n, double Bn);
-
-    /** Get transverse profile n-th term */
+    void setTransProfile(std::size_t n, double Bn);
+    /** Get transverse profile
+     *  \param n -> Power of x
+     */
     double getTransProfile(int n) const;
-
-    /** Get all term of transverse profile */
+    /** Get all terms of transverse profile */
     std::vector<double> getTransProfile() const;
-
-    /** Set fringe field model
-      *
-      * Tanh model used here
-      * @f[ 1/2 * \left [tanh \left( \frac{s + s_0}{\lambda_{left}} \right) 
-      * - tanh \left( \frac{s - s_0}{\lambda_{right}} \right) \right] @f] 
-      * s0 -> centre field length and
-      * @f$ \lambda_{left} @f$ -> left end field length
-      * @f$ \lambda_{right} @f$ -> right end field length
-      * max_index -> (default 10) used to set up for differentiation - cannot     
-      * calculate higher differentials than exist in max_index (this is always
-      * set to be 1 + twice the maxOrder_m when setting the fringe field) 
-      * return true after fringe field is set
-      */
+    /** Set fringe field model \n
+     *  Tanh model used here \n
+     *  @f[ 1/2 * \left [tanh \left( \frac{s + s_0}{\lambda_{left}} \right) 
+     *  - tanh \left( \frac{s - s_0}{\lambda_{right}} \right) \right] @f] 
+     *  \param s0 -> Centre field length
+     *  \param \lambda_{left} -> Left end field length
+     *  \param \lambda_{right} -> Right end field length
+     */
     bool setFringeField(double s0, double lambda_left, double lambda_right);
-    
     /** Return vector of 2 doubles
       * [left fringe length, right fringelength]  
       */
     std::vector<double> getFringeLength() const;
-
     /** Set the bending angle of the magnet */
     void setBendAngle(double angle);
-
     /** Get the bending angle of the magnet */
     double getBendAngle() const;
-    
-    /** Set the entrance angle */
+    /** Set the entrance angle
+     *  \param entranceAngle -> Entrance angle
+     */
     void setEntranceAngle(double entranceAngle);
-
     /** Get the entrance angle */
     double getEntranceAngle() const;
-
-    /** Get the bending radius 
-      * not used
-      * when needed radius is found from length_m / angle_m
+    /** Get the bending radius \n 
+      * Not used, when needed radius is found from length_m / angle_m
       */
     double getBendRadius() const;
-
-    /** Set the length of the magnet
-      * if straight-> actual length
-      * if curved -> arc length
+    /** Set the length of the magnet \n
+      * If straight-> Actual length \n
+      * If curved -> Arc length \n
       */
     void setLength(double length);
-
     /** Get the length of the magnet */
     double getLength() const;
-
-    /** not used */
+    /** Not used */
     double getChordLength() const;
-
     /** Set the aperture dimensions
-      * this element only supports a rectangular aperture
+      * This element only supports a rectangular aperture
+      * \param vertAp -> Vertical aperture length
+      * \param horizAp -> Horisontal aperture length
       */
     void setAperture(double vertAp, double horizAp);
-
     /** Get the aperture dimensions
-      * returns a vector of 2 doubles
+      * Returns a vector of 2 doubles
       */
     std::vector<double> getAperture() const;
-
-    /** Set the angle of rotation of the magnet around its axis
-      * -> enables to make skew components
-      */
+    /** Set the angle of rotation of the magnet around its axis \n
+     *  To make skew components
+     *  \param rot -> Angle of rotation
+     */
     void setRotation(double rot);
-
     /** Get the angle of rotation of the magnet around its axis */
     double getRotation() const;
-    
     /** Set variable radius flag to true */
     void setVarRadius();
-
     /** Get the value of variableRadius_m */
     bool getVarRadius() const;
+    /** Get distance between centre of magnet and entrance */
+    double getBoundingBoxLength() const;
+    /** Set distance between centre of magnet and enctrance
+     *  \param boundingBoxLength -> Distance between centre and entrance
+     */
+    void setBoundingBoxLength(const double &boundingBoxLength);
 
-    /** Set the step used in changing coord systems for variable radius 
-      * -> units used: mm
-      * -> default: 50
-      * -> has a considerable effect on tracking time
-      */
-    void setVarStep(double step);
-
-    /** Get the step used in changing coord system for variable radius */
-    double getVarStep() const;
-
- private:
+private:
     MultipoleT operator=(const MultipoleT& rhs);
-    
     // End fields
-    endfieldmodel::Tanh fringeField_l; // left
-    endfieldmodel::Tanh fringeField_r; // right
-    
-    /** Field expansion parameters */
-
-    // number of terms used in calculating field components
-    unsigned int maxOrder_m = 0;
-
-    // highest power in given mid-plane field
-    unsigned int transMaxOrder_m = 0; 
-
+    endfieldmodel::Tanh fringeField_l; // Left
+    endfieldmodel::Tanh fringeField_r; // Right
+    /** Field expansion parameters \n
+     *  Number of terms in z expansion used in calculating field components
+     */
+    std::size_t maxOrder_m = 0;
+    /** Highest order of polynomial expansions in x */
+    std::size_t maxOrderX_m = 0;
+    /** Objects for storing differential operator acting on Fn */
+    std::vector<polynomial::RecursionRelationTwo> recursion_VarRadius_m;
+    std::vector<polynomial::RecursionRelation> recursion_ConstRadius_m;
+    /** Highest power in given mid-plane field */
+    std::size_t transMaxOrder_m = 0; 
+    /** List of transverse profile coefficients */
     std::vector<double> transProfile_m;
-    
-    // Geometry
+    /** Geometry */
     PlanarArcGeometry planarArcGeometry_m;
-    
     /** Rotate frame for skew elements
-      * consecutive rotations:
-      * 1st -> about central axis
-      * 2nd -> azymuthal rotation
-      */
+     *  Consecutive rotations:
+     *  1st -> about central axis
+     *  2nd -> azimuthal rotation
+     *  \param R -> Vector to be rotated
+     */
     Vector_t rotateFrame(const Vector_t &R);
-
-    /** Inverse of the 1st rotation in rotateFrame() method
-      * -> used to rotate B field back to global coord system
-      */
+    /** Inverse of the 1st rotation in rotateFrame() method \n
+     *  Used to rotate B field back to global coordinate system
+     */
     Vector_t rotateFrameInverse(Vector_t &B);
-
-    /** Transform to Frenet-Serret coordinates for sector magnets
-      * -> if the radius is variable, the coord system is rotated 
-      *    step by step along the reference trajectory
-      * -> step size is set by setVarStep() method
-      */
+    /** Transform to Frenet-Serret coordinates for sector magnets */
     Vector_t transformCoords(const Vector_t &R);
-
-    // Step size for changing coordinates along trajectory 
-    // when radius is variable
-    double varStep_m;
-
-    // Magnet parameters
+    /** Magnet parameters */
     double length_m;
     double angle_m;
     double entranceAngle_m;
     double rotation_m;
-
-    // Variable radius flag;
+    /** Variable radius flag */
     bool variableRadius_m;
-
-    // Get field component methods
+    /** Distance between centre of magnet and entrance */
+    double boundingBoxLength_m;
+    /** Get field component methods
+     */
     double getBx (const Vector_t &R);
     double getBz (const Vector_t &R);
     double getBs (const Vector_t &R);
-
-    // Assume rectangular aperture with dimensions given by
+    /** Assume rectangular aperture with these dimensions */
     double verticalApert_m;
     double horizApert_m;
-
-    // Not implemented
+    /** Not implemented */
     BMultipoleField dummy;
-    
-    // Returns the value of the fringe field n-th derivative at s
+    /** Returns the value of the fringe field n-th derivative at s
+     *  \param n -> nth derivative
+     *  \param s -> Coordinate s
+     */
     double getFringeDeriv(int n, double s);
-
-    // Returns the value of the transverse field n-th derivative at x
-    double getTransDeriv(unsigned int n, double x);
-    
-    // Tests if inside the magnet
+    /** Returns the value of the transverse field n-th derivative at x
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     */
+    double getTransDeriv(std::size_t n, double x);
+    /** Tests if inside the magnet
+     *  \param R -> Coordinate vector
+     */
     bool insideAperture(const Vector_t &R);
-
+    /** Radius of curvature \n
+     *  If radius of curvature is infinite, -1 is returned \n
+     *  If radius is constant, then \n
+     *  @f$ \rho(s) = length_m / angle_m @f$ \n
+     *  If radius is variable, then
+     *  @f$ \rho(s) = rho(0) * S(0) / S(s) @f$
+     *  where S(s) is the fringe field
+     *  \param s -> Coordinate s
+     */
     double getRadius(double s);
-    /** The radius is calculated to be proportional to the field on the 
-      * refrence trajectory (central axis of magnet) 
-      * @f$ \rho (s) = \rho(0) * S(0) / S(s) @f$
-      * -> returns -1 if radius is infinite
-      */
-
-    double getRadiusFirstDeriv(double s);
-    /** @return the derivative of the function which describes the radius
-      * as a function of s; 
-      */
-
-    double getRadiusSecDeriv(double s);
-    /** @return The second derivative of radius function
-      */
-
+    /** Returns the scale factor @f$ h_s = 1 + x / \rho(s) @f$
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
     double getScaleFactor(double x, double s);
-    /** Returns the scale factor @f$ h_s = [(1 + x / \rho)^2 + 
-     *  (d \rho / ds)^2]^0.5 @f$
-      * rho -> radius
-      * used in field expansion, comes from Laplacian when curvature is variable
-      * if radius is fixed returns @f$  h_s = 1 + x / \rho @f$
-      */
-
-    double getScaleFactorDerivX(double x, double s);
-    /** Helper function to bunch terms together
-      * -> returns the partial deriv of the scale factor wrt. x
-      * -> @f$ \partial_x h_s = (1 + x / \rho) / (\rho * h_s ^ 0.5) @f$
-      */
-
-    double getScaleFactorDerivS(double x, double s);
-    /** Helper function to bunch terms together
-      * -> returns @f$ \partial_s h_s @f$
-      * -> @f$ = (h_s)^(-1/2) * \partial_s \rho * 
-      * [-(1+x/\rho)*x/\rho^2 + {\partial_x}^2 \rho] @f$
-      */ 
-    
-    double getFnDerivX(unsigned int n, double x, double s);
-    /** Returns the partial derivative of f_n(x, s) wrt x
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
-    
-    double getFnDerivS(unsigned int n, double x, double s);
-    /** Returns the partial derivative of f_n(x, s) wrt s
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
-
-    double getFnSecDerivX(unsigned int n, double x, double s);
-    /** Returns the second partial derivative of f_n(x, s) wrt x
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
-
-    double getFnSecDerivS(unsigned int n, double x, double s);
-    /** Returns the second partial derivative of f_n(x, s) wrt s
-      * -> numerical differentiation
-      * -> 5-points formula
-      * -> error of order stepSize ^ 4
-      */
-
-    double getFn(unsigned int n, double x, double s);
-    /** Returns the function @f$ f_n(x, s) @f$ for curved geometry
-      * -> based on recursion
-      */
-    
+    /** Calculate partial derivative of fn wrt x using a 5-point
+     *  finite difference formula \n
+     *  Error of order stepSize^4
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    double getFnDerivX(std::size_t n, double x, double s);
+    /** Calculate partial derivative of fn wrt s using a 5-point
+     *  finite difference formuln
+     *  Error of order stepSize^4
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    double getFnDerivS(std::size_t n, double x, double s);
+    /** Calculate fn(x, s) by expanding the differential operator
+     *  (from Laplacian and scalar potential) in terms of polynomials
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    double getFn(std::size_t n, double x, double s);
 };
-inline
-    void MultipoleT::setVarStep(double step) {
-    varStep_m = step;
-}
-inline
-    double MultipoleT::getVarStep() const {
-        return varStep_m;
-}
+
 inline
     void MultipoleT::setVarRadius() {
         variableRadius_m = true;
@@ -457,19 +377,25 @@ inline
          return transProfile_m[0];
 }
 inline
-    unsigned int MultipoleT::getMaxOrder() const {
+    std::size_t MultipoleT::getMaxOrder() const {
          return maxOrder_m;
 }
+
 inline
-    void MultipoleT::setMaxOrder(unsigned int maxOrder) {
-         maxOrder_m = maxOrder;
+    std::size_t MultipoleT::getMaxXOrder() const {
+        return maxOrderX_m;
 }
+
 inline
-    unsigned int MultipoleT::getTransMaxOrder() const {
+    void MultipoleT::setMaxXOrder(std::size_t maxOrderX) {
+        maxOrderX_m = maxOrderX;
+}
+inline
+    std::size_t MultipoleT::getTransMaxOrder() const {
         return transMaxOrder_m;
 }
 inline 
-    void MultipoleT::setTransMaxOrder(unsigned int transMaxOrder) {
+    void MultipoleT::setTransMaxOrder(std::size_t transMaxOrder) {
         transMaxOrder_m = transMaxOrder;
 	transProfile_m.resize(transMaxOrder + 1, 0.);
 }
@@ -497,5 +423,13 @@ inline
     double MultipoleT::getLength() const {
         return length_m;
 }
+inline
+    double MultipoleT::getBoundingBoxLength() const {
+        return boundingBoxLength_m;
+}
+inline
+    void MultipoleT::setBoundingBoxLength(const double &boundingBoxLength) {
+        boundingBoxLength_m = boundingBoxLength;
+}
 
 #endif
diff --git a/src/Classic/AbsBeamline/MultipoleTBase.cpp b/src/Classic/AbsBeamline/MultipoleTBase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a540eaffdb61e82a47ff68a8fb19f071b95037e7
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTBase.cpp
@@ -0,0 +1,263 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 "MultipoleTBase.h"
+#include <gsl/gsl_sf_gamma.h>
+#include "AbsBeamline/MultipoleTFunctions/tanhDeriv.h"
+
+using namespace endfieldmodel;
+
+MultipoleTBase::MultipoleTBase():
+    Component(),   
+    fringeField_l(endfieldmodel::Tanh()),
+    fringeField_r(endfieldmodel::Tanh()),
+    maxOrder_m(3),
+    transMaxOrder_m(0),
+    length_m(1.0),
+    entranceAngle_m(0.0),
+    rotation_m(0.0),
+    boundingBoxLength_m(0.0),
+    verticalApert_m(0.5),
+    horizontalApert_m(0.5) {
+}
+
+MultipoleTBase::MultipoleTBase(const std::string &name):
+    Component(name),   
+    fringeField_l(endfieldmodel::Tanh()),
+    fringeField_r(endfieldmodel::Tanh()),
+    maxOrder_m(3),
+    transMaxOrder_m(0),
+    length_m(1.0),
+    entranceAngle_m(0.0),
+    rotation_m(0.0),
+    boundingBoxLength_m(0.0),
+    verticalApert_m(0.5),
+    horizontalApert_m(0.5) {
+}
+
+MultipoleTBase::MultipoleTBase(const MultipoleTBase &right):
+    Component(right),
+    fringeField_l(right.fringeField_l),
+    fringeField_r(right.fringeField_r),
+    maxOrder_m(right.maxOrder_m),
+    transMaxOrder_m(right.transMaxOrder_m),
+    transProfile_m(right.transProfile_m),
+    length_m(right.length_m),
+    entranceAngle_m(right.entranceAngle_m),
+    rotation_m(right.rotation_m),
+    boundingBoxLength_m(right.boundingBoxLength_m),
+    verticalApert_m(right.verticalApert_m),
+    horizontalApert_m(right.horizontalApert_m),
+    dummy() {
+    RefPartBunch_m = right.RefPartBunch_m;
+}
+    
+MultipoleTBase::~MultipoleTBase() {
+} 
+
+bool MultipoleTBase::apply(const Vector_t &R, const Vector_t &P,
+                           const double &t,Vector_t &E, Vector_t &B) {
+    /** Rotate coordinates around the central axis of the magnet */
+    Vector_t R_prime = rotateFrame(R);
+    /** Go to local Frenet-Serret coordinates */
+    R_prime[2] *= -1; //OPAL uses a different sign convention...
+    transformCoords(R_prime);
+    if (insideAperture(R_prime)) {
+        /** Calculate B-field in the local Frenet-Serret frame */
+        B[0] = getBx(R_prime);
+        B[1] = getBz(R_prime);
+        B[2] = getBs(R_prime);
+        /** Transform B-field from local to lab coordinates */
+        transformBField(B, R_prime);
+        B[2] *= -1; //OPAL uses a different sign convention...
+        return false;
+    } else {
+        for(int i = 0; i < 3; i++) {
+            B[i] = 0.0;
+        }
+        return true;
+    }
+}
+
+Vector_t MultipoleTBase::rotateFrame(const Vector_t &R) {
+    Vector_t R_prime(3), R_pprime(3);
+    /** Apply two 2D rotation matrices to coordinate vector
+      * Rotate around central axis => skew fields
+      * Rotate azymuthaly => entrance angle
+      */
+    // 1st rotation
+    R_prime[0] = R[0] * cos(rotation_m) + R[1] * sin(rotation_m);
+    R_prime[1] = - R[0] * sin(rotation_m) + R[1] * cos(rotation_m);
+    R_prime[2] = R[2];
+    // 2nd rotation
+    R_pprime[0] = R_prime[2] * sin(entranceAngle_m) + 
+                  R_prime[0] * cos(entranceAngle_m);
+    R_pprime[1] = R_prime[1];
+    R_pprime[2] = R_prime[2] * cos(entranceAngle_m) - 
+                  R_prime[0] * sin(entranceAngle_m);
+    return R_pprime;
+
+}
+
+Vector_t MultipoleTBase::rotateFrameInverse(Vector_t &B) {
+    /** This function represents the inverse of the rotation 
+      * around the central axis performed by rotateFrame() method
+      * Used to rotate B field back to global coordinate system
+      */
+    Vector_t B_prime(3);
+    B_prime[0] = B[0] * cos(rotation_m) + B[1] * sin(rotation_m);
+    B_prime[1] = - B[0] * sin(rotation_m) + B[1] * cos(rotation_m);
+    B_prime[2] = B[2];
+    return B_prime;
+}
+
+bool MultipoleTBase::setFringeField(const double &s0,
+                                    const double &lambda_l,
+                                    const double &lambda_r) {
+    fringeField_l.Tanh::setLambda(lambda_l);
+    fringeField_l.Tanh::setX0(s0);
+    fringeField_l.Tanh::setTanhDiffIndices(2 * maxOrder_m + 1);
+    fringeField_r.Tanh::setLambda(lambda_r);
+    fringeField_r.Tanh::setX0(s0);
+    fringeField_r.Tanh::setTanhDiffIndices(2 * maxOrder_m + 1);
+    return true;
+}
+
+double MultipoleTBase::getBz(const Vector_t &R) {
+    if (fabs(getFringeDeriv(0, R[2])) < 1.0e-12) {
+        return 0.0; 
+    }
+    double Bz = 0.0;
+    std::size_t n = getMaxOrder() + 1;
+    while (n != 0) {
+        n--;
+        Bz = Bz * R[1] * R[1] + getFn(n, R[0], R[2])
+             / gsl_sf_fact(2 * n);
+    }
+    return Bz;
+} 
+
+double MultipoleTBase::getBx(const Vector_t &R) {
+    if (fabs(getFringeDeriv(0, R[2])) < 1.0e-12) {
+        return 0.0; 
+    }
+    double Bx = 0.0;
+    std::size_t n = getMaxOrder() + 1;
+    while (n != 0) {
+        n--;
+        Bx = Bx * R[1] * R[1] + getFnDerivX(n, R[0], R[2])
+             / gsl_sf_fact(2 * n + 1);
+    }
+    Bx *= R[1];
+    return Bx;
+} 
+
+double MultipoleTBase::getBs(const Vector_t &R) {
+    if (fabs(getFringeDeriv(0, R[2])) < 1.0e-12) {
+        return 0.0; 
+    }
+    double Bs = 0.0;
+    std::size_t n = getMaxOrder() + 1;
+    while (n != 0) {
+        n--;
+        Bs = Bs * R[1] * R[1] + getFnDerivS(n, R[0], R[2])
+             / gsl_sf_fact(2 * n + 1);
+    }
+    Bs *= R[1] / getScaleFactor(R[0], R[2]);
+    return Bs;
+}
+
+double MultipoleTBase::getFringeDeriv(const std::size_t &n, const double &s) {
+    if (n <= 10) {
+        return (fringeField_l.getTanh(s, n) - fringeField_r.getNegTanh(s, n))
+                / 2;
+    } else {
+        return tanhderiv::integrate(s,
+                                    fringeField_l.Tanh::getX0(),
+                                    fringeField_l.Tanh::getLambda(),
+                                    fringeField_r.Tanh::getLambda(),
+                                    n);
+    }
+}
+
+double MultipoleTBase::getTransDeriv(const std::size_t &n, const double &x) {
+    double func = 0.0;
+    std::size_t transMaxOrder = getTransMaxOrder();
+    if (n > transMaxOrder) {
+        return func;
+    }
+    std::vector<double> temp = getTransProfile();
+    for(std::size_t i = 1; i <= n; i++) {
+        for(std::size_t j = 0; j <= transMaxOrder; j++) {
+            if (j <= transMaxOrder_m - i) {
+                temp[j] = temp[j + 1] * (j + 1);
+            } else {
+                temp[j] = 0.0;
+            }
+        }
+    }
+    std::size_t k = transMaxOrder - n + 1;
+    while (k != 0) {
+        k--;
+        func = func * x + temp[k];
+    }
+    return func;
+}
+
+double MultipoleTBase::getFnDerivX(const std::size_t &n,
+                                   const double &x,
+                                   const double &s) {
+    if (n == 0) {
+        return getTransDeriv(1, x) * getFringeDeriv(0, s);
+    }
+    double deriv = 0.0;
+    double stepSize = 1e-3;
+    deriv += 1. * getFn(n, x - 2. * stepSize, s);
+    deriv += -8. * getFn(n, x - stepSize, s);
+    deriv += 8. * getFn(n, x + stepSize, s);
+    deriv += -1. * getFn(n, x + 2. * stepSize, s);
+    deriv /= 12 * stepSize;
+    return deriv;
+}
+
+double MultipoleTBase::getFnDerivS(const std::size_t &n,
+                                   const double &x, 
+                                   const double &s) {
+    if (n == 0) {
+        return getTransDeriv(0, x) * getFringeDeriv(1, s);
+    }
+    double deriv = 0.0;
+    double stepSize = 1e-3;
+    deriv += 1. * getFn(n, x, s - 2. * stepSize);
+    deriv += -8. * getFn(n, x, s - stepSize);
+    deriv += 8. * getFn(n, x, s + stepSize);
+    deriv += -1. * getFn(n, x, s + 2. * stepSize);
+    deriv /= 12 * stepSize;
+    return deriv;
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTBase.h b/src/Classic/AbsBeamline/MultipoleTBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..1ca42c3cec1759cc65861209d70aed20b57efdb6
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTBase.h
@@ -0,0 +1,469 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+
+#ifndef CLASSIC_MULTIPOLETBASE_H
+#define CLASSIC_MULTIPOLETBASE_H
+
+/** ---------------------------------------------------------------------
+  *
+  * MultipoleTBase is a base class for a straight or curved combined \n
+  * function magnet (up to arbitrary multipole component) with fringe fields.\n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * $Author: Titus Dascalu, Martin Duy Tat, Chris Rogers
+  *
+  * ---------------------------------------------------------------------
+  *
+  * The field is obtained from the scalar potential \n
+  *     @f[ V = f_0(x,s) z + f_1 (x,s) \frac{z^3}{3!} + f_2 (x,s) \frac{z^5}{5!} +
+  *     ...  @f] \n
+  *     (x,z,s) -> Frenet-Serret local coordinates along the magnet \n
+  *     z -> vertical component \n
+  *     assume mid-plane symmetry \n 
+  *     set field on mid-plane -> @f$ B_z = f_0(x,s) = T(x) \cdot S(s) @f$ \n
+  *     T(x) -> transverse profile; this is a polynomial describing
+  *             the field expansion on the mid-plane inside the magnet
+  *             (not in the fringe field);
+  *             1st term is the dipole strength, 2nd term is the 
+  *             quadrupole gradient * x, etc. \n
+  *          -> when setting the magnet, one gives the multipole
+  *             coefficients of this polynomial (i.e. dipole strength,  
+  *             quadrupole gradient, etc.) \n
+  * \n
+  * ------------- example ----------------------------------------------- \n
+  *     Setting a combined function magnet with dipole, quadrupole and 
+  *     sextupole components: \n
+  *     @f$ T(x) = B_0 + B_1 \cdot x + B_2 \cdot x^2 @f$\n
+  *     user gives @f$ B_0, B_1, B_2 @f$ \n
+  * ------------- example end ------------------------------------------- \n
+  * \n
+  *     S(s) -> fringe field \n
+  *     recursion -> @f$ f_n (x,s) = (-1)^n \cdot \sum_{i=0}^{n} C_n^i 
+  *     \cdot T^{(2i)} \cdot S^{(2n-2i)} @f$ \n
+  *     for curved magnets the above recursion is more complicated \n
+  *     @f$ C_n^i @f$ -> binomial coeff; 
+  *     @f$ T^{(n)} @f$ -> n-th derivative
+  *
+  * ---------------------------------------------------------------------
+  */
+
+#include "Algorithms/PartBunch.h"
+#include "AbsBeamline/EndFieldModel/Tanh.h"
+#include "Fields/BMultipoleField.h"
+#include "Algorithms/Vektor.h"
+#include "AbsBeamline/Component.h"
+#include "AbsBeamline/BeamlineVisitor.h"
+#include <vector>
+
+class MultipoleTBase: public Component {
+public:
+    /** Default constructor */
+    MultipoleTBase();
+    /** Constructor
+     *  \param name -> User-defined name
+     */
+    explicit MultipoleTBase(const std::string &name);
+    /** Copy constructor */
+    MultipoleTBase(const MultipoleTBase &right);
+    /** Destructor */
+    ~MultipoleTBase();
+    /** Return a dummy field value */
+    EMField &getField();
+    /** Return a dummy field value */
+    const EMField &getField() const;
+    /** Calculate the field at some arbitrary position \n
+     *  If particle is outside field map true is returned,
+     *  otherwise false is returned
+     *  \param R -> Position in the lab coordinate system of the multipole
+     *  \param P -> Not used
+     *  \param t -> Time at which the field is to be calculated
+     *  \param E -> Calculated electric field - always 0 (no E-field)
+     *  \param B -> Calculated magnetic field
+     */
+    bool apply(const Vector_t &R, const Vector_t &P, const double &t,
+               Vector_t &E, Vector_t &B);
+    /** Calculate the field at the position of the ith particle
+     *  \param i -> Index of the particle event; field is calculated at this
+     *  position
+     *  If particle is outside field map true is returned,
+     *  otherwise false is returned
+     *  \param t -> Time at which the field is to be calculated
+     *  \param E -> Calculated electric field - always 0 (no E-field)
+     *  \param B -> Calculated magnetic field
+     */
+    bool apply(const size_t &i, const double &t, Vector_t &E, Vector_t &B);
+    /** Initialise the MultipoleT
+     *  \param bunch -> Bunch the global bunch object
+     *  \param startField -> Not used
+     *  \param endField -> Not used
+     */
+    void initialise(PartBunchBase<double, 3>*,
+                    double &startField,
+                    double &endField);
+    /** Finalise the MultipoleT - sets bunch to NULL */
+    void finalise();
+    /** Return true if dipole component not zero */
+    bool bends() const;
+    /** Get the dipole constant B_0 */
+    double getDipoleConstant() const;
+    /** Set the dipole constant B_0 */
+    void setDipoleConstant(const double &B0);
+    /** Get the number of terms used in calculation of field components */
+    std::size_t getMaxOrder() const;
+    /** Set the number of terms used in calculation of field components \n
+     *  Maximum power of z in Bz is 2 * maxOrder_m
+     *  \param maxOrder -> Number of terms in expansion in z
+     */
+    virtual void setMaxOrder(const std::size_t &maxOrder);
+    /** Get the maximum order in the given transverse profile */
+    std::size_t getTransMaxOrder() const;
+    /** Set the maximum order in the given transverse profile
+     *  \param transMaxOrder -> Highest power of x in field expansion 
+     */
+    void setTransMaxOrder(const std::size_t &transMaxOrder);
+    /** Set transverse profile T(x)
+      * T(x) = B_0 + B1 x + B2 x^2 + B3 x^3 + ...
+      * \param n -> Order of the term (d^n/dx^n) to be set
+      * \param Bn -> Value of transverse profile coefficient
+      */
+    void setTransProfile(const std::size_t &n, const double &Bn);
+    /** Get transverse profile
+     *  \param n -> Power of x
+     */
+    double getTransProfile(const std::size_t &n) const;
+    /** Get all terms of transverse profile */
+    std::vector<double> getTransProfile() const;
+    /** Set fringe field model \n
+     *  Tanh model used here \n
+     *  @f[ 1/2 * \left [tanh \left( \frac{s + s_0}{\lambda_{left}} \right) 
+     *  - tanh \left( \frac{s - s_0}{\lambda_{right}} \right) \right] @f] 
+     *  \param s0 -> Centre field length and
+     *  \lambda_{left} -> Left end field length
+     *  \lambda_{right} -> Right end field length
+     */
+    bool setFringeField(const double &s0, 
+                        const double &lambda_left, 
+                        const double &lambda_right);
+    /** Return vector of 2 doubles
+      * [left fringe length, right fringelength]  
+      */
+    std::vector<double> getFringeLength() const;
+    /** Set the entrance angle
+     *  \param entranceAngle -> Entrance angle
+     */
+    void setEntranceAngle(const double &entranceAngle);
+    /** Set the bending angle of the magnet */
+    virtual void setBendAngle(const double &angle);
+    /** Get the bending angle of the magnet */
+    virtual double getBendAngle() const;
+    /** Get the entrance angle */
+    double getEntranceAngle() const;
+    /** Set the length of the magnet
+      * If straight-> Actual length
+      * If curved -> Arc length
+      */
+    void setLength(const double &length);
+    /** Get the length of the magnet */
+    double getLength() const;
+    /** Set the aperture dimensions \n
+      * This element only supports a rectangular aperture
+      * \param vertAp -> Vertical aperture length
+      * \param horizAp -> Horisontal aperture length
+      */
+    void setAperture(const double &vertAp, const double &horizAp);
+    /** Get the aperture dimensions
+      * Returns a vector of 2 doubles
+      */
+    std::vector<double> getAperture() const;
+    /** Set the angle of rotation of the magnet around its axis \n
+     *  To make skew components
+     *  \param rot -> Angle of rotation
+     */
+    void setRotation(const double &rot);
+    /** Get the angle of rotation of the magnet around its axis */
+    double getRotation() const;
+    /** Get distance between centre of magnet and entrance */
+    double getBoundingBoxLength() const;
+    /** Set distance between centre of magnet and enctrance
+     *  \param boundingBoxLength -> Distance between centre and entrance
+     */
+    void setBoundingBoxLength(const double &boundingBoxLength);
+    /** Not implemented */
+    virtual void getDimensions(double &zBegin, double &zEnd) const;
+protected:
+    /** Returns the value of the fringe field n-th derivative at s
+     *  \param n -> nth derivative
+     *  \param s -> Coordinate s
+     */
+    double getFringeDeriv(const std::size_t &n, const double &s);
+    /** Returns the value of the transverse field n-th derivative at x \n
+     *  Transverse field is a polynomial in x, differentiation follows
+     *  usual polynomial rules of differentiation
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     */
+    double getTransDeriv(const std::size_t &n, const double &x);
+private:
+    //MultipoleTBase operator=(const MultipoleTBase &rhs);
+    // End fields
+    endfieldmodel::Tanh fringeField_l; // Left
+    endfieldmodel::Tanh fringeField_r; // Right
+    /** Number of terms in z expansion used in calculating field components */
+    std::size_t maxOrder_m;
+    /** Highest power in given mid-plane field */
+    std::size_t transMaxOrder_m = 0;
+    /** List of transverse profile coefficients */
+    std::vector<double> transProfile_m;
+    /** Rotate frame for skew elements \n
+     *  Consecutive rotations:
+     *  1st -> about central axis
+     *  2nd -> azimuthal rotation
+     *  \param R -> Vector to be rotated
+     */
+    Vector_t rotateFrame(const Vector_t &R);
+    /** Inverse of the 1st rotation in rotateFrame() method \n
+     *  Used to rotate B field back to global coordinate system
+     */
+    Vector_t rotateFrameInverse(Vector_t &B);
+    /** Transform to Frenet-Serret coordinates for sector magnets */
+    virtual void transformCoords(Vector_t &R) = 0;
+    /** Transform B-field from Frenet-Serret coordinates to lab coordinates */
+    virtual void transformBField(Vector_t &B, const Vector_t &R) = 0;
+    /** Magnet parameters */
+    double length_m;
+    double entranceAngle_m;
+    double rotation_m;
+    /** Distance between centre of magnet and entrance */
+    double boundingBoxLength_m;
+    /** Returns the radial component of the field \n
+     *  Returns zero far outside fringe field
+     *  @f$ Bx = sum_n z^(2n+1) / (2n+1)! * \partial_x f_n @f$
+     */
+    virtual double getBx (const Vector_t &R);
+    /** Returns the vertical field component \n
+     *  Returns zero far outside fringe field
+     *  @f$ Bz = sum_n  f_n * z^(2n) / (2n)! @f$
+     */
+    double getBz (const Vector_t &R);
+    /** Returns the component of the field along the central axis \n
+     *  Returns zero far outside fringe field
+      * @f$ Bs = sum_n z^(2n+1) / (2n+1)! \partial_s f_n / h_s @f$
+      */
+    virtual double getBs (const Vector_t &R);
+    /** Assume rectangular aperture with these dimensions */
+    double verticalApert_m;
+    double horizontalApert_m;
+    /** Not implemented */
+    BMultipoleField dummy;
+    /** Tests if inside the magnet
+     *  \param R -> Coordinate vector
+     */
+    bool insideAperture(const Vector_t &R);
+    /** Radius of curvature
+     *  \param s -> Coordinate s
+     */
+    virtual double getRadius(const double &s) = 0;
+    /** Returns the scale factor @f$ h_s = 1 + x / \rho(s) @f$
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getScaleFactor(const double &x, const double &s) = 0;
+    /** Calculate partial derivative of fn wrt x using a 5-point
+     *  finite difference formula
+     *  Error of order stepSize^4
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    double getFnDerivX(const std::size_t &n,
+                       const double &x, 
+                       const double &s);
+    /** Calculate partial derivative of fn wrt s using a 5-point
+     *  finite difference formula
+     *  Error of order stepSize^4
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    double getFnDerivS(const std::size_t &n, 
+                       const double &x,
+                       const double &s);
+    /** Calculate fn(x, s) by expanding the differential operator
+     *  (from Laplacian and scalar potential) in terms of polynomials
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getFn(const std::size_t &n,
+                         const double &x,
+                         const double &s) = 0;
+};
+
+inline
+    void MultipoleTBase::finalise() {
+        RefPartBunch_m = NULL;
+}
+inline
+    bool MultipoleTBase::apply(const size_t &i, const double &t,
+                           Vector_t &E, Vector_t &B) {
+        return apply(RefPartBunch_m->R[i], RefPartBunch_m->P[i], t, E, B);
+}
+inline
+    void MultipoleTBase::setBendAngle(const double &angle) {
+}
+inline
+    double MultipoleTBase::getBendAngle() const {
+        return 0.0;
+}
+inline
+    void MultipoleTBase::setEntranceAngle(const double &entranceAngle) {
+        entranceAngle_m = entranceAngle;
+}
+inline
+    bool MultipoleTBase::insideAperture(const Vector_t &R) {
+        return (fabs(R[1]) <= (verticalApert_m / 2.0) &&
+                fabs(R[0]) <= (horizontalApert_m / 2.0));
+}
+inline
+    double MultipoleTBase::getEntranceAngle() const {
+        return entranceAngle_m;
+}
+inline
+    double MultipoleTBase::getTransProfile(const std::size_t &n) const {
+        return transProfile_m[n];
+}
+inline
+    std::vector<double> MultipoleTBase::getTransProfile() const {
+        return transProfile_m;
+}
+inline
+    double MultipoleTBase::getDipoleConstant() const {
+         return transProfile_m[0];
+} 
+inline
+    void MultipoleTBase::setMaxOrder(const std::size_t &maxOrder) {
+        maxOrder_m = maxOrder;
+}
+inline
+    std::size_t MultipoleTBase::getMaxOrder() const {
+         return maxOrder_m;
+}
+inline
+    std::size_t MultipoleTBase::getTransMaxOrder() const {
+        return transMaxOrder_m;
+}
+inline
+    void MultipoleTBase::setTransMaxOrder(const std::size_t &transMaxOrder) {
+        transMaxOrder_m = transMaxOrder;
+        transProfile_m.resize(transMaxOrder + 1, 0.);
+}
+inline
+    double MultipoleTBase::getRotation() const {
+         return rotation_m;
+}
+inline
+    void MultipoleTBase::setRotation(const double &rot) {
+         rotation_m = rot;
+}
+inline
+    void MultipoleTBase::setLength(const double &length) {
+        length_m = std::abs(length);
+}
+inline
+    double MultipoleTBase::getLength() const {
+        return length_m;
+}
+inline
+    double MultipoleTBase::getBoundingBoxLength() const {
+        return boundingBoxLength_m;
+}
+inline
+    void MultipoleTBase::setBoundingBoxLength(const double &boundingBoxLength) {
+        boundingBoxLength_m = boundingBoxLength;
+}
+inline
+    void MultipoleTBase::setTransProfile(const std::size_t &n,
+                                         const double &dTn) {
+        if (n > transMaxOrder_m) {
+            transMaxOrder_m = n;
+            transProfile_m.resize(n + 1, 0.0);
+        }
+        transProfile_m[n] = dTn;
+}
+inline
+    void MultipoleTBase::setDipoleConstant(const double &B0) {
+        if (transMaxOrder_m < 1) {
+            transProfile_m.resize(1, 0.);
+        }
+        transProfile_m[0] = B0;
+}
+inline
+    void MultipoleTBase::setAperture(const double &vertAp,
+                                     const double &horizAp) {
+        verticalApert_m = vertAp;
+        horizontalApert_m = horizAp;
+}
+inline
+    std::vector<double> MultipoleTBase::getAperture() const {
+        std::vector<double> temp(2, 0.0);
+        temp[0] = verticalApert_m;
+        temp[1] = horizontalApert_m;
+        return temp;
+}
+inline
+    std::vector<double> MultipoleTBase::getFringeLength() const {
+        std::vector<double> temp(2, 0.0);
+        temp[0] = fringeField_l.getLambda();
+        temp[1] = fringeField_r.getLambda();
+        return temp;
+}
+inline
+    void MultipoleTBase::initialise(PartBunchBase<double, 3>* bunch,
+                            double &startField,
+                            double &endField) {
+}
+inline
+    bool MultipoleTBase::bends() const {
+        return transProfile_m[0] != 0;
+}
+inline
+    EMField &MultipoleTBase::getField() {
+        return dummy;
+}
+inline
+    const EMField &MultipoleTBase::getField() const {
+        return dummy;
+}
+inline
+    void MultipoleTBase::getDimensions(double &zBegin, double &zEnd) const {
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTCurvedConstRadius.cpp b/src/Classic/AbsBeamline/MultipoleTCurvedConstRadius.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..35482105e6263b3a2e8f64ae4a0601a5b5a2cbae
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTCurvedConstRadius.cpp
@@ -0,0 +1,131 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 "MultipoleTCurvedConstRadius.h"
+#include "gsl/gsl_sf_pow_int.h"
+
+using namespace endfieldmodel;
+
+MultipoleTCurvedConstRadius::MultipoleTCurvedConstRadius(
+                             const std::string &name):
+    MultipoleTBase(name),   
+    maxOrderX_m(10),
+    planarArcGeometry_m(1.0, 1.0),
+    angle_m(0.0) {
+}
+
+MultipoleTCurvedConstRadius::MultipoleTCurvedConstRadius(
+                             const MultipoleTCurvedConstRadius &right):
+    MultipoleTBase(right),
+    maxOrderX_m(right.maxOrderX_m),
+    recursion_m(right.recursion_m),
+    planarArcGeometry_m(right.planarArcGeometry_m),
+    angle_m(right.angle_m) {
+    RefPartBunch_m = right.RefPartBunch_m;
+}
+
+MultipoleTCurvedConstRadius::~MultipoleTCurvedConstRadius() {
+} 
+
+ElementBase* MultipoleTCurvedConstRadius::clone() const {
+    return new MultipoleTCurvedConstRadius(*this);
+}
+
+void MultipoleTCurvedConstRadius::transformCoords(Vector_t &R) {
+    double radius = getLength() / angle_m;
+    double alpha = atan(R[2] / (R[0] + radius ));
+    if (alpha != 0.0 && angle_m != 0.0) {
+        R[0] = R[2] / sin(alpha) - radius;
+        R[2] = radius * alpha;// + getBoundingBoxLength();
+    } else {
+        //R[2] = R[2] + getBoundingBoxLength();
+    }
+}
+
+void MultipoleTCurvedConstRadius::transformBField(Vector_t &B,
+                                                  const Vector_t &R) {
+    double theta = R[2] * angle_m / getLength();
+    double Bx = B[0];
+    double Bs = B[2];
+    B[0] = Bx * cos(theta) - Bs * sin(theta);
+    B[2] = Bx * sin(theta) + Bs * cos(theta);
+}
+
+void MultipoleTCurvedConstRadius::setMaxOrder(const std::size_t &maxOrder) {
+    MultipoleTBase::setMaxOrder(maxOrder);
+    std::size_t N = recursion_m.size();
+    while (maxOrder >= N) {
+        polynomial::RecursionRelation r(N, 2 * (N + maxOrderX_m + 1));
+        r.resizeX(getTransMaxOrder());
+        r.truncate(maxOrderX_m);
+        recursion_m.push_back(r);
+        N = recursion_m.size();
+    }
+}
+
+double MultipoleTCurvedConstRadius::getRadius(const double &s) {
+    if (angle_m == 0.0) {
+        return -1.0;
+    } else {
+        return getLength() / angle_m;
+    }
+}
+
+double MultipoleTCurvedConstRadius::getScaleFactor(const double &x,
+                                                   const double &s) {
+    return (1 + x * angle_m / getLength());
+}
+
+double MultipoleTCurvedConstRadius::getFn(const std::size_t &n,
+                                          const double &x,
+                                          const double &s) {
+    if (n == 0) {
+        return getTransDeriv(0, x) * getFringeDeriv(0, s);
+    }
+    double rho = getLength() / angle_m;
+    double func = 0.0;
+    for (std::size_t j = 0;
+         j <= recursion_m.at(n).getMaxSDerivatives();
+         j++) {
+        double FringeDerivj = getFringeDeriv(2 * j, s);
+        for (std::size_t i = 0;
+             i <= recursion_m.at(n).getMaxXDerivatives();
+             i++) {
+            if (recursion_m.at(n).isPolynomialZero(i, j)) {
+                continue;
+            }
+            func += (recursion_m.at(n).evaluatePolynomial(x / rho, i, j)
+                    * getTransDeriv(i, x) * FringeDerivj)
+                    / gsl_sf_pow_int(rho, 2 * n - i - 2 * j);
+        }
+    }
+    func *= gsl_sf_pow_int(-1.0, n);
+    return func;
+}
+ 
diff --git a/src/Classic/AbsBeamline/MultipoleTCurvedConstRadius.h b/src/Classic/AbsBeamline/MultipoleTCurvedConstRadius.h
new file mode 100644
index 0000000000000000000000000000000000000000..b62ad836ffacd2c388b3278a2006e5d93746f6f3
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTCurvedConstRadius.h
@@ -0,0 +1,198 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+
+#ifndef CLASSIC_MULTIPOLET_CURVED_CONST_RADIUS_H
+#define CLASSIC_MULTIPOLET_CURVED_CONST_RADIUS_H
+
+/** ---------------------------------------------------------------------
+  *
+  * MultipoleT defines a curved combined function magnet with constant radius\n
+  * of curvature, (up to arbitrary multipole component) with fringe fields
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * $Author: Titus Dascalu, Martin Duy Tat, Chris Rogers
+  *
+  * ---------------------------------------------------------------------
+  *
+  * The field is obtained from the scalar potential \n
+  *     @f[ V = f_0(x,s) z + f_1 (x,s) \frac{z^3}{3!} + f_2 (x,s) \frac{z^5}{5!}
+  *      + ...  @f] \n
+  *     (x,z,s) -> Frenet-Serret local coordinates along the magnet \n
+  *     z -> vertical component \n
+  *     assume mid-plane symmetry \n 
+  *     set field on mid-plane -> @f$ B_z = f_0(x,s) = T(x) \cdot S(s) @f$ \n
+  *     T(x) -> transverse profile; this is a polynomial describing
+  *             the field expansion on the mid-plane inside the magnet
+  *             (not in the fringe field);
+  *             1st term is the dipole strength, 2nd term is the 
+  *             quadrupole gradient * x, etc. \n
+  *          -> when setting the magnet, one gives the multipole
+  *             coefficients of this polynomial (i.e. dipole strength,  
+  *             quadrupole gradient, etc.) \n
+  * \n
+  * ------------- example ----------------------------------------------- \n
+  *     Setting a combined function magnet with dipole, quadrupole and 
+  *     sextupole components: \n
+  *     @f$ T(x) = B_0 + B_1 \cdot x + B_2 \cdot x^2 @f$\n
+  *     user gives @f$ B_0, B_1, B_2 @f$ \n
+  * ------------- example end ------------------------------------------- \n
+  * \n
+  *     S(s) -> fringe field \n
+  *     recursion -> @f$ f_n (x,s) = (-1)^n \cdot \sum_{i=0}^{n} C_n^i 
+  *     \cdot T^{(2i)} \cdot S^{(2n-2i)} @f$ \n
+  *     for curved magnets the above recursion is more complicated \n
+  *     @f$ C_n^i @f$ -> binomial coeff; 
+  *     @f$ T^{(n)} @f$ -> n-th derivative
+  *
+  * ---------------------------------------------------------------------
+  */
+
+#include "BeamlineGeometry/PlanarArcGeometry.h"
+#include "AbsBeamline/MultipoleTBase.h"
+#include "AbsBeamline/MultipoleTFunctions/RecursionRelation.h"
+#include <vector>
+
+class MultipoleTCurvedConstRadius: public MultipoleTBase {
+public: 
+    /** Constructor
+     *  \param name -> User-defined name
+     */
+    explicit MultipoleTCurvedConstRadius(const std::string &name);
+    /** Copy constructor */
+    MultipoleTCurvedConstRadius(const MultipoleTCurvedConstRadius &right);
+    /** Destructor */ 
+    ~MultipoleTCurvedConstRadius();
+    /** Inheritable copy constructor */
+    virtual ElementBase* clone() const;
+    /** Accept a beamline visitor */
+    void accept(BeamlineVisitor &visitor) const;
+    /** Return the cell geometry */
+    PlanarArcGeometry& getGeometry();
+    /** Return the cell geometry */
+    const PlanarArcGeometry& getGeometry() const;
+    /** Set the number of terms used in calculation of field components \n
+     *  Maximum power of z in Bz is 2 * maxOrder_m
+     *  \param maxOrder -> Number of terms in expansion in z
+     */
+    virtual void setMaxOrder(const std::size_t &maxOrder);
+    /** Get highest power of x in polynomial expansions */
+    std::size_t getMaxXOrder() const;
+    /** Set the number of terms used in polynomial expansions
+     *  \param maxXOrder -> Number of terms in expansion in z
+     */
+    void setMaxXOrder(const std::size_t &maxXOrder);
+    /** Set the bending angle of the magnet */
+    virtual void setBendAngle(const double &angle);
+    /** Get the bending angle of the magnet */
+    virtual double getBendAngle() const;
+    /** Initialise the MultipoleT
+     *  \param bunch -> Bunch the global bunch object
+     *  \param startField -> Not used
+     *  \param endField -> Not used
+     */
+    virtual void initialise(PartBunchBase<double, 3>* bunch,
+                            double &startField,
+                            double &endField);
+private:
+    MultipoleTCurvedConstRadius operator=(
+                                const MultipoleTCurvedConstRadius &rhs);
+    /** Highest order of polynomial expansions in x */
+    std::size_t maxOrderX_m;
+    /** Object for storing differential operator acting on Fn */
+    std::vector<polynomial::RecursionRelation> recursion_m;
+    /** Geometry */
+    PlanarArcGeometry planarArcGeometry_m;
+    /** Transform to Frenet-Serret coordinates for sector magnets */
+    virtual void transformCoords(Vector_t &R);
+    /** Transform B-field from Frenet-Serret coordinates to lab coordinates */
+    virtual void transformBField(Vector_t &B, const Vector_t &R);
+    double angle_m;
+    /** Radius of curvature \n
+     *  If radius of curvature is infinite, -1 is returned \n
+     *  @f$ \rho(s) = length_m / angle_m @f$
+     *  \param s -> Coordinate s
+     */
+    virtual double getRadius(const double &s);
+    /** Returns the scale factor @f$ h_s = 1 + x / \rho @f$
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getScaleFactor(const double &x, const double &s);
+    /** Calculate fn(x, s) by expanding the differential operator
+     *  (from Laplacian and scalar potential) in terms of polynomials
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getFn(const std::size_t &n,
+                         const double &x,
+                         const double &s);
+};
+
+inline
+    void MultipoleTCurvedConstRadius::accept(BeamlineVisitor &visitor) const {
+        visitor.visitMultipoleTCurvedConstRadius(*this);
+}
+inline
+    void MultipoleTCurvedConstRadius::setMaxXOrder(
+                                      const std::size_t &maxXorder) {
+        maxOrderX_m = maxXorder;
+}
+inline
+    std::size_t MultipoleTCurvedConstRadius::getMaxXOrder() const {
+        return maxOrderX_m;
+}
+inline
+    PlanarArcGeometry& MultipoleTCurvedConstRadius::getGeometry() {
+        return planarArcGeometry_m;
+}
+inline
+    const PlanarArcGeometry& MultipoleTCurvedConstRadius::getGeometry() const {
+        return planarArcGeometry_m;
+}
+inline
+    void MultipoleTCurvedConstRadius::setBendAngle(const double &angle) {
+        angle_m = angle;
+}
+inline
+    double MultipoleTCurvedConstRadius::getBendAngle() const {
+        return angle_m;
+}
+inline
+    void MultipoleTCurvedConstRadius::initialise(PartBunchBase<double, 3>* bunch,
+                            double &startField,
+                            double &endField) {
+        RefPartBunch_m = bunch;
+        planarArcGeometry_m.setElementLength(2 * getBoundingBoxLength());
+        planarArcGeometry_m.setCurvature(angle_m / getLength());
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTCurvedVarRadius.cpp b/src/Classic/AbsBeamline/MultipoleTCurvedVarRadius.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..43e50b7ef1dd090352f79eea63e38db2edcc2c59
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTCurvedVarRadius.cpp
@@ -0,0 +1,149 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <vector>
+#include "gsl/gsl_sf_pow_int.h"
+#include "MultipoleTCurvedVarRadius.h"
+#include "AbsBeamline/MultipoleTFunctions/CoordinateTransform.h"
+
+using namespace endfieldmodel;
+
+MultipoleTCurvedVarRadius::MultipoleTCurvedVarRadius(const std::string &name):
+    MultipoleTBase(name),   
+    maxOrderX_m(10),
+    varRadiusGeometry_m(1.0, 1.0, 1.0, 1.0, 1.0),
+    angle_m(0.0) {
+}
+
+MultipoleTCurvedVarRadius::MultipoleTCurvedVarRadius(
+                           const MultipoleTCurvedVarRadius &right):
+    MultipoleTBase(right),
+    maxOrderX_m(right.maxOrderX_m),
+    recursion_m(right.recursion_m),
+    varRadiusGeometry_m(right.varRadiusGeometry_m),
+    angle_m(right.angle_m) {
+    RefPartBunch_m = right.RefPartBunch_m;
+}
+    
+
+MultipoleTCurvedVarRadius::~MultipoleTCurvedVarRadius() {
+} 
+
+ElementBase* MultipoleTCurvedVarRadius::clone() const {
+    return new MultipoleTCurvedVarRadius(*this);
+}
+
+void MultipoleTCurvedVarRadius::transformCoords(Vector_t &R) {
+    std::vector<double> fringeLength = getFringeLength();
+    coordinatetransform::CoordinateTransform t(R[0], R[1], R[2],
+                                               getLength() / 2,
+                                               fringeLength[0],
+                                               fringeLength[1],
+                                               (getLength() / angle_m));
+    std::vector<double> r = t.getTransformation();
+    R[0] = r[0];
+    R[1] = r[1];
+    R[2] = r[2];
+}
+
+void MultipoleTCurvedVarRadius::transformBField(Vector_t &B,
+                                                const Vector_t &R) {
+    double length = getLength();
+    std::vector<double> fringeLength = getFringeLength();
+    double prefactor = (length / angle_m) *
+                       (tanh((length / 2) / fringeLength[0])
+                       + tanh((length / 2) / fringeLength[1]));
+    double theta = fringeLength[0] * log(cosh((R[2]
+                   + (length / 2)) / fringeLength[0]))
+                   - fringeLength[1] * log(cosh((R[2]
+                   - (length / 2)) / fringeLength[1]));
+    theta /= prefactor;
+    double Bx = B[0], Bs = B[2];
+    B[0] = Bx * cos(theta) - Bs * sin(theta);
+    B[2] = Bx * sin(theta) + Bs * cos(theta);
+}
+
+void MultipoleTCurvedVarRadius::setMaxOrder(const std::size_t &maxOrder) {
+    MultipoleTBase::setMaxOrder(maxOrder);
+    std::size_t N = recursion_m.size();
+    while (maxOrder >= N) {
+        polynomial::RecursionRelationTwo r(N, 2 * (N + maxOrderX_m + 1));
+        r.resizeX(getTransMaxOrder());
+        r.truncate(maxOrderX_m);
+        recursion_m.push_back(r);
+        N = recursion_m.size();
+    }
+}
+
+double MultipoleTCurvedVarRadius::getRadius(const double &s) {
+    if (getFringeDeriv(0, s) > 1.0e-12 && angle_m != 0.0) {
+        return getLength() * getFringeDeriv(0, 0)
+               / (getFringeDeriv(0, s) * angle_m);
+    } else {
+        return 1e300; // Return -1 if radius is infinite 
+    }
+}
+
+double MultipoleTCurvedVarRadius::getScaleFactor(const double &x,
+                                                 const double &s) {
+    return (1 + x / getRadius(s));
+}
+
+double MultipoleTCurvedVarRadius::getFn(const std::size_t &n,
+                                        const double &x,
+                                        const double &s) {
+    if (n == 0) {
+        return getTransDeriv(0, x) * getFringeDeriv(0, s);
+    }
+    double rho = getLength() / angle_m;
+    double S_0 = getFringeDeriv(0, 0);
+    double y = getFringeDeriv(0, s) / (S_0 * rho);
+    double func = 0.0;
+    std::vector<double> fringeDerivatives;
+    for (std::size_t j = 0;
+         j <= recursion_m.at(n).getMaxSDerivatives();
+         j++) {
+        fringeDerivatives.push_back(getFringeDeriv(j, s) / (S_0 * rho));
+    }
+    for (std::size_t i = 0;
+         i <= recursion_m.at(n).getMaxXDerivatives();
+         i++) {
+        double temp = 0.0;
+        for (std::size_t j = 0;
+             j <= recursion_m.at(n).getMaxSDerivatives();
+             j++) {
+            temp += recursion_m.at(n)
+                    .evaluatePolynomial(x, y, i, j, fringeDerivatives)
+                    * fringeDerivatives.at(j);
+        }
+        func += temp * getTransDeriv(i, x);
+    }
+    func *= gsl_sf_pow_int(-1.0, n) * S_0 * rho;
+    return func;
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTCurvedVarRadius.h b/src/Classic/AbsBeamline/MultipoleTCurvedVarRadius.h
new file mode 100644
index 0000000000000000000000000000000000000000..11ab76d0b4f18c876a8b9982905d2ac5c469e3f8
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTCurvedVarRadius.h
@@ -0,0 +1,205 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+
+#ifndef CLASSIC_MULTIPOLET_CURVED_VAR_RADIUS_H
+#define CLASSIC_MULTIPOLET_CURVED_VAR_RADIUS_H
+
+/** ---------------------------------------------------------------------
+  *
+  * MultipoleTCurvedVarRadius defines a curved combined function magnet with\n
+  * variable radius of curvature (up to arbitrary multipole component) \n
+  * with fringe fields
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * $Author: Titus Dascalu, Martin Duy Tat, Chris Rogers
+  *
+  * ---------------------------------------------------------------------
+  *
+  * The field is obtained from the scalar potential \n
+  *     @f[ V = f_0(x,s) z + f_1 (x,s) \frac{z^3}{3!} + f_2 (x,s) \frac{z^5}{5!}
+  *      + ...  @f] \n
+  *     (x,z,s) -> Frenet-Serret local coordinates along the magnet \n
+  *     z -> vertical component \n
+  *     assume mid-plane symmetry \n 
+  *     set field on mid-plane -> @f$ B_z = f_0(x,s) = T(x) \cdot S(s) @f$ \n
+  *     T(x) -> transverse profile; this is a polynomial describing
+  *             the field expansion on the mid-plane inside the magnet
+  *             (not in the fringe field);
+  *             1st term is the dipole strength, 2nd term is the 
+  *             quadrupole gradient * x, etc. \n
+  *          -> when setting the magnet, one gives the multipole
+  *             coefficients of this polynomial (i.e. dipole strength,  
+  *             quadrupole gradient, etc.) \n
+  * \n
+  * ------------- example ----------------------------------------------- \n
+  *     Setting a combined function magnet with dipole, quadrupole and 
+  *     sextupole components: \n
+  *     @f$ T(x) = B_0 + B_1 \cdot x + B_2 \cdot x^2 @f$\n
+  *     user gives @f$ B_0, B_1, B_2 @f$ \n
+  * ------------- example end ------------------------------------------- \n
+  * \n
+  *     S(s) -> fringe field \n
+  *     recursion -> @f$ f_n (x,s) = (-1)^n \cdot \sum_{i=0}^{n} C_n^i 
+  *     \cdot T^{(2i)} \cdot S^{(2n-2i)} @f$ \n
+  *     for curved magnets the above recursion is more complicated \n
+  *     @f$ C_n^i @f$ -> binomial coeff; 
+  *     @f$ T^{(n)} @f$ -> n-th derivative
+  *
+  * ---------------------------------------------------------------------
+  */
+
+#include "BeamlineGeometry/VarRadiusGeometry.h"
+#include "Algorithms/Vektor.h"
+#include "AbsBeamline/MultipoleTBase.h"
+#include "AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.h"
+#include <vector>
+
+class MultipoleTCurvedVarRadius: public MultipoleTBase {
+public: 
+    /** Constructor
+     *  \param name -> User-defined name
+     */
+    explicit MultipoleTCurvedVarRadius(const std::string &name);
+    /** Copy constructor */
+    MultipoleTCurvedVarRadius(const MultipoleTCurvedVarRadius &right);
+    /** Destructor */ 
+    ~MultipoleTCurvedVarRadius();
+    /** Inheritable copy constructor */
+    virtual ElementBase* clone() const;
+    /** Accept a beamline visitor */
+    void accept(BeamlineVisitor &visitor) const;
+    /** Return the cell geometry */
+    VarRadiusGeometry& getGeometry();
+    /** Return the cell geometry */
+    const VarRadiusGeometry& getGeometry() const;
+    /** Set the number of terms used in calculation of field components \n
+     *  Maximum power of z in Bz is 2 * maxOrder_m
+     *  \param maxOrder -> Number of terms in expansion in z
+     */
+    virtual void setMaxOrder(const std::size_t &maxOrder);
+    /** Get highest power of x in polynomial expansions */
+    std::size_t getMaxXOrder() const;
+    /** Set the number of terms used in polynomial expansions
+     *  \param maxXOrder -> Number of terms in expansion in z
+     */
+    void setMaxXOrder(const std::size_t &maxXOrder);
+    /** Set the bending angle of the magnet */
+    virtual void setBendAngle(const double &angle);
+    /** Get the bending angle of the magnet */
+    virtual double getBendAngle() const;
+    /** Initialise the MultipoleT
+     *  \param bunch -> Bunch the global bunch object
+     *  \param startField -> Not used
+     *  \param endField -> Not used
+     */
+    virtual void initialise(PartBunchBase<double, 3>* bunch,
+                            double &startField,
+                            double &endField);
+private:
+    MultipoleTCurvedVarRadius operator=(const MultipoleTCurvedVarRadius& rhs);
+    /** Highest order of polynomial expansions in x */
+    std::size_t maxOrderX_m;
+    /** Objects for storing differential operator acting on Fn */
+    std::vector<polynomial::RecursionRelationTwo> recursion_m;
+    /** Geometry */
+    VarRadiusGeometry varRadiusGeometry_m;
+    /** Transform to Frenet-Serret coordinates for sector magnets */
+    virtual void transformCoords(Vector_t &R);
+    /** Transform B-field from Frenet-Serret coordinates to lab coordinates */
+    virtual void transformBField(Vector_t &B, const Vector_t &R);
+    double angle_m;
+    /** Radius of curvature \n
+     *  If radius of curvature is infinite, -1 is returned \n
+     *  @f$ \rho(s) = rho(0) * S(0) / S(s) @f$
+     *  where S(s) is the fringe field
+     *  \param s -> Coordinate s
+     */
+    virtual double getRadius(const double &s);
+    /** Returns the scale factor @f$ h_s = 1 + x / \rho(s) @f$
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getScaleFactor(const double &x, const double &s);
+    /** Calculate fn(x, s) by expanding the differential operator
+     *  (from Laplacian and scalar potential) in terms of polynomials
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getFn(const std::size_t &n,
+                         const double &x,
+                         const double &s);
+};
+
+inline
+    void MultipoleTCurvedVarRadius::accept(BeamlineVisitor &visitor) const {
+        visitor.visitMultipoleTCurvedVarRadius(*this);
+}
+inline
+    void MultipoleTCurvedVarRadius::setMaxXOrder(
+                                      const std::size_t &maxXorder) {
+        maxOrderX_m = maxXorder;
+}
+inline
+    std::size_t MultipoleTCurvedVarRadius::getMaxXOrder() const {
+        return maxOrderX_m;
+}
+inline
+    VarRadiusGeometry& MultipoleTCurvedVarRadius::getGeometry() {
+        return varRadiusGeometry_m;
+}
+inline
+    const VarRadiusGeometry& MultipoleTCurvedVarRadius::getGeometry() const {
+        return varRadiusGeometry_m;
+}
+inline
+    void MultipoleTCurvedVarRadius::setBendAngle(const double &angle) {
+        angle_m = angle;
+}
+inline
+    double MultipoleTCurvedVarRadius::getBendAngle() const {
+        return angle_m;
+}
+inline
+    void MultipoleTCurvedVarRadius::initialise(PartBunchBase<double, 3>* bunch,
+                            double &startField,
+                            double &endField) {
+        RefPartBunch_m = bunch;
+        double length = getLength();
+        varRadiusGeometry_m.setElementLength(2 * getBoundingBoxLength());
+        varRadiusGeometry_m.setRadius(length / angle_m);
+        varRadiusGeometry_m.setS0(length / 2);
+        std::vector<double> fringeLength = getFringeLength();
+        varRadiusGeometry_m.setLambdaLeft(fringeLength[0]);
+        varRadiusGeometry_m.setLambdaRight(fringeLength[1]);
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/CMakeLists.txt b/src/Classic/AbsBeamline/MultipoleTFunctions/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fbca45eb70d89c3a48dac45f5bfcfc5e08d7201e
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/CMakeLists.txt
@@ -0,0 +1,34 @@
+set (_SRCS
+  CoordinateTransform.cpp
+  DifferentialOperator.cpp
+  DifferentialOperatorTwo.cpp
+  Polynomial.cpp
+  PolynomialSum.cpp
+  RecursionRelation.cpp
+  RecursionRelationTwo.cpp
+  tanhDeriv.cpp
+  TwoPolynomial.cpp
+  )
+
+include_directories (
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  )
+
+ADD_OPAL_SOURCES(${_SRCS})
+
+set (HDRS
+  CoordinateTransform.h
+  DifferentialOperator.h
+  DifferentialOperatorTwo.h
+  Polynomial.h
+  PolynomialSum.h
+  RecursionRelation.h
+  RecursionRelationTwo.h
+  tanhDeriv.h
+  TwoPolynomial.h
+)
+
+install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/AbsBeamline/MultipoleTFunctions/")
+#Turn on gcov
+#set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)
+#set(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/CoordinateTransform.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/CoordinateTransform.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f7e372aa7a0cb825efcef396db79da6f2aa73f06
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/CoordinateTransform.cpp
@@ -0,0 +1,206 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <cmath>
+#include <vector>
+#include "gsl/gsl_integration.h"
+#include "gsl/gsl_sf_pow_int.h"
+#include "CoordinateTransform.h"
+
+namespace coordinatetransform {
+
+CoordinateTransform::CoordinateTransform(): x_m(0), z_m(0), s_m(0) {
+}
+
+CoordinateTransform::CoordinateTransform(const double &xlab,
+                                         const double &ylab,
+                                         const double &zlab,
+                                         const double &s_0,
+                                         const double &lambdaleft,
+                                         const double &lambdaright,
+                                         const double &rho):
+    s_0_m(s_0), lambdaleft_m(lambdaleft),
+    lambdaright_m(lambdaright), rho_m(rho) {
+    std::vector<double> coordinates;
+    coordinates.push_back(xlab);
+    coordinates.push_back(zlab);
+    //transformFromEntranceCoordinates(coordinates, boundingBoxLength);
+    calcSCoordinate(coordinates[0], coordinates[1]);
+    calcXCoordinate(coordinates[0], coordinates[1]);
+    z_m = ylab;
+}
+
+CoordinateTransform::CoordinateTransform(const CoordinateTransform &transform):
+    s_0_m(transform.s_0_m), lambdaleft_m(transform.lambdaleft_m),
+    lambdaright_m(transform.lambdaright_m),
+    rho_m(transform.rho_m), x_m(transform.x_m),
+    z_m(transform.z_m), s_m(transform.s_m) {
+}
+
+CoordinateTransform::~CoordinateTransform() {
+}
+
+CoordinateTransform& CoordinateTransform::operator= (
+                     const CoordinateTransform& transform) {
+    s_0_m = transform.s_0_m;
+    lambdaleft_m = transform.lambdaleft_m;
+    lambdaright_m = transform.lambdaright_m;
+    x_m = transform.x_m;
+    z_m = transform.z_m;
+    s_m = transform.s_m;
+    rho_m = transform.rho_m;
+    return *this;
+}
+
+std::vector<double> CoordinateTransform::getTransformation() const {
+    std::vector<double> result;
+    result.push_back(x_m);
+    result.push_back(z_m);
+    result.push_back(s_m);
+    return result;
+}
+
+std::vector<double> CoordinateTransform::getUnitTangentVector(
+                                         const double &s) const {
+    std::vector<double> result;
+    double prefactor = rho_m * (tanh(s_0_m / lambdaleft_m) -
+                       tanh(-s_0_m / lambdaright_m));
+    result.push_back(-sin((lambdaleft_m * log(cosh((s + s_0_m) / lambdaleft_m))
+                     - lambdaright_m * log(cosh((s - s_0_m) / lambdaright_m)))
+                     / prefactor));
+    result.push_back(cos((lambdaleft_m * log(cosh((s + s_0_m) / lambdaleft_m))
+                     - lambdaright_m * log(cosh((s - s_0_m) / lambdaright_m)))
+                     / prefactor));
+    return result;
+}
+
+std::vector<double> CoordinateTransform::calcReferenceTrajectory(
+                                         const double &s) const {
+    struct myParams params = {s_0_m, lambdaleft_m, lambdaright_m, rho_m};
+    gsl_function FX, FY;
+    FX.function = &getUnitTangentVectorX;
+    FY.function = &getUnitTangentVectorY;
+    FX.params = &params;
+    FY.params = &params;
+    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);
+    double error = gsl_sf_pow_int(10, -10);
+    double *resultX = new double;
+    double *resultY = new double;
+    double *abserr = new double;
+    gsl_integration_qag(&FX, 0, s, 0, error, 1000, 6, w, resultX, abserr);
+    gsl_integration_qag(&FY, 0, s, 0, error, 1000, 6, w, resultY, abserr);
+    gsl_integration_workspace_free(w); 
+    std::vector<double> result;
+    result.push_back(*resultX);
+    result.push_back(*resultY);
+    delete resultX;
+    delete resultY;
+    delete abserr;
+    return result;
+}
+
+void CoordinateTransform::calcSCoordinate(const double &xlab,
+                                          const double &zlab) {
+    double s1 = -(5 * lambdaleft_m + s_0_m), s2 = (5 * lambdaright_m + s_0_m);
+    std::vector<double> shat1 = getUnitTangentVector(s1);
+    std::vector<double> shat2 = getUnitTangentVector(s2);
+    std::vector<double> r_1 = calcReferenceTrajectory(s1);
+    std::vector<double> r_2 = calcReferenceTrajectory(s2);
+    double eqn1 = (r_1[0] - xlab) * shat1[0] + (r_1[1] - zlab) * shat1[1];
+    double eqn2 = (r_2[0] - xlab) * shat2[0] + (r_2[1] - zlab) * shat2[1];
+    if (eqn1 * eqn2 > 0) {
+        s_m = 10 * s_0_m;
+        return;
+    }
+    if (eqn1 > 0 && eqn2 < 0) {
+        double temp = eqn2;
+        eqn2 = eqn1;
+        eqn1 = temp;
+    }
+    int n = 0;
+    while (n < 10000 && fabs(s1 - s2) > 1e-12) {
+        double stemp = (s2 + s1) / 2;
+        std::vector<double> r_temp = calcReferenceTrajectory(stemp);
+        std::vector<double> shattemp = getUnitTangentVector(stemp);
+        double eqntemp = (r_temp[0] - xlab) * shattemp[0] +
+                         (r_temp[1] - zlab) * shattemp[1];
+        if (eqntemp > 0) {
+            s2 = stemp;
+        } else if (eqntemp < 0) {
+            s1 = stemp;
+        } else {
+            s_m = stemp;
+            return;
+        }
+        n++;
+    }
+    if (n == 10000) {
+        s_m = 0.0;
+        return;
+    }
+    s_m = (s2 + s1) / 2;
+}
+
+void CoordinateTransform::calcXCoordinate(const double &xlab,
+                                          const double &zlab) {
+    std::vector<double> shat = getUnitTangentVector(s_m);
+    std::vector<double> r_0 = calcReferenceTrajectory(s_m);
+    x_m = (xlab * shat[1] - zlab * shat[0] -
+           r_0[0] * shat[1] + r_0[1] * shat[0]);
+}
+
+void CoordinateTransform::transformFromEntranceCoordinates(
+                          std::vector<double> &coordinates,
+                          const double &boundingBoxLength) {
+    std::vector<double> r_entrance =
+                        calcReferenceTrajectory(-boundingBoxLength);
+    std::vector<double> shat = getUnitTangentVector(-boundingBoxLength);
+    double x = coordinates[0], z = coordinates[1];
+    coordinates[0] = x * shat[1] + z * shat[0] + r_entrance[0];
+    coordinates[1] = -x * shat[0] + z * shat[1] + r_entrance[1];
+}
+
+double getUnitTangentVectorX(double s, void *p) {
+    struct myParams *params = (struct myParams *)p;
+    double prefactor = params->rho * (tanh(params->s_0 / params->lambdaleft) -
+                       tanh(-params->s_0 / params->lambdaright));
+    return -sin((params->lambdaleft * log(cosh((s + params->s_0)
+           / params->lambdaleft)) - params->lambdaright
+           * log(cosh((s - params->s_0) / params->lambdaright))) / prefactor);
+}
+
+double getUnitTangentVectorY(double s, void *p) {
+    struct myParams *params = (struct myParams *)p;
+    double prefactor = params->rho * (tanh(params->s_0 / params->lambdaleft) -
+                       tanh(-params->s_0 / params->lambdaright));
+    return cos((params->lambdaleft * log(cosh((s + params->s_0)
+           / params->lambdaleft)) - params->lambdaright
+           * log(cosh((s - params->s_0) / params->lambdaright))) / prefactor);
+}
+
+}
+
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/CoordinateTransform.h b/src/Classic/AbsBeamline/MultipoleTFunctions/CoordinateTransform.h
new file mode 100644
index 0000000000000000000000000000000000000000..49de4b91ab14d8a21d5f56f971e5c3317e781514
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/CoordinateTransform.h
@@ -0,0 +1,131 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef COORDINATE_TRANSFORM_H
+#define COORDINATE_TRANSFORM_H
+
+/** ---------------------------------------------------------------------
+  *
+  * CoordinateTransform transforms between the coordinate system centred \n 
+  * at the entrance and the local Frenet-Serret coordinate system. Assuming \n
+  * CoordinateTransform takes in the fringe field parameters and calculates \n
+  * the coordinate transformation. First the coordinates are transformed to\n
+  * a coordinate system centred in the middle of the magnet. From here \n
+  * the Frenet-Serret coordinates are found using numerical integration.
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  */
+
+#include <vector>
+
+namespace coordinatetransform {
+
+struct myParams {
+    double s_0;
+    double lambdaleft;
+    double lambdaright;
+    double rho;
+};
+
+class CoordinateTransform {
+public:
+    /** Default constructor, transforms everything to the origin */
+    CoordinateTransform();
+    /** Constructor, calculates coordinate transformation from lab coordinates
+     *  to Frenet-Serret coordinates, given fringe field parameters
+     *  \param xlab -> x-coordinate in lab frame
+     *  \param ylab -> y-coordinate in lab frame
+     *  \param zlab -> z-coordinate in lab frame
+     *  \param s_0 -> Centre field length
+     *  \param lambdaleft -> Left end field length
+     *  \param lambdaright -> Right end field length
+     *  \param rho -> Centre radius of curvature
+     */
+    CoordinateTransform(const double &xlab,
+                        const double &ylab,
+                        const double &zlab,
+                        const double &s_0,
+                        const double &lambdaleft,
+                        const double &lambdaright,
+                        const double &rho);
+    /** Copy constructor */
+    CoordinateTransform(const CoordinateTransform &transform);
+    /** Destructor, does nothing */
+    ~CoordinateTransform();
+    /** Assigment operator */
+    CoordinateTransform& operator= (const CoordinateTransform &transform);
+    /** Returns a list of transformed coordinates */
+    std::vector<double> getTransformation() const;
+    /** Calculates reference trajectory by integrating the unit tangent vector
+     *  \param s -> s-coordinate in local Frenet-Serret coordinates
+     */
+    std::vector<double> calcReferenceTrajectory(const double &s) const;
+    /** Returns unit tangent vector
+     *  \param s -> s-coordinate in local Frenet-Serret coordinates
+     */
+    std::vector<double> getUnitTangentVector(const double &s) const;
+private:
+    /** Calculates the coordinate s
+     *  \param xlab -> x-coordinate in lab frame
+     *  \param ylab -> y-coordinate in lab frame
+     */
+    void calcSCoordinate(const double &xlab, const double &ylab);
+    /** Calculates the coordinate x, coordinate s must be calculated first! 
+     *  \param xlab -> x-coordinate in lab frame
+     *  \param ylab -> y-coordinate in lab frame
+     */
+    void calcXCoordinate(const double &xlab, const double &ylab);
+    /** Transforms from coordinate system centred in the middle of the magnet
+     *  to the coordinate system placed at the entrance
+     *  \param coordinates -> Coordinates in coordinate system centred
+     *  in the middle of the magnet
+     *  \param boundingBoxLength -> Length along the magnet from the magnet
+     *  entrance to the middle of the magnet
+     */
+    void transformFromEntranceCoordinates(std::vector<double> &coordinates,
+                                          const double &boundingBoxLength);
+    double s_0_m;
+    double lambdaleft_m;
+    double lambdaright_m;
+    double rho_m;
+    double x_m;
+    double z_m;
+    double s_m;
+};
+
+/** Internal function used for GSL numerical integration */
+double getUnitTangentVectorX(double s, void *p);
+/** Internal function used for GSL numerical integration */
+double getUnitTangentVectorY(double s, void *p);
+
+}
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperator.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7582474582220fe0433b966857df588f00abfe86
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperator.cpp
@@ -0,0 +1,188 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <iostream>
+#include <vector>
+#include "DifferentialOperator.h"
+
+namespace polynomial {
+
+DifferentialOperator::DifferentialOperator():
+    xDerivatives_m(0), sDerivatives_m(0){
+    std::vector<int> temp;
+    temp.push_back(1);
+    polynomials_m.resize(1);
+    polynomials_m[0].push_back(temp);
+}
+
+DifferentialOperator::DifferentialOperator(const std::size_t &xDerivatives,
+                                           const std::size_t &sDerivatives):
+    xDerivatives_m(xDerivatives), sDerivatives_m(sDerivatives) {
+    polynomials_m.resize(xDerivatives_m + 1);
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        polynomials_m[i].resize(sDerivatives_m + 1);
+    }
+}
+
+DifferentialOperator::DifferentialOperator(
+                      const DifferentialOperator &doperator):
+    polynomials_m(doperator.polynomials_m),
+    xDerivatives_m(doperator.xDerivatives_m),
+    sDerivatives_m(doperator.sDerivatives_m) {
+}
+
+DifferentialOperator::~DifferentialOperator() {
+}
+
+DifferentialOperator& DifferentialOperator::operator= (
+                      const DifferentialOperator &doperator) {
+    polynomials_m = doperator.polynomials_m;
+    xDerivatives_m = doperator.xDerivatives_m;
+    sDerivatives_m = doperator.sDerivatives_m;
+    return *this;
+}
+
+void DifferentialOperator::resizeX(const std::size_t &xDerivatives) {
+    std::size_t oldxDerivatives = xDerivatives_m;
+    xDerivatives_m = xDerivatives;
+    polynomials_m.resize(xDerivatives_m + 1);
+    if (xDerivatives_m > oldxDerivatives) {
+        for (std::size_t i = oldxDerivatives + 1; i <= xDerivatives_m; i++) {
+            polynomials_m[i].resize(sDerivatives_m + 1);
+        }
+    }
+}
+
+void DifferentialOperator::resizeS(const std::size_t &sDerivatives) {
+    sDerivatives_m = sDerivatives;
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        polynomials_m[i].resize(sDerivatives_m + 1);
+    }
+}
+
+void DifferentialOperator::differentiateX() {
+    resizeX(xDerivatives_m + 1);
+    for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+        std::size_t i = xDerivatives_m;
+        while (i != 0) {
+            i--;
+            polynomials_m[i + 1][j].addPolynomial(polynomials_m[i][j]);
+            polynomials_m[i][j].differentiatePolynomial();
+        }
+    }
+}
+
+void DifferentialOperator::doubleDifferentiateS() {
+    resizeS(sDerivatives_m + 1);
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        std::size_t j = sDerivatives_m;
+        while (j != 0) {
+            j--;
+            polynomials_m[i][j + 1].addPolynomial(polynomials_m[i][j]);
+            polynomials_m[i][j].setZero();
+        }
+    }
+}
+
+void DifferentialOperator::multiplyPolynomial(const Polynomial &poly) {
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+            polynomials_m[i][j].multiplyPolynomial(poly);
+        }
+    }
+}
+
+void DifferentialOperator::setPolynomial(const std::vector<int> &poly,
+                                         const std::size_t &x,
+                                         const std::size_t &s) {
+    if (x > xDerivatives_m) {
+        resizeX(x);
+    }
+    if (s > sDerivatives_m) {
+        resizeS(s);
+    }
+    polynomials_m[x][s] = Polynomial(poly);
+}
+
+void DifferentialOperator::printOperator() const {
+    polynomials_m[0][0].printPolynomial();
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+            if ((i != 0 || j != 0) &&
+                (polynomials_m[i][j].getMaxXorder() != 0 ||
+                 polynomials_m[i][j].getCoefficient(0) != 0)) {
+                std::cout << " + (";
+                polynomials_m[i][j].printPolynomial();
+                std::cout << ")" << "(d/dx)^" << i << "(d/ds)^" << j;
+            }
+        }
+    }
+    std::cout << std::endl;
+}
+
+void DifferentialOperator::addOperator(const DifferentialOperator &doperator) {
+    if (xDerivatives_m < doperator.xDerivatives_m) {
+        resizeX(doperator.xDerivatives_m);
+    }
+    if (sDerivatives_m < doperator.sDerivatives_m) {
+        resizeS(doperator.sDerivatives_m);
+    }
+    for (std::size_t i = 0; i <= doperator.xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= doperator.sDerivatives_m; j++) {
+            polynomials_m[i][j].addPolynomial(doperator.polynomials_m[i][j]);
+        }
+    }
+}
+
+bool DifferentialOperator::isPolynomialZero(const std::size_t &x,
+                                            const std::size_t &s) const {
+    if (x > xDerivatives_m || s > sDerivatives_m) {
+        return true;
+    }
+    return (polynomials_m[x][s].getCoefficient(0) == 0 &&
+            polynomials_m[x][s].getMaxXorder() == 0);
+}
+
+void DifferentialOperator::truncate(const std::size_t &truncateOrder) {
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+            polynomials_m[i][j].truncate(truncateOrder);
+        }
+    }
+}
+
+double DifferentialOperator::evaluatePolynomial(
+                             const double &x,
+                             const std::size_t &xDerivative,
+                             const std::size_t &sDerivative) const {
+    if (xDerivative > xDerivatives_m || sDerivative > sDerivatives_m) {
+        return 0.0;
+    }
+    return polynomials_m[xDerivative][sDerivative].evaluatePolynomial(x);
+}
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperator.h b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperator.h
new file mode 100644
index 0000000000000000000000000000000000000000..632c3cf0f34854d5929ecaee7c8cc755a5b642f3
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperator.h
@@ -0,0 +1,147 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef DIFFERENTIAL_OPERATOR_H
+#define DIFFERENTIAL_OPERATOR_H
+
+/** ---------------------------------------------------------------------
+  *
+  * DifferentialOperator describes a differential operator in terms of \n
+  * polynomial coefficients. \n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  *  A differential operator is a linear sum of operators in the form \n
+  *  @f[p(x)\frac{\partial^n}{\partial x^n}\frac{\partial^m}{\partial s^m}@f],
+  *  \n and the polynomials p(x) are stored in a n by m list.
+  */
+
+#include <vector>
+#include "Polynomial.h"
+
+namespace polynomial {
+
+class DifferentialOperator {
+public:
+    /** Default constructor, initialises identity operator (constant 1) */
+    DifferentialOperator();
+    /** Constructor, initialises operator with zero polynomials and derivatives 
+     *  up to xDerivatives in x and sDerivatives in s
+     *  \param xDerivatives -> Highest derivative of x
+     *  \param sDerivatives -> Highest serivative in s
+     */
+    DifferentialOperator(const std::size_t &xDerivatives,
+                         const std::size_t &sDerivatives);
+    /** Copy constructor */
+    DifferentialOperator(const DifferentialOperator &doperator);
+    /** Assigment operator */
+    DifferentialOperator& operator= (const DifferentialOperator &doperator);
+    /** Destructor, does nothing */
+    ~DifferentialOperator();
+    /** Set highest derivative in x to xDerivatives
+     *  \param xDerivatives -> Highest derivative in x
+     */
+    void resizeX(const std::size_t &xDerivatives);
+    /** Set highest derivative in s to sDerivatives
+     *  \param sDerivatives -> Highest derivative in s
+     */
+    void resizeS(const std::size_t &sDerivatives);
+    /** Differentiate wrt x using product rule */
+    void differentiateX();
+    /** Differentiate wrt s twice */
+    void doubleDifferentiateS();
+    /** Multiplies each term with given polynomial
+     *  \param poly -> Polynomial that multiplies each term
+     */
+    void multiplyPolynomial(const Polynomial &poly);
+    /** Assign the input polynomial \n
+     *  If x or s is greater than xDerivatives_m or sDerivatives_m,
+     *  they are adjusted accordingly and polynomials_m is resized
+     *  \param poly -> Polynomial to be assigned to the operator
+     *  \param x -> Number of x-derivatives
+     *  \param s -> Number of s-derivatives
+     */
+    void setPolynomial(const std::vector<int> &poly,
+                       const std::size_t &x,
+                       const std::size_t &s);
+    /** Print operator, for internal debugging */
+    void printOperator() const;
+    /** Add the operator to Operator, term by term
+     *  \param doperator -> DifferentialOperator object to be added with
+     *  this DifferentialOperator object
+     */
+    void addOperator(const DifferentialOperator &doperator);
+    /** Returns highest derivative in x */
+    std::size_t getXDerivatives() const;
+    /** Returns highest derivative in s */
+    std::size_t getSDerivatives() const;
+    /** Check if polynomial with x x-derivaties and s s-derivatives is a
+     *  zero polynomial \n
+     *  If x or s is larger than xDerivatives or sDerivatives true is returned
+     *  \param x -> Number of x-derivatives
+     *  \param s -> Number of s-derivatives
+     */
+    bool isPolynomialZero(const std::size_t &x, const std::size_t &s) const;
+    /** Truncate all polynomials to truncateOrder \n
+     *  If truncateOrder is greater than the highest power of x then
+     *  zeroes are appended to the end
+     *  \param truncateOrder -> Highest order of x after truncation
+     */
+    void truncate(const std::size_t &truncateOrder);
+    /** Evaluate polynomial with xDerivative x-derivatives and
+     *  sDerivative s-derivatives \n
+     *  If xDerivative or sDerivative is greater than xDerivative_m
+     *  or sDerivative then zero is returned
+     *  \param x -> Point at which polynomial is evaluated
+     *  \param xDerivative -> Number of x-derivatives
+     *  \param sDerivative -> Number of s-derivatives
+     */
+    double evaluatePolynomial(const double &x,
+                              const std::size_t &xDerivative,
+                              const std::size_t &sDerivative) const;
+private:
+    std::vector<std::vector<Polynomial>> polynomials_m;
+    std::size_t xDerivatives_m;
+    std::size_t sDerivatives_m;
+};
+
+inline
+    std::size_t DifferentialOperator::getXDerivatives() const {
+        return xDerivatives_m;
+}
+inline
+    std::size_t DifferentialOperator::getSDerivatives() const {
+        return sDerivatives_m;
+}
+
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperatorTwo.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperatorTwo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1e4b0db2daf04e03a25083a7dbd7e670337b5cda
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperatorTwo.cpp
@@ -0,0 +1,228 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <iostream>
+#include <vector>
+#include "DifferentialOperatorTwo.h"
+#include "PolynomialSum.h"
+#include "TwoPolynomial.h"
+
+namespace polynomial {
+
+DifferentialOperatorTwo::DifferentialOperatorTwo():
+    xDerivatives_m(0), sDerivatives_m(0){
+    std::vector<int> dummy1;
+    dummy1.push_back(1);
+    std::vector<std::vector<int>> dummy2;
+    dummy2.push_back(dummy1);
+    polynomials_m.resize(1);
+    polynomials_m[0].push_back(PolynomialSum(TwoPolynomial(dummy2)));
+}
+
+DifferentialOperatorTwo::DifferentialOperatorTwo(
+                         const std::size_t &xDerivatives,
+                         const std::size_t &sDerivatives):
+    xDerivatives_m(xDerivatives), sDerivatives_m(sDerivatives) {
+    polynomials_m.resize(xDerivatives_m + 1);
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        polynomials_m[i].resize(sDerivatives_m + 1);
+    }
+}
+
+DifferentialOperatorTwo::DifferentialOperatorTwo(
+                         const DifferentialOperatorTwo &doperator):
+    polynomials_m(doperator.polynomials_m),
+    xDerivatives_m(doperator.xDerivatives_m),
+    sDerivatives_m(doperator.sDerivatives_m) {
+}
+
+DifferentialOperatorTwo::~DifferentialOperatorTwo() {
+}
+
+DifferentialOperatorTwo& DifferentialOperatorTwo::operator= (
+                         const DifferentialOperatorTwo &doperator) {
+    polynomials_m = doperator.polynomials_m;
+    xDerivatives_m = doperator.xDerivatives_m;
+    sDerivatives_m = doperator.sDerivatives_m;
+    return *this;
+}
+
+void DifferentialOperatorTwo::resizeX(const std::size_t &xDerivatives) {
+    std::size_t oldxDerivatives = xDerivatives_m;
+    xDerivatives_m = xDerivatives;
+    polynomials_m.resize(xDerivatives_m + 1);
+    if (xDerivatives_m > oldxDerivatives) {
+        for (std::size_t i = oldxDerivatives + 1; i <= xDerivatives_m; i++) {
+            polynomials_m[i].resize(sDerivatives_m + 1);
+        }
+    }
+}
+
+void DifferentialOperatorTwo::resizeS(const std::size_t &sDerivatives) {
+    sDerivatives_m = sDerivatives;
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        polynomials_m[i].resize(sDerivatives_m + 1);
+    }
+}
+
+void DifferentialOperatorTwo::differentiateX() {
+    resizeX(xDerivatives_m + 1);
+    for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+        std::size_t i = xDerivatives_m;
+        while (i != 0) {
+            i--;
+            polynomials_m[i + 1][j].addPolynomial(polynomials_m[i][j]);
+            polynomials_m[i][j].differentiateX();
+        }
+    }
+}
+
+void DifferentialOperatorTwo::differentiateS() {
+    resizeS(sDerivatives_m + 1);
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        std::size_t j = sDerivatives_m;
+        while (j != 0) {
+            j--;
+            polynomials_m[i][j + 1].addPolynomial(polynomials_m[i][j]);
+            polynomials_m[i][j].differentiateS();
+        }
+    }
+}
+
+void DifferentialOperatorTwo::multiplyPolynomial(const TwoPolynomial &poly) {
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+            polynomials_m[i][j].multiplyPolynomial(poly);
+        }
+    }
+}
+
+void DifferentialOperatorTwo::setPolynomial(const TwoPolynomial &poly,
+                                            const std::size_t &x,
+                                            const std::size_t &s) {
+    if (x > xDerivatives_m) {
+        resizeX(x);
+    }
+    if (s > sDerivatives_m) {
+        resizeS(s);
+    }
+    polynomials_m[x][s] = TwoPolynomial(poly);
+}
+
+void DifferentialOperatorTwo::printOperator() const {
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+            std::size_t zeros = 0;
+            std::cout << "(";
+            for (std::size_t k = 0;
+                 k < polynomials_m[i][j].numberOfTerms(); k++) {
+                if (polynomials_m[i][j].isPolynomialZero(k)) zeros++;
+            }
+            if (zeros != polynomials_m[i][j].numberOfTerms()) {
+                polynomials_m[i][j].printPolynomial();
+                std::cout << "(d/dx)^" << i << "(d/ds)^" << j << ")";
+            }
+        }
+    }
+    std::cout << std::endl;
+}
+
+void DifferentialOperatorTwo::addOperator(
+                              const DifferentialOperatorTwo &doperator) {
+    if (xDerivatives_m < doperator.xDerivatives_m) {
+        resizeX(doperator.xDerivatives_m);
+    }
+    if (sDerivatives_m < doperator.sDerivatives_m) {
+        resizeS(doperator.sDerivatives_m);
+    }
+    for (std::size_t i = 0; i <= doperator.xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= doperator.sDerivatives_m; j++) {
+            polynomials_m[i][j].addPolynomial(doperator.polynomials_m[i][j]);
+        }
+    }
+}
+
+bool DifferentialOperatorTwo::isPolynomialZero(const std::size_t &x,
+                                               const std::size_t &s,
+                                               const std::size_t &term) const {
+    if (x > xDerivatives_m || s > sDerivatives_m) {
+        return true;
+    }
+    return polynomials_m[x][s].isPolynomialZero(term);
+}
+
+void DifferentialOperatorTwo::truncate(
+                              const std::size_t &truncateOrder) {
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+            polynomials_m[i][j].truncate(truncateOrder);
+        }
+    }
+}
+
+double DifferentialOperatorTwo::evaluatePolynomial(
+                                const double &x,
+                                const double &s,
+                                const std::size_t &xDerivative,
+                                const std::size_t &sDerivative,
+                                const std::vector<double> &dSvalues) const {
+    if (xDerivative > xDerivatives_m || sDerivative > sDerivatives_m) {
+        return 0.0;
+    }
+    return polynomials_m[xDerivative][sDerivative]
+           .evaluatePolynomial2(x, s, dSvalues);
+}
+
+std::size_t DifferentialOperatorTwo::numberOfTerms(
+                                     const std::size_t &xDerivatives,
+                                     const std::size_t &sDerivatives) const {
+    if (xDerivatives > xDerivatives_m || sDerivatives > sDerivatives_m) {
+        return 0;
+    }
+    return polynomials_m[xDerivatives][sDerivatives].numberOfTerms();
+}
+
+std::vector<std::size_t> DifferentialOperatorTwo::getdSFactors(
+                         const std::size_t &xDerivatives,
+                         const std::size_t &sDerivatives,
+                         const std::size_t &p) const{
+    if (xDerivatives > xDerivatives_m || sDerivatives > sDerivatives_m) {
+        std::vector<std::size_t> dummy;
+        return dummy;
+    }
+    return polynomials_m[xDerivatives][sDerivatives].getdSfactors(p);
+}
+
+void DifferentialOperatorTwo::sortTerms() {
+    for (std::size_t i = 0; i <= xDerivatives_m; i++) {
+        for (std::size_t j = 0; j <= sDerivatives_m; j++) {
+            polynomials_m[i][j].sortTerms();
+        }
+    }
+}
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperatorTwo.h b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperatorTwo.h
new file mode 100644
index 0000000000000000000000000000000000000000..ecac3df35ee5b7c44f542fcfab0d53f000b66aa0
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/DifferentialOperatorTwo.h
@@ -0,0 +1,173 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef DIFFERENTIAL_OPERATOR_TWO_H
+#define DIFFERENTIAL_OPERATOR_TWO_H
+
+/** ---------------------------------------------------------------------
+  *
+  * DifferentialOperatorTwo describes a differential operator in terms of \n
+  * polynomial coefficients. The polynomials have two variables x and S(s). \n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  *  A differential operator is a linear sum of operators in the form \n
+  *  @f[p(x, S(s))\frac{\partial^n}{\partial x^n}
+  *  \frac{\partial^m}{\partial s^m}@f], \n
+  *  and the polynomials p(x, S(s)) are stored in a n by m list.
+  */
+
+#include <vector>
+#include "PolynomialSum.h"
+#include "TwoPolynomial.h"
+
+namespace polynomial {
+
+class DifferentialOperatorTwo {
+public:
+    /** Default constructor, initalises identity operator */
+    DifferentialOperatorTwo();
+    /** Constructor, initialises operator with zero polynomials and
+      * derivatives up to xDerivatives in x and sDerivatives in s
+      * \param xDerivatives -> Highest x-derivative
+      * \param sDerivatives -> Highest s-derivative
+      */
+    DifferentialOperatorTwo(const std::size_t &xDerivatives,
+                            const std::size_t &sDerivatives);
+    /** Copy constructor */
+    DifferentialOperatorTwo(const DifferentialOperatorTwo &doperator);
+    /** Destructor */
+    ~DifferentialOperatorTwo();
+    /** Assigment operator */
+    DifferentialOperatorTwo& operator= (
+                             const DifferentialOperatorTwo &doperator);
+    /** Set highest derivative in x to xDerivatives111
+     *  \param xDerivatives -> Highest x-derivative
+     */
+    void resizeX(const std::size_t &xDerivatives);
+    /** Set highest derivative in s to xDerivatives
+     *  \param sDerivatives -> Highest s-derivative
+     */
+    void resizeS(const std::size_t &sDerivatives);
+    /** Differentiate wrt x using product rule */
+    void differentiateX();
+    /** Differentiate wrt s using product rule */
+    void differentiateS();
+    /** Multiplies each term with given polynomial
+     *  \param poly -> Polynomial that is multiplied with this polynomial
+     */
+    void multiplyPolynomial(const TwoPolynomial &poly);
+    /** Assign the polynomial poly to the operator
+     *  \param poly -> Polynomial to be assigned
+     *  \param x -> Number of x-derivatives
+     *  \param s -> Numbe of s-derivatives
+     */
+    void setPolynomial(const TwoPolynomial &poly,
+                       const std::size_t &x,
+                       const std::size_t &s);
+    /** Print operator, for internal debugging */
+    void printOperator() const;
+    /** Add the doperator to operator, term by term
+     *  \param doperator -> Differential operator to be added 
+     */
+    void addOperator(const DifferentialOperatorTwo &doperator);
+    /** Returns highest derivative in x */
+    std::size_t getXDerivatives() const;
+    /** Returns highest derivative in s */
+    std::size_t getSDerivatives() const;
+    /** Check if polynomial with x x-derivaties and s s-derivatives is
+     *  a zero polynomial \n
+     *  If x or s are out of range, true is returned
+     *  \param x -> Number of x-derivatives
+     *  \param s -> Number of s-derivatives
+     *  \param term -> Term index
+     */
+    bool isPolynomialZero(const std::size_t &x,
+                          const std::size_t &s,
+                          const std::size_t &term) const;
+    /** Truncate all polynomials to truncateOrder
+     *  \param truncateOrder -> Highest order of x after truncation
+     */
+    void truncate(const std::size_t &truncateOrder);
+    /** Evaluate polynomial specified
+     *  \param x -> Point x where polynomial is evaluated
+     *  \param s -> Point s where polynomial is evaluated
+     *  \param xDerivative -> Number of x-derivatives
+     *  \param sDerivative -> Number of s-derivatives
+     *  \param term -> Term index
+     */
+    double evaluatePolynomial(const double &x,
+                              const double &s,
+                              const std::size_t &xDerivative,
+                              const std::size_t &sDerivative,
+                              const std::vector<double> &dSvalues) const;
+    /** Returns number of terms in the sum with xDerivatives
+     *  x-derivatives and sDerivatives s-derivatives \n
+     *  If xDerivative or sDerivative are out of
+     *  range zero is returned
+     *  \param xDerivatives -> Number of x-derivatives
+     *  \param sDerivatives -> Number of s-derivatives
+     */
+    std::size_t numberOfTerms(const std::size_t &xDerivatives,
+                              const std::size_t &sDerivatives) const;
+    /** Returns list of S(s)-derivatives from term p in polynomial with
+     *  xDerivatives x-derivatives and sDerivatives s-derivatives \n
+     *  If xDerivatives or sDerivatives are out of range
+     *  an empty vector is returned
+     * \param xDerivatives -> Number of x-derivatives
+     * \param sDerivatives -> Number of s-derivatives
+     * \param p -> Term index, starting with term 0
+     */
+    std::vector<std::size_t> getdSFactors(const std::size_t &xDerivatives,
+                                          const std::size_t &sDerivatives,
+                                          const std::size_t &p) const;
+    /** Sort the terms in each sum, with fewest S(s)-derivatives first,
+     *  then in increasing powers
+     */
+    void sortTerms();
+private:
+    std::vector<std::vector<PolynomialSum>> polynomials_m;
+    std::size_t xDerivatives_m;
+    std::size_t sDerivatives_m;
+};
+
+inline
+    std::size_t DifferentialOperatorTwo::getXDerivatives() const {
+        return xDerivatives_m;
+}
+inline
+    std::size_t DifferentialOperatorTwo::getSDerivatives() const {
+        return sDerivatives_m;
+}
+
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/Polynomial.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/Polynomial.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dd7862bafe3406d47afedd12140c983f837eb85c
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/Polynomial.cpp
@@ -0,0 +1,136 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <cmath>
+#include <iostream>
+#include "Polynomial.h"
+
+namespace polynomial {
+
+Polynomial::Polynomial() {
+    coefficients_m.push_back(0);
+    maxXorder_m = 0;
+}
+
+Polynomial::Polynomial(const std::vector<int> &coefficients) {
+    if (coefficients.size() == 0) {
+        coefficients_m.push_back(0);
+        return;
+    }
+    coefficients_m = coefficients;
+    maxXorder_m = coefficients_m.size() - 1;
+}
+
+Polynomial::Polynomial(const Polynomial &poly) {
+    coefficients_m = poly.coefficients_m;
+    maxXorder_m = coefficients_m.size() - 1;
+}
+
+Polynomial::~Polynomial() {
+}
+
+Polynomial& Polynomial::operator= (const Polynomial& poly) {
+    maxXorder_m = poly.maxXorder_m;
+    coefficients_m = poly.coefficients_m;
+    return *this;
+}
+
+void Polynomial::differentiatePolynomial() {
+    if (maxXorder_m == 0 && coefficients_m[0] == 0) {
+        return;
+    }
+    if (maxXorder_m == 0 && coefficients_m[0] != 0) {
+        coefficients_m[0] = 0;
+        return;
+    }
+    for (std::size_t i = 0; i < maxXorder_m; i++) {
+        coefficients_m[i] = coefficients_m[i + 1] * (i + 1);
+    }
+    coefficients_m.pop_back();
+    maxXorder_m--;
+}
+
+void Polynomial::multiplyPolynomial(const Polynomial &poly) {
+    if (maxXorder_m == 0 && coefficients_m[0] == 0) {
+        return;
+    }
+    if (poly.maxXorder_m == 0 && poly.coefficients_m[0] == 0) {
+        setZero();
+        return;
+    }
+    std::vector<int> newPoly(maxXorder_m + poly.maxXorder_m + 1, 0);
+    for (std::size_t i = 0; i <= maxXorder_m; i++) {
+        for (std::size_t j = 0; j <= poly.maxXorder_m; j ++) {
+            newPoly[i + j] = newPoly[i + j] +
+            coefficients_m[i] * poly.coefficients_m[j];
+        }
+    }
+    coefficients_m = newPoly;
+    maxXorder_m = coefficients_m.size() - 1;
+}
+
+void Polynomial::printPolynomial() const {
+    std::cout << coefficients_m[0];
+    for (std::size_t i = 1; i <= maxXorder_m; i++) {
+        if (coefficients_m[i] >= 0) {
+            std::cout << " + " << coefficients_m[i] << "x^" << i;
+        } else {
+            std::cout << " - " << -coefficients_m[i] << "x^" << i;
+        }
+    }
+}
+
+void Polynomial::addPolynomial(const Polynomial &poly) {
+    if (poly.maxXorder_m > maxXorder_m) {
+        maxXorder_m = poly.maxXorder_m;
+        coefficients_m.resize(maxXorder_m + 1, 0);
+    }
+    for (std::size_t i = 0; i <= poly.maxXorder_m; i++) {
+        coefficients_m[i] = coefficients_m[i] + poly.coefficients_m[i];
+    }
+}
+
+void Polynomial::setCoefficient(const int &coefficient,
+                                const std::size_t &order) {
+    if (order > maxXorder_m) {
+        coefficients_m.resize(order + 1, 0);
+        maxXorder_m = coefficients_m.size() - 1;
+    }
+    coefficients_m[order] = coefficient;
+}
+
+double Polynomial::evaluatePolynomial(const double &x) const {
+    double result = 0.0;
+    std::size_t i = maxXorder_m + 1;
+    while (i != 0) {
+        i--;
+        result = result * x + coefficients_m[i];
+    }
+    return result;
+} 
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/Polynomial.h b/src/Classic/AbsBeamline/MultipoleTFunctions/Polynomial.h
new file mode 100644
index 0000000000000000000000000000000000000000..9847b14f1abe202de63578837857697771cb847c
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/Polynomial.h
@@ -0,0 +1,151 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef POLYNOMIAL_H
+#define POLYNOMIAL_H
+
+/** ---------------------------------------------------------------------
+  *
+  * Polynomial describes a polynomial of one variable.\n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  *  The polynomial @f[p(x) = a_0 + a_1x + ... + a_nx^n @f] is stored as a \n
+  *  list (a_0, a_1, ..., a_n).\n
+  *  BUG: For large n the integer type might overflow. If you need this many \n
+  *  terms change all int to long int. \n
+  */
+
+#include <vector>
+
+namespace polynomial {
+
+class Polynomial {
+public:
+    /** Default constructor, initialises zero polynomial */
+    Polynomial();
+    /** Initialises polynomial with input coefficients, starting with the
+     *  const term \n
+     *  If coefficients is an empty vector, a zero polynomial with one
+     *  constant zero term is created 
+     *  \param coefficients -> List of polynomial coefficients, starting
+     *  with the const term
+     */
+    Polynomial(const std::vector<int> &coefficients);
+    /** Copy constructor */
+    Polynomial(const Polynomial &poly);
+    /** Destructor, does nothing */
+    ~Polynomial();
+    /** Assigment operator */
+    Polynomial& operator= (const Polynomial &poly);
+    /** Differentiate polynomial using \f$\frac{d}{dx}x^n = nx^{n - 1}\f$ */
+    void differentiatePolynomial();
+    /** Multiply polynomial with input polynomial by multiplying
+     *  term by term \n
+     *  maxXorder_m is adjusted accordingly and coefficients_m is resized 
+     *  \param poly -> Polynomial to be multiplied with this polynomial
+     */
+    void multiplyPolynomial(const Polynomial &poly);
+    /** Add polynomial to input polynomial \n
+     *  If input polynomial has higher order terms,
+     *  then maxXorder_m is adjusted and coefficients_m is resized
+     *  \param poly -> Polynomial to be added to this polynomial
+     */
+    void addPolynomial(const Polynomial &poly);
+    /** Print polynomial for internal debugging */
+    void printPolynomial() const;
+    /** Returns coefficient in front of x^order
+     *  \param order -> The power of x belonging to this coefficient
+     */
+    int getCoefficient(const std::size_t &order) const;
+    /** Set the coefficient in front of x^order \n
+     *  If order is larger than maxXorder_m maxXorder_m is adjusted
+     *  and coefficients_m is resized
+     *  \param coefficient -> Coefficient we wish to insert
+     *  \param order -> The power of x belonging to this coefficient
+     */
+    void setCoefficient(const int &coefficient, const std::size_t &order);
+    /** Returns the highest order of x, does nothing if order is negative */
+    int getMaxXorder() const;
+    /** Set the highest order of x to maxXorder, filling in zeros if necessary
+     *  \param maxXorder -> Highest power of x
+     */
+    void setMaxXorder(const std::size_t &maxXorder);
+    /** Set polynomial to zero */
+    void setZero();
+    /** Evaluate the polynomial
+     *  \param x -> The point at which polynomial is evaluated
+     */
+    double evaluatePolynomial(const double &x) const;
+    /** Truncate polynomial to truncateOrder \n
+     *  If truncateOrder is greater than maxXorder_m, maxXorder_m is adjusted
+     *  and coefficients is resized with zero coefficients at the end
+     *  \param truncateOrder -> Highest order of x after truncation
+     */
+    void truncate(const std::size_t &truncateOrder);
+private:
+    std::size_t maxXorder_m;
+    std::vector<int> coefficients_m;
+};
+
+inline
+    int Polynomial::getCoefficient(const std::size_t &order) const {
+        if (order > maxXorder_m || order < 0) {
+            return 0;
+    }
+        return coefficients_m[order];
+}
+inline
+    int Polynomial::getMaxXorder() const{
+        return maxXorder_m;
+}
+inline
+    void Polynomial::setMaxXorder(const std::size_t &maxXorder) {
+        coefficients_m.resize(maxXorder + 1, 0);
+        maxXorder_m = maxXorder;
+}
+inline
+    void Polynomial::setZero() {
+        maxXorder_m = 0;
+        coefficients_m.resize(1);
+        coefficients_m[0] = 0;
+}
+inline
+    void Polynomial::truncate(const std::size_t &truncateOrder) {
+        if (truncateOrder < maxXorder_m) {
+            coefficients_m.resize(truncateOrder + 1);
+            maxXorder_m = truncateOrder;
+        }
+}
+
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/PolynomialSum.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/PolynomialSum.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6a2d85368d9bd6d43feb3744000fbf21e6b7e83b
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/PolynomialSum.cpp
@@ -0,0 +1,226 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <iostream>
+#include <algorithm>
+#include <gsl/gsl_sf_pow_int.h>
+#include "PolynomialSum.h"
+#include "TwoPolynomial.h"
+
+namespace polynomial {
+
+PolynomialSum::PolynomialSum() {
+}
+
+PolynomialSum::PolynomialSum(const TwoPolynomial &polynomial) {
+    polynomialSum_m.push_back(polynomial);
+}
+
+PolynomialSum::PolynomialSum(const PolynomialSum &polynomialSum):
+    polynomialSum_m(polynomialSum.polynomialSum_m) {
+}
+
+PolynomialSum::~PolynomialSum() {
+}
+
+PolynomialSum& PolynomialSum::operator= (const PolynomialSum &sum) {
+    polynomialSum_m = sum.polynomialSum_m;
+    return *this;
+}
+
+void PolynomialSum::differentiateX() {
+    for (std::size_t i = 0; i < polynomialSum_m.size(); i++) {
+        polynomialSum_m[i].differentiateX();
+    }
+}
+
+void PolynomialSum::differentiateS() {
+    std::size_t pSize = polynomialSum_m.size();
+    for (std::size_t i = 0; i < pSize; i++) {
+        std::size_t N = polynomialSum_m[i].getNdSfactors();
+        for (std::size_t j = 0; j < N; j++) {
+            std::vector<std::size_t> tempdS =
+                                     polynomialSum_m[i].getdSfactors();
+            if (tempdS[j] != 0) {
+                polynomialSum_m.push_back(polynomialSum_m[i]);
+                polynomialSum_m.back().multiplyConstant(tempdS[j]);
+                tempdS[j] = tempdS[j] - 1;
+                if ((j + 1) == N) tempdS.push_back(0);
+                tempdS[j + 1] = tempdS[j + 1] + 1;
+                polynomialSum_m.back().setdSfactors(tempdS);
+            }
+        }
+        polynomialSum_m[i].differentiateS();
+    }      
+}
+
+void PolynomialSum::multiplyPolynomial(const TwoPolynomial &poly) {
+    for (std::size_t i = 0; i < polynomialSum_m.size(); i++) {
+        polynomialSum_m[i].multiplyPolynomial(poly);
+    }
+}
+
+void PolynomialSum::printPolynomial() const {
+    for (std::size_t i = 0; i < polynomialSum_m.size(); i++) {
+        if (!polynomialSum_m[i].isZero()) {
+            std::cout << " + ";
+            polynomialSum_m[i].printPolynomial();
+        }
+    }
+}
+
+bool PolynomialSum::isPolynomialZero(const std::size_t &p) const {
+    if (p < polynomialSum_m.size()) {
+        return polynomialSum_m[p].isZero();
+    }
+    else {
+        return true;
+    }
+}
+
+void PolynomialSum::truncate(const std::size_t &truncateOrder) {
+    for (std::size_t i = 0; i < polynomialSum_m.size(); i++) {
+        polynomialSum_m[i].truncate(truncateOrder);
+    }
+}
+
+void PolynomialSum::addPolynomial(const PolynomialSum &poly) {
+    std::vector<TwoPolynomial> newPoly;
+    newPoly.reserve(polynomialSum_m.size() + poly.polynomialSum_m.size());
+    newPoly.insert(newPoly.end(),
+                   polynomialSum_m.begin(),
+                   polynomialSum_m.end());
+    newPoly.insert(newPoly.end(),
+                   poly.polynomialSum_m.begin(),
+                   poly.polynomialSum_m.end());
+    polynomialSum_m = newPoly;
+}
+
+std::vector<std::size_t> PolynomialSum::getdSfactors(
+                                        const std::size_t &p) const {
+    if (p >= polynomialSum_m.size()) {
+        std::vector<std::size_t> dummy;
+        return dummy;
+    }
+    return polynomialSum_m[p].getdSfactors();
+}
+
+void PolynomialSum::sortTerms() {
+    std::size_t N = polynomialSum_m.size();
+    if (N < 2) {
+        return;
+    }
+    std::sort(polynomialSum_m.begin(), polynomialSum_m.end());
+    while (polynomialSum_m[0].isZero()) {
+        polynomialSum_m.erase(polynomialSum_m.begin());
+        if (polynomialSum_m.size() < 2) {
+            return;
+        }
+    }
+    std::size_t i = 1;
+    while (i < polynomialSum_m.size()) {
+        if (polynomialSum_m[i].isZero()) {
+            polynomialSum_m.erase(polynomialSum_m.begin() + i);
+            continue;
+        }
+        if (polynomialSum_m[i - 1] == polynomialSum_m[i]) {
+            polynomialSum_m[i - 1].addPolynomial(polynomialSum_m[i]);
+            polynomialSum_m.erase(polynomialSum_m.begin() + i);
+        } else {
+            i++;
+        }
+    }
+}
+
+void PolynomialSum::putSumTogether(
+                    const std::vector<double> &dSvalues,
+                    std::vector<std::vector<double>> &finalPolynomial) const {
+    finalPolynomial.resize(1);
+    finalPolynomial[0].resize(1, 0.0);
+    std::size_t nx = 0, ns = 0;
+    for (std::size_t p = 0; p < polynomialSum_m.size(); p++) {
+        if (polynomialSum_m[p].isZero()) {
+            continue;
+        }
+        double dSfactoreval = 1.0;
+        for (std::size_t q = 0;
+             q < polynomialSum_m[p].dSfactors_m.size();
+             q++) {
+            dSfactoreval *= gsl_sf_pow_int(dSvalues[q + 1],
+                            polynomialSum_m[p].dSfactors_m[q]);
+        }
+        if (nx < polynomialSum_m[p].maxXorder_m) {
+            finalPolynomial.resize(polynomialSum_m[p].maxXorder_m + 1);
+            for (std::size_t k = nx + 1;
+                 k <= polynomialSum_m[p].maxXorder_m;
+                 k++) {
+                finalPolynomial[nx].resize(ns + 1, 0.0);
+            }
+            nx = polynomialSum_m[p].maxXorder_m;
+        }
+        if (ns < polynomialSum_m[p].maxSorder_m) {
+            ns = polynomialSum_m[p].maxSorder_m;
+            for (std::size_t k = 0; k <= nx; k++) {
+                finalPolynomial[k].resize(ns + 1, 0.0);
+            }
+        }
+        for (std::size_t i = 0;
+             i <= polynomialSum_m[p].maxXorder_m;
+             i++) {
+            for (std::size_t j = 0;
+                 j <= polynomialSum_m[p].maxSorder_m;
+                 j++) {
+                finalPolynomial[i][j] += polynomialSum_m[p]
+                                               .coefficients_m[i][j] 
+                                               * dSfactoreval;
+            }
+        }
+    }
+}
+
+double PolynomialSum::evaluatePolynomial2(
+                      const double &x,
+                      const double &s,
+                      const std::vector<double> &dSvalues) const {
+    std::vector<std::vector<double>> coefficients;
+    putSumTogether(dSvalues, coefficients);
+    double result = 0.0;
+    std::size_t i = coefficients.size();
+    while (i != 0) {
+        i--;
+        std::size_t j = coefficients[0].size();
+        double temp = 0.0;
+        while (j != 0) {
+            j--;
+            temp = temp * s + coefficients[i][j];
+        }
+        result = result * x + temp;
+    }
+    return result;
+}
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/PolynomialSum.h b/src/Classic/AbsBeamline/MultipoleTFunctions/PolynomialSum.h
new file mode 100644
index 0000000000000000000000000000000000000000..c51772f2afeed1ecfa95fca377ccf0ff85aa7788
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/PolynomialSum.h
@@ -0,0 +1,151 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef POLYNOMIAL_SUM_H
+#define POLYNOMIAL_SUM_H
+
+/** ---------------------------------------------------------------------
+  *
+  * PolynomialSum describes a sum of TwoPolynomial objects.\n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  *  The polynomial of two variables @f[p(x) = a_{00} + a_{10}x + a_{11}xS(s)
+  *  + ... + a_{nm}x^nS(s)^m @f] cannot be summed with other polynomials \n
+  *  unless all the powers of S(s)-derivatives are identical. \n
+  *  Instead all terms are just stored seperately in a list.
+  */
+
+#include <vector>
+#include "TwoPolynomial.h"
+
+namespace polynomial {
+
+class PolynomialSum {
+public:
+    /** Default constructor, initialises empty sum */
+    PolynomialSum();
+    /** Constructor, initalises sum with a single polynomial from input
+     *  \param polynomial -> The polynomial sum is initialised with this \n
+     *  polynomial as the first and only term
+     */
+    PolynomialSum(const TwoPolynomial &polynomial);
+    /** Copy constructor */
+    PolynomialSum(const PolynomialSum &polynomialSum);
+    /** Desctructor, does nothing */
+    ~PolynomialSum();
+    /** Assigment operator */
+    PolynomialSum& operator= (const PolynomialSum &sum);
+    /** Differentiate each term wrt x */
+    void differentiateX();
+    /** Differentiate each term wrt s */
+    void differentiateS();
+    /** Multiply term with input polynomial 
+     *  \param poly -> Polynomial to be multiplied with this polynomial
+     */
+    void multiplyPolynomial(const TwoPolynomial &poly);
+    /** Print polynomial, for internal debugging */
+    void printPolynomial() const;
+    /** Returns number of terms in the sum */
+    std::size_t numberOfTerms() const;
+    /** Check if term p is a zero polynomial \n
+     *  Returns true if p is negative or outside the range of list range
+     *  \param p -> Term index, starting with term 0
+     */
+    bool isPolynomialZero(const std::size_t &p) const;
+    /** Truncate series in x at truncateOrder
+     *  \param truncateOrder -> Highest power of x after truncation
+     */
+    void truncate(const std::size_t &truncateOrder);
+    /** Evaluate polynomial in term p at the point (x, s) \n
+     *  If p is outside list range zero is returned
+     *  \param p -> Term index, starting with term 0
+     *  \param x -> Point x where polynomial is evaluated
+     *  \param s -> Point s where polynomial is evaluated
+     */
+    double evaluatePolynomial(const std::size_t &p,
+                              const double &x,
+                              const double &s) const;
+    /** Put together all terms in the PolynomialSum by evaluating the \n
+     *  S(s)-derivatives and multiply them with the polynomial coefficients \n
+     *  Polynomial coefficients are now of type double 
+     *  \param dSvalues -> List of evaluated S(s)-derivates, starting
+     *  with zeroth (no) derivative
+     *  \param finalPolynomial -> Resulting polynomial, must pass an
+     *  empty vector of vectors into this function
+     */
+    void putSumTogether(const std::vector<double> &dSvalues,
+                        std::vector<std::vector<double>> &finalPolynomial) const;
+    /** Evaluate polynomial after putting the sum together into one polynomial
+     *  \param x -> Point x where polynomial is evaluated
+     *  \param s -> Point s where polynomial is evaluated
+     *  \param dSvalues -> List of evaluated S(s)-derivatives, starting 
+     *  with zeroth (no) derivative
+     */
+    double evaluatePolynomial2(const double &x,
+                               const double &s,
+                               const std::vector<double> &dSvalues) const;
+    /** Add poly to the sum by concatenating the lists
+     *  \param poly -> Polynomial that is added to the list
+     */
+    void addPolynomial(const PolynomialSum &poly);
+    /** Returns lists of S(s)-derivatives in term p \n
+     *  Returns empty list if p is negative or outside list range
+     *  \param p -> Term index, starting with term 0
+     */
+    std::vector<std::size_t> getdSfactors(const std::size_t &p) const;
+    /** Sort polynomialSum_m such that the TwoPolynomial objects with fewest \n
+     *  S(s)-derivatives come first, in ascending order \n
+     *  If any TwoPolynomial objects have identical S(s)-derivatives these 
+     *  are put together by adding the polynomials
+     */
+    void sortTerms();
+private:
+    std::vector<TwoPolynomial> polynomialSum_m;
+};
+
+inline
+    std::size_t PolynomialSum::numberOfTerms() const {
+        return polynomialSum_m.size();
+}
+inline
+    double PolynomialSum::evaluatePolynomial(const std::size_t &p,
+                                             const double &x,
+                                             const double &s) const {
+        if (p >= polynomialSum_m.size()) {
+            return 0.0;
+        }
+        return polynomialSum_m[p].evaluatePolynomial(x, s);
+}
+
+} 
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelation.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..470c28b352dd29816b72930b784be684ef48b4e7
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelation.cpp
@@ -0,0 +1,104 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <cmath>
+#include <iostream>
+#include <vector>
+#include "RecursionRelation.h"
+#include "DifferentialOperator.h"
+
+namespace polynomial {
+
+RecursionRelation::RecursionRelation(): power_m(0), highestXorder_m(10) {
+    std::vector<int> poly(1, 1);
+    operator_m.setPolynomial(poly, 0, 0);
+}
+
+RecursionRelation::RecursionRelation(const std::size_t &power,
+                                     const std::size_t &highestXorder):
+    power_m(power), highestXorder_m(highestXorder) {
+    std::vector<int> poly(1, 1);
+    operator_m.setPolynomial(poly, 0, 0);
+    for (std::size_t i = 0; i < power_m; i++) {
+        applyOperator();
+    }
+}
+
+RecursionRelation::RecursionRelation(const RecursionRelation &doperator):
+    power_m(doperator.power_m) {
+    operator_m = DifferentialOperator(doperator.operator_m);
+}
+
+RecursionRelation::~RecursionRelation() {
+}
+
+RecursionRelation& RecursionRelation::operator= (
+                   const RecursionRelation &recursion) {
+     operator_m = recursion.operator_m;
+     power_m = recursion.power_m;
+     highestXorder_m = recursion.highestXorder_m;
+     return *this;
+}
+
+void RecursionRelation::truncate(std::size_t highestXorder) {
+    highestXorder_m = highestXorder;
+    operator_m.truncate(highestXorder);
+}
+
+/*  This function increases n by 1 in the differential operator used to find \n
+ *  the magnetic scalar potential
+ */
+void RecursionRelation::applyOperator() {
+/*  Differential operator has 3 terms, make three copies of current operator */
+    DifferentialOperator firstTerm(operator_m);
+    DifferentialOperator secondTerm(operator_m);
+    DifferentialOperator thirdTerm(operator_m);
+/*  Initialise polynomials
+ *  p(x) = 1 - x + x^2 - ...
+ *  q(x) = 1 - 2x + 3x^2 - ...
+ */
+    std::vector<int> p, q;
+    for (std::size_t i = 0; i <= highestXorder_m; i++) {
+        p.push_back(pow(-1, i));
+        q.push_back(pow(-1, i) * (i + 1));
+    }
+/*  Differentiate first term by x, then multiply by p(x) */
+    firstTerm.differentiateX();
+    firstTerm.multiplyPolynomial(Polynomial(p));
+/*  Differentiate second term twice by x */
+    secondTerm.differentiateX();
+    secondTerm.differentiateX();
+/*  Differentiate third term by s twice, then multiply by q(x) */
+    thirdTerm.doubleDifferentiateS();
+    thirdTerm.multiplyPolynomial(Polynomial(q));
+/*  Add operators to obtain final operator */
+    operator_m = DifferentialOperator(firstTerm);
+    operator_m.addOperator(secondTerm);
+    operator_m.addOperator(thirdTerm);
+}
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelation.h b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelation.h
new file mode 100644
index 0000000000000000000000000000000000000000..a31fbee48550361a62cf9987f53ac826c300bb8e
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelation.h
@@ -0,0 +1,161 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef RECURSION_RELATION_H
+#define RECURSION_RELATION_H
+
+/** ---------------------------------------------------------------------
+  *
+  * RecursionRelation describes the differential operator used to find the \n 
+  * coefficients in the expansion of the magnetic scalar potential \n
+  * It contains member functions for extracting all information required to \n
+  * reconstruct the differential operator and evaluate its terms.\n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  * The operator of interest is
+  *  \f{eqnarray*} {
+  *  \Big(\frac{1}{\rho(1 &+& x/\rho)}\frac{\partial}{\partial x} 
+  *  + \frac{\partial^2}{\partial x^2}
+  *  &+& \frac{1}{(1 + x/\rho)^2}\frac{\partial^2}{\partial s^2}\Big)^n
+  *  \f}
+  *  and it can be initialised to any power of x and up to any n.
+  */
+
+#include "DifferentialOperator.h"
+#include "Polynomial.h"
+
+namespace polynomial {
+
+class RecursionRelation {
+public:
+    /** Default constructor, initialises identity operator */
+    RecursionRelation();
+    /** Constructor, initialises the operator
+     *  \f{eqnarray*} {
+     *  \Big(\frac{1}{\rho(1 + x/\rho)}\frac{\partial}{\partial x}
+     *  + \frac{\partial^2}{\partial x^2}
+     *  + \frac{1}{(1 + x/\rho)^2}\frac{\partial^2}{\partial s^2}\Big)^n
+     *  \f}
+     *  where power = n. The denominators are expanded in x and \n
+     *  highestXorder is the highest power of x \n
+     *  \param power -> Number of times the differential operator is applied
+     *  \param highestXorder -> Highest order of x before truncation
+     */
+    RecursionRelation(const std::size_t &power,
+                      const std::size_t &highestXorder);
+    /** Copy constructor */
+    RecursionRelation(const RecursionRelation &doperator);
+    /** Desctructor, does nothing */
+    ~RecursionRelation();
+    /** Assigment operator */
+    RecursionRelation& operator= (const RecursionRelation &recursion);
+    /** Print operator, used for internal debugging */
+    void printOperator() const;
+    /** Truncate series in x at highestXorder
+     *  \param highestXorder -> Highest order of x after truncation
+     */
+    void truncate(std::size_t highestXorder);
+    /** Increase power n by one */
+    void applyOperator();
+    /** Apply a differential operator in x */
+    void differentiateX();
+    /** Returns highest derivative of x */
+    std::size_t getMaxXDerivatives() const;
+    /** Returns highest derivative of s */
+    std::size_t getMaxSDerivatives() const;
+    /** Evaluates polynomial
+     *  \param x -> Point at which polynomial is evaluated
+     *  \param xDerivative -> Number of x-derivatives
+     *  \param sDerivative -> Number of s-derivatives
+     */
+    double evaluatePolynomial(const double &x,
+                              const std::size_t &xDerivative,
+                              const std::size_t &sDerivative) const;
+    /** Check if polynomial with x x-derivatives and s s-derivatives is zero
+     *  \param xDerivative -> Number of x-derivatives
+     *  \param sDerivative -> Number of s-derivatives
+     */
+    bool isPolynomialZero(const std::size_t &x, const std::size_t &s) const;
+    /** Change number of x-derivatives to xDerivatives
+     *  \param xDerivative -> Number of x-derivatives
+     */  
+    void resizeX(const std::size_t &xDerivatives);
+    /** Change number of s-derivatives to sDerivatives
+     *  \param sDerivative -> Number of s-derivatives
+     */
+    void resizeS(const std::size_t &sDerivatives);
+private:
+    DifferentialOperator operator_m;
+    std::size_t power_m;
+    std::size_t highestXorder_m;
+};
+
+inline
+    void RecursionRelation::printOperator() const {
+        operator_m.printOperator();
+}
+inline
+    void RecursionRelation::differentiateX() {
+        operator_m.differentiateX();
+}
+inline
+    std::size_t RecursionRelation::getMaxXDerivatives() const {
+        return operator_m.getXDerivatives();
+}
+inline
+    std::size_t RecursionRelation::getMaxSDerivatives() const {
+        return operator_m.getSDerivatives();
+}
+inline
+    double RecursionRelation::evaluatePolynomial(
+                              const double &x,
+                              const std::size_t &xDerivative,
+                              const std::size_t &sDerivative) const {
+        return operator_m.evaluatePolynomial(x, xDerivative, sDerivative);
+}
+inline
+    bool RecursionRelation::isPolynomialZero(const std::size_t &x,
+                                             const std::size_t &s) const {
+        return operator_m.isPolynomialZero(x, s);
+}
+inline
+    void RecursionRelation::resizeX(const std::size_t &xDerivatives) {
+        operator_m.resizeX(xDerivatives);
+}
+inline
+    void RecursionRelation::resizeS(const std::size_t &sDerivatives) {
+        operator_m.resizeS(sDerivatives);
+}
+
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4c4485ac4b132100b87c5ccdc7a53260664515e1
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.cpp
@@ -0,0 +1,117 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <cmath>
+#include <vector>
+#include <iostream>
+#include "RecursionRelationTwo.h"
+#include "DifferentialOperatorTwo.h"
+#include "TwoPolynomial.h"
+
+namespace polynomial {
+
+RecursionRelationTwo::RecursionRelationTwo(): power_m(0), highestXorder_m(10) {
+    std::vector<int> poly2(1, 1);
+    std::vector<std::vector<int>> poly1;
+    poly1.push_back(poly2);
+    TwoPolynomial poly(poly1);
+    operator_m.setPolynomial(poly, 0, 0);
+}
+
+RecursionRelationTwo::RecursionRelationTwo(const std::size_t &power,
+                                           const std::size_t &highestXorder):
+    power_m(power), highestXorder_m(highestXorder) {
+    std::vector<int> poly2(1, 1);
+    std::vector<std::vector<int>> poly1;
+    poly1.push_back(poly2);
+    TwoPolynomial poly(poly1);
+    operator_m.setPolynomial(poly, 0, 0);
+    for (std::size_t i = 0; i < power_m; i++) {
+        applyOperator();
+        truncate(highestXorder);
+        sortTerms();
+    }
+}
+
+RecursionRelationTwo::RecursionRelationTwo(
+                      const RecursionRelationTwo &doperator):
+    power_m(doperator.power_m) {
+    operator_m = DifferentialOperatorTwo(doperator.operator_m);
+}
+
+RecursionRelationTwo::~RecursionRelationTwo() {
+}
+
+RecursionRelationTwo& RecursionRelationTwo::operator= (
+                      const RecursionRelationTwo &recursion) {
+    operator_m = recursion.operator_m;
+    power_m = recursion.power_m;
+    highestXorder_m = recursion.highestXorder_m;
+    return *this;
+}
+
+/*  This function increases n by 1 in the differential operator used to find \n
+ *  the magnetic scalar potential
+ */
+void RecursionRelationTwo::applyOperator() {
+/*  Differential operator has 3 terms, make three copies of current operator */
+    DifferentialOperatorTwo firstTerm(operator_m);
+    DifferentialOperatorTwo secondTerm(operator_m);
+    DifferentialOperatorTwo thirdTerm(operator_m);
+/*  Initialise polynomials
+ *  p(x) = s - xs^2 + x^2s^3 - ...
+ *  q(x) = 1 - x + x^2 - ...
+ */
+    std::vector<std::vector<int>> p, q;
+    p.resize(highestXorder_m + 1);
+    q.resize(highestXorder_m + 1);
+    for (std::size_t i = 0; i <= highestXorder_m; i++) {
+        p[i].resize(highestXorder_m + 2, 0);
+        q[i].resize(highestXorder_m + 1, 0);
+        p[i][i + 1] = pow(-1, i);
+        q[i][i] = pow(-1, i);
+    }
+/*  Differentiate first term by x, then multiply by p(x) */
+    firstTerm.differentiateX();
+    firstTerm.multiplyPolynomial(TwoPolynomial(p));
+/*  Differentiate second term twice by x */
+    secondTerm.differentiateX();
+    secondTerm.differentiateX();
+/*  Differentiate third term by s, multiply by q(x)
+ *  and then differentiate by s again
+ */
+    thirdTerm.differentiateS();
+    thirdTerm.multiplyPolynomial(TwoPolynomial(q));
+    thirdTerm.differentiateS();
+    thirdTerm.multiplyPolynomial(TwoPolynomial(q));
+/*  Add operators to obtain final operator */
+    operator_m = DifferentialOperatorTwo(firstTerm);
+    operator_m.addOperator(secondTerm);
+    operator_m.addOperator(thirdTerm);
+}
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.h b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.h
new file mode 100644
index 0000000000000000000000000000000000000000..5d3cd9cae5c9ec52a08d45c444b3338cc6a926f8
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.h
@@ -0,0 +1,233 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef RECURSION_RELATION_TWO_H
+#define RECURSION_RELATION_TWO_H
+
+/** ---------------------------------------------------------------------
+  *
+  * RecursionRelationTwo describes the differential operator used to find \n
+  * the coefficients in the expansion of the magnetic scalar potential. It \n
+  * contains member functions for extracting all information required to \n
+  * reconstruct the differential operator and evaluate its terms.\n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  * The operator of interest is
+  *  \f{eqnarray*} {
+  *  \Big(\frac{1}{\rho(s)(1 &+& x/\rho(s))}\frac{\partial}{\partial x} +
+  *  \frac{\partial^2}{\partial x^2} &+& \frac{1}{1 +
+  *  x/\rho(s)}\frac{\partial}{\partial s}\Big(\frac{1}{1 +
+  *  x/\rho(s)}\frac{\partial}{\partial s}\Big)\Big)^n
+  *  \f}
+  *  and it can be initialised to any power of x and up to any n.
+  */
+
+#include <vector>
+#include "DifferentialOperatorTwo.h"
+#include "PolynomialSum.h"
+#include "TwoPolynomial.h"
+
+namespace polynomial {
+
+class RecursionRelationTwo {
+public:
+    /** Default constructor, initialises identity operator */
+    RecursionRelationTwo();
+    /** Constructor, initialises the operator
+     *  \f{eqnarray*} {
+     *  \Big(\frac{1}{\rho(s)(1 + x/\rho(s))}\frac{\partial}{\partial x} +
+     *  \frac{\partial^2}{\partial x^2} + \frac{1}{1 + x/\rho(s)}
+     *  \frac{\partial}{\partial s}\Big(\frac{1}{1 + x/\rho(s)}
+     *  \frac{\partial}{\partial s}\Big)\Big)^n
+     *  \f}
+     *  where power = n. The denominators are expanded in x and highestXorder \n
+     *  is the highest power of x \n
+     *  \param power -> Value of n of the coefficient fn
+     *  \param highestXorder -> Highest power of x
+     */
+    RecursionRelationTwo(const std::size_t &power,
+                         const std::size_t &highestXorder);
+    /** Copy constructor */
+    RecursionRelationTwo(const RecursionRelationTwo &doperator);
+    /** Desctructor, does nothing */
+    ~RecursionRelationTwo();
+    /** Assigment operator */
+    RecursionRelationTwo& operator= (const RecursionRelationTwo &recursion);
+    /** Print operator, used for internal debugging */
+    void printOperator() const;
+    /** Truncate series in x at highestXorder
+     *  \param highestXorder -> Highest power of x after truncation
+     */
+    void truncate(std::size_t highestXorder);
+    /** Applies another differential operator to the existing operator */
+    void applyOperator();
+    /** Apply a differential operator in x */
+    void differentiateX();
+    /** Apply a differential operator in s */
+    void differentiateS();
+    /** Returns highest derivative of x */
+    std::size_t getMaxXDerivatives() const;
+    /** Returns highest derivative of s */
+    std::size_t getMaxSDerivatives() const;
+    /** Evaluates polynomial term p with xDerivative x-derivatives \n
+     *  and sDerivative s-derivatives
+     *  If xDerivative, sDerivative or term are out or range
+     *  then zero is returned \n
+     *  \param x -> Point x where polynomial is evaluated
+     *  \param s -> Point s where polynomial is evaluated
+     *  \param xDerivative -> Number of x-derivatives
+     *  \param sDerivative -> Number of s-derivatives
+     *  \param term -> Term index
+     */
+    double evaluatePolynomial(const double &x,
+                              const double &s,
+                              const std::size_t &xDerivative,
+                              const std::size_t &sDerivative,
+                              const std::vector<double> &dSvalues) const;
+    /** Check if polynomial term p with x x-derivatives
+     *  and s s-derivatives is zero \n
+     *  If x, s or term are negative true is returned
+     *  \param x -> Number of x-derivatives
+     *  \param s -> Number of s-derivatives
+     *  \param term -> Term index
+     */
+    bool isPolynomialZero(const std::size_t &x,
+                          const std::size_t &s,
+                          const std::size_t &term) const;
+    /** Change number of x-derivatives to xDerivatives
+     *  \param xDerivatives -> Number of x-derivatives after resize
+     */
+    void resizeX(const std::size_t &xDerivatives);
+    /** Change number of s-derivatives to sDerivatives
+     *  \param sDerivatives -> Number of s-derivatives after resize
+     */
+    void resizeS(const std::size_t &sDerivatives);
+    /** Returns number of terms in the sum with xDerivative x-derivatives
+     *  and sDerivative s-derivatives \n
+     *  If xDerivative or sDerivative is negative zero is returned
+     *  \param x -> Number of x-derivatives
+     *  \param s -> Number of s-derivatives
+     */
+    std::size_t numberOfTerms(const std::size_t &xDerivative,
+                              const std::size_t &sDerivative) const;
+    /** Returns list of S(s)-derivatives in term p with xDerivative
+     *  x-derivatives and sDerivative s-derivatives \n
+     *  If xDerivative or sDerivative are out of range
+     *  an empty list is returned \n
+     *  \param x -> Number of x-derivatives
+     *  \param s -> Number of s-derivatives
+     *  \param term -> Term index
+     */
+    std::vector<std::size_t> getdSfactors(const std::size_t &xDerivative,
+                                  const std::size_t &sDerivative,
+                                  const std::size_t &p) const;
+    /** Sort the terms in each sum, with fewest S(s)-derivatives first,
+     *  then in increasing powers
+     */
+    void sortTerms();
+private:
+    DifferentialOperatorTwo operator_m;
+    std::size_t power_m;
+    std::size_t highestXorder_m;
+};
+
+/*inline
+    void RecursionRelationTwo::printOperator() const {
+        operator_m.printOperator();
+}*/
+inline
+    void RecursionRelationTwo::truncate(std::size_t highestXorder) {
+        highestXorder_m = highestXorder;
+        operator_m.truncate(highestXorder);
+}
+inline
+    void RecursionRelationTwo::differentiateX() {
+        operator_m.differentiateX();
+}
+inline
+    void RecursionRelationTwo::differentiateS() {
+        operator_m.differentiateS();
+}
+inline
+    std::size_t RecursionRelationTwo::getMaxXDerivatives() const {
+        return operator_m.getXDerivatives();
+}
+inline
+    std::size_t RecursionRelationTwo::getMaxSDerivatives() const {
+        return operator_m.getSDerivatives();
+}
+inline
+    double RecursionRelationTwo::evaluatePolynomial(
+                                 const double &x,
+                                 const double &s,
+                                 const std::size_t &xDerivative,
+                                 const std::size_t &sDerivative,
+                                 const std::vector<double> &dSvalues) const {
+        return operator_m.evaluatePolynomial(x, s,
+                                             xDerivative, sDerivative,
+                                             dSvalues);
+}
+inline
+    bool RecursionRelationTwo::isPolynomialZero(const std::size_t &x,
+                                                const std::size_t &s,
+                                                const std::size_t &term) const {
+        return operator_m.isPolynomialZero(x, s, term);
+}
+inline
+    void RecursionRelationTwo::resizeX(const std::size_t &xDerivatives) {
+        operator_m.resizeX(xDerivatives);
+}
+inline
+    void RecursionRelationTwo::resizeS(const std::size_t &sDerivatives) {
+        operator_m.resizeS(sDerivatives);
+}
+inline
+    std::size_t RecursionRelationTwo::numberOfTerms(
+                                      const std::size_t &xDerivative,
+                                      const std::size_t &sDerivative) const {
+        return operator_m.numberOfTerms(xDerivative, sDerivative);
+}
+inline
+    std::vector<std::size_t> RecursionRelationTwo::getdSfactors(
+                                           const std::size_t &xDerivative,
+                                           const std::size_t &sDerivative,
+                                           const std::size_t &p) const {
+        return operator_m.getdSFactors(xDerivative, sDerivative, p);
+}
+inline
+    void RecursionRelationTwo::sortTerms() {
+        operator_m.sortTerms();
+}
+
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/TwoPolynomial.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/TwoPolynomial.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d3e8aeeedd0c370c5e0d2f26e7864cd0fac4499b
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/TwoPolynomial.cpp
@@ -0,0 +1,404 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <cmath>
+#include <iostream>
+#include <stdexcept>
+#include "gsl/gsl_fft_complex.h"
+#include "TwoPolynomial.h"
+
+namespace polynomial {
+
+TwoPolynomial::TwoPolynomial(): maxXorder_m(0), maxSorder_m(0) {
+    std::vector<int> temp;
+    temp.push_back(0);
+    coefficients_m.push_back(temp);
+}
+
+TwoPolynomial::TwoPolynomial(const std::vector<std::vector<int>> &coefficients):
+    maxXorder_m(coefficients.size() - 1),
+    coefficients_m(coefficients) {
+    if (coefficients.size() == 0) {
+        maxXorder_m = 0;
+        maxSorder_m = 0;
+        std::vector<int> temp;
+        temp.push_back(0);
+        coefficients_m.push_back(temp);
+        return;
+    }
+    maxSorder_m = coefficients[0].size() - 1;
+    std::size_t len = maxSorder_m + 1;
+    for (std::size_t i = 0; i < coefficients.size(); i++) {
+        if (coefficients[i].size() != len) {
+            throw std::length_error("2D vector not rectangular");
+        }
+        if (coefficients[i].size() == 0) {
+            maxXorder_m = 0;
+            maxSorder_m = 0;
+            std::vector<int> temp;
+            temp.push_back(0);
+            coefficients_m.resize(0);
+            coefficients_m.push_back(temp);
+            return;
+        }
+    }
+}
+
+TwoPolynomial::TwoPolynomial(const TwoPolynomial &poly):
+    maxXorder_m(poly.maxXorder_m), maxSorder_m(poly.maxSorder_m),
+    coefficients_m(poly.coefficients_m), dSfactors_m(poly.dSfactors_m) {
+}
+
+TwoPolynomial::~TwoPolynomial() {
+}
+
+TwoPolynomial& TwoPolynomial::operator= (const TwoPolynomial &poly) {
+    maxXorder_m = poly.maxXorder_m;
+    maxSorder_m = poly.maxSorder_m;
+    coefficients_m = poly.coefficients_m;
+    dSfactors_m = poly.dSfactors_m;
+    return *this;
+}
+
+void TwoPolynomial::differentiateX() {
+    if (maxXorder_m == 0 && maxSorder_m == 0 && coefficients_m[0][0] == 0) {
+        return;
+    }
+    if (maxXorder_m == 0) {
+        std::vector<std::vector<int>> temp {{0}};
+        coefficients_m = temp;
+        maxSorder_m = 0;
+        return;
+    }
+    for (std::size_t i = 0; i < maxXorder_m; i++) {
+        for (std::size_t j = 0; j <= maxSorder_m; j++) {
+            coefficients_m[i][j] = coefficients_m[i + 1][j] * (i + 1);
+        }
+    }
+    coefficients_m.pop_back();
+    maxXorder_m--;
+}
+
+void TwoPolynomial::differentiateS() {
+    if (maxXorder_m == 0 && maxSorder_m == 0 && coefficients_m[0][0] == 0) {
+        return;
+    }
+    if (maxSorder_m == 0) {
+        std::vector<std::vector<int>> temp {{0}};
+        coefficients_m = temp;
+        maxXorder_m = 0;
+        return;
+    }
+    for (std::size_t i = 0; i <= maxXorder_m; i++) {
+        for (std::size_t j = 0; j < maxSorder_m; j++) {
+            coefficients_m[i][j] = coefficients_m[i][j + 1] * (j + 1);
+        }
+        coefficients_m[i].pop_back();
+    }
+    if (dSfactors_m.size() != 0) {
+        dSfactors_m[0] = dSfactors_m[0] + 1;
+    } else {
+        dSfactors_m.push_back(1);
+    }
+    maxSorder_m--;
+}
+
+void TwoPolynomial::multiplyConstant (const int &constant) {
+    if (maxXorder_m == 0 && maxSorder_m == 0 && coefficients_m[0][0] == 0) {
+        return;
+    }
+    if (constant == 0) {
+        setZero();
+        return;
+    }
+    for (std::size_t i = 0; i <= maxXorder_m; i++) {
+        for (std::size_t j = 0; j <= maxSorder_m; j++) {
+            coefficients_m[i][j] = constant * coefficients_m[i][j];
+        }
+    }
+}
+
+void TwoPolynomial::multiplydSfactors(const TwoPolynomial &poly) {
+    if (poly.dSfactors_m.size() > dSfactors_m.size()) {
+        dSfactors_m.resize(poly.dSfactors_m.size(), 0);
+    }
+    for (std::size_t k = 0; k < poly.dSfactors_m.size(); k++) {
+        dSfactors_m[k] = dSfactors_m[k] + poly.dSfactors_m[k];
+    }
+}
+
+void TwoPolynomial::convert2Dto1Darray(double *vec1, double *vec2,
+                                       const vectorLengths &nn,
+                                       const TwoPolynomial &poly) const {
+    for (std::size_t i = 0; i < nn.nx; i++) {
+        for (std::size_t j = 0; j < nn.ny; j++) {
+            if (i < nn.nx1 && j < nn.ny1) {
+                vec1[2 * (nn.ny * i + j)] = coefficients_m[i][j];
+                vec1[2 * (nn.ny * i + j) + 1] = 0;
+            } else {
+                vec1[2 * (nn.ny * i + j)] = 0;
+                vec1[2 * (nn.ny * i + j) + 1] = 0;
+            }
+            if (i < nn.nx2 && j < nn.ny2) {
+                vec2[2 * (nn.ny * i + j)] = poly.coefficients_m[i][j];
+                vec2[2 * (nn.ny * i + j) + 1] = 0;
+            } else {
+                vec2[2 * (nn.ny * i + j)] = 0;
+                vec2[2 * (nn.ny * i + j) + 1] = 0;
+            }
+        }
+    }
+}
+
+void TwoPolynomial::convolution(double *vec1,
+                                double *vec2,
+                                const std::size_t &nx,
+                                const std::size_t &ny) const {
+    /** Do 2D FFT, 1D FFT on each row and column */
+    for (std::size_t i = 0; i < nx; i++) {
+        double *p1 = &vec1[0] + 2 * i * ny;
+        gsl_fft_complex_radix2_forward(p1, 1, ny);
+        double *p2 = &vec2[0] + 2 * i * ny;
+        gsl_fft_complex_radix2_forward(p2, 1, ny);
+    }
+    for (std::size_t j = 0; j < ny; j++) {
+        double *p1 = &vec1[0] + 2 * j;
+        gsl_fft_complex_radix2_forward(p1, ny, nx);
+        double *p2 = &vec2[0] + 2 * j;
+        gsl_fft_complex_radix2_forward(p2, ny, nx);
+    }
+    /** Convolution, multiply each element (complex numbers) */
+    for (std::size_t i = 0; i < nx; i++) {
+        for (std::size_t j = 0; j < ny; j++) {
+            double real1 = vec1[2 * (ny * i + j)];
+            double real2 = vec2[2 * (ny * i + j)];
+            double imag1 = vec1[2 * (ny * i + j) + 1];
+            double imag2 = vec2[2 * (ny * i + j) + 1];
+            vec1[2 * (ny * i + j)] = real1 * real2 - imag1 * imag2;
+            vec1[2 * (ny * i + j) + 1] = real1 * imag2 + imag1 * real2;
+        }
+    }
+    /** Inverse 2D FFT */
+    for (std::size_t i = 0; i < nx; i++) {
+        double *p1 = &vec1[0] + 2 * i * ny;
+        gsl_fft_complex_radix2_inverse(p1, 1, ny);
+    }
+    for (std::size_t j = 0; j < ny; j++) {
+        double *p1 = &vec1[0] + 2 * j;
+        gsl_fft_complex_radix2_inverse(p1, ny, nx);
+    }
+}
+    
+void TwoPolynomial::convert1Dto2Darray(double *vec1,
+                                       const vectorLengths &nn) {
+    coefficients_m.resize(nn.nx1 + nn.nx2 - 1);
+    for (std::size_t i = 0; i < (nn.nx1 + nn.nx2 - 1); i++) {
+        coefficients_m[i].resize(nn.ny1 + nn.ny2 - 1);
+        for (std::size_t j = 0; j < (nn.ny1 + nn.ny2 - 1); j++) {
+            coefficients_m[i][j] = std::round(vec1[2 * (nn.ny * i + j)]);
+        }
+    }
+}
+
+void TwoPolynomial::multiplyPolynomial(const TwoPolynomial &poly) {
+    if (maxXorder_m == 0 && maxSorder_m == 0 && coefficients_m[0][0] == 0) {
+        return;
+    }
+    if (poly.maxXorder_m == 0 &&
+        poly.maxSorder_m == 0 &&
+        poly.coefficients_m[0][0] == 0) {
+        setZero();
+        return;
+    }
+    /** Multiply the S(s)-derivatives */
+    multiplydSfactors(poly);
+    /** Find vector sizes and round up to nearest power of 2 */
+    vectorLengths nn;
+    nn.nx = 1;
+    nn.ny = 1;
+    nn.nx1 = coefficients_m.size();
+    nn.nx2 = poly.coefficients_m.size();
+    nn.ny1 = coefficients_m[0].size();
+    nn.ny2 = poly.coefficients_m[0].size();
+    while ((nn.nx1 + nn.nx2 - 1) > nn.nx) {
+        nn.nx *= 2;
+    }
+    while ((nn.ny1 + nn.ny2 - 1) > nn.ny) {
+        nn.ny *= 2;
+    }
+    /** Allocate memory for 1D array */
+    double *vec1 = new double[2 * nn.nx * nn.ny];
+    double *vec2 = new double[2 * nn.nx * nn.ny];
+    /** Convert 2D vectors into 1D vectors with indexing, change to double */
+    convert2Dto1Darray(vec1, vec2, nn, poly);
+    /** Do convolution */
+    convolution(vec1, vec2, nn.nx, nn.ny);
+    /** Convert back to 2D vector and change to int by rounding */
+    convert1Dto2Darray(vec1, nn);
+    delete[] vec1;
+    delete[] vec2;
+    maxXorder_m = coefficients_m.size() - 1;
+    maxSorder_m = coefficients_m[0].size() - 1;
+}
+
+void TwoPolynomial::printPolynomial() const {
+    if (maxXorder_m == 0 && maxSorder_m == 0 && coefficients_m[0][0] == 0) {
+        return;
+    }
+    std::cout << "(";
+    for (std::size_t i = 0; i <= maxXorder_m; i++) {
+        for (std::size_t j = 0; j <= maxSorder_m; j++) {
+            if (coefficients_m[i][j] > 0) {
+                std::cout << " + " << coefficients_m[i][j];
+                std::cout << "x^" << i << "s^" << j;
+            } else if (coefficients_m[i][j] < 0) {
+                std::cout << " - " << -coefficients_m[i][j];
+                std::cout << "x^" << i << "s^" << j;
+            }
+        }
+    }
+    std::cout << ")";
+    for (std::size_t i = 0; i < dSfactors_m.size(); i++) {
+        std::cout << "(d^" << i + 1 << "S/ds)^" << dSfactors_m[i];
+    }
+}
+
+int TwoPolynomial::getCoefficient(const std::size_t &Xorder,
+                                  const std::size_t &Sorder) const {
+    if (Xorder > maxXorder_m || Sorder > maxSorder_m) {
+        return 0;
+    }
+    return coefficients_m[Xorder][Sorder];
+}
+
+void TwoPolynomial::setCoefficient(const int &coefficient,
+                                   const std::size_t &Xorder,
+                                   const std::size_t &Sorder) {
+    if (Xorder > maxXorder_m) {
+        coefficients_m.resize(Xorder + 1);
+        for (std::size_t i = (maxXorder_m + 1); i <= Xorder; i++) {
+            coefficients_m[i].resize(maxSorder_m + 1, 0);
+        }
+        maxXorder_m = coefficients_m.size() - 1;
+    }
+    if (Sorder > maxSorder_m) {
+        for (std::size_t i = 0; i < maxXorder_m; i++) {
+            coefficients_m[i].resize(Sorder + 1, 0);
+        }
+        maxSorder_m = coefficients_m[0].size() - 1;
+    }
+    coefficients_m[Xorder][Sorder] = coefficient;
+}
+    
+void TwoPolynomial::setMaxXorder(const std::size_t &maxXorder) {
+    coefficients_m.resize(maxXorder + 1);
+    for (std::size_t i = (maxXorder_m + 1); i <= maxXorder; i++) {
+        coefficients_m[i].resize(maxSorder_m + 1);
+    }
+    maxXorder_m = maxXorder;
+}
+
+void TwoPolynomial::setMaxSorder(const std::size_t &maxSorder) {
+    for (std::size_t i = 0; i <= maxXorder_m; i++) {
+        coefficients_m[i].resize(maxSorder + 1);
+    }
+    maxSorder_m = maxSorder;
+}
+
+void TwoPolynomial::setZero() {
+    maxXorder_m = 0;
+    maxSorder_m = 0;
+    coefficients_m.resize(1);
+    coefficients_m[0].resize(1);
+    coefficients_m[0][0] = 0;
+}
+
+double TwoPolynomial::evaluatePolynomial(const double &x,
+                                         const double &s) const {
+    if (maxXorder_m == 0 && maxSorder_m == 0 &&
+        coefficients_m[0][0] == 0) {
+        return 0;
+    }
+    double result = 0.0;
+    std::size_t i = maxXorder_m + 1;
+    while (i != 0) {
+        i--;
+        std::size_t j = maxSorder_m + 1;
+        double temp = 0.0;
+        while (j != 0) {
+            j--;
+            temp = temp * s + coefficients_m[i][j];
+        }
+        result = result * x + temp;
+    }
+    return result;
+}
+
+void TwoPolynomial::addPolynomial(const TwoPolynomial &poly) {
+    if (maxXorder_m < poly.maxXorder_m) {
+        setMaxXorder(poly.maxXorder_m);
+    }
+    if (maxSorder_m < poly.maxSorder_m) {
+        setMaxSorder(poly.maxSorder_m);
+    }
+    for (std::size_t i = 0; i <= poly.maxXorder_m; i++) {
+        for (std::size_t j = 0; j <= poly.maxSorder_m; j++) {
+            coefficients_m[i][j] += poly.coefficients_m[i][j];
+        }
+    }
+}
+
+bool operator < (const TwoPolynomial &left, const TwoPolynomial &right) {
+    std::size_t nleft = left.dSfactors_m.size();
+    std::size_t nright = right.dSfactors_m.size();
+    if (nleft < nright) {
+        return true;
+    } else if (nright < nleft) {
+        return false;
+    } else {
+        for (std::size_t n = 0; n < nleft; n++) {
+            std::size_t elemleft = left.dSfactors_m[n];
+            std::size_t elemright = right.dSfactors_m[n];
+            if (elemleft < elemright) {
+                return true;
+            } else if (elemright < elemleft) {
+                return false;
+            }
+        }
+        return false;
+    }
+}
+
+bool operator == (const TwoPolynomial &left, const TwoPolynomial &right) {
+    if (left < right || right < left) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/TwoPolynomial.h b/src/Classic/AbsBeamline/MultipoleTFunctions/TwoPolynomial.h
new file mode 100644
index 0000000000000000000000000000000000000000..e3b121e18623d563b518dd9a1893914c94449094
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/TwoPolynomial.h
@@ -0,0 +1,250 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef TWO_POLYNOMIAL_H
+#define TWO_POLYNOMIAL_H
+
+/** ---------------------------------------------------------------------
+  *
+  * TwoPolynomial describes a polynomial of two variables. In addition, \n
+  * TwoPolynomial also stores a list of derivatives of the fringe field S(s), \n
+  * because the second variable is S(s). Every s-derivative will therefore \n
+  * give various terms of S(s)-derivatives from the chain rule. \n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  *  The polynomial @f[p(x) = a_{00} + a_{10}x + a_{11}xS(s) + ...
+  *  + a_{nm}x^nS(s)^m $f] is \n stored as a two-dimensional list \n
+  *  (a_{00}, a_{10}, ..., a_{n0}; a_{01}, ...). \n
+  *  In addition a list @f[b_k] of powers of S(s)-derivatives is stored,
+  *  representing @f[\prod_k\frac{dS(s)}{ds}^{b_k}]
+  *  A TwoPolynomial object therefore represents a product of a polynomial \n
+  *  in x and S(s), multiplied by derivatives of S(s), and it is the powers \n
+  *  that are stored in this list
+  *  BUG: For large n the integer type might overflow. If you need this \n
+  *  many terms change all int to long int. \n
+  */
+
+#include <vector>
+
+namespace polynomial {
+
+/** Struct for storing vector lengths used in multiplyPolynomial()
+ *  \param nx -> Size of first dimension of resulting array
+ *  \param ny -> Size of second dimension of resulting array
+ *  \param nx1 -> Size of first dimension of first array
+ *  \param ny1 -> Size of second dimension of first array
+ *  \param nx2 -> Size of first dimension of second array
+ *  \param ny2 -> Size of second dimension of second array
+ */
+struct vectorLengths {
+    std::size_t nx;
+    std::size_t ny;
+    std::size_t nx1;
+    std::size_t ny1;
+    std::size_t nx2;
+    std::size_t ny2;
+};
+
+class TwoPolynomial {
+public:
+    /** Default constructor, initialises zero polynomial */
+    TwoPolynomial();
+    /** Constructor, initialises polynomial with the input coefficients \n
+     *  If either list dimensions are empty, a zero polynomial is initialised
+     *  \param coefficients -> 2D list of polynomial coefficients, with
+     *  x powers as first argument and s powers as second argument, starting
+     *  with a constant term
+     */
+    TwoPolynomial(const std::vector<std::vector<int>> &coefficients);
+    /** Copy constructor */
+    TwoPolynomial(const TwoPolynomial &poly);
+    /** Assigment operator */
+    TwoPolynomial& operator= (const TwoPolynomial &poly);
+    /** Destructor, does nothing */
+    ~TwoPolynomial();
+    /** Differentiate polynomial wrt x */
+    void differentiateX();
+    /** Differentiate polynomial wrt s */
+    void differentiateS();
+    /** Multiply polynomial with input constant
+     *  \param constant -> Constant to be multiplied with polynomial
+     */
+    void multiplyConstant (const int &constant);
+    /** Multiply S(s)-derivatives part with input polynomial
+     *  \param poly -> Polynomial to be multiplied with current polynomial
+     */
+    void multiplydSfactors(const TwoPolynomial &poly);
+    /** Convert two 2D vector<int> array into 1D double C-array by indexing
+     *  \param vec1 -> Pointer to the first 1D array
+     *  \param vec2 -> Pointer to the second 1D array
+     *  \param nn -> Vector sizes
+     *  \param poly -> Polynomial to be multiplied with current polynomial
+     */
+    void convert2Dto1Darray(double *vec1, double *vec2,
+                            const vectorLengths &nn,
+                            const TwoPolynomial &poly) const;
+    /** Perform a 2D convolution on two 1D indexed arrays using FFT \n
+     *  First a 2D FFT is done by doing 1D FFT on each row and and column \n
+     *  Convolution in Fourier space is a simple product \n
+     *  Then the inverse 2D FFT is done on first list
+     *  \param vec1 -> First array, also resulting array
+     *  \param vec2 -> Second array
+     *  \param nx -> Size of first dimension of resulting array
+     *  \param ny -> Size of second dimension of resulting array
+     */
+    void convolution(double *vec1, double *vec2,
+                     const std::size_t &nx, const std::size_t &ny) const;
+    /** Convert 1D double indexed C-array to 2D vector<int> array by rounding
+     *  \param vec1 -> Pointer to resulting 1D array
+     *  \param nn -> Vector sizes
+     */
+    void convert1Dto2Darray(double *vec1, const vectorLengths &nn);
+    /** Multiply polynomial with input polynomial using FFT and convolution
+     *  \param poly -> Polynomial to be multiplied with current polynomial
+     */
+    void multiplyPolynomial(const TwoPolynomial &poly);
+    /** Print polynomial, for internal debugging */
+    void printPolynomial() const;
+    /** Returns coefficient with power Xorder in x and Sorder in s
+     *  \param Xorder -> Power of x
+     *  \param Sorder -> Power of s
+     */
+    int getCoefficient(const std::size_t &Xorder,
+                       const std::size_t &Sorder) const;
+    /** Set coefficient of term with Xorder powers of x and Sorder powers of s
+     *  \param coefficient -> Value assigned to polynomial coefficient
+     *  \param Xorder -> Power of x
+     *  \param Sorder -> Power of s
+     */
+    void setCoefficient(const int &coefficient,
+                        const std::size_t &Xorder,
+                        const std::size_t &Sorder);
+    /** Returns highest power of x */
+    std::size_t getMaxXorder() const;
+    /** Returns highest power of s */
+    std::size_t getMaxSorder() const;
+    /** Set highest power of x
+     *  \param maxXorder -> Highest power of x
+     */
+    void setMaxXorder(const std::size_t &maxXorder);
+    /** Set highest power of s
+     *  \param maxSorder -> Highest power of s
+     */
+    void setMaxSorder(const std::size_t &maxSorder);
+    /** Return highest derivative of S(s) */
+    std::size_t getNdSfactors() const;
+    /** Returns the list of powers of S(s)-derivatives */
+    std::vector<std::size_t> getdSfactors() const;
+    /** Set the list of S(s)-derivatives
+     *  \param dSfactors -> List of S(s)-derivatives
+     */
+    void setdSfactors(const std::vector<std::size_t> &dSfactors);
+    /** Set polynomial to zero */
+    void setZero();
+    /** Check if polynomial is zero */
+    bool isZero() const;
+    /** Evaluate polynomial at point (x, s) 
+     *  \param x -> x-coordinate where polynomial is evaluated
+     *  \param s -> s-coordinate where polynomial is evaluated
+     */
+    double evaluatePolynomial(const double &x, const double &s) const;
+    /** Truncate polynomial in x at truncateOrder 
+     *  \param truncateOrder -> Highest power of x after truncation
+     */
+    void truncate(const std::size_t &truncateOrder);
+    /** Add two polynomials \n
+     *  The S(s)-derivatives are untouched, the user must ensure 
+     *  that these are identical!
+     *  \param poly -> TwoPolynomial added to this polynomial
+     */
+    void addPolynomial(const TwoPolynomial &poly);
+    /** Operator < definition
+     *  First the TwoPolynomial with the longest dSfactors_m list is greatest \n
+     *  If dSfactors_m have the same length, each list element is compared
+     *  individually, starting with the first element (lowest derivative)
+     *  \param poly -> The TwoPolynomial to be compared with
+     */
+    friend bool operator < (const TwoPolynomial &left,
+                            const TwoPolynomial &right);
+    /** Operator == definition \n
+     *  Returns true if dSfactors_m are identical \n
+     *  Only uses the < operator
+     *  \param poly -> The TwoPolynomial to be compared with
+     */
+    friend bool operator == (const TwoPolynomial &left,
+                             const TwoPolynomial &right);
+    /** PolynomialSum is a sum of TwoPolynomials, useful to have as friend */
+    friend class PolynomialSum;
+private:
+    std::size_t maxXorder_m;
+    std::size_t maxSorder_m;
+    std::vector<std::vector<int>> coefficients_m;
+    std::vector<std::size_t> dSfactors_m;
+};
+
+inline
+    std::size_t TwoPolynomial::getMaxXorder() const {
+        return maxXorder_m;
+}
+inline
+    std::size_t TwoPolynomial::getMaxSorder() const {
+        return maxSorder_m;
+}
+inline
+    std::size_t TwoPolynomial::getNdSfactors() const {
+        return dSfactors_m.size();
+}
+inline
+    std::vector<std::size_t> TwoPolynomial::getdSfactors() const {
+        return dSfactors_m;
+}
+inline
+    void TwoPolynomial::setdSfactors(
+                        const std::vector<std::size_t> &dSfactors) {
+        dSfactors_m = dSfactors;
+}
+inline
+    bool TwoPolynomial::isZero() const {
+        return (maxXorder_m == 0 && maxSorder_m == 0 &&
+                coefficients_m[0][0] == 0);
+}
+inline
+    void TwoPolynomial::truncate(const std::size_t &truncateOrder) {
+        if (truncateOrder < maxXorder_m) {
+            coefficients_m.resize(truncateOrder + 1);
+            maxXorder_m = truncateOrder;
+        }
+}
+
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/tanhDeriv.cpp b/src/Classic/AbsBeamline/MultipoleTFunctions/tanhDeriv.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6463c3d3b96b73d0204c895882e922fac762dc4c
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/tanhDeriv.cpp
@@ -0,0 +1,95 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <gsl/gsl_integration.h>
+#include <gsl/gsl_complex.h>
+#include <gsl/gsl_complex_math.h>
+#include <gsl/gsl_sf_pow_int.h>
+#include <gsl/gsl_math.h>
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_sf_gamma.h>
+#include "tanhDeriv.h"
+
+namespace tanhderiv {
+
+struct my_f_params {
+    double a;
+    double s0;
+    double lambdaleft;
+    double lambdaright;
+    double r;
+    int n;
+};
+
+double my_f (double x, void *p) {
+    struct my_f_params *params = (struct my_f_params *)p;
+    gsl_complex z = gsl_complex_add(gsl_complex_rect(params->a, 0),
+                                    gsl_complex_polar(params->r, x));
+    gsl_complex z1 = gsl_complex_div(gsl_complex_add(z,
+                                     gsl_complex_rect(params->s0, 0)),
+                                     gsl_complex_rect(params->lambdaleft, 0));
+    gsl_complex z2 = gsl_complex_div(gsl_complex_sub(z,
+                                     gsl_complex_rect(params->s0, 0)),
+                                     gsl_complex_rect(params->lambdaright, 0));
+    gsl_complex func = gsl_complex_div(gsl_complex_sub(gsl_complex_tanh(z1),
+                                       gsl_complex_tanh(z2)),
+                                       gsl_complex_rect(2, 0));
+    func = gsl_complex_mul(func, gsl_complex_polar(1, -params->n * x));
+    return gsl_sf_fact(params->n) * GSL_REAL(func)
+           / (2 * M_PI * gsl_sf_pow_int(params->r, params->n));
+}
+
+double integrate(const double &a,
+                 const double &s0,
+                 const double &lambdaleft,
+                 const double &lambdaright,
+                 const int &n) {
+    gsl_function F;
+    double radius = gsl_hypot(a - 2, lambdaright * M_PI / 2) - 0.01;
+    double radius2 = gsl_hypot(a + 2, lambdaleft * M_PI / 2) - 0.01;
+    if (radius > radius2) radius = radius2;
+    struct my_f_params params = {a, s0, lambdaleft, lambdaright, radius, n};
+    F.function = &my_f;
+    F.params = &params;
+    gsl_integration_workspace *w = gsl_integration_workspace_alloc(100);
+    double error = gsl_sf_pow_int(10, -12);
+    double *result = new double;
+    double *abserr = new double;
+    gsl_set_error_handler_off();
+    int status = gsl_integration_qag(&F, 0, 2 * M_PI, 0, error,
+                                     100, 6, w, result, abserr);
+    gsl_integration_workspace_free(w);
+    double finalResult = *result;
+    delete result;
+    delete abserr;
+    if (status) {
+        return 0;
+    }
+    else return finalResult;
+}
+
+}
diff --git a/src/Classic/AbsBeamline/MultipoleTFunctions/tanhDeriv.h b/src/Classic/AbsBeamline/MultipoleTFunctions/tanhDeriv.h
new file mode 100644
index 0000000000000000000000000000000000000000..cccde2c8e2b10bfa96760e752d54d70c4c04c8fd
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTFunctions/tanhDeriv.h
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef TANH_INTEGRAL
+#define TANH_INTEGRAL
+
+/** ---------------------------------------------------------------------
+  *
+  * Integrate performs a contour integral to find the nth derivative of the \n
+  * Tanh model fringe function. It uses Cauchy's integral formula to do so. \n
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * Author: Martin Duy Tat\n
+  *
+  * ---------------------------------------------------------------------
+  */
+
+namespace tanhderiv {
+
+/** Integrand
+ *  \param x -> Independent parameter to be integrated
+ *  \param p -> Pointer to struct of parameters
+ */
+double my_f (double x, void *p);
+/** Perform Cauchy's integral to find derivative
+ *  \param a -> Point of differentiation
+ *  \param s0 -> Centre fringe length
+ *  \param lambdaleft -> Left fringe field length
+ *  \param lambdaright -> Left fringe field length
+ *  \param n -> nth derivative
+ */
+double integrate(const double &a,
+                 const double &s0,
+                 const double &lambdaleft,
+                 const double &lambdaright,
+                 const int &n);
+
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/MultipoleTStraight.cpp b/src/Classic/AbsBeamline/MultipoleTStraight.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..21769e69fc695252da0bc3a5d16381686d7137d0
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTStraight.cpp
@@ -0,0 +1,105 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 "gsl/gsl_sf_gamma.h"
+#include "gsl/gsl_sf_pow_int.h"
+#include "MultipoleTStraight.h"
+
+using namespace endfieldmodel;
+
+MultipoleTStraight::MultipoleTStraight(const std::string &name):
+    MultipoleTBase(name),   
+    straightGeometry_m(getLength()) {
+}
+
+MultipoleTStraight::MultipoleTStraight(const MultipoleTStraight &right):
+    MultipoleTBase(right),
+    straightGeometry_m(right.straightGeometry_m) {
+    RefPartBunch_m = right.RefPartBunch_m;
+}
+    
+
+MultipoleTStraight::~MultipoleTStraight() {
+} 
+
+ElementBase* MultipoleTStraight::clone() const {
+    return new MultipoleTStraight(*this);
+}
+
+void MultipoleTStraight::transformCoords(Vector_t &R) {
+    //R[2] += getBoundingBoxLength();
+}
+
+void MultipoleTStraight::setMaxOrder(const std::size_t &maxOrder) {
+    MultipoleTBase::setMaxOrder(maxOrder);
+}
+
+double MultipoleTStraight::getBx(const Vector_t &R) {
+    double Bx = 0.0;
+    for(std::size_t n = 0; n <= getMaxOrder(); n++) {
+        double f_n = 0.0;
+        for(std::size_t i = 0; i <= n; i++) {
+            f_n += gsl_sf_choose(n, i) * getTransDeriv(2 * i + 1, R[0]) *
+                   getFringeDeriv(2 * n - 2 * i, R[2]);
+        }
+        f_n *= gsl_sf_pow_int(-1.0, n);
+        Bx += f_n * gsl_sf_pow_int(R[1], 2 * n + 1) / gsl_sf_fact(2 * n + 1);
+    }
+    return Bx;
+}
+
+double MultipoleTStraight::getBs(const Vector_t &R) {
+    double Bs = 0.0;
+    for(std::size_t n = 0; n <= getMaxOrder(); n++) {
+        double f_n = 0.0;
+        for(std::size_t i = 0; i <= n; i++) {
+            f_n += gsl_sf_choose(n, i) * getTransDeriv(2 * i, R[0]) *
+                   getFringeDeriv(2 * n - 2 * i + 1, R[2]);
+        }
+        f_n *= gsl_sf_pow_int(-1.0, n);
+        Bs += f_n * gsl_sf_pow_int(R[1], 2 * n + 1) / gsl_sf_fact(2 * n + 1);
+    }
+    return Bs;
+}
+
+double MultipoleTStraight::getFn(const std::size_t &n,
+                                 const double &x, 
+                                 const double &s) {
+    if (n == 0) {
+        return getTransDeriv(0, x) * getFringeDeriv(0, s);
+    }
+    double f_n = 0.0;
+    for (std::size_t i = 0; i <= n; i++) {
+        f_n += gsl_sf_choose(n, i) * getTransDeriv(2 * i, x) *
+               getFringeDeriv(2 * n - 2 * i, s);
+    }
+    f_n *= gsl_sf_pow_int(-1.0, n);
+    return f_n;
+}
+ 
diff --git a/src/Classic/AbsBeamline/MultipoleTStraight.h b/src/Classic/AbsBeamline/MultipoleTStraight.h
new file mode 100644
index 0000000000000000000000000000000000000000..38157a9742f5a43e3354ff546cdb58781c64cda9
--- /dev/null
+++ b/src/Classic/AbsBeamline/MultipoleTStraight.h
@@ -0,0 +1,184 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+
+#ifndef CLASSIC_MULTIPOLET_STRAIGHT_H
+#define CLASSIC_MULTIPOLET_STRAIGHT_H
+
+/** ---------------------------------------------------------------------
+  *
+  * MultipoleTiStraight defines a straight combined function magnet (up 
+  * to arbitrary multipole component) with fringe fields
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * $Author: Titus Dascalu, Martin Duy Tat, Chris Rogers
+  *
+  * ---------------------------------------------------------------------
+  *
+  * The field is obtained from the scalar potential \n
+  *     @f[ V = f_0(x,s) z + f_1 (x,s) \frac{z^3}{3!} + f_2 (x,s) \frac{z^5}{5!}
+  *      + ...  @f] \n
+  *     (x,z,s) -> Frenet-Serret local coordinates along the magnet \n
+  *     z -> vertical component \n
+  *     assume mid-plane symmetry \n 
+  *     set field on mid-plane -> @f$ B_z = f_0(x,s) = T(x) \cdot S(s) @f$ \n
+  *     T(x) -> transverse profile; this is a polynomial describing
+  *             the field expansion on the mid-plane inside the magnet
+  *             (not in the fringe field);
+  *             1st term is the dipole strength, 2nd term is the 
+  *             quadrupole gradient * x, etc. \n
+  *          -> when setting the magnet, one gives the multipole
+  *             coefficients of this polynomial (i.e. dipole strength,  
+  *             quadrupole gradient, etc.) \n
+  * \n
+  * ------------- example ----------------------------------------------- \n
+  *     Setting a combined function magnet with dipole, quadrupole and 
+  *     sextupole components: \n
+  *     @f$ T(x) = B_0 + B_1 \cdot x + B_2 \cdot x^2 @f$\n
+  *     user gives @f$ B_0, B_1, B_2 @f$ \n
+  * ------------- example end ------------------------------------------- \n
+  * \n
+  *     S(s) -> fringe field \n
+  *     recursion -> @f$ f_n (x,s) = (-1)^n \cdot \sum_{i=0}^{n} C_n^i 
+  *     \cdot T^{(2i)} \cdot S^{(2n-2i)} @f$ \n
+  *     for curved magnets the above recursion is more complicated \n
+  *     @f$ C_n^i @f$ -> binomial coeff; 
+  *     @f$ T^{(n)} @f$ -> n-th derivative
+  *
+  * ---------------------------------------------------------------------
+  */
+
+#include "BeamlineGeometry/StraightGeometry.h"
+#include "AbsBeamline/MultipoleTBase.h"
+#include <vector>
+
+class MultipoleTStraight: public MultipoleTBase {
+public: 
+    /** Constructor
+     *  \param name -> User-defined name
+     */
+    explicit MultipoleTStraight(const std::string &name);
+    /** Copy constructor */
+    MultipoleTStraight(const MultipoleTStraight &right);
+    /** Destructor */ 
+    ~MultipoleTStraight();
+    /** Inheritable copy constructor */
+    virtual ElementBase* clone() const;
+    /** Accept a beamline visitor */
+    void accept(BeamlineVisitor &visitor) const;
+    /** Set the number of terms used in calculation of field components \n
+     *  Maximum power of z in Bz is 2 * maxOrder_m
+     *  \param maxOrder -> Number of terms in expansion in z
+     */
+    virtual void setMaxOrder(const std::size_t &maxOrder);
+    /** Return the cell geometry */
+    StraightGeometry& getGeometry();
+    /** Return the cell geometry */
+    const StraightGeometry& getGeometry() const;
+    /** Initialise the MultipoleT
+     *  \param bunch -> Bunch the global bunch object
+     *  \param startField -> Not used
+     *  \param endField -> Not used
+     */
+    virtual void initialise(PartBunchBase<double, 3>* bunch,
+                            double &startField,
+                            double &endField);
+private:
+    MultipoleTStraight operator=(const MultipoleTStraight &rhs);
+    /** Geometry */
+    StraightGeometry straightGeometry_m;
+    /** Transform to Frenet-Serret coordinates for sector magnets */
+    virtual void transformCoords(Vector_t &R);
+    /** Transform B-field from Frenet-Serret coordinates to lab coordinates */
+    virtual void transformBField(Vector_t &B, const Vector_t &R);
+    /** Radius of curvature \n
+     *  Straight magnet, infinite radius, infinity (1.0e300) is returned
+     *  \param s -> Coordinate s
+     */
+    virtual double getRadius(const double &s);
+    /** Returns the scale factor @f$ h_s = 1@f$
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getScaleFactor(const double &x, const double &s);
+    /** Get x-component of the B-field \n
+     *  This function has been overloaded because calculating \n
+     *  the B-field directly is quicker and more accurate
+     */
+    virtual double getBx (const Vector_t &R);
+    /** Get s-component of the B-field \n
+     *  This function has been overloaded because calculating \n
+     *  the B-field directly is quicker and more accurate
+     */
+    virtual double getBs (const Vector_t &R);
+    /** Calculate fn(x, s) by expanding the differential operator
+     *  (from Laplacian and scalar potential) in terms of polynomials
+     *  \param n -> nth derivative
+     *  \param x -> Coordinate x
+     *  \param s -> Coordinate s
+     */
+    virtual double getFn(const std::size_t &n,
+                         const double &x,
+                         const double &s);
+};
+
+inline
+    void MultipoleTStraight::accept(BeamlineVisitor &visitor) const {
+        visitor.visitMultipoleTStraight(*this);
+}
+inline
+    void MultipoleTStraight::transformBField(Vector_t &B, const Vector_t &R) {
+}
+inline
+    double MultipoleTStraight::getRadius(const double &s) {
+        return 1e300;
+}
+inline
+    double MultipoleTStraight::getScaleFactor(const double &x,
+                                              const double &s) {
+    return 1.0;
+}
+inline
+    StraightGeometry& MultipoleTStraight::getGeometry() {
+        return straightGeometry_m;
+}
+inline
+    const StraightGeometry& MultipoleTStraight::getGeometry() const {
+        return straightGeometry_m;
+}
+inline
+    void MultipoleTStraight::initialise(PartBunchBase<double, 3>* bunch,
+                            double &startField,
+                            double &endField) {
+        RefPartBunch_m = bunch;
+        straightGeometry_m.setElementLength(2 * getBoundingBoxLength());
+}
+
+#endif
diff --git a/src/Classic/AbsBeamline/SpecificElementVisitor.h b/src/Classic/AbsBeamline/SpecificElementVisitor.h
index 62af615761fd52a80b135300bed27462fca86e71..255f0b47a09b1514f693a3c3da8362a7161b4357 100644
--- a/src/Classic/AbsBeamline/SpecificElementVisitor.h
+++ b/src/Classic/AbsBeamline/SpecificElementVisitor.h
@@ -21,6 +21,9 @@
 #include "AbsBeamline/Monitor.h"
 #include "AbsBeamline/Multipole.h"
 #include "AbsBeamline/MultipoleT.h"
+#include "AbsBeamline/MultipoleTStraight.h"
+#include "AbsBeamline/MultipoleTCurvedConstRadius.h"
+#include "AbsBeamline/MultipoleTCurvedVarRadius.h"
 #include "AbsBeamline/Patch.h"
 #include "AbsBeamline/Probe.h"
 #include "AbsBeamline/RBend.h"
@@ -121,6 +124,15 @@ public:
     /// Apply the algorithm to a multipoleT.
     virtual void visitMultipoleT(const MultipoleT &);
 
+    /// Apply the algorithm to a multipoleTStraight.
+    virtual void visitMultipoleTStraight(const MultipoleTStraight &);
+
+    /// Apply the algorithm to a multipoleT.
+    virtual void visitMultipoleTCurvedConstRadius(const MultipoleTCurvedConstRadius &);
+
+    /// Apply the algorithm to a multipoleT.
+    virtual void visitMultipoleTCurvedVarRadius(const MultipoleTCurvedVarRadius &);
+
     /// Apply the algorithm to an Offset.
     virtual void visitOffset(const Offset &);
 
@@ -322,6 +334,21 @@ void SpecificElementVisitor<ELEM>::visitMultipoleT(const MultipoleT &element) {
     CastsTrait<ELEM, MultipoleT>::apply(allElementsOfTypeE, element);
 }
 
+template<class ELEM>
+void SpecificElementVisitor<ELEM>::visitMultipoleTStraight(const MultipoleTStraight &element) {
+    CastsTrait<ELEM, MultipoleTStraight>::apply(allElementsOfTypeE, element);
+}
+
+template<class ELEM>
+void SpecificElementVisitor<ELEM>::visitMultipoleTCurvedConstRadius(const MultipoleTCurvedConstRadius &element) {
+    CastsTrait<ELEM, MultipoleTCurvedConstRadius>::apply(allElementsOfTypeE, element);
+}
+
+template<class ELEM>
+void SpecificElementVisitor<ELEM>::visitMultipoleTCurvedVarRadius(const MultipoleTCurvedVarRadius &element) {
+    CastsTrait<ELEM, MultipoleTCurvedVarRadius>::apply(allElementsOfTypeE, element);
+}
+
 template<class ELEM>
 void SpecificElementVisitor<ELEM>::visitOffset(const Offset &element) {
     CastsTrait<ELEM, Offset>::apply(allElementsOfTypeE, element);
@@ -509,4 +536,4 @@ typename SpecificElementVisitor<ELEM>::const_reference_t SpecificElementVisitor<
     return allElementsOfTypeE.front();
 }
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/Classic/Algorithms/DefaultVisitor.cpp b/src/Classic/Algorithms/DefaultVisitor.cpp
index 2125b2f0f51c84d4bdfc24dc3a7c9b96bf073cd9..1bb44a3a220773d347b46a395be88dd15a257e1a 100644
--- a/src/Classic/Algorithms/DefaultVisitor.cpp
+++ b/src/Classic/Algorithms/DefaultVisitor.cpp
@@ -36,6 +36,9 @@
 #include "AbsBeamline/Monitor.h"
 #include "AbsBeamline/Multipole.h"
 #include "AbsBeamline/MultipoleT.h"
+#include "AbsBeamline/MultipoleTStraight.h"
+#include "AbsBeamline/MultipoleTCurvedConstRadius.h"
+#include "AbsBeamline/MultipoleTCurvedVarRadius.h"
 #include "AbsBeamline/Patch.h"
 #include "AbsBeamline/Probe.h"
 #include "AbsBeamline/RBend.h"
@@ -151,6 +154,18 @@ void DefaultVisitor::visitMultipoleT(const MultipoleT &multT) {
     applyDefault(multT);
 }
 
+void DefaultVisitor::visitMultipoleTStraight(const MultipoleTStraight &multTstraight) {
+    applyDefault(multTstraight);
+}
+
+void DefaultVisitor::visitMultipoleTCurvedConstRadius(const MultipoleTCurvedConstRadius &multTccurv) {
+    applyDefault(multTccurv);
+}
+
+void DefaultVisitor::visitMultipoleTCurvedVarRadius(const MultipoleTCurvedVarRadius &multTvcurv) {
+    applyDefault(multTvcurv);
+}
+
 void DefaultVisitor::visitOffset(const Offset& off) {
     applyDefault(off);
 }
@@ -308,4 +323,4 @@ void DefaultVisitor::visitTrackIntegrator(const TrackIntegrator &i) {
 
 
 void DefaultVisitor::applyDefault(const ElementBase &)
-{}
\ No newline at end of file
+{}
diff --git a/src/Classic/Algorithms/DefaultVisitor.h b/src/Classic/Algorithms/DefaultVisitor.h
index 3006f0e9381430d99e654d05229abf2e85266166..df664cfa9b006e1e53163da0c32ae01c3e0185dc 100644
--- a/src/Classic/Algorithms/DefaultVisitor.h
+++ b/src/Classic/Algorithms/DefaultVisitor.h
@@ -97,6 +97,15 @@ public:
     /// Apply the algorithm to a multipoleT.
     virtual void visitMultipoleT(const MultipoleT &);
 
+    /// Apply the algorithm to a multipoleTStraight.
+    virtual void visitMultipoleTStraight(const MultipoleTStraight &);
+
+    /// Apply the algorithm to a multipoleTCurvedConstRadius.
+    virtual void visitMultipoleTCurvedConstRadius(const MultipoleTCurvedConstRadius &);
+
+    /// Apply the algorithm to a multipoleTCurvedVarRadius.
+    virtual void visitMultipoleTCurvedVarRadius(const MultipoleTCurvedVarRadius &);
+
     /// Apply the algorithm to an Offset.
     virtual void visitOffset(const Offset &);
 
diff --git a/src/Classic/BeamlineGeometry/CMakeLists.txt b/src/Classic/BeamlineGeometry/CMakeLists.txt
index 520c5b47931486b0352a1af508899a8781d0a68a..bae0b0b3cc984a3e44f6ebbfa8534bc3ebefe672 100644
--- a/src/Classic/BeamlineGeometry/CMakeLists.txt
+++ b/src/Classic/BeamlineGeometry/CMakeLists.txt
@@ -6,6 +6,7 @@ set (_SRCS
   NullGeometry.cpp
   OffsetGeometry.cpp
   PlanarArcGeometry.cpp
+  VarRadiusGeometry.cpp
   RBendGeometry.cpp
   Rotation3D.cpp
   SRotatedGeometry.cpp
@@ -27,6 +28,7 @@ set (HDRS
     NullGeometry.h
     OffsetGeometry.h
     PlanarArcGeometry.h
+    VarRadiusGeometry.h
     RBendGeometry.h
     Rotation3D.h
     SRotatedGeometry.h
@@ -34,4 +36,4 @@ set (HDRS
     Vector3D.h
 )
 
-install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/BeamlineGeometry")
\ No newline at end of file
+install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/BeamlineGeometry")
diff --git a/src/Classic/BeamlineGeometry/VarRadiusGeometry.cpp b/src/Classic/BeamlineGeometry/VarRadiusGeometry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..32bc5d5aea68c2de332388e757c347c8978958da
--- /dev/null
+++ b/src/Classic/BeamlineGeometry/VarRadiusGeometry.cpp
@@ -0,0 +1,47 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 <cmath>
+#include <vector>
+#include "BeamlineGeometry/VarRadiusGeometry.h"
+#include "BeamlineGeometry/Euclid3D.h"
+#include "AbsBeamline/MultipoleTFunctions/CoordinateTransform.h"
+
+Euclid3D VarRadiusGeometry::getTransform(double fromS, double toS) const {
+    Euclid3D v;
+    coordinatetransform::CoordinateTransform t(0.0, 0.0, 0.0,
+                                               s_0_m, lambda_left_m,
+                                               lambda_right_m, rho_m);
+    double phifrom = acos(t.getUnitTangentVector(fromS)[1]);
+    double phito = acos(t.getUnitTangentVector(toS)[1]);
+    std::vector<double> ref_from = t.calcReferenceTrajectory(fromS);
+    std::vector<double> ref_to = t.calcReferenceTrajectory(toS);
+    v = Euclid3D::YRotation(-(phifrom + phito));
+    v.setX(ref_to[0] - ref_from[0]);
+    v.setZ(ref_to[1] - ref_from[1]);
+    return v;
+}
diff --git a/src/Classic/BeamlineGeometry/VarRadiusGeometry.h b/src/Classic/BeamlineGeometry/VarRadiusGeometry.h
new file mode 100644
index 0000000000000000000000000000000000000000..b3cf41ef7fbe4c3b9f3b83ef0d2787987fe567a1
--- /dev/null
+++ b/src/Classic/BeamlineGeometry/VarRadiusGeometry.h
@@ -0,0 +1,218 @@
+/*
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+#ifndef CLASSIC_VarRadiusGeometry_HH
+#define CLASSIC_VarRadiusGeometry_HH
+
+/** ---------------------------------------------------------------------
+  *
+  * VarRadiusGeometry represents a Geometry with variable radius. Assuming \n
+  * a Tanh model for fringe fields, the radius of curvature varies inversely \n
+  * proportional with the fringe field. Such a magnet will follow the \n
+  * trajectory of the reference particle.
+  * The origin is defined at the centre and extends from -length / 2 to \n
+  * +length / 2.
+  * Transformations are calculated using a CoordinateTransformation object, \n
+  * which integrates to find the reference trajectory.
+  *
+  * ---------------------------------------------------------------------
+  *
+  * Class category: AbsBeamline \n
+  * $Author: Martin Duy Tat, Chris Rogers
+  *
+  * ---------------------------------------------------------------------
+  *
+  *
+  * ---------------------------------------------------------------------
+  */
+
+#include "BeamlineGeometry/Geometry.h"
+
+class VarRadiusGeometry: public BGeometryBase {
+public:
+    /** Build VarRadiusGeometry with given length, centre radius of curvature 
+     *  and fringe field
+     *  \param length -> Length of geometry
+     *  \param rho -> Centre radius of curvature of geometry
+     *  \param s_0 -> Length of central fringe field
+     *  \param lambda_left -> Length of left end fringe field
+     *  \param lambda_right -> Length of right end fringe field
+     */
+    VarRadiusGeometry(double length,
+                      double rho,
+                      double s_0,
+                      double lambda_left,
+                      double lambda_right);
+    /** Copy constructor */
+    VarRadiusGeometry(const VarRadiusGeometry &right);
+    /** Destructor */
+    virtual ~VarRadiusGeometry();
+    /** Assigment operator */
+    const VarRadiusGeometry &operator=(const VarRadiusGeometry &right);
+    /** Arc length along the design arc */
+    virtual double getArcLength() const;
+    /** Get element length measured along hte design arc */
+    virtual double getElementLength() const;
+    /** Set arc length
+     *  \param length -> Length of element
+     */
+    virtual void setElementLength(double length);
+    /** Get centre radius of curvature */
+    double getRadius() const;
+    /** Set centre radius of curvature
+     *  \param rho -> Central radius of curvature
+     */
+    void setRadius(const double &rho);
+    /** Get central fringe field length */
+    double getS0() const;
+    /** Set central fringe field length
+     *  \param s_0 -> Central fringe field length
+     */
+    void setS0(const double &s_0);
+    /** Get left end fringe field length */
+    double getLambdaLeft() const;
+    /** Set left end fringe field length
+     *  \param lambda_left -> Left end fringe field length
+     */
+    void setLambdaLeft(const double &lambda_left);
+    /** Get right end fringe field length */
+    double getLambdaRight() const;
+    /** Set right end fringe field length
+     *  \param lambda_right -> Right end fringe field length
+     */
+    void setLambdaRight(const double &lambda_right);
+    /** Transform of the local coordinate system
+     *  \param fromS -> Transform from this position
+     *  \param toS -> Transform to this position
+     */
+    virtual Euclid3D getTransform(double fromS, double toS) const;
+    /** Transform of the local coordinate system from the origin
+     *  to the entrance of the element
+     *  Equivalent to getTransform(0.0, getEntrance())
+     */
+    virtual Euclid3D getEntranceFrame() const;
+    /** Transform of the local coordinate system from the origin
+     *  to the exit of the element
+     *  Equivalent to getTransform(0.0, getExit())
+     */
+    virtual Euclid3D getExitFrame() const;
+private:
+    double length_m;
+    double rho_m;
+    double s_0_m;
+    double lambda_left_m;
+    double lambda_right_m;
+};
+
+// inlined (trivial) member functions
+
+inline
+    VarRadiusGeometry::VarRadiusGeometry(double length,
+                                         double rho,
+                                         double s_0,
+                                         double lambda_left,
+                                         double lambda_right):
+    length_m(length), rho_m(rho), s_0_m(s_0),
+    lambda_left_m(lambda_left), lambda_right_m(lambda_right) {
+}
+
+inline
+    VarRadiusGeometry::VarRadiusGeometry(const VarRadiusGeometry &rhs):
+    BGeometryBase(rhs),
+    length_m(rhs.length_m), rho_m(rhs.rho_m), s_0_m(rhs.s_0_m),
+    lambda_left_m(rhs.lambda_left_m), lambda_right_m(rhs.lambda_right_m) {
+}
+
+
+inline
+    const VarRadiusGeometry &VarRadiusGeometry::operator= (
+                            const VarRadiusGeometry &rhs) {
+    length_m = rhs.length_m;
+    rho_m = rhs.rho_m;
+    s_0_m = rhs.s_0_m;
+    lambda_left_m = rhs.lambda_left_m;
+    lambda_right_m = rhs.lambda_right_m;
+    return *this;
+}
+inline
+    VarRadiusGeometry::~VarRadiusGeometry() {
+}
+inline
+    double VarRadiusGeometry::getArcLength() const {
+        return length_m;
+}
+inline
+    double VarRadiusGeometry::getElementLength() const {
+        return length_m;
+}
+inline
+    void VarRadiusGeometry::setElementLength(double length) {
+        length_m = length;
+}
+inline
+    double VarRadiusGeometry::getRadius() const {
+        return rho_m;
+}
+inline
+    void VarRadiusGeometry::setRadius(const double &rho) {
+        rho_m = rho;
+}
+inline
+    double VarRadiusGeometry::getS0() const {
+        return s_0_m;
+}
+inline
+    void VarRadiusGeometry::setS0(const double &s_0) {
+        s_0_m = s_0;
+}
+inline
+    double VarRadiusGeometry::getLambdaLeft() const {
+        return lambda_left_m;
+}
+inline
+    void VarRadiusGeometry::setLambdaLeft(const double &lambda_left) {
+        lambda_left_m = lambda_left;
+}
+inline
+    double VarRadiusGeometry::getLambdaRight() const {
+        return lambda_right_m;
+}
+inline
+    void VarRadiusGeometry::setLambdaRight(const double &lambda_right) {
+        lambda_right_m = lambda_right;
+}
+inline
+    Euclid3D VarRadiusGeometry::getEntranceFrame() const {
+        return getTransform(getOrigin(), getEntrance());
+}
+inline
+    Euclid3D VarRadiusGeometry::getExitFrame() const {
+        return getTransform(getOrigin(), getExit());
+}
+
+#endif
+
diff --git a/src/Classic/CMakeLists.txt b/src/Classic/CMakeLists.txt
index 14b8cdfc9011c93e34c4bb0e253d326da17669fb..8eae3ea09cfdf2e6e39cef20c3566e02aef190e1 100644
--- a/src/Classic/CMakeLists.txt
+++ b/src/Classic/CMakeLists.txt
@@ -25,3 +25,7 @@ install (FILES Utilities/ClassicException.h Utilities/ClassicRandom.h Utilities/
 )
 
 set(OPAL_SRCS "${OPAL_SRCS}" PARENT_SCOPE)
+
+#Turn on gcov
+set(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
+set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)
diff --git a/src/Elements/CMakeLists.txt b/src/Elements/CMakeLists.txt
index 826582a49f5e4b3ac462f1e1acb873e7a303bdff..9e1e1319c56bddeab2d695b989341923e6e7b247 100644
--- a/src/Elements/CMakeLists.txt
+++ b/src/Elements/CMakeLists.txt
@@ -20,6 +20,9 @@ set (_SRCS
   OpalMonitor.cpp
   OpalMultipole.cpp
   OpalMultipoleT.cpp
+  OpalMultipoleTStraight.cpp
+  OpalMultipoleTCurvedConstRadius.cpp
+  OpalMultipoleTCurvedVarRadius.cpp
   OpalOctupole.cpp
   OpalOffset/OpalLocalCylindricalOffset.cpp
   OpalOffset/OpalLocalCartesianOffset.cpp
@@ -82,6 +85,9 @@ set (HDRS
     OpalMonitor.h
     OpalMultipole.h
     OpalMultipoleT.h
+    OpalMultipoleTStraight.h
+    OpalMultipoleTCurvedConstRadius.h
+    OpalMultipoleTCurvedVarRadius.h
     OpalOctupole.h
     OpalParallelPlate.h
     OpalPatch.h
@@ -114,4 +120,4 @@ set (HDRS
     OpalOffset/OpalLocalCylindricalOffset.h
 )
 
-install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/Elements/OpalOffset")
\ No newline at end of file
+install (FILES ${HDRS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/Elements/OpalOffset")
diff --git a/src/Elements/OpalMultipoleT.cpp b/src/Elements/OpalMultipoleT.cpp
index e27147877254f9a077d4fadd3162d4844525c485..0c99707e436f66f589fe5446962a755d539b2a91 100644
--- a/src/Elements/OpalMultipoleT.cpp
+++ b/src/Elements/OpalMultipoleT.cpp
@@ -68,15 +68,18 @@ OpalMultipoleT::OpalMultipoleT():
     itsAttr[MAXFORDER] = Attributes::makeReal
                   ("MAXFORDER", 
                    "Number of terms used in each field component");
+    itsAttr[MAXXORDER] = Attributes::makeReal
+                  ("MAXXORDER", 
+                   "Number of terms used in polynomial expansions");
     itsAttr[ROTATION] = Attributes::makeReal
                   ("ROTATION", 
                    "Rotation angle about its axis for skew elements (rad)");
     itsAttr[VARRADIUS] = Attributes::makeBool
                   ("VARRADIUS",
                    "Set true if radius of magnet is variable");
-    itsAttr[VARSTEP] = Attributes::makeReal
-                  ("VARSTEP",
-                   "Step size used in rotating coords along ref trajectory");
+    itsAttr[BBLENGTH] = Attributes::makeReal
+                  ("BBLENGTH",
+                   "Distance between centre of magnet and entrance in m");
     //registerRealAttribute("FRINGELEN");
 
     registerOwnership();
@@ -111,7 +114,6 @@ fillRegisteredAttributes(const ElementBase &base, ValueFlag flag) {
     OpalElement::fillRegisteredAttributes(base, flag);   
     const MultipoleT *multT = 
         dynamic_cast<const MultipoleT*>(base.removeAlignWrapper());
-    
     for(unsigned int order = 1; order <= multT->getTransMaxOrder(); order++) {
         std::ostringstream ss;
 	ss << order;
@@ -125,10 +127,11 @@ fillRegisteredAttributes(const ElementBase &base, ValueFlag flag) {
     registerRealAttribute("VAPERT")->setReal(multT->getAperture()[0]);
     registerRealAttribute("HAPERT")->setReal(multT->getAperture()[1]);
     registerRealAttribute("MAXFORDER")->setReal(multT->getMaxOrder());
+    registerRealAttribute("MAXXORDER")->setReal(multT->getMaxXOrder());
     registerRealAttribute("ROTATION")->setReal(multT->getRotation());
     registerRealAttribute("EANGLE")->setReal(multT->getEntranceAngle());
-    registerRealAttribute("VARSTEP")->setReal(multT->getVarStep());
-    //registerRealAttribute("VARRADIUS")->setReal(multT->getVarRadius());
+    //registerRealAttribute("VARRADIUS")->setBool(multT->getVarRadius());
+    registerRealAttribute("BBLENGTH")->setReal(multT->getBoundingBoxLength());
     
 }
 
@@ -138,10 +141,11 @@ void OpalMultipoleT::update() {
 
     // Magnet length.
     MultipoleT *multT =
-        dynamic_cast<MultipoleT*>(getElement()->removeWrappers());
+    dynamic_cast<MultipoleT*>(getElement()->removeWrappers());
     double length = Attributes::getReal(itsAttr[LENGTH]);
     double angle = Attributes::getReal(itsAttr[ANGLE]);
-    multT->setElementLength(length/2);
+    double boundingBoxLength = Attributes::getReal(itsAttr[BBLENGTH]);
+    multT->setElementLength(length);
     multT->setLength(length);
     multT->setBendAngle(angle);
     multT->setAperture(Attributes::getReal(itsAttr[VAPERT]), 
@@ -152,23 +156,26 @@ void OpalMultipoleT::update() {
                           Attributes::getReal(itsAttr[RFRINGE])); 
     if (Attributes::getBool(itsAttr[VARRADIUS])) {
         multT->setVarRadius();
-	if(Attributes::getReal(itsAttr[VARSTEP])) {
-	    multT->setVarStep(Attributes::getReal(itsAttr[VARSTEP]));
-	}
     }
+    multT->setBoundingBoxLength(Attributes::getReal(itsAttr[BBLENGTH]));
     const std::vector<double> transProfile = 
                               Attributes::getRealArray(itsAttr[TP]);
     int transSize = transProfile.size();
 
-    multT->setTransMaxOrder(transSize - 1);
+    if (transSize == 0) {
+        multT->setTransMaxOrder(0);
+    } else {
+        multT->setTransMaxOrder(transSize - 1);
+    }
     multT->setMaxOrder(Attributes::getReal(itsAttr[MAXFORDER]));
+    multT->setMaxXOrder(Attributes::getReal(itsAttr[MAXXORDER]));
     multT->setRotation(Attributes::getReal(itsAttr[ROTATION]));
     multT->setEntranceAngle(Attributes::getReal(itsAttr[EANGLE]));
 
     PlanarArcGeometry &geometry = multT->getGeometry();
-    
+
     if(length) {
-        geometry = PlanarArcGeometry(length/2, angle / length);
+        geometry = PlanarArcGeometry(2 * boundingBoxLength, angle / length);
     } else {
         geometry = PlanarArcGeometry(angle);
     }
diff --git a/src/Elements/OpalMultipoleT.h b/src/Elements/OpalMultipoleT.h
index ada5375b388db75bff0ee8115233ecb93b045d9a..53a663d64d9e7a6dd19ada9681e1ef50b988f83a 100644
--- a/src/Elements/OpalMultipoleT.h
+++ b/src/Elements/OpalMultipoleT.h
@@ -48,12 +48,12 @@ public:
 	HAPERT,          // Aperture horizontal dimension
 	VAPERT,          // Aperture vertical dimension
 	MAXFORDER,       // Maximum order in the field expansion
+        MAXXORDER,       // Maximum order in x in polynomial expansions
 	ANGLE,           // Bending angle of a sector magnet
 	ROTATION,        // Rotation angle about central axis for skew elements
 	EANGLE,          // Entrance angle
 	VARRADIUS,       // Variable radius flag
-	VARSTEP,         // Step size used in rotating coords 
-	                 // along ref trajectory (mm)
+        BBLENGTH,        // Distance between centre of magnet and entrance
 	SIZE             // size of the enum
     };
     
diff --git a/src/Elements/OpalMultipoleTCurvedConstRadius.cpp b/src/Elements/OpalMultipoleTCurvedConstRadius.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8082f62016418b71394574d358097f4a9237cad8
--- /dev/null
+++ b/src/Elements/OpalMultipoleTCurvedConstRadius.cpp
@@ -0,0 +1,186 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 "Elements/OpalMultipoleTCurvedConstRadius.h"
+#include "AbstractObjects/AttributeHandler.h"
+#include "AbstractObjects/Expressions.h"
+#include "AbstractObjects/OpalData.h"
+#include "Attributes/Attributes.h"
+#include "ComponentWrappers/MultipoleWrapper.h"
+#include "Expressions/SValue.h"
+#include "Expressions/SRefExpr.h"
+#include "Physics/Physics.h"
+#include "Utilities/Options.h"
+#include <iostream>
+#if defined(__GNUC__) && __GNUC__ < 3
+#include <strstream>
+#else
+#include <sstream>
+#endif
+#include <vector>
+
+
+// Class OpalMultipoleTCurvedConstRadius
+// ------------------------------------------------------------------------
+
+OpalMultipoleTCurvedConstRadius::OpalMultipoleTCurvedConstRadius():
+    OpalElement(SIZE, "MULTIPOLETCURVEDCONSTRADIUS",
+    "The \"MULTIPOLETCURVEDCONSTRADIUS\" element defines a curved combined function multipole magnet of constant curvature.") {
+    itsAttr[TP] = Attributes::makeRealArray
+                  ("TP", "Transverse Profile derivatives in m^(-k)");
+    itsAttr[LFRINGE] = Attributes::makeReal
+                  ("LFRINGE", "The length of the left end field in m");
+    itsAttr[RFRINGE] = Attributes::makeReal
+                  ("RFRINGE", "The length of the right end field in m");
+    itsAttr[HAPERT] = Attributes::makeReal
+                  ("HAPERT", "The aperture width in m");
+    itsAttr[VAPERT] = Attributes::makeReal
+                  ("VAPERT", "The aperture height in m");
+    itsAttr[ANGLE] = Attributes::makeReal
+                  ("ANGLE", "The azimuthal angle of the magnet in ring (rad)");
+    itsAttr[EANGLE] = Attributes::makeReal
+                  ("EANGLE", "The entrance angle (rad)");
+    itsAttr[MAXFORDER] = Attributes::makeReal
+                  ("MAXFORDER", 
+                   "Number of terms used in each field component");
+    itsAttr[MAXXORDER] = Attributes::makeReal
+                  ("MAXXORDER", 
+                   "Number of terms used in polynomial expansions");
+    itsAttr[ROTATION] = Attributes::makeReal
+                  ("ROTATION", 
+                   "Rotation angle about its axis for skew elements (rad)");
+    itsAttr[BBLENGTH] = Attributes::makeReal
+                  ("BBLENGTH",
+                   "Distance between centre of magnet and entrance in m");
+
+    registerOwnership();
+
+
+    setElement((new MultipoleTCurvedConstRadius("MULTIPOLETCURVEDCONSTRADIUS"))->makeWrappers());
+}
+
+
+OpalMultipoleTCurvedConstRadius::OpalMultipoleTCurvedConstRadius(const std::string &name, 
+			       OpalMultipoleTCurvedConstRadius *parent):
+    OpalElement(name, parent) {
+    setElement((new MultipoleTCurvedConstRadius(name))->makeWrappers());
+}
+
+
+OpalMultipoleTCurvedConstRadius::~OpalMultipoleTCurvedConstRadius()
+{}
+
+
+OpalMultipoleTCurvedConstRadius *OpalMultipoleTCurvedConstRadius::clone(const std::string &name) {
+    return new OpalMultipoleTCurvedConstRadius(name, this);
+}
+
+
+void OpalMultipoleTCurvedConstRadius::print(std::ostream &os) const {
+    OpalElement::print(os);
+}
+
+
+void OpalMultipoleTCurvedConstRadius::
+fillRegisteredAttributes(const ElementBase &base, ValueFlag flag) {
+    OpalElement::fillRegisteredAttributes(base, flag);   
+    const MultipoleTCurvedConstRadius *multT = 
+        dynamic_cast<const MultipoleTCurvedConstRadius*>(base.removeAlignWrapper());
+    
+    for(unsigned int order = 1; order <= multT->getTransMaxOrder(); order++) {
+        std::ostringstream ss;
+	ss << order;
+	std::string orderString = ss.str();
+        std::string attrName = "TP" + orderString;
+	registerRealAttribute(attrName)->setReal(multT->getTransProfile(order));
+    }
+
+    registerRealAttribute("LFRINGE")->setReal(multT->getFringeLength().at(0));
+    registerRealAttribute("RFRINGE")->setReal(multT->getFringeLength().at(1));
+    registerRealAttribute("VAPERT")->setReal(multT->getAperture()[0]);
+    registerRealAttribute("HAPERT")->setReal(multT->getAperture()[1]);
+    registerRealAttribute("MAXFORDER")->setReal(multT->getMaxOrder());
+    registerRealAttribute("MAXXORDER")->setReal(multT->getMaxXOrder());
+    registerRealAttribute("ROTATION")->setReal(multT->getRotation());
+    registerRealAttribute("ANGLE")->setReal(multT->getBendAngle());
+    registerRealAttribute("EANGLE")->setReal(multT->getEntranceAngle());
+    registerRealAttribute("BBLENGTH")->setReal(multT->getBoundingBoxLength());
+    
+}
+
+
+void OpalMultipoleTCurvedConstRadius::update() {
+    OpalElement::update();
+
+    // Magnet length.
+    MultipoleTCurvedConstRadius *multT =
+    dynamic_cast<MultipoleTCurvedConstRadius*>(getElement()->removeWrappers());
+    double length = Attributes::getReal(itsAttr[LENGTH]);
+    double angle = Attributes::getReal(itsAttr[ANGLE]);
+    double boundingBoxLength = Attributes::getReal(itsAttr[BBLENGTH]);
+    multT->setElementLength(length);
+    multT->setLength(length);
+    multT->setBendAngle(angle);
+    multT->setAperture(Attributes::getReal(itsAttr[VAPERT]), 
+		       Attributes::getReal(itsAttr[HAPERT]));
+  
+    multT->setFringeField(Attributes::getReal(itsAttr[LENGTH])/2,
+                          Attributes::getReal(itsAttr[LFRINGE]),
+                          Attributes::getReal(itsAttr[RFRINGE])); 
+    multT->setBoundingBoxLength(Attributes::getReal(itsAttr[BBLENGTH]));
+    const std::vector<double> transProfile = 
+                              Attributes::getRealArray(itsAttr[TP]);
+    std::size_t transSize = transProfile.size();
+    if (transSize == 0) {
+        multT->setTransMaxOrder(0);
+    } else {
+        multT->setTransMaxOrder(transSize - 1);
+    }
+
+    multT->setMaxOrder(Attributes::getReal(itsAttr[MAXFORDER]));
+    multT->setMaxXOrder(Attributes::getReal(itsAttr[MAXXORDER]));
+    multT->setRotation(Attributes::getReal(itsAttr[ROTATION]));
+    multT->setEntranceAngle(Attributes::getReal(itsAttr[EANGLE]));
+
+    PlanarArcGeometry &geometry = multT->getGeometry();
+    
+    if(length) {
+        geometry = PlanarArcGeometry(2 * boundingBoxLength, angle / length);
+    } else {
+        geometry = PlanarArcGeometry(angle);
+    }
+    
+    for(std::size_t comp = 0; comp < transSize; comp++) {
+        multT->setTransProfile(comp, transProfile[comp]);
+    }
+    // Transmit "unknown" attributes.
+    OpalElement::updateUnknown(multT);
+
+    setElement(multT->makeWrappers());
+}
diff --git a/src/Elements/OpalMultipoleTCurvedConstRadius.h b/src/Elements/OpalMultipoleTCurvedConstRadius.h
new file mode 100644
index 0000000000000000000000000000000000000000..8adcced7fcac6c60b1b6f083abaff25b697156fc
--- /dev/null
+++ b/src/Elements/OpalMultipoleTCurvedConstRadius.h
@@ -0,0 +1,94 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+
+#ifndef OPAL_OPALMULTIPOLET_CURVED_CONST_RADIUS_HH
+#define OPAL_OPALMULTIPOLET_CURVED_CONST_RADIUS_HH
+
+#include "Elements/OpalElement.h"
+#include "AbsBeamline/MultipoleTCurvedConstRadius.h"
+
+/** OpalMultipoleTCurvedConstRadius provides user interface information for the MultipoleTCurvedConstRadius object
+ *
+ *  Defines input parameters 
+ */
+class OpalMultipoleTCurvedConstRadius: public OpalElement {
+
+public:
+
+    // The attributes of class OpalMultipoleTCurvedConstRadius
+    enum {
+        TP = COMMON,     // Transverse field components
+	RFRINGE,         // Length of right fringe field
+	LFRINGE,         // Length of left fringe field
+	HAPERT,          // Aperture horizontal dimension
+	VAPERT,          // Aperture vertical dimension
+	MAXFORDER,       // Maximum order in the field expansion
+        MAXXORDER,       // Maximum order in x in polynomial expansions
+	ANGLE,           // Bending angle of a sector magnet
+	ROTATION,        // Rotation angle about central axis for skew elements
+	EANGLE,          // Entrance angle
+        BBLENGTH,        // Distance between centre of magnet and entrance
+	SIZE             // size of the enum
+    };
+    
+    /** Default constructor initialises UI parameters. */
+    OpalMultipoleTCurvedConstRadius();
+
+    /** Destructor does nothing */
+    virtual ~OpalMultipoleTCurvedConstRadius();
+
+    /** Inherited copy constructor */
+    virtual OpalMultipoleTCurvedConstRadius *clone(const std::string &name);
+
+    /** Fill in all registered attributes
+     *
+     *  Just calls fillRegisteredAttributes on the base class
+     */
+    virtual void fillRegisteredAttributes(const ElementBase &, ValueFlag);
+
+    /** Update the MultipoleTCurvedConstRadius with new parameters from UI parser */
+    virtual void update();
+
+    void print(std::ostream &os) const;
+
+  private:
+    // Not implemented.
+    OpalMultipoleTCurvedConstRadius(const OpalMultipoleTCurvedConstRadius &);
+    void operator=(const OpalMultipoleTCurvedConstRadius &);
+
+    // Clone constructor.
+    OpalMultipoleTCurvedConstRadius(const std::string &name, OpalMultipoleTCurvedConstRadius *parent);
+};
+
+#endif
+   
+ 
+  
+
+
diff --git a/src/Elements/OpalMultipoleTCurvedVarRadius.cpp b/src/Elements/OpalMultipoleTCurvedVarRadius.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..225e09a10c8d2120a767bb6ac7d5dc0a93b771d5
--- /dev/null
+++ b/src/Elements/OpalMultipoleTCurvedVarRadius.cpp
@@ -0,0 +1,184 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 "Elements/OpalMultipoleTCurvedVarRadius.h"
+#include "AbstractObjects/AttributeHandler.h"
+#include "AbstractObjects/Expressions.h"
+#include "AbstractObjects/OpalData.h"
+#include "Attributes/Attributes.h"
+#include "ComponentWrappers/MultipoleWrapper.h"
+#include "Expressions/SValue.h"
+#include "Expressions/SRefExpr.h"
+#include "Physics/Physics.h"
+#include "Utilities/Options.h"
+#include <iostream>
+#if defined(__GNUC__) && __GNUC__ < 3
+#include <strstream>
+#else
+#include <sstream>
+#endif
+#include <vector>
+
+
+// Class OpalMultipoleTCurvedVarRadius
+// ------------------------------------------------------------------------
+
+OpalMultipoleTCurvedVarRadius::OpalMultipoleTCurvedVarRadius():
+    OpalElement(SIZE, "MULTIPOLETCURVEDVARRADIUS",
+    "The \"MULTIPOLETCURVEDVARRADIUS\" element defines a combined function multipole.") {
+    itsAttr[TP] = Attributes::makeRealArray
+                  ("TP", "Transverse Profile derivatives in m^(-k)");
+    itsAttr[LFRINGE] = Attributes::makeReal
+                  ("LFRINGE", "The length of the left end field in m");
+    itsAttr[RFRINGE] = Attributes::makeReal
+                  ("RFRINGE", "The length of the right end field in m");
+    itsAttr[HAPERT] = Attributes::makeReal
+                  ("HAPERT", "The aperture width in m");
+    itsAttr[VAPERT] = Attributes::makeReal
+                  ("VAPERT", "The aperture height in m");
+    itsAttr[ANGLE] = Attributes::makeReal
+                  ("ANGLE", "The azimuthal angle of the magnet in ring (rad)");
+    itsAttr[EANGLE] = Attributes::makeReal
+                  ("EANGLE", "The entrance angle (rad)");
+    itsAttr[MAXFORDER] = Attributes::makeReal
+                  ("MAXFORDER", 
+                   "Number of terms used in each field component");
+    itsAttr[MAXXORDER] = Attributes::makeReal
+                  ("MAXXORDER", 
+                   "Number of terms used in polynomial expansions");
+    itsAttr[ROTATION] = Attributes::makeReal
+                  ("ROTATION", 
+                   "Rotation angle about its axis for skew elements (rad)");
+    itsAttr[BBLENGTH] = Attributes::makeReal
+                  ("BBLENGTH",
+                   "Distance between centre of magnet and entrance in m");
+
+    registerOwnership();
+
+    setElement((new MultipoleTCurvedVarRadius("MULTIPOLETCURVEDVARRADIUS"))->makeWrappers());
+}
+
+
+OpalMultipoleTCurvedVarRadius::OpalMultipoleTCurvedVarRadius(const std::string &name, 
+			       OpalMultipoleTCurvedVarRadius *parent):
+    OpalElement(name, parent) {
+    setElement((new MultipoleTCurvedVarRadius(name))->makeWrappers());
+}
+
+
+OpalMultipoleTCurvedVarRadius::~OpalMultipoleTCurvedVarRadius()
+{}
+
+
+OpalMultipoleTCurvedVarRadius *OpalMultipoleTCurvedVarRadius::clone(const std::string &name) {
+    return new OpalMultipoleTCurvedVarRadius(name, this);
+}
+
+
+void OpalMultipoleTCurvedVarRadius::print(std::ostream &os) const {
+    OpalElement::print(os);
+}
+
+
+void OpalMultipoleTCurvedVarRadius::
+fillRegisteredAttributes(const ElementBase &base, ValueFlag flag) {
+    OpalElement::fillRegisteredAttributes(base, flag);   
+    const MultipoleTCurvedVarRadius *multT = 
+        dynamic_cast<const MultipoleTCurvedVarRadius*>(base.removeAlignWrapper());
+    
+    for(unsigned int order = 1; order <= multT->getTransMaxOrder(); order++) {
+        std::ostringstream ss;
+	ss << order;
+	std::string orderString = ss.str();
+        std::string attrName = "TP" + orderString;
+	registerRealAttribute(attrName)->setReal(multT->getTransProfile(order));
+    }
+
+    registerRealAttribute("LFRINGE")->setReal(multT->getFringeLength().at(0));
+    registerRealAttribute("RFRINGE")->setReal(multT->getFringeLength().at(1));
+    registerRealAttribute("VAPERT")->setReal(multT->getAperture()[0]);
+    registerRealAttribute("HAPERT")->setReal(multT->getAperture()[1]);
+    registerRealAttribute("MAXFORDER")->setReal(multT->getMaxOrder());
+    registerRealAttribute("MAXXORDER")->setReal(multT->getMaxXOrder());
+    registerRealAttribute("ROTATION")->setReal(multT->getRotation());
+    registerRealAttribute("EANGLE")->setReal(multT->getEntranceAngle());
+    registerRealAttribute("BBLENGTH")->setReal(multT->getBoundingBoxLength());
+    
+}
+
+
+void OpalMultipoleTCurvedVarRadius::update() {
+    OpalElement::update();
+
+    // Magnet length.
+    MultipoleTCurvedVarRadius *multT =
+    dynamic_cast<MultipoleTCurvedVarRadius*>(getElement()->removeWrappers());
+    double length = Attributes::getReal(itsAttr[LENGTH]);
+    double angle = Attributes::getReal(itsAttr[ANGLE]);
+    double boundingBoxLength = Attributes::getReal(itsAttr[BBLENGTH]);
+    multT->setElementLength(length);
+    multT->setLength(length);
+    multT->setBendAngle(angle);
+    multT->setAperture(Attributes::getReal(itsAttr[VAPERT]), 
+		       Attributes::getReal(itsAttr[HAPERT]));
+  
+    multT->setFringeField(Attributes::getReal(itsAttr[LENGTH])/2,
+                          Attributes::getReal(itsAttr[LFRINGE]),
+                          Attributes::getReal(itsAttr[RFRINGE])); 
+    multT->setBoundingBoxLength(Attributes::getReal(itsAttr[BBLENGTH]));
+    const std::vector<double> transProfile = 
+                              Attributes::getRealArray(itsAttr[TP]);
+    std::size_t transSize = transProfile.size();
+
+    if (transSize == 0) {
+        multT->setTransMaxOrder(0);
+    } else {
+        multT->setTransMaxOrder(transSize - 1);
+    }
+    multT->setMaxOrder(Attributes::getReal(itsAttr[MAXFORDER]));
+    multT->setMaxXOrder(Attributes::getReal(itsAttr[MAXXORDER]));
+    multT->setRotation(Attributes::getReal(itsAttr[ROTATION]));
+    multT->setEntranceAngle(Attributes::getReal(itsAttr[EANGLE]));
+
+    VarRadiusGeometry &geometry = multT->getGeometry();
+    
+    geometry = VarRadiusGeometry(2 * boundingBoxLength,
+                                 (length / angle),
+                                  length / 2,
+                                  Attributes::getReal(itsAttr[LFRINGE]),
+                                  Attributes::getReal(itsAttr[RFRINGE]));
+    
+    for(std::size_t comp = 0; comp < transSize; comp++) {
+        multT->setTransProfile(comp, transProfile[comp]);
+    }
+    // Transmit "unknown" attributes.
+    OpalElement::updateUnknown(multT);
+
+    setElement(multT->makeWrappers());
+}
diff --git a/src/Elements/OpalMultipoleTCurvedVarRadius.h b/src/Elements/OpalMultipoleTCurvedVarRadius.h
new file mode 100644
index 0000000000000000000000000000000000000000..e3bddee939f62d88ff606b8e3649ad61f921c2f8
--- /dev/null
+++ b/src/Elements/OpalMultipoleTCurvedVarRadius.h
@@ -0,0 +1,94 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+
+#ifndef OPAL_OPALMULTIPOLET_CURVED_VAR_RADIUS_HH
+#define OPAL_OPALMULTIPOLET_CURVED_VAR_RADIUS_HH
+
+#include "Elements/OpalElement.h"
+#include "AbsBeamline/MultipoleTCurvedVarRadius.h"
+
+/** OpalMultipoleTCurvedVarRadius provides user interface information for the MultipoleTCurvedVarRadius object
+ *
+ *  Defines input parameters 
+ */
+class OpalMultipoleTCurvedVarRadius: public OpalElement {
+
+public:
+
+    // The attributes of class OpalMultipoleTCurvedVarRadius
+    enum {
+        TP = COMMON,     // Transverse field components
+	RFRINGE,         // Length of right fringe field
+	LFRINGE,         // Length of left fringe field
+	HAPERT,          // Aperture horizontal dimension
+	VAPERT,          // Aperture vertical dimension
+	MAXFORDER,       // Maximum order in the field expansion
+        MAXXORDER,       // Maximum order in x in polynomial expansions
+	ANGLE,           // Bending angle of a sector magnet
+	ROTATION,        // Rotation angle about central axis for skew elements
+	EANGLE,          // Entrance angle
+        BBLENGTH,        // Distance between centre of magnet and entrance
+	SIZE             // size of the enum
+    };
+    
+    /** Default constructor initialises UI parameters. */
+    OpalMultipoleTCurvedVarRadius();
+
+    /** Destructor does nothing */
+    virtual ~OpalMultipoleTCurvedVarRadius();
+
+    /** Inherited copy constructor */
+    virtual OpalMultipoleTCurvedVarRadius *clone(const std::string &name);
+
+    /** Fill in all registered attributes
+     *
+     *  Just calls fillRegisteredAttributes on the base class
+     */
+    virtual void fillRegisteredAttributes(const ElementBase &, ValueFlag);
+
+    /** Update the MultipoleTCurvedVarRadius with new parameters from UI parser */
+    virtual void update();
+
+    void print(std::ostream &os) const;
+
+  private:
+    // Not implemented.
+    OpalMultipoleTCurvedVarRadius(const OpalMultipoleTCurvedVarRadius &);
+    void operator=(const OpalMultipoleTCurvedVarRadius &);
+
+    // Clone constructor.
+    OpalMultipoleTCurvedVarRadius(const std::string &name, OpalMultipoleTCurvedVarRadius *parent);
+};
+
+#endif
+   
+ 
+  
+
+
diff --git a/src/Elements/OpalMultipoleTStraight.cpp b/src/Elements/OpalMultipoleTStraight.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0a380a6a3e90fd9e41e86c27e101c000e8e0bf57
--- /dev/null
+++ b/src/Elements/OpalMultipoleTStraight.cpp
@@ -0,0 +1,167 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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 "Elements/OpalMultipoleTStraight.h"
+#include "AbstractObjects/AttributeHandler.h"
+#include "AbstractObjects/Expressions.h"
+#include "AbstractObjects/OpalData.h"
+#include "Attributes/Attributes.h"
+#include "ComponentWrappers/MultipoleWrapper.h"
+#include "Expressions/SValue.h"
+#include "Expressions/SRefExpr.h"
+#include "Physics/Physics.h"
+#include "Utilities/Options.h"
+#include <iostream>
+#if defined(__GNUC__) && __GNUC__ < 3
+#include <strstream>
+#else
+#include <sstream>
+#endif
+#include <vector>
+
+
+// Class OpalMultipoleTStraight
+// ------------------------------------------------------------------------
+
+OpalMultipoleTStraight::OpalMultipoleTStraight():
+    OpalElement(SIZE, "MULTIPOLETSTRAIGHT",
+    "The \"MULTIPOLETSTRAIGHT\" element defines a straight, combined function multipole magnet.") {
+    itsAttr[TP] = Attributes::makeRealArray
+                  ("TP", "Transverse Profile derivatives in m^(-k)");
+    itsAttr[LFRINGE] = Attributes::makeReal
+                  ("LFRINGE", "The length of the left end field in m");
+    itsAttr[RFRINGE] = Attributes::makeReal
+                  ("RFRINGE", "The length of the right end field in m");
+    itsAttr[HAPERT] = Attributes::makeReal
+                  ("HAPERT", "The aperture width in m");
+    itsAttr[VAPERT] = Attributes::makeReal
+                  ("VAPERT", "The aperture height in m");
+    itsAttr[EANGLE] = Attributes::makeReal
+                  ("EANGLE", "The entrance angle (rad)");
+    itsAttr[MAXFORDER] = Attributes::makeReal
+                  ("MAXFORDER", 
+                   "Number of terms used in each field component");
+    itsAttr[ROTATION] = Attributes::makeReal
+                  ("ROTATION", 
+                   "Rotation angle about its axis for skew elements (rad)");
+    itsAttr[BBLENGTH] = Attributes::makeReal
+                  ("BBLENGTH",
+                   "Distance between centre of magnet and entrance in m");
+
+    registerOwnership();
+
+    setElement((new MultipoleTStraight("MULTIPOLETSTRAIGHT"))->makeWrappers());
+}
+
+
+OpalMultipoleTStraight::OpalMultipoleTStraight(const std::string &name, 
+			       OpalMultipoleTStraight *parent):
+    OpalElement(name, parent) {
+    setElement((new MultipoleTStraight(name))->makeWrappers());
+}
+
+
+OpalMultipoleTStraight::~OpalMultipoleTStraight()
+{}
+
+
+OpalMultipoleTStraight *OpalMultipoleTStraight::clone(const std::string &name) {
+    return new OpalMultipoleTStraight(name, this);
+}
+
+
+void OpalMultipoleTStraight::print(std::ostream &os) const {
+    OpalElement::print(os);
+}
+
+
+void OpalMultipoleTStraight::
+fillRegisteredAttributes(const ElementBase &base, ValueFlag flag) {
+    OpalElement::fillRegisteredAttributes(base, flag);   
+    const MultipoleTStraight *multT = 
+        dynamic_cast<const MultipoleTStraight*>(base.removeAlignWrapper());
+    
+    for(unsigned int order = 1; order <= multT->getTransMaxOrder(); order++) {
+        std::ostringstream ss;
+	ss << order;
+	std::string orderString = ss.str();
+        std::string attrName = "TP" + orderString;
+	registerRealAttribute(attrName)->setReal(multT->getTransProfile(order));
+    }
+
+    registerRealAttribute("LFRINGE")->setReal(multT->getFringeLength().at(0));
+    registerRealAttribute("RFRINGE")->setReal(multT->getFringeLength().at(1));
+    registerRealAttribute("VAPERT")->setReal(multT->getAperture()[0]);
+    registerRealAttribute("HAPERT")->setReal(multT->getAperture()[1]);
+    registerRealAttribute("MAXFORDER")->setReal(multT->getMaxOrder());
+    registerRealAttribute("ROTATION")->setReal(multT->getRotation());
+    registerRealAttribute("EANGLE")->setReal(multT->getEntranceAngle());
+    registerRealAttribute("BBLENGTH")->setReal(multT->getBoundingBoxLength());
+    
+}
+
+
+void OpalMultipoleTStraight::update() {
+    OpalElement::update();
+
+    // Magnet length.
+    MultipoleTStraight *multT =
+    dynamic_cast<MultipoleTStraight*>(getElement()->removeWrappers());
+    double length = Attributes::getReal(itsAttr[LENGTH]);
+    double boundingBoxLength = Attributes::getReal(itsAttr[BBLENGTH]);
+    multT->setElementLength(length);
+    multT->setLength(length);
+    multT->setAperture(Attributes::getReal(itsAttr[VAPERT]), 
+		       Attributes::getReal(itsAttr[HAPERT]));
+  
+    multT->setFringeField(Attributes::getReal(itsAttr[LENGTH])/2,
+                          Attributes::getReal(itsAttr[LFRINGE]),
+                          Attributes::getReal(itsAttr[RFRINGE])); 
+    multT->setBoundingBoxLength(Attributes::getReal(itsAttr[BBLENGTH]));
+    const std::vector<double> transProfile = 
+                              Attributes::getRealArray(itsAttr[TP]);
+    int transSize = transProfile.size();
+
+    multT->setTransMaxOrder(transSize - 1);
+    multT->setMaxOrder(Attributes::getReal(itsAttr[MAXFORDER]));
+    multT->setRotation(Attributes::getReal(itsAttr[ROTATION]));
+    multT->setEntranceAngle(Attributes::getReal(itsAttr[EANGLE]));
+
+    StraightGeometry &geometry = multT->getGeometry();
+    
+    geometry = StraightGeometry(2 * boundingBoxLength);
+    
+    for(int comp = 0; comp < transSize; comp++) {
+        multT->setTransProfile(comp, transProfile[comp]);
+    }
+    // Transmit "unknown" attributes.
+    OpalElement::updateUnknown(multT);
+
+    setElement(multT->makeWrappers());
+}
diff --git a/src/Elements/OpalMultipoleTStraight.h b/src/Elements/OpalMultipoleTStraight.h
new file mode 100644
index 0000000000000000000000000000000000000000..956a58eea7e340b3bf1badb6cd6f7f736f208d09
--- /dev/null
+++ b/src/Elements/OpalMultipoleTStraight.h
@@ -0,0 +1,92 @@
+/*
+ *  Copyright (c) 2017, Titus Dascalu
+ *  Copyright (c) 2018, Martin Duy Tat
+ *  All rights reserved.
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. 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.
+ *  3. Neither the name of STFC nor the names of its contributors may be used to
+ *     endorse or promote products derived from this software without specific
+ *     prior written permission.
+ *
+ *  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 COPYRIGHT OWNER 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.
+ */
+
+
+#ifndef OPAL_OPALMULTIPOLET_STRAIGHT_HH
+#define OPAL_OPALMULTIPOLET_STRAIGHT_HH
+
+#include "Elements/OpalElement.h"
+#include "AbsBeamline/MultipoleTStraight.h"
+
+/** OpalMultipoleTStraight provides user interface information for the MultipoleTStraight object
+ *
+ *  Defines input parameters 
+ */
+class OpalMultipoleTStraight: public OpalElement {
+
+public:
+
+    // The attributes of class OpalMultipoleTStraight
+    enum {
+        TP = COMMON,     // Transverse field components
+	RFRINGE,         // Length of right fringe field
+	LFRINGE,         // Length of left fringe field
+	HAPERT,          // Aperture horizontal dimension
+	VAPERT,          // Aperture vertical dimension
+	MAXFORDER,       // Maximum order in the field expansion
+	ROTATION,        // Rotation angle about central axis for skew elements
+	EANGLE,          // Entrance angle
+        BBLENGTH,        // Distance between centre of magnet and entrance
+	SIZE             // size of the enum
+    };
+    
+    /** Default constructor initialises UI parameters. */
+    OpalMultipoleTStraight();
+
+    /** Destructor does nothing */
+    virtual ~OpalMultipoleTStraight();
+
+    /** Inherited copy constructor */
+    virtual OpalMultipoleTStraight *clone(const std::string &name);
+
+    /** Fill in all registered attributes
+     *
+     *  Just calls fillRegisteredAttributes on the base class
+     */
+    virtual void fillRegisteredAttributes(const ElementBase &, ValueFlag);
+
+    /** Update the MultipoleT with new parameters from UI parser */
+    virtual void update();
+
+    void print(std::ostream &os) const;
+
+  private:
+    // Not implemented.
+    OpalMultipoleTStraight(const OpalMultipoleTStraight &);
+    void operator=(const OpalMultipoleTStraight &);
+
+    // Clone constructor.
+    OpalMultipoleTStraight(const std::string &name, OpalMultipoleTStraight *parent);
+};
+
+#endif
+   
+ 
+  
+
+
diff --git a/src/OpalConfigure/Configure.cpp b/src/OpalConfigure/Configure.cpp
index 93d0c942469c6293be4a998ae26a6d5b070fb3c0..e22e8890207201ee91dd40b750cffa5d87b1be99 100644
--- a/src/OpalConfigure/Configure.cpp
+++ b/src/OpalConfigure/Configure.cpp
@@ -104,6 +104,10 @@
 #include "Elements/OpalMarker.h"
 #include "Elements/OpalMonitor.h"
 #include "Elements/OpalMultipole.h"
+#include "Elements/OpalMultipoleT.h"
+#include "Elements/OpalMultipoleTStraight.h"
+#include "Elements/OpalMultipoleTCurvedConstRadius.h"
+#include "Elements/OpalMultipoleTCurvedVarRadius.h"
 #include "Elements/OpalOctupole.h"
 #include "Elements/OpalOffset/OpalLocalCartesianOffset.h"
 #include "Elements/OpalOffset/OpalLocalCylindricalOffset.h"
@@ -253,6 +257,10 @@ namespace {
         opal->create(new OpalMarker());
         opal->create(new OpalMonitor());
         opal->create(new OpalMultipole());
+        opal->create(new OpalMultipoleT());
+        opal->create(new OpalMultipoleTStraight());
+        opal->create(new OpalMultipoleTCurvedConstRadius());
+        opal->create(new OpalMultipoleTCurvedVarRadius());
         opal->create(new OpalOctupole());
         opal->create(new OpalOffset::OpalLocalCartesianOffset());
 //        opal->create(new OpalOffset::OpalLocalCylindricalOffset());
@@ -298,4 +306,4 @@ namespace Configure {
         makeActions();
         Versions::fillChanges();
     }
-};
\ No newline at end of file
+};
diff --git a/src/OpalConfigure/Configure.h b/src/OpalConfigure/Configure.h
index 0ff434be8a9b6f1b6a70be1e2fcea35d8f4cddce..aea42345f87100646eef2acb34c1ae7cdc6e502c 100644
--- a/src/OpalConfigure/Configure.h
+++ b/src/OpalConfigure/Configure.h
@@ -1,5 +1,5 @@
 #ifndef OPAL_Configure_HH
-#define OPAL_Configure_HH 1
+#define OPAL_Configure_HH
 
 // ------------------------------------------------------------------------
 // $RCSfile: Configure.h,v $
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index cc53315c7e02d0909e51af44d64fd8e926ec8810..e4d61e2ce15c743251339a93ec6dbf5757ad2af9 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -69,3 +69,12 @@ target_link_libraries (
 	${GTEST_BOTH_LIBRARIES}
 	-lpthread
 )
+
+#Flags for gprof
+#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
+#SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
+#SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
+################
+#Turn of gcov
+#set(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
+#set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)
diff --git a/tests/classic_src/AbsBeamline/CMakeLists.txt b/tests/classic_src/AbsBeamline/CMakeLists.txt
index 0dfd61844fd510a059dbe1ee7ac09887d378fdae..008588eeedfe86555c2f5a93ef57921e3e232beb 100644
--- a/tests/classic_src/AbsBeamline/CMakeLists.txt
+++ b/tests/classic_src/AbsBeamline/CMakeLists.txt
@@ -2,6 +2,7 @@ set (_SRCS
     DipoleFieldTest.cpp
     MultipoleTTest.cpp
     OffsetTest.cpp
+    PolynomialTest.cpp
     RingTest.cpp
     SBend3DTest.cpp
     ScalingFFAGMagnetTest.cpp
diff --git a/tests/classic_src/AbsBeamline/MultipoleTTest.cpp b/tests/classic_src/AbsBeamline/MultipoleTTest.cpp
index 25d4855302cbac3ee1279058c98475a47360cbfe..07aaf89618bf520c9d96352774de1449c9351f8a 100644
--- a/tests/classic_src/AbsBeamline/MultipoleTTest.cpp
+++ b/tests/classic_src/AbsBeamline/MultipoleTTest.cpp
@@ -1,13 +1,21 @@
 #include "gtest/gtest.h"
+#include "AbsBeamline/Component.h"
 #include "AbsBeamline/MultipoleT.h"
+#include "AbsBeamline/MultipoleTBase.h"
+#include "AbsBeamline/MultipoleTStraight.h"
+#include "AbsBeamline/MultipoleTCurvedConstRadius.h"
+#include "AbsBeamline/MultipoleTCurvedVarRadius.h"
+#include "AbsBeamline/MultipoleTFunctions/CoordinateTransform.h"
+#include "AbsBeamline/EndFieldModel/Tanh.h"
 
 #include "opal_test_utilities/SilenceTest.h"
 
-#include<fstream>
+#include <fstream>
+#include <cmath>
 
 using namespace std;
 
-vector< vector<double> > partialsDerivB(const Vector_t &R,const Vector_t B, double stepSize, MultipoleT* dummyField)
+/*vector< vector<double> > partialsDerivB(const Vector_t &R,const Vector_t B, double stepSize, Component* dummyField)
 {
     // builds a matrix of all partial derivatives of B -> dx_i B_j
     vector< vector<double> > allPartials(3, vector<double>(3));
@@ -26,9 +34,9 @@ vector< vector<double> > partialsDerivB(const Vector_t &R,const Vector_t B, doub
         allPartials[i][j] = (B_next[j] - B_prev[j]) / (2 * stepSize);
     }
      return allPartials;
-}
+}*/
 
-vector< vector<double> > partialsDerivB_5(const Vector_t &R,const Vector_t B, double stepSize, MultipoleT* dummyField)
+vector< vector<double> > partialsDerivB(const Vector_t &R,const Vector_t B, double stepSize, Component* dummyField)
 {
     // builds a matrix of all partial derivatives of B -> dx_i B_j
     vector< vector<double> > allPartials(3, vector<double>(3));
@@ -53,7 +61,7 @@ vector< vector<double> > partialsDerivB_5(const Vector_t &R,const Vector_t B, do
     return allPartials;
 }
 
-double calcDivB(Vector_t &R, Vector_t B, double stepSize, MultipoleT* dummyField )
+double calcDivB(Vector_t &R, Vector_t B, double stepSize, Component* dummyField )
 {
     double div = 0;
     vector< vector<double> > partials (3, vector<double>(3));
@@ -63,7 +71,7 @@ double calcDivB(Vector_t &R, Vector_t B, double stepSize, MultipoleT* dummyField
     return div;
 }
 
-vector<double> calcCurlB(Vector_t &R, Vector_t B, double stepSize, MultipoleT* dummyField)
+vector<double> calcCurlB(Vector_t &R, Vector_t B, double stepSize, Component* dummyField)
 {
     vector<double> curl(3);
     vector< vector<double> > partials(3, vector<double>(3));
@@ -74,7 +82,7 @@ vector<double> calcCurlB(Vector_t &R, Vector_t B, double stepSize, MultipoleT* d
     return curl;
 }
 
-TEST(MultipoleTTest, Field)
+TEST(MultipoleTTest, DISABLED_Field)
 {
     OpalTestUtilities::SilenceTest silencer;
 
@@ -109,6 +117,7 @@ TEST(MultipoleTTest, Field)
         }
     }
     fout.close();
+    delete myMagnet;
 }
 
 TEST(MultipoleTTest, Maxwell) {
@@ -118,7 +127,7 @@ TEST(MultipoleTTest, Maxwell) {
     double centralField = 5;
     double fringeLength = 0.5;
     // the highest differential in the fringe field
-    double max_index = 20;
+    double max_index = 0.5;
     //Set the magnet
     myMagnet->setFringeField(centralField, fringeLength, max_index);
     myMagnet->setTransMaxOrder(1);
@@ -129,8 +138,8 @@ TEST(MultipoleTTest, Maxwell) {
     myMagnet->setMaxOrder(3);
     //ofstream fout("Quad_CurlB_off");
     Vector_t R(0., 0., 0.), P(3), E(3);
-    double t = 0., x, z, s, stepSize= 1e-6;
-    for(x = 0.0; x <= 0.0 ; x += 0.001) {
+    double t = 0., x, z, s, stepSize= 1e-7;
+    for(x = -0.2; x <= 0.2 ; x += 0.1) {
         for(z = 0.0; z <= 0.02 ; z += 0.001) {
             for(s = -10; s <= 10 ; s += 0.1) {
                 R[0] = x;
@@ -138,62 +147,207 @@ TEST(MultipoleTTest, Maxwell) {
                 R[2] = s;
                 Vector_t B(0., 0., 0.);
                 myMagnet->apply(R, P, t, E, B);
-                double div = 0.;
-                div = calcDivB(R, B, stepSize, myMagnet);
+                double div = calcDivB(R, B, stepSize, myMagnet);
+                vector<double> curl = calcCurlB(R, B, stepSize, myMagnet);
                 EXPECT_NEAR(div, 0.0, 0.01);
-                vector<double> curl;
-                curl = calcCurlB(R, B, stepSize, myMagnet);
                 EXPECT_NEAR(curl[0], 0.0, 1e-4);
                 EXPECT_NEAR(curl[1], 0.0, 1e-4);
                 EXPECT_NEAR(curl[2], 0.0, 1e-4);
             }
         }
     }
+    delete myMagnet;
 }
 
 TEST(MultipoleTTest, CurvedMagnet) {
     OpalTestUtilities::SilenceTest silencer;
-
+    
     MultipoleT* myMagnet = new MultipoleT("Combined function");
-    myMagnet->setLength(4.0);
-    myMagnet->setBendAngle(0.0); // BUG small, non-zero bend angle ruins the convergence
-    myMagnet->setAperture(0.4, 0.4);
-    myMagnet->setFringeField(2, 0.5, 0.5);
+    myMagnet->setLength(4.4);
+    myMagnet->setBoundingBoxLength(0.0);
+    myMagnet->setBendAngle(0.628);
+    myMagnet->setAperture(3.5, 3.5);
+    myMagnet->setFringeField(2.2, 0.3, 0.3);
     myMagnet->setVarRadius();
-    myMagnet->setVarStep(0.1);
     myMagnet->setTransMaxOrder(1);
-    myMagnet->setMaxOrder(4);
     myMagnet->setRotation(0.0);
     myMagnet->setEntranceAngle(0.0);
-    myMagnet->setTransProfile(0, 10);
+    myMagnet->setTransProfile(0, 1);
     myMagnet->setTransProfile(1, 1);
-    double t=0., x, y, z;
-    double stepSize = 1e-7;
+    myMagnet->setMaxXOrder(10);
+    myMagnet->setMaxOrder(5);
+    double t = 0.0;
+    double stepSize = 1e-3;
+    double x[21] = {-1.12, -0.99, -0.86, -0.77, -0.65, -0.53, -0.42, -0.29, -0.19, -0.11, -0.039, 0.00, -0.030, -0.12, -0.26, -0.40, -0.56, -0.72, -0.86, -0.96, -1.12};
+    double y[21] = {-2.74, -2.58, -2.30, -2.27, -2.00, -1.83, -1.62, -1.45, -1.13, -0.87, 0.53, 0.00, 0.46, 0.90, 1.36, 1.60, 1.83, 2.17, 2.30, 2.45, 2.77};
+    double z = 0.2;
     Vector_t R(0.0, 0.0, 0.0), P(3), E(3);
-    for(x = -0.2 ; x <= 0.2 ; x += 0.1) {
-        for(y = -6.0; y <= 6.0 ; y += 1.0) {
-            for(z = 0.1; z <= 0.101 ; z += 0.1) {
-                R[0] = x;
-                R[1] = z;
-                R[2] = y;
-                Vector_t B(0., 0., 0.);
-                myMagnet->apply(R, P, t, E, B);
-                double div = calcDivB(R, B, stepSize, myMagnet);
-                vector<double> curl = calcCurlB(R, B, stepSize, myMagnet);
-                double curlMag = 0.0;
-                curlMag += gsl_sf_pow_int(curl[0], 2.0);
-                curlMag += gsl_sf_pow_int(curl[1], 2.0);
-                curlMag += gsl_sf_pow_int(curl[2], 2.0);
-                curlMag = sqrt(curlMag);
-                //if (sqrt(gsl_sf_pow_int(B[0], 2) + gsl_sf_pow_int(B[1], 2) + gsl_sf_pow_int(B[2], 2)) != 0) {
-                //  abs /= sqrt(gsl_sf_pow_int(B[0], 2) + gsl_sf_pow_int(B[1], 2) + gsl_sf_pow_int(B[2], 2));
-                //}
-                EXPECT_NEAR(curlMag+fabs(div), 0, 1e-6)
-                     << "R: " << x << " " << y << " " << z
-                     << " B: " << B[0] << " " << B[1] << " " << B[2]
-                     << " Del: " << div << " " << curlMag << std::endl;
-            }
+    for (int n = 0; n < 21; n++) {
+        R[0] = x[n];
+        R[1] = z;
+        R[2] = y[n];
+        Vector_t B(0., 0., 0.);
+        myMagnet->apply(R, P, t, E, B);
+        double div = calcDivB(R, B, stepSize, myMagnet);
+        vector<double> curl = calcCurlB(R, B, stepSize, myMagnet);
+        double curlMag = 0.0;
+        curlMag += gsl_sf_pow_int(curl[0], 2.0);
+        curlMag += gsl_sf_pow_int(curl[1], 2.0);
+        curlMag += gsl_sf_pow_int(curl[2], 2.0);
+        curlMag = sqrt(curlMag);
+        coordinatetransform::CoordinateTransform t(x[n], z, y[n], 2.2, 0.3, 0.3, 4.4 / 0.628);
+        std::vector<double> r = t.getTransformation();
+        EXPECT_NEAR(div, 0, 5e-3)
+                     << "R: " << r[0] << " " << r[1] << " " << r[2] << std::endl
+                     << "R: " << x[n] << " " << z << " " << y[n] << std::endl
+                     << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                     << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
+        EXPECT_NEAR(curlMag, 0, 1e-9)
+                     << "R: " << r[0] << " " << r[1] << " " << r[2] << std::endl
+                     << "R: " << x[n] << " " << z << " " << y[n] << std::endl
+                     << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                     << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
+    }
+    delete myMagnet;
+}
+
+TEST(MultipoleTTest, MultipoleTStraight) {
+    OpalTestUtilities::SilenceTest silencer;
+    
+    MultipoleTStraight* myMagnet = new MultipoleTStraight("Combined function");
+    myMagnet->setLength(4.4);
+    myMagnet->setAperture(3.5, 3.5);
+    myMagnet->setFringeField(2.2, 0.3, 0.3);
+    myMagnet->setTransMaxOrder(1);
+    myMagnet->setRotation(0.0);
+    myMagnet->setEntranceAngle(0.0);
+    myMagnet->setTransProfile(0, 1);
+    myMagnet->setTransProfile(1, 1);
+    myMagnet->setMaxOrder(5);
+    double t = 0.0;
+    double stepSize = 1e-3;
+    double z = -0.3;
+    Vector_t R(0.0, 0.0, 0.0), P(3), E(3);
+    for (double x = -0.3; x <= 0.300001; x += 0.1) {
+        for (double y = -3.0; y <= 3.00001; y += 1.0) {
+            R[0] = x;
+            R[1] = z;
+            R[2] = y;
+            Vector_t B(0., 0., 0.);
+            myMagnet->apply(R, P, t, E, B);
+            double div = calcDivB(R, B, stepSize, myMagnet);
+            vector<double> curl = calcCurlB(R, B, stepSize, myMagnet);
+            double curlMag = 0.0;
+            curlMag += gsl_sf_pow_int(curl[0], 2.0);
+            curlMag += gsl_sf_pow_int(curl[1], 2.0);
+            curlMag += gsl_sf_pow_int(curl[2], 2.0);
+            curlMag = sqrt(curlMag);
+            EXPECT_NEAR(div, 0, 1e-6)
+                << "R: " << x << " " << z << " " << y << std::endl
+                << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
+            EXPECT_NEAR(curlMag, 0, 1e-9)
+                << "R: " << x << " " << z << " " << y << std::endl
+                << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
         }
     }
+    delete myMagnet;
+}
+
+TEST(MultipoleTTest, MultipoleTCurvedConstRadius) {
+    OpalTestUtilities::SilenceTest silencer;
+    
+    MultipoleTCurvedConstRadius* myMagnet = new MultipoleTCurvedConstRadius("Combined function");
+    myMagnet->setLength(4.4);
+    myMagnet->setBendAngle(0.628);
+    myMagnet->setAperture(3.5, 3.5);
+    myMagnet->setFringeField(2.2, 0.3, 0.3);
+    myMagnet->setTransMaxOrder(1);
+    myMagnet->setRotation(0.0);
+    myMagnet->setEntranceAngle(0.0);
+    myMagnet->setTransProfile(0, 1);
+    myMagnet->setTransProfile(1, 1);
+    myMagnet->setMaxXOrder(20);
+    myMagnet->setMaxOrder(5);
+    double t = 0.0;
+    double stepSize = 1e-3;
+    double radius = 4.4 / 0.628;
+    double z = 0.2;
+    Vector_t R(0.0, 0.0, 0.0), P(3), E(3);
+    for (double theta = 0; theta <= 0.3001; theta += 0.2) {
+        double x = radius * cos(theta) - radius;
+        double y = radius * sin(theta);
+        for (double delta = -0.3; delta <= 0.3001; delta += 0.02) {
+            Vector_t R(0.0, 0.0, 0.0), P(3), E(3);
+            R[0] = x + delta * cos(theta);
+            R[1] = z;
+            R[2] = y + delta * sin(theta);
+            Vector_t B(0., 0., 0.);
+            myMagnet->apply(R, P, t, E, B);
+            double div = calcDivB(R, B, stepSize, myMagnet);
+            vector<double> curl = calcCurlB(R, B, stepSize, myMagnet);
+            double curlMag = 0.0;
+            curlMag += gsl_sf_pow_int(curl[0], 2.0);
+            curlMag += gsl_sf_pow_int(curl[1], 2.0);
+            curlMag += gsl_sf_pow_int(curl[2], 2.0);
+            curlMag = sqrt(curlMag);
+            EXPECT_NEAR(div, 0, 5e-6)
+                     << "R: " << delta << " " << z << " " << radius * theta << std::endl
+                     << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                     << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
+            EXPECT_NEAR(curlMag, 0, 1e-9)
+                     << "R: " << delta << " " << z << " " << radius * theta << std::endl
+                     << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                     << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
+        }
+    }
+    delete myMagnet;
+}
+
+TEST(MultipoleTTest, MultipoleTCurvedVarRadius) {
+    OpalTestUtilities::SilenceTest silencer;
+    
+    MultipoleTCurvedVarRadius* myMagnet = new MultipoleTCurvedVarRadius("Combined function");
+    myMagnet->setLength(4.4);
+    myMagnet->setBendAngle(0.628);
+    myMagnet->setAperture(3.5, 3.5);
+    myMagnet->setFringeField(2.2, 0.3, 0.3);
+    myMagnet->setTransMaxOrder(1);
+    myMagnet->setRotation(0.0);
+    myMagnet->setEntranceAngle(0.0);
+    myMagnet->setTransProfile(0, 1);
+    myMagnet->setTransProfile(1, 1);
+    myMagnet->setMaxXOrder(10);
+    myMagnet->setMaxOrder(5);
+    double t = 0.0;
+    double stepSize = 1e-3;
+    double x[21] = {-1.12, -0.99, -0.86, -0.77, -0.65, -0.53, -0.42, -0.29, -0.19, -0.11, -0.039, 0.00, -0.030, -0.12, -0.26, -0.40, -0.56, -0.72, -0.86, -0.96, -1.12};
+    double y[21] = {-2.74, -2.58, -2.30, -2.27, -2.00, -1.83, -1.62, -1.45, -1.13, -0.87, 0.53, 0.00, 0.46, 0.90, 1.36, 1.60, 1.83, 2.17, 2.30, 2.45, 2.77};
+    double z = 0.2;
+    Vector_t R(0.0, 0.0, 0.0), P(3), E(3);
+    for (int n = 0; n < 21; n++) {
+        R[0] = x[n];
+        R[1] = z;
+        R[2] = y[n];
+        Vector_t B(0., 0., 0.);
+        myMagnet->apply(R, P, t, E, B);
+        double div = calcDivB(R, B, stepSize, myMagnet);
+        vector<double> curl = calcCurlB(R, B, stepSize, myMagnet);
+        double curlMag = 0.0;
+        curlMag += gsl_sf_pow_int(curl[0], 2.0);
+        curlMag += gsl_sf_pow_int(curl[1], 2.0);
+        curlMag += gsl_sf_pow_int(curl[2], 2.0);
+        curlMag = sqrt(curlMag);
+        EXPECT_NEAR(div, 0, 5e-3)
+                     << "R: " << x[n] << " " << z << " " << y[n] << std::endl
+                     << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                     << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
+        EXPECT_NEAR(curlMag, 0, 1e-9)
+                     << "R: " << x[n] << " " << z << " " << y[n] << std::endl
+                     << "B: " << B[0] << " " << B[1] << " " << B[2] << std::endl
+                     << "Del: " << div << " " << curl[0] << " " << curl[1] << " " << curl[2] << std::endl;
+    }
+    delete myMagnet;
+}
 
-}
\ No newline at end of file
diff --git a/tests/classic_src/AbsBeamline/PolynomialTest.cpp b/tests/classic_src/AbsBeamline/PolynomialTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..396a392ef4d29d3e21a8ca9093dc9644bf2d44c9
--- /dev/null
+++ b/tests/classic_src/AbsBeamline/PolynomialTest.cpp
@@ -0,0 +1,573 @@
+#include "gtest/gtest.h"
+#include "opal_test_utilities/SilenceTest.h"
+#include "AbsBeamline/MultipoleTFunctions/Polynomial.h"
+#include "AbsBeamline/MultipoleTFunctions/DifferentialOperator.h"
+#include "AbsBeamline/MultipoleTFunctions/RecursionRelation.h"
+#include "AbsBeamline/MultipoleTFunctions/TwoPolynomial.h"
+#include "AbsBeamline/MultipoleTFunctions/PolynomialSum.h"
+#include "AbsBeamline/MultipoleTFunctions/DifferentialOperatorTwo.h"
+#include "AbsBeamline/MultipoleTFunctions/RecursionRelationTwo.h"
+#include "AbsBeamline/MultipoleTFunctions/tanhDeriv.h"
+#include <vector>
+
+TEST(PolynomialTest, Polynomial) {
+    OpalTestUtilities::SilenceTest silencer;
+    std::vector<int> vec1, vec2;
+    for (int i = 0; i < 10; i++) {
+        vec1.push_back(i);
+        vec2.push_back(1);
+    }
+    polynomial::Polynomial poly1, poly2(vec1);
+    polynomial::Polynomial poly3(poly2);
+    polynomial::Polynomial poly4;
+    poly4 = poly3;
+    polynomial::Polynomial poly5(vec2);
+    for (int i = 0; i < 10; i++) {
+        EXPECT_EQ(poly1.getCoefficient(i), 0);
+        EXPECT_EQ(poly2.getCoefficient(i), i);
+        EXPECT_EQ(poly3.getCoefficient(i), i);
+        EXPECT_EQ(poly4.getCoefficient(i), i);
+        EXPECT_EQ(poly5.getCoefficient(i), 1);
+    }
+    poly2.differentiatePolynomial();
+    poly3.multiplyPolynomial(poly1);
+    poly4.addPolynomial(poly4);
+    poly5.multiplyPolynomial(poly5);
+    for (int i = 0; i < 9; i++) {
+        EXPECT_EQ(poly1.getCoefficient(i), 0);
+        EXPECT_EQ(poly2.getCoefficient(i), (i + 1) * (i + 1));
+        EXPECT_EQ(poly3.getCoefficient(i), 0);
+        EXPECT_EQ(poly4.getCoefficient(i), 2 * i);
+        EXPECT_EQ(poly5.getCoefficient(i), i + 1);
+    }
+    EXPECT_EQ(poly1.getMaxXorder(), 0);
+    EXPECT_EQ(poly2.getMaxXorder(), 8);
+    EXPECT_EQ(poly3.getMaxXorder(), 0);
+    poly3.setMaxXorder(17);
+    EXPECT_EQ(poly3.getMaxXorder(), 17);
+    EXPECT_EQ(poly4.getMaxXorder(), 9);
+    EXPECT_EQ(poly5.getMaxXorder(), 18);
+    poly2.setZero();
+    EXPECT_EQ(poly2.getMaxXorder(), 0);
+    poly5.truncate(5);
+    EXPECT_EQ(poly5.getMaxXorder(), 5);
+    EXPECT_NEAR(poly5.evaluatePolynomial(0.2), 1.56192, 1e-5);
+    poly2.setCoefficient(18, 2);
+    EXPECT_EQ(poly2.getCoefficient(2), 18);
+    poly2.setCoefficient(7, 6);
+    EXPECT_EQ(poly2.getCoefficient(6), 7);
+    poly2.setCoefficient(0, 0);
+    EXPECT_EQ(poly2.getCoefficient(0), 0);
+}
+
+TEST(PolynomialTest, DifferentialOperator) {
+    OpalTestUtilities::SilenceTest silencer;
+    std::vector<int> vec1, vec2;
+    for (int i = 0; i < 2; i++) {
+        vec1.push_back(i + 1);
+        vec2.push_back(1);
+    }
+    polynomial::DifferentialOperator op1, op2(1, 1), op3(1, 1);
+    polynomial::DifferentialOperator op4(op3), op5;
+    op5 = op4;
+    for (int i = 0; i < 2; i++) {
+        EXPECT_EQ((int)op1.getXDerivatives(), 0);
+        EXPECT_EQ((int)op1.getSDerivatives(), 0);
+        EXPECT_EQ((int)op2.getXDerivatives(), 1);
+        EXPECT_EQ((int)op2.getXDerivatives(), 1);
+        EXPECT_EQ((int)op3.getXDerivatives(), 1);
+        EXPECT_EQ((int)op3.getXDerivatives(), 1);
+        EXPECT_EQ((int)op4.getXDerivatives(), 1);
+        EXPECT_EQ((int)op4.getXDerivatives(), 1);
+        EXPECT_EQ((int)op5.getXDerivatives(), 1);
+        EXPECT_EQ((int)op5.getXDerivatives(), 1);
+    }
+    op3.resizeX(5);
+    EXPECT_EQ((int)op3.getXDerivatives(), 5);
+    op3.resizeS(6);
+    EXPECT_EQ((int)op3.getSDerivatives(), 6);
+    op2.setPolynomial(vec1, 1, 1);
+    op2.setPolynomial(vec2, 0, 0);
+    EXPECT_TRUE(op2.isPolynomialZero(1, 0));
+    EXPECT_TRUE(op2.isPolynomialZero(0, 1));
+    op2.addOperator(op2);
+    EXPECT_TRUE(op2.isPolynomialZero(1, 0));
+    EXPECT_TRUE(op2.isPolynomialZero(0, 1));
+    op5 = op2;
+    op1 = op2;
+    op2.differentiateX();
+    op5.doubleDifferentiateS();
+    EXPECT_NEAR(op2.evaluatePolynomial(0.2, 0, 0), 2.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(0.2, 0, 1), 0.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(0.2, 1, 0), 2.4, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(0.2, 1, 1), 4.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(0.2, 1, 2), 0.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(0.2, 2, 1), 2.8, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(0.2, 0, 0), 0.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(0.2, 0, 1), 2.4, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(0.2, 1, 0), 0.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(0.2, 1, 1), 0.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(0.2, 2, 1), 0.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(0.2, 1, 2), 2.8, 1e-6);
+    std::vector<int> vec3;
+    vec3.resize(10);
+    vec3[9] = 10;
+    vec3[8] = 1;
+    polynomial::DifferentialOperator op6;
+    op6.setPolynomial(vec3, 2, 2);
+    EXPECT_NEAR(op6.evaluatePolynomial(1.2, 2, 2), 55.897620, 1e-6);
+    op6.truncate(8);
+    EXPECT_NEAR(op6.evaluatePolynomial(1.2, 2, 2), 4.299817, 1e-6);
+}
+
+TEST (PolynomialTest, RecursionRelation) {
+    polynomial::RecursionRelation r1, r2(1, 4);
+    polynomial::RecursionRelation r3(r2), r4, r5(1, 4);
+    r4 = r3;
+    r5.applyOperator();
+    EXPECT_EQ(r1.getMaxXDerivatives(), 0);
+    EXPECT_EQ(r1.getMaxSDerivatives(), 0);
+    EXPECT_EQ(r2.getMaxXDerivatives(), 2);
+    EXPECT_EQ(r2.getMaxSDerivatives(), 1);
+    EXPECT_EQ(r3.getMaxXDerivatives(), 2);
+    EXPECT_EQ(r3.getMaxSDerivatives(), 1);
+    EXPECT_EQ(r4.getMaxXDerivatives(), 2);
+    EXPECT_EQ(r4.getMaxSDerivatives(), 1);
+    EXPECT_EQ(r5.getMaxXDerivatives(), 4);
+    EXPECT_EQ(r5.getMaxSDerivatives(), 2);
+    EXPECT_NEAR(r4.evaluatePolynomial(0.2, 1, 0), 0.8336, 1e-6);
+    r4.truncate(2);
+    EXPECT_NEAR(r4.evaluatePolynomial(0.2, 1, 0), 0.84, 1e-6);
+    r5.truncate(2);
+    EXPECT_NEAR(r5.evaluatePolynomial(0.2, 4, 0), 1.0, 1e-6);
+    r5.truncate(1);
+    EXPECT_NEAR(r5.evaluatePolynomial(0.2, 2, 1), 1.2, 1e-6);
+    EXPECT_TRUE(r2.isPolynomialZero(0, 0));
+    EXPECT_TRUE(r2.isPolynomialZero(2, 1));
+    EXPECT_TRUE(r2.isPolynomialZero(1, 1));
+    r2.resizeX(4);
+    EXPECT_EQ(r2.getMaxXDerivatives(), 4);
+    r2.resizeS(7);
+    EXPECT_EQ(r2.getMaxSDerivatives(), 7);
+    r3.differentiateX();
+    EXPECT_NEAR(r3.evaluatePolynomial(0.2, 3, 0), 1.0, 1e-6);
+    EXPECT_NEAR(r3.evaluatePolynomial(0.2, 1, 1), 0.696, 1e-6);
+}
+
+TEST (PolynomialTest, TwoPolynomial) {
+    std::vector<int> v1, v2, v3;
+    std::vector<std::size_t> vv1, vv2, vv3;
+    v1.resize(3);
+    v2.resize(3);
+    v3.resize(3);
+    vv1.resize(3);
+    vv2.resize(3);
+    vv3.resize(3);
+    for (int i = 0; i < 3; i++) {
+        v1[i] = i + 1;
+        vv1[i] = i + 1;
+        v2[i] = (i + 1) * (i + 1);
+        vv2[i] = (i + 1) * (i + 1);
+        v3[i] = 0;
+        vv3[i] = 0;
+    }
+    std::vector<std::vector<int>> vec1, vec2;
+    polynomial::TwoPolynomial zpoly(vec1);
+    EXPECT_EQ(zpoly.getMaxXorder(), 0);
+    EXPECT_EQ(zpoly.getMaxSorder(), 0);
+    EXPECT_EQ(zpoly.getCoefficient(0, 0), 0);
+    vec1.resize(2);
+    vec2.resize(3);
+    polynomial::TwoPolynomial zpoly2(vec1);
+    EXPECT_EQ(zpoly2.getMaxXorder(), 0);
+    EXPECT_EQ(zpoly2.getMaxSorder(), 0);
+    EXPECT_EQ(zpoly2.getCoefficient(0, 0), 0);
+    vec1[0] = v1;
+    EXPECT_THROW({polynomial::TwoPolynomial dummy(vec1);}, std::length_error);
+    vec1[1] = v2;
+    vec2[0] = v3;
+    vec2[1] = v2;
+    vec2[2] = v1;
+    polynomial::TwoPolynomial poly1, poly2(vec1), poly3(vec2);
+    polynomial::TwoPolynomial poly4(poly2), poly5;
+    poly5 = poly3;
+    EXPECT_TRUE(poly1.isZero());
+    EXPECT_EQ(poly1.getCoefficient(0, 0), 0);
+    EXPECT_EQ(poly2.getCoefficient(0, 0), 1);
+    EXPECT_EQ(poly3.getCoefficient(0, 0), 0);
+    EXPECT_EQ(poly4.getCoefficient(0, 0), 1);
+    EXPECT_EQ(poly5.getCoefficient(0, 0), 0);
+    EXPECT_EQ(poly1.getCoefficient(0, 1), 0);
+    EXPECT_EQ(poly2.getCoefficient(0, 1), 2);
+    EXPECT_EQ(poly3.getCoefficient(0, 1), 0);
+    EXPECT_EQ(poly4.getCoefficient(0, 1), 2);
+    EXPECT_EQ(poly5.getCoefficient(0, 1), 0);
+    EXPECT_EQ(poly1.getCoefficient(1, 0), 0);
+    EXPECT_EQ(poly2.getCoefficient(1, 0), 1);
+    EXPECT_EQ(poly3.getCoefficient(1, 0), 1);
+    EXPECT_EQ(poly4.getCoefficient(1, 0), 1);
+    EXPECT_EQ(poly5.getCoefficient(1, 0), 1);
+    EXPECT_EQ(poly1.getCoefficient(1, 1), 0);
+    EXPECT_EQ(poly2.getCoefficient(1, 1), 4);
+    EXPECT_EQ(poly3.getCoefficient(1, 1), 4);
+    EXPECT_EQ(poly4.getCoefficient(1, 1), 4);
+    EXPECT_EQ(poly5.getCoefficient(1, 1), 4);
+    EXPECT_EQ(poly1.getCoefficient(0, 2), 0);
+    EXPECT_EQ(poly2.getCoefficient(0, 2), 3);
+    EXPECT_EQ(poly3.getCoefficient(0, 2), 0);
+    EXPECT_EQ(poly4.getCoefficient(0, 2), 3);
+    EXPECT_EQ(poly5.getCoefficient(0, 2), 0);
+    EXPECT_EQ(poly1.getCoefficient(1, 2), 0);
+    EXPECT_EQ(poly2.getCoefficient(1, 2), 9);
+    EXPECT_EQ(poly3.getCoefficient(1, 2), 9);
+    EXPECT_EQ(poly4.getCoefficient(1, 2), 9);
+    EXPECT_EQ(poly5.getCoefficient(1, 2), 9);
+    EXPECT_EQ(poly3.getCoefficient(2, 2), 3);
+    EXPECT_EQ(poly5.getCoefficient(2, 2), 3);
+    EXPECT_EQ(poly3.getCoefficient(2, 1), 2);
+    EXPECT_EQ(poly5.getCoefficient(2, 1), 2);
+    poly1.setCoefficient(6, 2, 0);
+    EXPECT_NEAR(poly1.evaluatePolynomial(2, 0), 24.0, 1e-6);
+    EXPECT_NEAR(poly1.evaluatePolynomial(0, 2), 0.0, 1e-6);
+    poly1.multiplyConstant(4);
+    EXPECT_NEAR(poly1.evaluatePolynomial(2, 0), 96.0, 1e-6);
+    EXPECT_NEAR(poly1.evaluatePolynomial(0, 2), 0.0, 1e-6);
+    poly2.multiplyPolynomial(poly1);
+    EXPECT_EQ(poly2.getCoefficient(0, 0), 0);
+    EXPECT_EQ(poly2.getCoefficient(2, 0), 24);
+    EXPECT_EQ(poly2.getCoefficient(2, 2), 72);
+    EXPECT_EQ(poly2.getCoefficient(3, 1), 96);
+    EXPECT_EQ(poly2.getMaxXorder(), 3);
+    EXPECT_EQ(poly2.getMaxSorder(), 2);
+    poly2.setMaxXorder(6);
+    poly2.setMaxSorder(7);
+    EXPECT_EQ(poly2.getMaxXorder(), 6);
+    EXPECT_EQ(poly2.getMaxSorder(), 7);
+    EXPECT_EQ(poly3.getNdSfactors(), 0);
+    poly5.differentiateX();
+    EXPECT_EQ(poly5.getCoefficient(0, 1), 4);
+    EXPECT_EQ(poly5.getCoefficient(1, 1), 4);
+    EXPECT_EQ(poly5.getCoefficient(0, 2), 9);
+    EXPECT_EQ(poly5.getCoefficient(1, 0), 2);
+    poly3.differentiateS();
+    EXPECT_EQ(poly3.getCoefficient(0, 1), 0);
+    EXPECT_EQ(poly3.getCoefficient(1, 1), 18);
+    EXPECT_EQ(poly3.getCoefficient(0, 2), 0);
+    EXPECT_EQ(poly3.getCoefficient(1, 0), 4);
+    EXPECT_EQ(poly3.getNdSfactors(), 1);
+    poly3.setdSfactors(vv1);
+    EXPECT_EQ(poly3.getNdSfactors(), 3);
+    std::vector<std::size_t> v4 = poly3.getdSfactors();
+    EXPECT_EQ(v4[0], 1);
+    EXPECT_EQ(v4[1], 2);
+    EXPECT_EQ(v4[2], 3);
+    poly3.setZero();
+    EXPECT_TRUE(poly3.isZero());
+    EXPECT_NEAR(poly2.evaluatePolynomial(2, 1), 3264.0, 1e-6);
+    EXPECT_NEAR(poly2.evaluatePolynomial(1, 2), 1488.0, 1e-6);
+    EXPECT_NEAR(poly2.evaluatePolynomial(2, 2), 10272.0, 1e-6);
+    poly2.truncate(2);
+    EXPECT_NEAR(poly2.evaluatePolynomial(2, 1), 576.0, 1e-6);
+    EXPECT_NEAR(poly2.evaluatePolynomial(1, 2), 408.0, 1e-6);
+    EXPECT_NEAR(poly2.evaluatePolynomial(2, 2), 1632.0, 1e-6);
+    polynomial::TwoPolynomial greater, smaller;
+    greater.setdSfactors(vv2);
+    smaller.setdSfactors(vv1);
+    EXPECT_TRUE(smaller < greater);
+    EXPECT_FALSE(smaller == greater);
+    greater.setdSfactors(vv1);
+    smaller.setdSfactors(vv3);
+    EXPECT_TRUE(smaller < greater);
+    EXPECT_FALSE(smaller == greater);
+    greater.setdSfactors(vv2);
+    smaller.setdSfactors(vv3);
+    EXPECT_TRUE(smaller < greater);
+    EXPECT_FALSE(smaller == greater);
+    greater.setdSfactors(vv2);
+    smaller.setdSfactors(vv2);
+    EXPECT_FALSE(smaller < greater);
+    EXPECT_TRUE(smaller == greater);
+    greater.setdSfactors(vv2);
+    smaller.setdSfactors(vv2);
+    EXPECT_FALSE(greater < smaller);
+    EXPECT_TRUE(smaller == greater);
+    vv2[2] = 1000;
+    greater.setdSfactors(vv2);
+    EXPECT_TRUE(smaller < greater);
+}
+
+TEST (PolynomialTest, PolynomialSum) {
+    std::vector<int> v1, v2, v3;
+    v1.resize(3);
+    v2.resize(3);
+    v3.resize(3);
+    for (int i = 0; i < 3; i++) {
+        v1[i] = i + 1;
+        v2[i] = (i + 1) * (i + 1);
+        v3[i] = 0;
+    }
+    std::vector<std::vector<int>> vec1, vec2;
+    vec1.resize(2);
+    vec2.resize(3);
+    vec1[0] = v1;
+    vec1[1] = v2;
+    vec2[0] = v3;
+    vec2[1] = v2;
+    vec2[2] = v1;
+    polynomial::TwoPolynomial poly1, poly2(vec1), poly3(vec2);
+    polynomial::TwoPolynomial poly4(poly2), poly5;
+    poly5 = poly3;
+    poly1.setCoefficient(6, 2, 0);
+    polynomial::PolynomialSum sum1, sum2(poly2), sum3(poly3);
+    polynomial::PolynomialSum sum4(sum2), sum5;
+    sum5 = sum3;
+    EXPECT_TRUE(sum1.isPolynomialZero(1));
+    EXPECT_TRUE(sum2.isPolynomialZero(1));
+    EXPECT_TRUE(sum3.isPolynomialZero(2));
+    EXPECT_TRUE(sum4.isPolynomialZero(6));
+    EXPECT_TRUE(sum5.isPolynomialZero(1));
+    EXPECT_NEAR(sum2.evaluatePolynomial(0, 2, 1), 34.0, 1e-6);
+    EXPECT_NEAR(sum2.evaluatePolynomial(0, 1, 2), 62.0, 1e-6);
+    EXPECT_NEAR(sum2.evaluatePolynomial(0, 2, 2), 107.0, 1e-6);
+    EXPECT_NEAR(sum3.evaluatePolynomial(0, 2, 1), 52.0, 1e-6);
+    EXPECT_NEAR(sum3.evaluatePolynomial(0, 1, 2), 62.0, 1e-6);
+    EXPECT_NEAR(sum3.evaluatePolynomial(0, 2, 2), 158.0, 1e-6);
+    sum2.differentiateX();
+    sum3.differentiateS();
+    EXPECT_NEAR(sum2.evaluatePolynomial(0, 2, 1), 14.0, 1e-6);
+    EXPECT_NEAR(sum2.evaluatePolynomial(0, 1, 2), 45.0, 1e-6);
+    EXPECT_NEAR(sum2.evaluatePolynomial(0, 2, 2), 45.0, 1e-6);
+    EXPECT_NEAR(sum3.evaluatePolynomial(0, 2, 1), 76.0, 1e-6);
+    EXPECT_NEAR(sum3.evaluatePolynomial(0, 1, 2), 54.0, 1e-6);
+    EXPECT_NEAR(sum3.evaluatePolynomial(0, 2, 2), 136.0, 1e-6);
+    EXPECT_EQ(sum1.numberOfTerms(), 0);
+    EXPECT_EQ(sum2.numberOfTerms(), 1);
+    EXPECT_EQ(sum3.numberOfTerms(), 1);
+    EXPECT_EQ(sum4.numberOfTerms(), 1);
+    EXPECT_EQ(sum5.numberOfTerms(), 1);
+    EXPECT_TRUE(sum2.isPolynomialZero(1));
+    EXPECT_FALSE(sum2.isPolynomialZero(0));
+    EXPECT_TRUE(sum2.isPolynomialZero(3));
+    EXPECT_TRUE(sum3.isPolynomialZero(1));
+    EXPECT_FALSE(sum3.isPolynomialZero(0));
+    std::vector<std::size_t> v4 = sum3.getdSfactors(0);
+    EXPECT_EQ(v4[0], 1);
+    sum3.differentiateS();
+    v4 = sum3.getdSfactors(0);
+    EXPECT_EQ(v4[0], 2);
+    v4 = sum3.getdSfactors(1);
+    EXPECT_EQ(v4[0], 0);
+    EXPECT_EQ(v4[1], 1);
+    sum4.multiplyPolynomial(poly1);
+    EXPECT_NEAR(sum4.evaluatePolynomial(0, 2, 1), 816.0, 1e-6);
+    EXPECT_NEAR(sum4.evaluatePolynomial(0, 1, 2), 372.0, 1e-6);
+    EXPECT_NEAR(sum4.evaluatePolynomial(0, 2, 2), 2568.0, 1e-6);
+    sum4.truncate(2);
+    EXPECT_NEAR(sum4.evaluatePolynomial(0, 2, 1), 144.0, 1e-6);
+    EXPECT_NEAR(sum4.evaluatePolynomial(0, 1, 2), 102.0, 1e-6);
+    EXPECT_NEAR(sum4.evaluatePolynomial(0, 2, 2), 408.0, 1e-6);
+    sum4.addPolynomial(sum5);
+    EXPECT_EQ(sum4.numberOfTerms(), 2);
+    EXPECT_NEAR(sum4.evaluatePolynomial(1, 2, 1), 52.0, 1e-6);
+    EXPECT_NEAR(sum4.evaluatePolynomial(1, 1, 2), 62.0, 1e-6);
+    EXPECT_NEAR(sum4.evaluatePolynomial(1, 2, 2), 158.0, 1e-6);
+}
+
+TEST (PolynomialTest, DifferentialOperatorTwo) {
+    std::vector<double> dSvalues;
+    dSvalues.resize(20, 1.0);
+    std::vector<int> v1, v2, v3;
+    v1.resize(3);
+    v2.resize(3);
+    v3.resize(3);
+    for (int i = 0; i < 3; i++) {
+        v1[i] = i + 1;
+        v2[i] = (i + 1) * (i + 1);
+        v3[i] = 0;
+    }
+    std::vector<std::vector<int>> vec1, vec2;
+    vec1.resize(2);
+    vec2.resize(3);
+    vec1[0] = v1;
+    vec1[1] = v2;
+    vec2[0] = v3;
+    vec2[1] = v2;
+    vec2[2] = v1;
+    polynomial::TwoPolynomial poly1, poly2(vec1), poly3(vec2);
+    polynomial::TwoPolynomial poly4(poly2), poly5;
+    poly5 = poly3;
+    poly1.setCoefficient(6, 2, 0);
+    polynomial::PolynomialSum sum1, sum2(poly2), sum3(poly3);
+    polynomial::PolynomialSum sum4(sum2), sum5;
+    sum5 = sum3;
+    polynomial::DifferentialOperatorTwo op1, op2(1, 1), op3(1, 1);
+    polynomial::DifferentialOperatorTwo op4(op3), op5;
+    op5 = op4;
+    for (int i = 0; i < 2; i++) {
+        EXPECT_EQ(op1.getXDerivatives(), 0);
+        EXPECT_EQ(op1.getSDerivatives(), 0);
+        EXPECT_EQ(op2.getXDerivatives(), 1);
+        EXPECT_EQ(op2.getXDerivatives(), 1);
+        EXPECT_EQ(op3.getXDerivatives(), 1);
+        EXPECT_EQ(op3.getXDerivatives(), 1);
+        EXPECT_EQ(op4.getXDerivatives(), 1);
+        EXPECT_EQ(op4.getXDerivatives(), 1);
+        EXPECT_EQ(op5.getXDerivatives(), 1);
+        EXPECT_EQ(op5.getXDerivatives(), 1);
+    }
+    op3.resizeX(5);
+    EXPECT_EQ(op3.getXDerivatives(), 5);
+    op3.resizeS(6);
+    EXPECT_EQ(op3.getSDerivatives(), 6);
+    op2.setPolynomial(poly2, 1, 1);
+    op2.setPolynomial(poly3, 0, 0);
+    op3.setPolynomial(poly3, 1, 1);
+    op3.setPolynomial(poly2, 0, 0);
+    op4.setPolynomial(poly3, 1, 0);
+    op4.setPolynomial(poly2, 0, 1);
+    op5.setPolynomial(poly3, 1, 1);
+    op5.setPolynomial(poly2, 0, 0);
+    EXPECT_TRUE(op2.isPolynomialZero(1, 0, 0));
+    EXPECT_TRUE(op2.isPolynomialZero(0, 1, 0));
+    EXPECT_EQ(op2.numberOfTerms(0, 0), 1);
+    EXPECT_EQ(op2.numberOfTerms(0, 1), 0);
+    EXPECT_EQ(op2.numberOfTerms(1, 0), 0);
+    EXPECT_EQ(op2.numberOfTerms(1, 1), 1);
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 1, 0, 0, dSvalues), 52.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(1, 2, 0, 0, dSvalues), 62.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 2, 0, 0, dSvalues), 158.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 1, 1, 1, dSvalues), 34.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(1, 2, 1, 1, dSvalues), 62.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 2, 1, 1, dSvalues), 107.0, 1e-6);
+    op2.differentiateX();
+    op3.differentiateS();
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 1, 0, 0, dSvalues), 38.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(1, 2, 0, 0, dSvalues), 79.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 2, 0, 0, dSvalues), 113.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 1, 1, 1, dSvalues), 14.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(1, 2, 1, 1, dSvalues), 45.0, 1e-6);
+    EXPECT_NEAR(op2.evaluatePolynomial(2, 2, 1, 1, dSvalues), 45.0, 1e-6);
+    EXPECT_NEAR(op3.evaluatePolynomial(2, 1, 0, 0, dSvalues), 52.0, 1e-6);
+    EXPECT_NEAR(op3.evaluatePolynomial(1, 2, 0, 0, dSvalues), 54.0, 1e-6);
+    EXPECT_NEAR(op3.evaluatePolynomial(2, 2, 0, 0, dSvalues), 94.0, 1e-6);
+    EXPECT_NEAR(op3.evaluatePolynomial(2, 1, 1, 1, dSvalues), 76.0, 1e-6);
+    EXPECT_NEAR(op3.evaluatePolynomial(1, 2, 1, 1, dSvalues), 54.0, 1e-6);
+    EXPECT_NEAR(op3.evaluatePolynomial(2, 2, 1, 1, dSvalues), 136.0, 1e-6);
+    op3.differentiateS();
+    EXPECT_EQ(op3.numberOfTerms(0, 0), 2);
+    EXPECT_EQ(op3.numberOfTerms(0, 1), 2);
+    EXPECT_EQ(op3.numberOfTerms(1, 0), 0);
+    EXPECT_EQ(op3.numberOfTerms(1, 1), 2);
+    std::vector<std::size_t> v4 = op3.getdSFactors(0, 0, 0);
+    EXPECT_EQ(v4[0], 2);
+    v4 = op3.getdSFactors(0, 0, 1);
+    EXPECT_EQ(v4[0], 0);
+    EXPECT_EQ(v4[1], 1);
+    v4 = op3.getdSFactors(0, 1, 0);
+    EXPECT_EQ(v4[0], 1);
+    op5.addOperator(op4);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 1, 0, dSvalues), 52.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 1, 0, dSvalues), 62.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 1, 0, dSvalues), 158.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 0, 1, dSvalues), 34.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 0, 1, dSvalues), 62.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 0, 1, dSvalues), 107.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(2, 1, 1, 1, dSvalues), 52.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(1, 2, 1, 1, dSvalues), 62.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(2, 2, 1, 1, dSvalues), 158.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(2, 1, 0, 0, dSvalues), 34.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(1, 2, 0, 0, dSvalues), 62.0, 1e-6);
+    EXPECT_NEAR(op5.evaluatePolynomial(2, 2, 0, 0, dSvalues), 107.0, 1e-6);
+    op4.multiplyPolynomial(poly1);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 0, 1, dSvalues), 816.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 0, 1, dSvalues), 372.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 0, 1, dSvalues), 2568.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 1, 0, dSvalues), 1248.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 1, 0, dSvalues), 372.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 1, 0, dSvalues), 3792.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 0, 0, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 0, 0, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 0, 0, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 1, 1, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 1, 1, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 1, 1, dSvalues), 0.0, 1e-6);
+    op4.truncate(2);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 0, 1, dSvalues), 144.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 0, 1, dSvalues), 102.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 0, 1, dSvalues), 408.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 1, 1, 0, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(1, 2, 1, 0, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(op4.evaluatePolynomial(2, 2, 1, 0, dSvalues), 0.0, 1e-6);
+}
+
+TEST (PolynomialTest, RecursionRelationTwo) {
+    std::vector<double> dSvalues;
+    dSvalues.resize(20, 1.0);
+    polynomial::RecursionRelationTwo r1, r2(1, 4);
+    polynomial::RecursionRelationTwo r3(r2), r4, r5(1, 4);
+    r4 = r3;
+    r5.applyOperator();
+    EXPECT_EQ(r1.getMaxXDerivatives(), 0);
+    EXPECT_EQ(r1.getMaxSDerivatives(), 0);
+    EXPECT_EQ(r2.getMaxXDerivatives(), 2);
+    EXPECT_EQ(r2.getMaxSDerivatives(), 2);
+    EXPECT_EQ(r3.getMaxXDerivatives(), 2);
+    EXPECT_EQ(r3.getMaxSDerivatives(), 2);
+    EXPECT_EQ(r4.getMaxXDerivatives(), 2);
+    EXPECT_EQ(r4.getMaxSDerivatives(), 2);
+    EXPECT_EQ(r5.getMaxXDerivatives(), 4);
+    EXPECT_EQ(r5.getMaxSDerivatives(), 4);
+    r4.truncate(4);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 0, 0, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 1, 0, dSvalues), 11.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 0, 1, dSvalues), 122.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 1, 1, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 2, 0, dSvalues), 1.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 0, 2, dSvalues), 57.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 1, 2, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(2, 1, 2, 1, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 0, 0, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 1, 0, dSvalues), 22.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 0, 1, dSvalues), 61.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 1, 1, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 2, 0, dSvalues), 1.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 0, 2, dSvalues), 57.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 1, 2, dSvalues), 0.0, 1e-6);
+    EXPECT_NEAR(r4.evaluatePolynomial(1, 2, 2, 1, dSvalues), 0.0, 1e-6);
+    std::vector<std::size_t> v4 = r4.getdSfactors(2, 0, 0);
+    EXPECT_EQ((int)v4.size(), 0);
+    v4 = r4.getdSfactors(0, 1, 0);
+    EXPECT_EQ((int)v4.size(), 1);
+    EXPECT_EQ(v4[0], 1);
+    r4.truncate(2);
+    EXPECT_NEAR(r4.evaluatePolynomial(0.2, 1, 1, 0, dSvalues), 0.84, 1e-6);
+    r5.truncate(2);
+    EXPECT_NEAR(r5.evaluatePolynomial(0.2, 1, 4, 0, dSvalues), 1.0, 1e-6);
+    r5.truncate(1);
+    EXPECT_NEAR(r5.evaluatePolynomial(0.2, 1, 2, 2, dSvalues), 1.2, 1e-6);
+    EXPECT_TRUE(r2.isPolynomialZero(0, 0, 0));
+    EXPECT_TRUE(r2.isPolynomialZero(2, 1, 0));
+    EXPECT_TRUE(r2.isPolynomialZero(1, 1, 0));
+    r2.resizeX(4);
+    EXPECT_EQ(r2.getMaxXDerivatives(), 4);
+    r2.resizeS(7);
+    EXPECT_EQ(r2.getMaxSDerivatives(), 7);
+    r3.differentiateX();
+    EXPECT_NEAR(r3.evaluatePolynomial(0.2, 1, 3, 0, dSvalues), 1.0, 1e-6);
+    EXPECT_NEAR(r3.evaluatePolynomial(0.2, 1, 1, 2, dSvalues), 0.696, 1e-6);
+    EXPECT_NEAR(r3.evaluatePolynomial(1, 0.2, 1, 2, dSvalues), 0.696, 1e-6);
+    r4.differentiateS();
+    EXPECT_EQ(r4.numberOfTerms(0, 1), 3);
+}
+
+TEST (PolynomialTest, TanhDeriv) {
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 2), -0.0148895, 1e-7);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 3), -0.0991297, 1e-7);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 4), -0.659093, 1e-6);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 5), -4.37031, 1e-5);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 6), -28.8209, 1e-4);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 7), -187.956, 1e-3);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 8), -1197.48, 1e-2);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 9), -7246.6, 1e-1);
+    EXPECT_NEAR(tanhderiv::integrate(1.0, 2.2, 0.3, 0.3, 10), -38575, 1e0);
+}