diff --git a/src/soap/fieldtensor.cpp b/src/soap/fieldtensor.cpp
index 6cdefebcf51b111836d240b52838d17b9c23f77e..3118f4a0ddd2777e24d8f9fd89aa870267865866 100644
--- a/src/soap/fieldtensor.cpp
+++ b/src/soap/fieldtensor.cpp
@@ -1,5 +1,6 @@
 #include "soap/fieldtensor.hpp"
 #include "soap/linalg/numpy.hpp"
+#include "soap/linalg/wigner.hpp"
 #include <boost/math/special_functions/legendre.hpp>
 
 namespace soap {
@@ -144,6 +145,100 @@ AtomicSpectrumFT::field_t *AtomicSpectrumFT::getField(int k, std::string type) {
     }
 }
 
+void AtomicSpectrumFT::contractDeep() {
+    /*
+    for (int l1=0; l1<=_L; ++l1) {
+    for (int l2=0; l2<=_L; ++l2) {
+    for (int l3=0; l3<=_L; ++l3) {
+       int m1 = -l1;
+       int m2 = -l2;
+       int m3 = -m1-m2;
+       double w = WignerSymbols::wigner3j(l1, l2, l3, m1, m2, m3);
+       GLOG() << l1 << ":" << l2 << ":" << l3 << " " << m1 << ":" << m2 << ":" << m3 << " " << w << std::endl;
+    }}}
+
+    for (int l1=0; l1<=_L; ++l1) {
+    for (int l2=0; l2<=_L; ++l2) {
+    for (int l3=0; l3<=0; ++l3) {
+        for (int m1=-l1; m1<=l1; ++m1) {
+            int m2 = -m1;
+            int m3 = 0;
+            double w = WignerSymbols::wigner3j(l1, l2, l3, m1, m2, m3);
+            GLOG() << l1 << ":" << l2 << ":" << l3 << " " << m1 << ":" << m2 << ":" << m3 << " " << w << std::endl;
+        }
+    }}}
+    */
+
+
+    // Wig. 3j(2)     _L =  0  1  2  3   4   5   6   7    8    9    10   11   12   13   14   15
+    std::vector<int> lll = {0, 1, 5, 13, 27, 48, 78, 118, 170, 235, 315, 411, 525, 658, 812, 988 };
+    int k = 1;
+    int length = (_L+1) + (_L+1)*(_L+1) + lll[_L];
+    field_map_t &field_map = _body_map[k];
+
+    // Contract channels
+    for (auto it1 = field_map.begin(); it1 != field_map.end(); ++it1) {
+        std::string s1 = it1->first; // <- Channel 1 (e.g., 'C')
+        field_t &f1 = *(it1->second);
+        for (auto it2 = field_map.begin(); it2 != field_map.end(); ++it2) {
+            std::string s2 = it2->first; // <- Channel 2 (e.g., 'O')
+            field_t &f2 = *(it2->second);
+            GLOG() << " " << s1 << ":" << s2 << std::flush;
+
+            // Allocate channel if necessary
+            channel_t channel(s1, s2);
+            coeff_t &coeffs = *(this->getCreateContraction(channel, length, 1)); // TODO Will not work with k-dependent L-cutoff
+
+            // a,l <> a',l contractions
+            int offset = 0;
+            for (int l = 0; l <= _L; ++l) {
+                double inv_alpha_l = 1./this->_alpha[l];
+                cmplx_t phi_l_s1s2 = 0.0;
+                for (int m = -l; m <= l; ++m) {
+                    int lm = l*l+l+m;
+                    phi_l_s1s2 += f1(0, lm)*std::conj(f2(0, lm));
+                }
+                int l1l2 = offset+l;
+                coeffs(l1l2, 0) = pow(this->getCenter()->getSigma(), 2*l)*inv_alpha_l*phi_l_s1s2.real();
+            } // l
+
+            // al, a'l', a'l' contractions
+            offset = _L+1;
+            for (int l1 = 0; l1 <= _L; ++l1) {
+                double inv_alpha_l1 = 1./this->_alpha[l1];
+            for (int l2 = 0; l2 <= _L; ++l2) {
+                double inv_alpha_l2 = 1./this->_alpha[l2];
+                cmplx_t phi_l1l2l2_s1s2 = 0.0;
+                double w122_sq = 0.0;
+                for (int m1 = -l1; m1 <= l1; ++m1) {
+                    int lm1 = l1*l1+l1+m1;
+                for (int m2 = -l2; m2 <= l2; ++m2) {
+                    int lm2 = l2*l2+l2+m2;
+                for (int m3 = -l2; m3 <= l2; ++m3) {
+                    int lm3 = l2*l2+l2+m3;
+                    // TODO Either (al1 bl2 bl2) or (al2 bl1 bl2)
+                    double w122 = WignerSymbols::wigner3j(l2, l1, l2, m2, m1, m3);
+                    w122_sq += w122*w122;
+                    //GLOG() << l1 << ":" << l2 << ":" << m1 << ":" << m2 << "  " << w122 << std::endl;
+                    phi_l1l2l2_s1s2 += w122 * f1(0,lm2) * f2(0,lm1) * f2(0,lm3);
+                }}}
+                GLOG() << l1 << ":" << l2 << " " << w122_sq << std::endl;
+                int l1l2l2 = offset + l1*(_L+1) + l2;
+                coeffs(l1l2l2, 0) = 
+                        sqrt(inv_alpha_l1)*sqrt(inv_alpha_l2)*sqrt(inv_alpha_l2)
+                      * pow(this->getCenter()->getSigma(), l1+0.5)
+                      * pow(this->getCenter()->getSigma(), 2*l2) *
+                        pow(-1, l2-l1+l2)
+                      * phi_l1l2l2_s1s2.real();
+            }}
+
+        } // Channel type 2
+    } // Channel type 1
+    GLOG() << std::endl;
+
+    return;
+}
+
 void AtomicSpectrumFT::contract() {
     int k = 0;
     int Lambda_total = (1 - pow(_L+1, _K))/(1 - (_L+1)); // from geometric series
@@ -229,7 +324,10 @@ void AtomicSpectrumFT::contract() {
                                     << " = " << phi_l_s1s2_l1l2
                                     <<  std::endl;
                             #endif
-                            coeffs(l1l2, l) = inv_alpha*phi_l_s1s2_l1l2.real();
+                            //coeffs(l1l2, l) = inv_alpha*phi_l_s1s2_l1l2.real(); // TODO HACK
+                            coeffs(l1l2, l) = pow(this->getCenter()->getSigma(), 2*l)*inv_alpha*phi_l_s1s2_l1l2.real(); // TODO HACK
+                            //coeffs(l1l2, l) = phi_l_s1s2_l1l2.real(); // TODO HACK
+
                         } // l
                     } // lambda 2
                 } // lambda 1
