diff --git a/bob/learn/misc/cpp/IVectorMachine.cpp b/bob/learn/misc/cpp/IVectorMachine.cpp
index b717c70de0ccad8f4f6163b5b46e5b05b27612ff..933cfe9870f9f249925969724eaff53ddfc9ad01 100644
--- a/bob/learn/misc/cpp/IVectorMachine.cpp
+++ b/bob/learn/misc/cpp/IVectorMachine.cpp
@@ -18,7 +18,7 @@ bob::learn::misc::IVectorMachine::IVectorMachine()
 bob::learn::misc::IVectorMachine::IVectorMachine(const boost::shared_ptr<bob::learn::misc::GMMMachine> ubm,
     const size_t rt, const double variance_threshold):
   m_ubm(ubm), m_rt(rt),
-  m_T(getDimCD(),rt), m_sigma(getDimCD()),
+  m_T(getSupervectorLength(),rt), m_sigma(getSupervectorLength()),
   m_variance_threshold(variance_threshold)
 {
   resizePrecompute();
@@ -204,7 +204,7 @@ void bob::learn::misc::IVectorMachine::resizeTmp()
 void bob::learn::misc::IVectorMachine::forward(const bob::learn::misc::GMMStats& gs,
   blitz::Array<double,1>& ivector) const
 {
-  bob::core::array::assertSameDimensionLength(ivector.extent(0), (int)m_rt);
+  bob::core::array::assertSameDimensionLength(ivector.extent(0), (int)m_rt);  
   forward_(gs, ivector);
 }
 
@@ -214,7 +214,7 @@ void bob::learn::misc::IVectorMachine::computeIdTtSigmaInvT(
   // Computes \f$(Id + \sum_{c=1}^{C} N_{i,j,c} T^{T} \Sigma_{c}^{-1} T)\f$
   blitz::Range rall = blitz::Range::all();
   bob::math::eye(output);
-  for (int c=0; c<(int)getDimC(); ++c)
+  for (int c=0; c<(int)getNGaussians(); ++c)
     output += gs.n(c) * m_cache_Tct_sigmacInv_Tc(c, rall, rall);
 }
 
@@ -224,7 +224,7 @@ void bob::learn::misc::IVectorMachine::computeTtSigmaInvFnorm(
   // Computes \f$T^{T} \Sigma^{-1} \sum_{c=1}^{C} (F_c - N_c ubmmean_{c})\f$
   blitz::Range rall = blitz::Range::all();
   output = 0;
-  for (int c=0; c<(int)getDimC(); ++c)
+  for (int c=0; c<(int)getNGaussians(); ++c)
   {
     m_tmp_d = gs.sumPx(c,rall) - gs.n(c) * m_ubm->getGaussian(c)->getMean();
     blitz::Array<double,2> Tct_sigmacInv = m_cache_Tct_sigmacInv(c, rall, rall);
diff --git a/bob/learn/misc/gmm_machine.cpp b/bob/learn/misc/gmm_machine.cpp
index 356a800e0a19a596e411ca5a7e5d9973ecb659a7..9bea2a88943616ed71568945b8d797c7828f99c2 100644
--- a/bob/learn/misc/gmm_machine.cpp
+++ b/bob/learn/misc/gmm_machine.cpp
@@ -48,13 +48,11 @@ static int PyBobLearnMiscGMMMachine_init_number(PyBobLearnMiscGMMMachineObject*
 
   if(n_gaussians < 0){
     PyErr_Format(PyExc_TypeError, "gaussians argument must be greater than or equal to zero");
-    GMMMachine_doc.print_usage();
     return -1;
   }
 
   if(n_inputs < 0){
     PyErr_Format(PyExc_TypeError, "input argument must be greater than or equal to zero");
-    GMMMachine_doc.print_usage();
     return -1;
    }
 
diff --git a/bob/learn/misc/include/bob.learn.misc/IVectorMachine.h b/bob/learn/misc/include/bob.learn.misc/IVectorMachine.h
index efff4b7b8ed160a91ece4e091e55dcd0af4597f7..88dd5c1f6f26e683edd76610a9fdc2434e504665 100644
--- a/bob/learn/misc/include/bob.learn.misc/IVectorMachine.h
+++ b/bob/learn/misc/include/bob.learn.misc/IVectorMachine.h
@@ -9,7 +9,6 @@
 #define BOB_LEARN_MISC_IVECTOR_MACHINE_H
 
 #include <blitz/array.h>
-#include <bob.learn.misc/Machine.h>
 #include <bob.learn.misc/GMMMachine.h>
 #include <bob.learn.misc/GMMStats.h>
 #include <bob.io.base/HDF5File.h>
@@ -24,7 +23,7 @@ namespace bob { namespace learn { namespace misc {
  *    N. Dehak, P. Kenny, R. Dehak, P. Dumouchel, P. Ouellet,
  *   IEEE Trans. on Audio, Speech and Language Processing
  */
-class IVectorMachine: public bob::learn::misc::Machine<bob::learn::misc::GMMStats, blitz::Array<double,1> >
+class IVectorMachine
 {
   public:
     /**
@@ -124,7 +123,7 @@ class IVectorMachine: public bob::learn::misc::Machine<bob::learn::misc::GMMStat
      * @warning An exception is thrown if no Universal Background Model has
      *   been set yet.
      */
-    const size_t getDimC() const
+    const size_t getNGaussians() const
     { return m_ubm->getNGaussians(); }
 
     /**
@@ -132,7 +131,7 @@ class IVectorMachine: public bob::learn::misc::Machine<bob::learn::misc::GMMStat
      * @warning An exception is thrown if no Universal Background Model has
      *   been set yet.
      */
-    const size_t getDimD() const
+    const size_t getNInputs() const
     { return m_ubm->getNInputs(); }
 
     /**
@@ -141,7 +140,7 @@ class IVectorMachine: public bob::learn::misc::Machine<bob::learn::misc::GMMStat
      * @warning An exception is thrown if no Universal Background Model has
      *   been set yet.
      */
-    const size_t getDimCD() const
+    const size_t getSupervectorLength() const
     { return m_ubm->getNGaussians()*m_ubm->getNInputs(); }
 
     /**
@@ -229,7 +228,7 @@ class IVectorMachine: public bob::learn::misc::Machine<bob::learn::misc::GMMStat
      */
     void forward_(const bob::learn::misc::GMMStats& input, blitz::Array<double,1>& output) const;
 
-  protected:
+  private:
     /**
      * @brief Apply the variance flooring thresholds.
      * This method is called when using setVarianceThresholds()
diff --git a/bob/learn/misc/isv_base.cpp b/bob/learn/misc/isv_base.cpp
index ca169377dc62fc6d353e9057f26197686fac63c3..023a6d36b1375de442d32a11bfc4361dc21b137a 100644
--- a/bob/learn/misc/isv_base.cpp
+++ b/bob/learn/misc/isv_base.cpp
@@ -82,6 +82,11 @@ static int PyBobLearnMiscISVBase_init_ubm(PyBobLearnMiscISVBaseObject* self, PyO
     return -1;
   }
   
+  if(ru < 0){
+    PyErr_Format(PyExc_TypeError, "ru argument must be greater than or equal to one");
+    return -1;
+  }
+  
   self->cxx.reset(new bob::learn::misc::ISVBase(ubm->cxx, ru));
   return 0;
 }
diff --git a/bob/learn/misc/ivector_machine.cpp b/bob/learn/misc/ivector_machine.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eaae8264c71b2373c041e147340d29470c8fe210
--- /dev/null
+++ b/bob/learn/misc/ivector_machine.cpp
@@ -0,0 +1,522 @@
+/**
+ * @date Wed Jan 28 17:46:15 2015 +0200
+ * @author Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
+ *
+ * @brief Python API for bob::learn::em
+ *
+ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
+ */
+
+#include "main.h"
+
+/******************************************************************/
+/************ Constructor Section *********************************/
+/******************************************************************/
+
+static auto IVectorMachine_doc = bob::extension::ClassDoc(
+  BOB_EXT_MODULE_PREFIX ".IVectorMachine",
+  "An IVectorMachine consists of a Total Variability subspace \f$T\f$ and allows the extraction of IVector"
+  "References: [Dehak2010]",
+  ""
+).add_constructor(
+  bob::extension::FunctionDoc(
+    "__init__",
+    "Constructor. Builds a new IVectorMachine",
+    "",
+    true
+  )
+  .add_prototype("ubm, rt, variance_threshold","")
+  .add_prototype("other","")
+  .add_prototype("hdf5","")
+
+  .add_parameter("ubm", ":py:class:`bob.learn.misc.GMMMachine`", "The Universal Background Model.")
+  .add_parameter("rt", "int", "Size of the Total Variability matrix (CD x rt).")
+  .add_parameter("variance_threshold", "double", "Variance flooring threshold for the Sigma (diagonal) matrix")
+
+  .add_parameter("other", ":py:class:`bob.learn.misc.IVectorMachine`", "A IVectorMachine object to be copied.")
+  .add_parameter("hdf5", ":py:class:`bob.io.base.HDF5File`", "An HDF5 file open for reading")
+
+);
+
+
+static int PyBobLearnMiscIVectorMachine_init_copy(PyBobLearnMiscIVectorMachineObject* self, PyObject* args, PyObject* kwargs) {
+
+  char** kwlist = IVectorMachine_doc.kwlist(1);
+  PyBobLearnMiscIVectorMachineObject* o;
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", kwlist, &PyBobLearnMiscIVectorMachine_Type, &o)){
+    IVectorMachine_doc.print_usage();
+    return -1;
+  }
+
+  self->cxx.reset(new bob::learn::misc::IVectorMachine(*o->cxx));
+  return 0;
+}
+
+
+static int PyBobLearnMiscIVectorMachine_init_hdf5(PyBobLearnMiscIVectorMachineObject* self, PyObject* args, PyObject* kwargs) {
+
+  char** kwlist = IVectorMachine_doc.kwlist(2);
+
+  PyBobIoHDF5FileObject* config = 0;
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&", kwlist, &PyBobIoHDF5File_Converter, &config)){
+    IVectorMachine_doc.print_usage();
+    return -1;
+  }
+
+  self->cxx.reset(new bob::learn::misc::IVectorMachine(*(config->f)));
+
+  return 0;
+}
+
+
+static int PyBobLearnMiscIVectorMachine_init_ubm(PyBobLearnMiscIVectorMachineObject* self, PyObject* args, PyObject* kwargs) {
+
+  char** kwlist = IVectorMachine_doc.kwlist(0);
+  
+  PyBobLearnMiscGMMMachineObject* gmm_machine;
+  int rt = 1;
+  double variance_threshold = 1e-10;
+
+  //Here we have to select which keyword argument to read  
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!i|d", kwlist, &PyBobLearnMiscGMMMachine_Type, &gmm_machine, &rt, &variance_threshold)){
+    IVectorMachine_doc.print_usage();
+    return -1;
+  }
+    
+  if(rt < 1){
+    PyErr_Format(PyExc_TypeError, "rt argument must be greater than or equal to one");
+    return -1;
+  }
+  
+  if(variance_threshold <= 0){
+    PyErr_Format(PyExc_TypeError, "variance_threshold argument must be greater than zero");
+    return -1;
+  }
+
+  self->cxx.reset(new bob::learn::misc::IVectorMachine(gmm_machine->cxx, rt, variance_threshold));
+  return 0;
+}
+
+
+static int PyBobLearnMiscIVectorMachine_init(PyBobLearnMiscIVectorMachineObject* self, PyObject* args, PyObject* kwargs) {
+  BOB_TRY
+
+  // get the number of command line arguments
+  int nargs = (args?PyTuple_Size(args):0) + (kwargs?PyDict_Size(kwargs):0);
+
+  if(nargs == 1){
+    //Reading the input argument
+    PyObject* arg = 0;
+    if (PyTuple_Size(args))
+      arg = PyTuple_GET_ITEM(args, 0);
+    else {
+      PyObject* tmp = PyDict_Values(kwargs);
+      auto tmp_ = make_safe(tmp);
+      arg = PyList_GET_ITEM(tmp, 0);
+    }
+
+    // If the constructor input is Gaussian object
+    if (PyBobLearnMiscIVectorMachine_Check(arg))
+      return PyBobLearnMiscIVectorMachine_init_copy(self, args, kwargs);
+    // If the constructor input is a HDF5
+    else
+      return PyBobLearnMiscIVectorMachine_init_hdf5(self, args, kwargs);
+  }
+  else if ((nargs == 2) || (nargs == 3))
+    PyBobLearnMiscIVectorMachine_init_ubm(self, args, kwargs);
+  else{
+    PyErr_Format(PyExc_RuntimeError, "number of arguments mismatch - %s requires 1,2 or 3 argument, but you provided %d (see help)", Py_TYPE(self)->tp_name, nargs);
+    IVectorMachine_doc.print_usage();
+    return -1;
+  }
+  
+  BOB_CATCH_MEMBER("cannot create IVectorMachine", 0)
+  return 0;
+}
+
+static void PyBobLearnMiscIVectorMachine_delete(PyBobLearnMiscIVectorMachineObject* self) {
+  self->cxx.reset();
+  Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+static PyObject* PyBobLearnMiscIVectorMachine_RichCompare(PyBobLearnMiscIVectorMachineObject* self, PyObject* other, int op) {
+  BOB_TRY
+
+  if (!PyBobLearnMiscIVectorMachine_Check(other)) {
+    PyErr_Format(PyExc_TypeError, "cannot compare `%s' with `%s'", Py_TYPE(self)->tp_name, Py_TYPE(other)->tp_name);
+    return 0;
+  }
+  auto other_ = reinterpret_cast<PyBobLearnMiscIVectorMachineObject*>(other);
+  switch (op) {
+    case Py_EQ:
+      if (*self->cxx==*other_->cxx) Py_RETURN_TRUE; else Py_RETURN_FALSE;
+    case Py_NE:
+      if (*self->cxx==*other_->cxx) Py_RETURN_FALSE; else Py_RETURN_TRUE;
+    default:
+      Py_INCREF(Py_NotImplemented);
+      return Py_NotImplemented;
+  }
+  BOB_CATCH_MEMBER("cannot compare IVectorMachine objects", 0)
+}
+
+int PyBobLearnMiscIVectorMachine_Check(PyObject* o) {
+  return PyObject_IsInstance(o, reinterpret_cast<PyObject*>(&PyBobLearnMiscIVectorMachine_Type));
+}
+
+
+/******************************************************************/
+/************ Variables Section ***********************************/
+/******************************************************************/
+
+/***** shape *****/
+static auto shape = bob::extension::VariableDoc(
+  "shape",
+  "(int,int, int)",
+  "A tuple that represents the number of gaussians, dimensionality of each Gaussian, dimensionality of the rT (total variability matrix) ``(#Gaussians, #Inputs, #rT)``.",
+  ""
+);
+PyObject* PyBobLearnMiscIVectorMachine_getShape(PyBobLearnMiscIVectorMachineObject* self, void*) {
+  BOB_TRY
+  return Py_BuildValue("(i,i,i)", self->cxx->getNGaussians(), self->cxx->getNInputs(), self->cxx->getDimRt());
+  BOB_CATCH_MEMBER("shape could not be read", 0)
+}
+
+/***** supervector_length *****/
+static auto supervector_length = bob::extension::VariableDoc(
+  "supervector_length",
+  "int",
+
+  "Returns the supervector length."
+  "NGaussians x NInputs: Number of Gaussian components by the feature dimensionality",
+  
+  "@warning An exception is thrown if no Universal Background Model has been set yet."
+);
+PyObject* PyBobLearnMiscIVectorMachine_getSupervectorLength(PyBobLearnMiscIVectorMachineObject* self, void*) {
+  BOB_TRY
+  return Py_BuildValue("i", self->cxx->getSupervectorLength());
+  BOB_CATCH_MEMBER("supervector_length could not be read", 0)
+}
+
+
+/***** T *****/
+static auto T = bob::extension::VariableDoc(
+  "t",
+  "array_like <float, 2D>",
+  "Returns the Total Variability matrix",
+  ""
+);
+PyObject* PyBobLearnMiscIVectorMachine_getT(PyBobLearnMiscIVectorMachineObject* self, void*){
+  BOB_TRY
+  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getT());
+  BOB_CATCH_MEMBER("`t` could not be read", 0)
+}
+int PyBobLearnMiscIVectorMachine_setT(PyBobLearnMiscIVectorMachineObject* self, PyObject* value, void*){
+  BOB_TRY
+  PyBlitzArrayObject* o;
+  if (!PyBlitzArray_Converter(value, &o)){
+    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 2D array of floats", Py_TYPE(self)->tp_name, T.name());
+    return -1;
+  }
+  auto o_ = make_safe(o);
+  auto b = PyBlitzArrayCxx_AsBlitz<double,2>(o, "t");
+  if (!b) return -1;
+  self->cxx->setT(*b);
+  return 0;
+  BOB_CATCH_MEMBER("`t` vector could not be set", -1)
+}
+
+
+/***** sigma *****/
+static auto sigma = bob::extension::VariableDoc(
+  "sigma",
+  "array_like <float, 1D>",
+  "The residual matrix of the model sigma",
+  ""
+);
+PyObject* PyBobLearnMiscIVectorMachine_getSigma(PyBobLearnMiscIVectorMachineObject* self, void*){
+  BOB_TRY
+  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getSigma());
+  BOB_CATCH_MEMBER("`sigma` could not be read", 0)
+}
+int PyBobLearnMiscIVectorMachine_setSigma(PyBobLearnMiscIVectorMachineObject* self, PyObject* value, void*){
+  BOB_TRY
+  PyBlitzArrayObject* o;
+  if (!PyBlitzArray_Converter(value, &o)){
+    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 1D array of floats", Py_TYPE(self)->tp_name, sigma.name());
+    return -1;
+  }
+  auto o_ = make_safe(o);
+  auto b = PyBlitzArrayCxx_AsBlitz<double,1>(o, "sigma");
+  if (!b) return -1;
+  self->cxx->setSigma(*b);
+  return 0;
+  BOB_CATCH_MEMBER("`sigma` vector could not be set", -1)
+}
+
+
+/***** variance_threshold *****/
+static auto variance_threshold = bob::extension::VariableDoc(
+  "variance_threshold",
+  "double",
+  "Threshold for the variance contained in sigma",
+  ""
+);
+PyObject* PyBobLearnMiscIVectorMachine_getVarianceThreshold(PyBobLearnMiscIVectorMachineObject* self, void*) {
+  BOB_TRY
+  return Py_BuildValue("d", self->cxx->getVarianceThreshold());
+  BOB_CATCH_MEMBER("variance_threshold could not be read", 0)
+}
+int PyBobLearnMiscIVectorMachine_setVarianceThreshold(PyBobLearnMiscIVectorMachineObject* self, PyObject* value, void*){
+  BOB_TRY
+
+  if (!PyNumber_Check(value)){
+    PyErr_Format(PyExc_RuntimeError, "%s %s expects an double", Py_TYPE(self)->tp_name, variance_threshold.name());
+    return -1;
+  }
+
+  if (PyFloat_AS_DOUBLE(value) < 0){
+    PyErr_Format(PyExc_TypeError, "variance_threshold must be greater than or equal to zero");
+    return -1;
+  }
+
+  self->cxx->setVarianceThreshold(PyFloat_AS_DOUBLE(value));
+  BOB_CATCH_MEMBER("variance_threshold could not be set", -1)
+  return 0;
+}
+
+
+
+static PyGetSetDef PyBobLearnMiscIVectorMachine_getseters[] = { 
+  {
+   shape.name(),
+   (getter)PyBobLearnMiscIVectorMachine_getShape,
+   0,
+   shape.doc(),
+   0
+  },
+  
+  {
+   supervector_length.name(),
+   (getter)PyBobLearnMiscIVectorMachine_getSupervectorLength,
+   0,
+   supervector_length.doc(),
+   0
+  },
+  
+  {
+   T.name(),
+   (getter)PyBobLearnMiscIVectorMachine_getT,
+   (setter)PyBobLearnMiscIVectorMachine_setT,
+   T.doc(),
+   0
+  },
+
+  {
+   variance_threshold.name(),
+   (getter)PyBobLearnMiscIVectorMachine_getVarianceThreshold,
+   (setter)PyBobLearnMiscIVectorMachine_setVarianceThreshold,
+   variance_threshold.doc(),
+   0
+  },
+
+  {
+   sigma.name(),
+   (getter)PyBobLearnMiscIVectorMachine_getSigma,
+   (setter)PyBobLearnMiscIVectorMachine_setSigma,
+   sigma.doc(),
+   0
+  },
+
+
+  {0}  // Sentinel
+};
+
+
+/******************************************************************/
+/************ Functions Section ***********************************/
+/******************************************************************/
+
+
+/*** save ***/
+static auto save = bob::extension::FunctionDoc(
+  "save",
+  "Save the configuration of the IVectorMachine to a given HDF5 file"
+)
+.add_prototype("hdf5")
+.add_parameter("hdf5", ":py:class:`bob.io.base.HDF5File`", "An HDF5 file open for writing");
+static PyObject* PyBobLearnMiscIVectorMachine_Save(PyBobLearnMiscIVectorMachineObject* self,  PyObject* args, PyObject* kwargs) {
+
+  BOB_TRY
+  
+  // get list of arguments
+  char** kwlist = save.kwlist(0);  
+  PyBobIoHDF5FileObject* hdf5;
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&", kwlist, PyBobIoHDF5File_Converter, &hdf5)) return 0;
+
+  auto hdf5_ = make_safe(hdf5);
+  self->cxx->save(*hdf5->f);
+
+  BOB_CATCH_MEMBER("cannot save the data", 0)
+  Py_RETURN_NONE;
+}
+
+/*** load ***/
+static auto load = bob::extension::FunctionDoc(
+  "load",
+  "Load the configuration of the IVectorMachine to a given HDF5 file"
+)
+.add_prototype("hdf5")
+.add_parameter("hdf5", ":py:class:`bob.io.base.HDF5File`", "An HDF5 file open for reading");
+static PyObject* PyBobLearnMiscIVectorMachine_Load(PyBobLearnMiscIVectorMachineObject* self, PyObject* args, PyObject* kwargs) {
+  BOB_TRY
+  
+  char** kwlist = load.kwlist(0);  
+  PyBobIoHDF5FileObject* hdf5;
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&", kwlist, PyBobIoHDF5File_Converter, &hdf5)) return 0;
+  
+  auto hdf5_ = make_safe(hdf5);  
+  self->cxx->load(*hdf5->f);
+
+  BOB_CATCH_MEMBER("cannot load the data", 0)
+  Py_RETURN_NONE;
+}
+
+
+/*** is_similar_to ***/
+static auto is_similar_to = bob::extension::FunctionDoc(
+  "is_similar_to",
+  
+  "Compares this IVectorMachine with the ``other`` one to be approximately the same.",
+  "The optional values ``r_epsilon`` and ``a_epsilon`` refer to the "
+  "relative and absolute precision for the ``weights``, ``biases`` "
+  "and any other values internal to this machine."
+)
+.add_prototype("other, [r_epsilon], [a_epsilon]","output")
+.add_parameter("other", ":py:class:`bob.learn.misc.IVectorMachine`", "A IVectorMachine object to be compared.")
+.add_parameter("r_epsilon", "float", "Relative precision.")
+.add_parameter("a_epsilon", "float", "Absolute precision.")
+.add_return("output","bool","True if it is similar, otherwise false.");
+static PyObject* PyBobLearnMiscIVectorMachine_IsSimilarTo(PyBobLearnMiscIVectorMachineObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  char** kwlist = is_similar_to.kwlist(0);
+
+  //PyObject* other = 0;
+  PyBobLearnMiscIVectorMachineObject* other = 0;
+  double r_epsilon = 1.e-5;
+  double a_epsilon = 1.e-8;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|dd", kwlist,
+        &PyBobLearnMiscIVectorMachine_Type, &other,
+        &r_epsilon, &a_epsilon)){
+
+        is_similar_to.print_usage(); 
+        return 0;        
+  }
+
+  if (self->cxx->is_similar_to(*other->cxx, r_epsilon, a_epsilon))
+    Py_RETURN_TRUE;
+  else
+    Py_RETURN_FALSE;
+}
+
+
+
+/*** forward ***/
+static auto forward = bob::extension::FunctionDoc(
+  "forward",
+  "Execute the machine",
+  "", 
+  true
+)
+.add_prototype("stats")
+.add_parameter("stats", ":py:class:`bob.learn.misc.GMMStats`", "Statistics as input");
+static PyObject* PyBobLearnMiscIVectorMachine_Forward(PyBobLearnMiscIVectorMachineObject* self, PyObject* args, PyObject* kwargs) {
+  BOB_TRY
+
+  char** kwlist = forward.kwlist(0);
+
+  PyBobLearnMiscGMMStatsObject* stats = 0;
+  
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", kwlist, &PyBobLearnMiscGMMStats_Type, &stats))
+    Py_RETURN_NONE;
+
+   blitz::Array<double,1> ivector(self->cxx->getDimRt());
+   self->cxx->forward(*stats->cxx, ivector);
+
+  return PyBlitzArrayCxx_AsConstNumpy(ivector);
+  
+  BOB_CATCH_MEMBER("cannot forward", 0)
+
+}
+
+
+static PyMethodDef PyBobLearnMiscIVectorMachine_methods[] = {
+  {
+    save.name(),
+    (PyCFunction)PyBobLearnMiscIVectorMachine_Save,
+    METH_VARARGS|METH_KEYWORDS,
+    save.doc()
+  },
+  {
+    load.name(),
+    (PyCFunction)PyBobLearnMiscIVectorMachine_Load,
+    METH_VARARGS|METH_KEYWORDS,
+    load.doc()
+  },
+  {
+    is_similar_to.name(),
+    (PyCFunction)PyBobLearnMiscIVectorMachine_IsSimilarTo,
+    METH_VARARGS|METH_KEYWORDS,
+    is_similar_to.doc()
+  },
+
+/*
+  {
+    forward.name(),
+    (PyCFunction)PyBobLearnMiscIVectorMachine_Forward,
+    METH_VARARGS|METH_KEYWORDS,
+    forward.doc()
+  },*/
+
+
+  {0} /* Sentinel */
+};
+
+
+/******************************************************************/
+/************ Module Section **************************************/
+/******************************************************************/
+
+// Define the JFA type struct; will be initialized later
+PyTypeObject PyBobLearnMiscIVectorMachine_Type = {
+  PyVarObject_HEAD_INIT(0,0)
+  0
+};
+
+bool init_BobLearnMiscIVectorMachine(PyObject* module)
+{
+  // initialize the type struct
+  PyBobLearnMiscIVectorMachine_Type.tp_name      = IVectorMachine_doc.name();
+  PyBobLearnMiscIVectorMachine_Type.tp_basicsize = sizeof(PyBobLearnMiscIVectorMachineObject);
+  PyBobLearnMiscIVectorMachine_Type.tp_flags     = Py_TPFLAGS_DEFAULT;
+  PyBobLearnMiscIVectorMachine_Type.tp_doc       = IVectorMachine_doc.doc();
+
+  // set the functions
+  PyBobLearnMiscIVectorMachine_Type.tp_new         = PyType_GenericNew;
+  PyBobLearnMiscIVectorMachine_Type.tp_init        = reinterpret_cast<initproc>(PyBobLearnMiscIVectorMachine_init);
+  PyBobLearnMiscIVectorMachine_Type.tp_dealloc     = reinterpret_cast<destructor>(PyBobLearnMiscIVectorMachine_delete);
+  PyBobLearnMiscIVectorMachine_Type.tp_richcompare = reinterpret_cast<richcmpfunc>(PyBobLearnMiscIVectorMachine_RichCompare);
+  PyBobLearnMiscIVectorMachine_Type.tp_methods     = PyBobLearnMiscIVectorMachine_methods;
+  PyBobLearnMiscIVectorMachine_Type.tp_getset      = PyBobLearnMiscIVectorMachine_getseters;
+  PyBobLearnMiscIVectorMachine_Type.tp_call        = reinterpret_cast<ternaryfunc>(PyBobLearnMiscIVectorMachine_Forward);
+
+
+  // check that everything is fine
+  if (PyType_Ready(&PyBobLearnMiscIVectorMachine_Type) < 0) return false;
+
+  // add the type to the module
+  Py_INCREF(&PyBobLearnMiscIVectorMachine_Type);
+  return PyModule_AddObject(module, "IVectorMachine", (PyObject*)&PyBobLearnMiscIVectorMachine_Type) >= 0;
+}
+
diff --git a/bob/learn/misc/jfa_base.cpp b/bob/learn/misc/jfa_base.cpp
index d995114e65cee760d96697e5645903243a644e05..5793919e134042ed6e2840e90847e74e4bd1eb3e 100644
--- a/bob/learn/misc/jfa_base.cpp
+++ b/bob/learn/misc/jfa_base.cpp
@@ -84,6 +84,16 @@ static int PyBobLearnMiscJFABase_init_ubm(PyBobLearnMiscJFABaseObject* self, PyO
     return -1;
   }
   
+  if(ru < 0){
+    PyErr_Format(PyExc_TypeError, "ru argument must be greater than or equal to one");
+    return -1;
+  }
+  
+  if(rv < 0){
+    PyErr_Format(PyExc_TypeError, "rv argument must be greater than or equal to one");
+    return -1;
+  }
+  
   self->cxx.reset(new bob::learn::misc::JFABase(ubm->cxx, ru, rv));
   return 0;
 }
diff --git a/bob/learn/misc/main.cpp b/bob/learn/misc/main.cpp
index f57fdfe3f96beb72d0f3eaeb9870793f3c5ac4f2..316a9c17a743f2a780c21a6c85ad586710097f7e 100644
--- a/bob/learn/misc/main.cpp
+++ b/bob/learn/misc/main.cpp
@@ -52,7 +52,8 @@ static PyObject* create_module (void) {
   if (!init_BobLearnMiscISVBase(module)) return 0;
 
   if (!init_BobLearnMiscJFAMachine(module)) return 0;
-  if (!init_BobLearnMiscISVMachine(module)) return 0;  
+  if (!init_BobLearnMiscISVMachine(module)) return 0;
+  if (!init_BobLearnMiscIVectorMachine(module)) return 0;  
 
 
   static void* PyBobLearnMisc_API[PyBobLearnMisc_API_pointers];
diff --git a/bob/learn/misc/main.h b/bob/learn/misc/main.h
index aa36669e70d44455100f17211043fa698fad586a..50b5de618822780333b9013a2c41b40d86ddeb83 100644
--- a/bob/learn/misc/main.h
+++ b/bob/learn/misc/main.h
@@ -32,6 +32,7 @@
 
 #include <bob.learn.misc/JFAMachine.h>
 #include <bob.learn.misc/ISVMachine.h>
+#include <bob.learn.misc/IVectorMachine.h>
 
 
 #if PY_VERSION_HEX >= 0x03000000
@@ -204,5 +205,16 @@ bool init_BobLearnMiscISVMachine(PyObject* module);
 int PyBobLearnMiscISVMachine_Check(PyObject* o);
 
 
+// IVectorMachine
+typedef struct {
+  PyObject_HEAD
+  boost::shared_ptr<bob::learn::misc::IVectorMachine> cxx;
+} PyBobLearnMiscIVectorMachineObject;
+
+extern PyTypeObject PyBobLearnMiscIVectorMachine_Type;
+bool init_BobLearnMiscIVectorMachine(PyObject* module);
+int PyBobLearnMiscIVectorMachine_Check(PyObject* o);
+
+
 
 #endif // BOB_LEARN_EM_MAIN_H
diff --git a/bob/learn/misc/test_ivector.py b/bob/learn/misc/test_ivector.py
index f16638ae36449f9fc9a9558afb10b012e560fe94..fb9c8b23e9afedc8013d89ce254fcea5ec02db3e 100644
--- a/bob/learn/misc/test_ivector.py
+++ b/bob/learn/misc/test_ivector.py
@@ -32,14 +32,15 @@ class IVectorMachinePy():
 
   def resize(self):
     if self.m_ubm:
-      dim_cd = self.m_ubm.dim_c * self.m_ubm.dim_d
+      dim_cd = self.m_ubm.shape[0] * self.m_ubm.shape[1]
       self.m_t = numpy.random.randn(dim_cd, self.m_dim_t)
       self.m_sigma = numpy.random.randn(dim_cd)
 
   def precompute(self):
     if self.m_ubm and self.m_t is not None and self.m_sigma is not None:
-      dim_c = self.m_ubm.dim_c
-      dim_d = self.m_ubm.dim_d
+      #dim_c = self.m_ubm.dim_c
+      #dim_d = self.m_ubm.dim_d
+      dim_c,dim_d = self.m_ubm.shape
       self.m_cache_TtSigmaInv = {}
       self.m_cache_TtSigmaInvT = {}
       for c in range(dim_c):
@@ -78,13 +79,14 @@ class IVectorMachinePy():
 
   def _get_TtSigmaInv_Fnorm(self, N, F):
     # Initialization
-    dim_c = self.m_ubm.dim_c
-    dim_d = self.m_ubm.dim_d
+    #dim_c = self.m_ubm.dim_c
+    #dim_d = self.m_ubm.dim_d
+    dim_c,dim_d = self.m_ubm.shape
     mean_supervector = self.m_ubm.mean_supervector
     TtSigmaInv_Fnorm = numpy.zeros(shape=(self.m_dim_t,), dtype=numpy.float64)
 
     # Loop over each Gaussian component
-    dim_c = self.m_ubm.dim_c
+    dim_c = self.m_ubm.shape[0]
     for c in range(dim_c):
       start             = c*dim_d
       end               = (c+1)*dim_d
@@ -94,8 +96,9 @@ class IVectorMachinePy():
 
   def _get_I_TtSigmaInvNT(self, N):
     # Initialization
-    dim_c = self.m_ubm.dim_c
-    dim_d = self.m_ubm.dim_d
+    #dim_c = self.m_ubm.dim_c
+    #dim_d = self.m_ubm.dim_d
+    dim_c, dim_d = self.m_ubm.shape
 
     TtSigmaInvNT = numpy.eye(self.m_dim_t, dtype=numpy.float64)
     for c in range(dim_c):
@@ -152,5 +155,5 @@ def test_machine():
   mc.sigma = sigma
 
   wij_ref = numpy.array([-0.04213415, 0.21463343]) # Reference from original Chris implementation
-  wij = mc.forward(gs)
+  wij = mc(gs)
   assert numpy.allclose(wij_ref, wij, 1e-5)
diff --git a/doc/index.rst b/doc/index.rst
index 04bf7cd0bdb654f06e62096b97f38f52a99c6cde..058e2263595cf5b3ba034fc2170c945100bec97c 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -30,6 +30,7 @@ References
 .. [Reynolds2000] *Reynolds, Douglas A., Thomas F. Quatieri, and Robert B. Dunn*. **Speaker Verification Using Adapted Gaussian Mixture Models**, Digital signal processing 10.1 (2000): 19-41.
 ..   [Vogt2008]   *R. Vogt, S. Sridharan*. **'Explicit Modelling of Session Variability for Speaker Verification'**, Computer Speech & Language, 2008, vol. 22, no. 1, pp. 17-38
 ..   [McCool2013] *C. McCool, R. Wallace, M. McLaren, L. El Shafey, S. Marcel*. **'Session Variability Modelling for Face Authentication'**, IET Biometrics, 2013
+..   [Dehak2010] *N. Dehak, P. Kenny, R. Dehak, P. Dumouchel, P. Ouellet*, **'Front End Factor Analysis for Speaker Verification'**, IEEE Transactions on Audio, Speech and Language Processing, 2010, vol. 19, issue 4, pp. 788-798
 
 
 Indices and tables
diff --git a/setup.py b/setup.py
index 187d11ccf38b0d4026ab960b0075de5d0d3f0664..7e139cc4f246253bdc0e18a83d793ae627039df6 100644
--- a/setup.py
+++ b/setup.py
@@ -57,7 +57,7 @@ setup(
           "bob/learn/misc/cpp/Gaussian.cpp",
           "bob/learn/misc/cpp/GMMMachine.cpp",
           "bob/learn/misc/cpp/GMMStats.cpp",
-          #"bob/learn/misc/cpp/IVectorMachine.cpp",
+          "bob/learn/misc/cpp/IVectorMachine.cpp",
           "bob/learn/misc/cpp/KMeansMachine.cpp",
           "bob/learn/misc/cpp/LinearScoring.cpp",
           #"bob/learn/misc/cpp/PLDAMachine.cpp",
@@ -117,6 +117,8 @@ setup(
           "bob/learn/misc/isv_base.cpp",
           "bob/learn/misc/jfa_machine.cpp",
           "bob/learn/misc/isv_machine.cpp",
+          
+          "bob/learn/misc/ivector_machine.cpp",
 
           "bob/learn/misc/main.cpp",
         ],