diff --git a/CMakeLists.txt b/CMakeLists.txt
index 57991c886fc85752b0d7a791375ba8004ce69012..ccc56d8a708f7c3270277f2c54796656655aacfc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,6 +14,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS "YES")
 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
 
+include_directories(${CMAKE_SOURCE_DIR}/external)
 include_directories(${CMAKE_SOURCE_DIR}/include)
 include_directories(${CMAKE_SOURCE_DIR}/src)
 include_directories(${CMAKE_SOURCE_DIR})
diff --git a/data/ball_and_stick.swc b/data/ball_and_stick.swc
new file mode 100644
index 0000000000000000000000000000000000000000..1737539b1b4d73d202cecab2ecb7e5599d93064e
--- /dev/null
+++ b/data/ball_and_stick.swc
@@ -0,0 +1,8 @@
+# ball and stick model with
+#   - soma with radius 12.6157/2
+#   - dendrite with length 200 and radius 0.5
+
+1 1     0.0     0.0     0.0     6.30785 -1
+2 2     6.30785 0.0     0.0     0.5      1
+3 2   206.30785 0.0     0.0     0.5      2
+
diff --git a/mechanisms/generate.sh b/mechanisms/generate.sh
index d9033f0dbc1cb34e6ef10b1b42d6bdd9e37b0cd7..94755fbb91b912e5eada0148af1c72ea91945ecd 100755
--- a/mechanisms/generate.sh
+++ b/mechanisms/generate.sh
@@ -1,4 +1,4 @@
 for mech in pas hh
 do
-    ../modparser/bin/modcc -t cpu -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod
+    ../external/modparser/bin/modcc -t cpu -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod
 done