@@ -497,7 +595,9 @@ void InteractUpdateSourceTarget(
                                     << std::endl;
                             #endif
                             int lm_in = l2*l2+l2+m2;
-                            q_lambda_lm_out += T_ab(lm_out, lm_in)*f_a(lambda_in, lm_in);
+                            //q_lambda_lm_out += T_ab(lm_out, lm_in)*f_a(lambda_in, lm_in); // BACK-UP
+                            //q_lambda_lm_out += pow(sigma1, l1+0.5)*pow(sigma2, l2+0.5)*T_ab(lm_out, lm_in)*f_a(lambda_in, lm_in); // TODO HACK
+                            q_lambda_lm_out += pow(sigma1, -l1)*pow(sigma2, l2)*T_ab(lm_out, lm_in)*f_a(lambda_in, lm_in); // TODO HACK
                         }
                         // Apply weight and parity
                         q_lambda_lm_out *= alpha*w12*w2*parity;
@@ -874,8 +974,18 @@ void FTSpectrum::compute() {
         L_k_in = L;
     }
 
-    for (auto it = beginAtomic(); it != endAtomic(); ++it) {
-        (*it)->contract();
+    if (_options->get<std::string>("fieldtensor.contract") == "normal") {
+        for (auto it = beginAtomic(); it != endAtomic(); ++it) {
+            (*it)->contract();
+        }
+    }
+    else if (_options->get<std::string>("fieldtensor.contract") == "deep") {
+        for (auto it = beginAtomic(); it != endAtomic(); ++it) {
+            (*it)->contractDeep();
+        }
+    }
+    else {
+        assert(false && "fieldtensor.contract has invalid value (should be 'normal' or 'deep')");
     }
 
     /*
diff --git a/src/soap/fieldtensor.hpp b/src/soap/fieldtensor.hpp
index 885d74f4d7a40e426f522eaa360a8fffea46f909..e408ec51e0e999ea76a917fd967429a2bde674ef 100644
--- a/src/soap/fieldtensor.hpp
+++ b/src/soap/fieldtensor.hpp
@@ -53,6 +53,7 @@ public:
     coeff_t *getCreateContraction(channel_t &channel, int size1, int size2);
     coeff_map_t &getCoeffMap() { return _coeff_map; }
     void contract();
+    void contractDeep();
 
 private:
     Particle *_center;