diff --git a/nrn/ball_and_3stick.py b/nrn/ball_and_3stick.py
new file mode 100644
index 0000000000000000000000000000000000000000..15ece3d3da151daf7bdb90f1af1d6cd368352489
--- /dev/null
+++ b/nrn/ball_and_3stick.py
@@ -0,0 +1,154 @@
+from timeit import default_timer as timer
+import os.path
+from matplotlib import pyplot
+import numpy as np
+import json
+import argparse
+from neuron import gui, h
+
+parser = argparse.ArgumentParser(description='generate spike train ball and stick model with hh channels at soma and pas channels in dendrite')
+parser.add_argument('--plot', action='store_true', dest='plot')
+args = parser.parse_args()
+
+soma = h.Section(name='soma')
+dend = [
+        h.Section(name='dend0'),
+        h.Section(name='dend1'),
+        h.Section(name='dend2'),
+    ]
+
+dend[0].connect(soma(1))
+dend[1].connect(dend[0](1))
+dend[2].connect(dend[0](1))
+
+# Surface area of cylinder is 2*pi*r*h (sealed ends are implicit).
+# Here we make a square cylinder in that the diameter
+# is equal to the height, so diam = h. ==> Area = 4*pi*r^2
+# We want a soma of 500 microns squared:
+# r^2 = 500/(4*pi) ==> r = 6.2078, diam = 12.6157
+soma.L = soma.diam = 12.6157 # Makes a soma of 500 microns squared.
+for d in dend :
+    d.L = 100
+    d.diam = 1
+
+for sec in h.allsec():
+    sec.Ra = 100    # Axial resistance in Ohm * cm
+    sec.cm = 1      # Membrane capacitance in micro Farads / cm^2
+
+# Insert active Hodgkin-Huxley current in the soma
+soma.insert('hh')
+soma.gnabar_hh = 0.12  # Sodium conductance in S/cm2
+soma.gkbar_hh = 0.036  # Potassium conductance in S/cm2
+soma.gl_hh = 0.0003    # Leak conductance in S/cm2
+soma.el_hh = -54.3     # Reversal potential in mV
+
+# Insert passive current in the dendrite
+for d in dend :
+    d.insert('pas')
+    d.g_pas = 0.001  # Passive conductance in S/cm2
+    d.e_pas = -65    # Leak reversal potential mV
+
+stim = [
+     h.IClamp(dend[1](1)),
+     h.IClamp(dend[2](1))
+   ]
+stim[0].delay = 5
+stim[0].dur = 80
+stim[0].amp = 4.5*0.1
+
+stim[1].delay = 40
+stim[1].dur = 10
+stim[1].amp = -2*0.1
+
+if args.plot :
+    pyplot.figure(figsize=(8,4)) # Default figsize is (8,6)
+    pyplot.grid()
+
+simdur = 100.0
+h.tstop = simdur
+h.dt = 0.001
+
+start = timer()
+results = []
+for nseg in [5, 11, 51, 101] :
+
+    print 'simulation with ', nseg, ' compartments in dendrite...'
+
+    for d in dend :
+        d.nseg=nseg
+
+    # record voltages
+    v_soma = h.Vector() # soma
+    v_dend = h.Vector() # at the dendrite branching point
+    v_clamp= h.Vector() # end of dendrite at clamp location
+
+    v_soma.record( soma(0.5)._ref_v)
+    v_dend.record( dend[0](1)._ref_v)
+    v_clamp.record(dend[1](1)._ref_v)
+
+    # record spikes
+    # this is a bit verbose, no?
+    spike_counter_soma = h.APCount(soma(0.5))
+    spike_counter_soma.thresh = 0
+    spike_counter_dend = h.APCount(dend[0](1))
+    spike_counter_dend.thresh = -20
+    spike_counter_clamp = h.APCount(dend[1](1.0))
+    spike_counter_clamp.thresh = 20
+
+    spikes_soma = h.Vector() # soma
+    spikes_dend = h.Vector() # middle of dendrite
+    spikes_clamp= h.Vector() # end of dendrite at clamp location
+
+    spike_counter_soma.record(spikes_soma)
+    spike_counter_dend.record(spikes_dend)
+    spike_counter_clamp.record(spikes_clamp)
+
+    # record time stamps
+    t_vec = h.Vector()
+    t_vec.record(h._ref_t)
+
+    # finally it's time to run the simulation
+    h.run()
+
+    results.append(
+        {
+            "nseg": nseg,
+            "dt" : h.dt,
+            "measurements": {
+               "soma" : {
+                   "thresh" :  spike_counter_soma.thresh,
+                   "spikes" :  spikes_soma.to_python()
+               },
+               "dend" : {
+                   "thresh" :  spike_counter_dend.thresh,
+                   "spikes" :  spikes_dend.to_python()
+               },
+               "clamp" : {
+                   "thresh" :  spike_counter_clamp.thresh,
+                   "spikes" :  spikes_clamp.to_python()
+               }
+           }
+        }
+    )
+
+    if args.plot :
+        pyplot.plot(t_vec, v_soma,  'k', linewidth=1, label='soma ' + str(nseg))
+        pyplot.plot(t_vec, v_dend,  'b', linewidth=1, label='dend ' + str(nseg))
+        pyplot.plot(t_vec, v_clamp, 'r', linewidth=1, label='clamp ' + str(nseg))
+
+# time the simulations
+end = timer()
+print "took ", end-start, " seconds"
+
+# save the spike info as in json format
+fp = open('ball_and_3stick.json', 'w')
+json.dump(results, fp, indent=2)
+
+if args.plot :
+    pyplot.xlabel('time (ms)')
+    pyplot.ylabel('mV')
+    pyplot.xlim([0, simdur])
+    pyplot.legend()
+
+    pyplot.show()
+
diff --git a/nrn/ball_and_stick.py b/nrn/ball_and_stick.py
index 5fe977fe594ada556ebce1da09c9a15c3f3b03e3..24562337a4baffd7a1ae3a1e7885cb07e06987ec 100644
--- a/nrn/ball_and_stick.py
+++ b/nrn/ball_and_stick.py
@@ -55,7 +55,7 @@ h.dt = 0.001
 
 start = timer()
 results = []
-for nseg in [5, 11, 21, 41, 81, 161] :
+for nseg in [5, 11, 51, 101] :
 
     print 'simulation with ', nseg, ' compartments in dendrite...'
 
diff --git a/nrn/generate_validation.sh b/nrn/generate_validation.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f35d3521e9d2ce785a01bc14cd966d4910ecf7f7
--- /dev/null
+++ b/nrn/generate_validation.sh
@@ -0,0 +1,3 @@
+python2.7 ./soma.py
+python2.7 ./ball_and_stick.py
+python2.7 ./ball_and_3stick.py
diff --git a/nrn/generate_validation_data.sh b/nrn/generate_validation_data.sh
deleted file mode 100755
index 8627a0c56ecd1f2137178a193be99207cb06ef46..0000000000000000000000000000000000000000
--- a/nrn/generate_validation_data.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-python2.7 soma.py
-
diff --git a/src/cell.cpp b/src/cell.cpp
index a6986f0673ca05827ee89493aca2230ec16cec30..8fdd1bb6e0f55cdfa2a3c7ea6632e0fdee74bba6 100644
--- a/src/cell.cpp
+++ b/src/cell.cpp
@@ -195,5 +195,41 @@ std::vector<int> const& cell::segment_parents() const
     return parents_;
 }
 
+// Rough and ready comparison of two cells.
+// We don't use an operator== because equality of two cells is open to
+// interpretation. For example, it is possible to have two viable representations
+// of a cell: with and without location information for the cables.
+//
+// Checks that two cells have the same
+//  - number and type of segments
+//  - volume and area properties of each segment
+//  - number of compartments in each segment
+bool cell_basic_equality(cell const& lhs, cell const& rhs)
+{
+    if(lhs.num_segments() != rhs.num_segments()) {
+        return false;
+    }
+    if(lhs.segment_parents() != rhs.segment_parents()) {
+        return false;
+    }
+    for(auto i=0; i<lhs.num_segments(); ++i) {
+        // a quick and dirty test
+        auto& l = *lhs.segment(i);
+        auto& r = *rhs.segment(i);
+
+        if(l.kind() != r.kind()) return false;
+        if(l.area() != r.area()) return false;
+        if(l.volume() != r.volume()) return false;
+        if(l.as_cable()) {
+            if(   l.as_cable()->num_compartments()
+               != r.as_cable()->num_compartments())
+            {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
 } // namespace mc
 } // namespace nest
diff --git a/src/cell.hpp b/src/cell.hpp
index ba5f9b6bff227231cbb4f46e27dd2af4ff2943ce..fece134947041d705564008e54be61ce93d50f60 100644
--- a/src/cell.hpp
+++ b/src/cell.hpp
@@ -123,6 +123,12 @@ class cell {
     std::vector<std::pair<segment_location, i_clamp>> stimulii_;
 };
 
+// Checks that two cells have the same
+//  - number and type of segments
+//  - volume and area properties of each segment
+//  - number of compartments in each segment
+bool cell_basic_equality(cell const& lhs, cell const& rhs);
+
 // create a cable by forwarding cable construction parameters provided by the user
 template <typename... Args>
 cable_segment* cell::add_cable(cell::index_type parent, Args ...args)
diff --git a/src/segment.hpp b/src/segment.hpp
index fd1d8ded6101cc8e9834b9062df3da97de5cbc8c..decb0cdd88665c421cf159b8d7f49e4f91e03f0f 100644
--- a/src/segment.hpp
+++ b/src/segment.hpp
@@ -76,6 +76,16 @@ class segment {
         return nullptr;
     }
 
+    virtual const cable_segment* as_cable() const
+    {
+        return nullptr;
+    }
+
+    virtual const soma_segment* as_soma() const
+    {
+        return nullptr;
+    }
+
     virtual bool is_placeholder() const
     {
         return false;
@@ -230,6 +240,11 @@ class soma_segment : public segment
         return this;
     }
 
+    const soma_segment* as_soma() const override
+    {
+        return this;
+    }
+
     /// soma has one and one only compartments
     int num_compartments() const override
     {
@@ -359,6 +374,11 @@ class cable_segment : public segment
         return this;
     }
 
+    const cable_segment* as_cable() const override
+    {
+        return this;
+    }
+
     int num_compartments() const override
     {
         return num_compartments_;
diff --git a/src/util.hpp b/src/util.hpp
index 706ab7da5a17d493823b8c74d1f18fa6b4a9efaf..afa029ff9cfcba65258473875003c00f58c3d16b 100644
--- a/src/util.hpp
+++ b/src/util.hpp
@@ -45,6 +45,21 @@ std::ostream& print(std::ostream &o, std::vector<T>const& v)
     return o;
 }
 
+template <typename T>
+bool operator ==(const std::vector<T>& lhs, const std::vector<T>& rhs)
+{
+    if(lhs.size() != rhs.size()) {
+        return false;
+    }
+    return std::equal(lhs.begin(), lhs.end(), rhs.begin());
+}
+
+template <typename T>
+bool operator !=(const std::vector<T>& lhs, const std::vector<T>& rhs)
+{
+    return !(lhs==rhs);
+}
+
 namespace nest {
 namespace mc {
 namespace util {
diff --git a/tests/test_swcio.cpp b/tests/test_swcio.cpp
index 679260d656c9c7964f0eea599951c371886123f4..6968fd432a89842023807c8ed0ffb2ca8140b231 100644
--- a/tests/test_swcio.cpp
+++ b/tests/test_swcio.cpp
@@ -487,3 +487,31 @@ TEST(swc_io, cell_construction)
                   cell.cable(3)->radius(0));
     }
 }
+
+// check that simple ball and stick model with one dendrite attached to a soma
+// which is used in the validation tests can be loaded from file and matches
+// the one generated with the C++ interface
+TEST(swc_parser, from_file_ball_and_stick)
+{
+    auto fname = "../data/ball_and_stick.swc";
+    std::ifstream fid(fname);
+    if(!fid.is_open()) {
+        std::cerr << "unable to open file " << fname << "... skipping test\n";
+        return;
+    }
+
+    // read the file into a cell object
+    auto cell = nest::mc::io::swc_read_cell(fid);
+
+    // verify that the correct number of nodes was read
+    EXPECT_EQ(cell.num_segments(), 2);
+    EXPECT_EQ(cell.num_compartments(), 2u);
+
+    // make an equivalent cell via C++ interface
+    nest::mc::cell local_cell;
+    local_cell.add_soma(6.30785);
+    local_cell.add_cable(0, nest::mc::segmentKind::dendrite, 0.5, 0.5, 200);
+
+    EXPECT_TRUE(nest::mc::cell_basic_equality(local_cell, cell));
+}
+
diff --git a/tests/util.hpp b/tests/util.hpp
index 7a0027448e46c860471aeed68ebd8ed8e411dfe0..aeb2f8d3e585a1603de6c7745b566a8b11aee12c 100644
--- a/tests/util.hpp
+++ b/tests/util.hpp
@@ -3,6 +3,7 @@
 #include <string>
 #include <vector>
 #include <iomanip>
+#include <chrono>
 
 #include <cmath>
 
@@ -14,6 +15,22 @@
 
 namespace testing{
 
+using time_point    = std::chrono::time_point<std::chrono::system_clock>;
+using duration_type = std::chrono::duration<double>;
+
+static inline
+time_point tic()
+{
+    return std::chrono::system_clock::now();
+}
+
+static inline
+double toc(time_point start)
+{
+    return duration_type(tic() - start).count();
+}
+
+
 [[gnu::unused]] static
 void write_vis_file(const std::string& fname, std::vector<std::vector<double>> values)
 {
@@ -111,7 +128,7 @@ operator<< (std::ostream& o, spike_comparison const& spikes)
         buffer, sizeof(buffer),
         "min,max = %10.8f,%10.8f | mean,rms = %10.8f,%10.8f | max_rel = %10.8f",
         spikes.min, spikes.max, spikes.mean, spikes.rms,
-        spikes.max_relative_error()*100
+        spikes.max_relative_error()
     );
     return o << buffer;
 }
diff --git a/tests/validate_ball_and_stick.cpp b/tests/validate_ball_and_stick.cpp
index eac29e2e84f20653acdb7555714f9ed6c6e3e555..a2aef0ada111aa7ecb8d150034a437016e987ce1 100644
--- a/tests/validate_ball_and_stick.cpp
+++ b/tests/validate_ball_and_stick.cpp
@@ -8,7 +8,7 @@
 
 #include <json/src/json.hpp>
 
-// compares results with those generated by nrn/soma.py
+// compares results with those generated by nrn/ball_and_stick.py
 TEST(ball_and_stick, neuron_baseline)
 {
     using namespace nest::mc;
@@ -27,7 +27,7 @@ TEST(ball_and_stick, neuron_baseline)
     auto dendrite = cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 200);
     dendrite->add_mechanism(pas_parameters());
 
-    dendrite->mechanism("membrane").set("r_L", 100); // no effect for single compartment cell
+    dendrite->mechanism("membrane").set("r_L", 100);
 
     // add stimulus
     cell.add_stimulus({1,1}, {5., 80., 0.3});
@@ -119,17 +119,173 @@ TEST(ball_and_stick, neuron_baseline)
     }
 
     // print results
+    auto colors = {memory::util::kWhite, memory::util::kGreen, memory::util::kYellow};
     for(auto& r : results){
-        // select the location with the largest error for printing
-        auto m =
-            std::max_element(
-                r.comparisons.begin(), r.comparisons.end(),
-                [](testing::spike_comparison& l, testing::spike_comparison& r)
-                {return l.max_relative_error()<r.max_relative_error();}
+        auto color = colors.begin();
+        for(auto const& result : r.comparisons) {
+            std::cout << std::setw(5) << r.n_comparments << " compartments : ";
+            std::cout << memory::util::colorize(util::pprintf("%\n", result), *(color++));
+        }
+    }
+
+    // sort results in ascending order of compartments
+    std::sort(
+        results.begin(), results.end(),
+        [](const result& l, const result& r)
+            {return l.n_comparments<r.n_comparments;}
+    );
+
+    // the strategy for testing is the following:
+    //  1. test that the solution converges to the finest reference solution as
+    //     the number of compartments increases (i.e. spatial resolution is
+    //     refined)
+    for(auto i=1u; i<results.size(); ++i) {
+        for(auto j=0; j<3; ++j) {
+            EXPECT_TRUE(
+                  results[i].comparisons[j].max_relative_error()
+                < results[i-1].comparisons[j].max_relative_error()
             );
-        std::cout << std::setw(5) << r.n_comparments
-                  << " compartments : " << *m
-                  << "\n";
+        }
+    }
+
+    //  2. test that the best solution (i.e. with most compartments) matches the
+    //     reference solution closely (less than 0.5% over the course of 100ms
+    //     simulation)
+    auto tol = 0.5;
+    for(auto j=0; j<3; ++j) {
+        EXPECT_TRUE(results.back().comparisons[j].max_relative_error()*100<tol);
+    }
+}
+
+// compares results with those generated by nrn/ball_and_3sticks.py
+TEST(ball_and_3stick, neuron_baseline)
+{
+    using namespace nest::mc;
+    using namespace nlohmann;
+
+    nest::mc::cell cell;
+
+    // setup global state for the mechanisms
+    nest::mc::mechanisms::setup_mechanism_helpers();
+
+    // Soma with diameter 12.6157 um and HH channel
+    auto soma = cell.add_soma(12.6157/2.0);
+    soma->add_mechanism(hh_parameters());
+
+    // add dendrite of length 200 um and diameter 1 um with passive channel
+    std::vector<cable_segment*> dendrites;
+    dendrites.push_back(cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 100));
+    dendrites.push_back(cell.add_cable(1, segmentKind::dendrite, 0.5, 0.5, 100));
+    dendrites.push_back(cell.add_cable(1, segmentKind::dendrite, 0.5, 0.5, 100));
+
+    for(auto dend : dendrites) {
+        dend->add_mechanism(pas_parameters());
+        dend->mechanism("membrane").set("r_L", 100);
+    }
+
+    // add stimulus
+    cell.add_stimulus({2,1}, {5.,  80., 0.45});
+    cell.add_stimulus({3,1}, {40., 10.,-0.2});
+
+    // load data from file
+    auto cell_data = testing::load_spike_data("../nrn/ball_and_3stick.json");
+    EXPECT_TRUE(cell_data.size()>0);
+    if(cell_data.size()==0) return;
+
+    json& nrn =
+        *std::max_element(
+            cell_data.begin(), cell_data.end(),
+            [](json& lhs, json& rhs) {return lhs["nseg"]<rhs["nseg"];}
+        );
+
+    auto& measurements = nrn["measurements"];
+
+    double dt = nrn["dt"];
+    double tfinal =   100.; // ms
+    int nt = tfinal/dt;
+
+    // inline type for storing the results of a simulation along with
+    // the information required to compare two simulations for accuracy
+    struct result {
+        std::vector<std::vector<double>> spikes;
+        std::vector<std::vector<double>> baseline_spikes;
+        std::vector<testing::spike_comparison> comparisons;
+        std::vector<double> thresh;
+        int n_comparments;
+
+        result(int nseg, double dt,
+          std::vector<std::vector<double>> &v,
+          json& measurements
+        ) {
+            n_comparments = nseg;
+            baseline_spikes = {
+                measurements["soma"]["spikes"],
+                measurements["dend"]["spikes"],
+                measurements["clamp"]["spikes"]
+            };
+            thresh = {
+                measurements["soma"]["thresh"],
+                measurements["dend"]["thresh"],
+                measurements["clamp"]["thresh"]
+            };
+            for(auto i=0; i<3; ++i) {
+                // calculate the NEST MC spike times
+                spikes.push_back
+                    (testing::find_spikes(v[i], thresh[i], dt));
+                // compare NEST MC and baseline NEURON spike times
+                comparisons.push_back
+                    (testing::compare_spikes(spikes[i], baseline_spikes[i]));
+            }
+        }
+    };
+
+    std::vector<result> results;
+    auto start = testing::tic();
+    for(auto run_index=0u; run_index<cell_data.size(); ++run_index) {
+        auto& run = cell_data[run_index];
+        int num_compartments = run["nseg"];
+        for(auto dend : dendrites) {
+            dend->set_compartments(num_compartments);
+        }
+        std::vector<std::vector<double>> v(3);
+
+        // make the lowered finite volume cell
+        fvm::fvm_cell<double, int> model(cell);
+        auto graph = cell.model();
+
+        // set initial conditions
+        using memory::all;
+        model.voltage()(all) = -65.;
+        model.initialize(); // have to do this _after_ initial conditions are set
+
+        // run the simulation
+        auto soma_comp  = nest::mc::find_compartment_index({0,0.}, graph);
+        auto dend_comp  = nest::mc::find_compartment_index({1,1.}, graph);
+        auto clamp_comp = nest::mc::find_compartment_index({2,1.}, graph);
+        v[0].push_back(model.voltage()[soma_comp]);
+        v[1].push_back(model.voltage()[dend_comp]);
+        v[2].push_back(model.voltage()[clamp_comp]);
+        for(auto i=0; i<nt; ++i) {
+            model.advance(dt);
+            // save voltage at soma
+            v[0].push_back(model.voltage()[soma_comp]);
+            v[1].push_back(model.voltage()[dend_comp]);
+            v[2].push_back(model.voltage()[clamp_comp]);
+        }
+
+        results.push_back( {num_compartments, dt, v, measurements} );
+    }
+    auto time_taken = testing::toc(start);
+    std::cout << "took " << time_taken << " seconds\n";
+
+    // print results
+    auto colors = {memory::util::kWhite, memory::util::kGreen, memory::util::kYellow};
+    for(auto& r : results){
+        auto color = colors.begin();
+        for(auto const& result : r.comparisons) {
+            std::cout << std::setw(5) << r.n_comparments << " compartments : ";
+            std::cout << memory::util::colorize(util::pprintf("%\n", result), *(color++));
+        }
     }
 
     // sort results in ascending order of compartments
@@ -153,10 +309,11 @@ TEST(ball_and_stick, neuron_baseline)
     }
 
     //  2. test that the best solution (i.e. with most compartments) matches the
-    //     reference solution closely (less than 0.1% over the course of 100ms
+    //     reference solution closely (less than 0.5% over the course of 100ms
     //     simulation)
+    auto tol = 0.5;
     for(auto j=0; j<3; ++j) {
-        EXPECT_TRUE(results.back().comparisons[j].max_relative_error()*100<0.1);
+        EXPECT_TRUE(results.back().comparisons[j].max_relative_error()*100<tol);
     }
 }