diff --git a/Geo/CellComplex.cpp b/Geo/CellComplex.cpp
index 726882c9f41d9103f9a06a66da04a0ca45dfcec1..b6f025039847ac954258a87a7b8c2644cfa6cb7e 100644
--- a/Geo/CellComplex.cpp
+++ b/Geo/CellComplex.cpp
@@ -7,6 +7,9 @@
 
 #include "CellComplex.h"
 #include "MElement.h"
+#include "OS.h"
+
+double CellComplex::_patience = 10;
 
 CellComplex::CellComplex(GModel* model,
 			 std::vector<MElement*>& domainElements,
@@ -72,7 +75,10 @@ bool CellComplex::_insertCells(std::vector<MElement*>& elements,
   }
   _dim = 0;
 
+  double t1 = Cpu();
+
   for(unsigned int i=0; i < elements.size(); i++){
+
     MElement* element = elements.at(i);
     int dim = element->getDim();
     int type = element->getType();
@@ -112,6 +118,15 @@ bool CellComplex::_insertCells(std::vector<MElement*>& elements,
   _biggestCell = biggestElement[_dim];
 
   for (int dim = 3; dim > 0; dim--){
+
+    double t2 = Cpu();
+    if(t2-t1 > CellComplex::_patience && dim > 1) {
+      if(domain == 0)
+        Msg::Info(" ... creating domain %d-cells", dim);
+      else if(domain == 1)
+        Msg::Info(" ... creating subdomain %d-cells", dim);
+    }
+
     for(citer cit = firstCell(dim); cit != lastCell(dim); cit++){
       Cell* cell = *cit;
       for(int i = 0; i < cell->getNumBdElements(); i++){
@@ -470,14 +485,13 @@ int CellComplex::reduceComplex(int combine, bool omit, bool homseq)
             getSize(3), getSize(2), getSize(1), getSize(0));
 
   if(!getSize(0)) return 0;
+
+  double t1 = Cpu();
   int count = 0;
   if(relative() && !homseq) removeSubdomain();
   std::vector<Cell*> empty;
   for(int i = 3; i > 0; i--) count = count + reduction(i, -1, empty);
 
-  Msg::Debug(" %d volumes, %d faces, %d edges, and %d vertices",
-             getSize(3), getSize(2), getSize(1), getSize(0));
-
   if(omit && !homseq){
 
     std::vector<Cell*> newCells;
@@ -495,15 +509,26 @@ int CellComplex::reduceComplex(int combine, bool omit, bool homseq)
     }
   }
 
-  Msg::Debug(" %d volumes, %d faces, %d edges, and %d vertices",
-             getSize(3), getSize(2), getSize(1), getSize(0));
+  double t2 = Cpu();
+  if(t2-t1 > CellComplex::_patience) {
+    Msg::Info(" .. %d volumes, %d faces, %d edges, and %d vertices",
+              getSize(3), getSize(2), getSize(1), getSize(0));
+  }
 
   if(combine > 0) this->combine(3);
-  reduction(2, -1, empty);
+
+  if(combine > 2) for(int i = 3; i > 0; i--) reduction(i, -1, empty);
+  else reduction(2, -1, empty);
+
   if(combine > 0) this->combine(2);
-  reduction(1, -1, empty);
+
+  if(combine > 2) for(int i = 3; i > 0; i--) reduction(i, -1, empty);
+  else reduction(1, -1, empty);
+
   if(combine > 0) this->combine(1);
 
+  if(combine > 2) for(int i = 3; i > 0; i--) reduction(i, -1, empty);
+
   Msg::Debug(" %d volumes, %d faces, %d edges, and %d vertices",
              getSize(3), getSize(2), getSize(1), getSize(0));
 
@@ -542,6 +567,9 @@ int CellComplex::coreduceComplex(int combine, bool omit, int heuristic)
             getSize(3), getSize(2), getSize(1), getSize(0));
 
   if(!getSize(0)) return 0;
+
+  double t1 = Cpu();
+
   int count = 0;
   if(relative()) removeSubdomain();
   std::vector<Cell*> empty;
@@ -557,9 +585,6 @@ int CellComplex::coreduceComplex(int combine, bool omit, int heuristic)
   for(int j = 1; j <= getDim(); j++)
     count += coreduction(j, -1, empty);
 
-  Msg::Debug(" %d volumes, %d faces, %d edges, and %d vertices",
-             getSize(3), getSize(2), getSize(1), getSize(0));
-
   if(omit){
 
     std::vector<Cell*> newCells;
@@ -589,15 +614,26 @@ int CellComplex::coreduceComplex(int combine, bool omit, int heuristic)
 
   }
 
-  Msg::Debug(" %d volumes, %d faces, %d edges, and %d vertices",
-             getSize(3), getSize(2), getSize(1), getSize(0));
+  double t2 = Cpu();
+  if(t2-t1 > CellComplex::_patience) {
+    Msg::Info(" .. %d volumes, %d faces, %d edges, and %d vertices",
+              getSize(3), getSize(2), getSize(1), getSize(0));
+  }
 
   if(combine > 0) this->cocombine(0);
-  coreduction(1, -1, empty);
+
+  if(combine > 2) for(int i = 1; i < 4; i++) coreduction(i, -1, empty);
+  else coreduction(1, -1, empty);
+
   if(combine > 0) this->cocombine(1);
-  coreduction(2, -1, empty);
+
+  if(combine > 2)  for(int i = 1; i < 4; i++) coreduction(i, -1, empty);
+  else coreduction(2, -1, empty);
+
   if(combine > 0) this->cocombine(2);
-  coreduction(3, -1, empty);
+
+  if(combine > 2) for(int i = 1; i < 4; i++) coreduction(i, -1, empty);
+  else coreduction(3, -1, empty);
 
   coherent();
   Msg::Debug(" %d volumes, %d faces, %d edges, and %d vertices",
@@ -669,15 +705,26 @@ int CellComplex::combine(int dim)
   //           getSize(3), getSize(2), getSize(1), getSize(0));
   if(dim < 1 || dim > 3) return 0;
 
+  double t1 = Cpu();
+
   std::queue<Cell*> Q;
   std::set<Cell*, Less_Cell> Qset;
   std::map<Cell*, short int, Less_Cell> bd_c;
   int count = 0;
 
   for(citer cit = firstCell(dim); cit != lastCell(dim); cit++){
+
+    double t2 = Cpu();
+    if(t2-t1 > CellComplex::_patience) {
+      t1 = Cpu();
+      Msg::Info(" ... %d volumes, %d faces, %d edges, and %d vertices",
+                getSize(3), getSize(2), getSize(1), getSize(0));
+    }
+
     Cell* cell = *cit;
     cell->getBoundary(bd_c);
     enqueueCells(bd_c, Q, Qset);
+
     while(Q.size() != 0){
       Cell* s = Q.front();
       Q.pop();
@@ -741,12 +788,22 @@ int CellComplex::cocombine(int dim)
 
   if(dim < 0 || dim > 2) return 0;
 
+  double t1 = Cpu();
+
   std::queue<Cell*> Q;
   std::set<Cell*, Less_Cell> Qset;
   std::map<Cell*, short int, Less_Cell> cbd_c;
   int count = 0;
 
   for(citer cit = firstCell(dim); cit != lastCell(dim); cit++){
+
+    double t2 = Cpu();
+    if(t2-t1 > CellComplex::_patience) {
+      t1 = Cpu();
+      Msg::Info(" ... %d volumes, %d faces, %d edges, and %d vertices",
+                getSize(3), getSize(2), getSize(1), getSize(0));
+    }
+
     Cell* cell = *cit;
 
     cell->getCoboundary(cbd_c);
diff --git a/Geo/CellComplex.h b/Geo/CellComplex.h
index b782873549bc2f8ff58969b649789ae052f47f25..e80238ce42844496fd00ad11815e27d18edc8056 100644
--- a/Geo/CellComplex.h
+++ b/Geo/CellComplex.h
@@ -83,6 +83,8 @@ class CellComplex
   int coreduction(Cell* startCell, int omit,
 		  std::vector<Cell*>& omittedCells);
 
+  static double _patience;
+
  public:
   CellComplex(GModel* model,
 	      std::vector<MElement*>& domainElements,
diff --git a/Geo/Chain.cpp b/Geo/Chain.cpp
index 29e06b67d0d3d66852c9120aad22280fefa7138f..142315c9bdba782ffda9c1025ca88f5cafc15c85 100644
--- a/Geo/Chain.cpp
+++ b/Geo/Chain.cpp
@@ -48,6 +48,33 @@ inline void ElemChain::_sortVertexIndices()
     _si.push_back(it->second);
 }
 
+void findEntitiesInPhysicalGroups
+(GModel* m, const std::vector<int>& physicalGroups,
+std::vector<GEntity*>& entities)
+{
+  std::map<int, std::vector<GEntity*> > groups[4];
+  m->getPhysicalGroups(groups);
+  std::map<int, std::vector<GEntity*> >::iterator it;
+  for(unsigned int i = 0; i < physicalGroups.size(); i++){
+    bool found = false;
+    for(int j = 0; j < 4; j++){
+      it = groups[j].find(physicalGroups.at(i));
+      if(it != groups[j].end()){
+        found = true;
+        std::vector<GEntity*> physicalGroup = it->second;
+        for(unsigned int k = 0; k < physicalGroup.size(); k++){
+          entities.push_back(physicalGroup.at(k));
+        }
+      }
+    }
+    if(!found) {
+      Msg::Error("Physical group %d does not exist",
+                 physicalGroups.at(i));
+    }
+  }
+}
+
+
 bool ElemChain::_equalVertices(const std::vector<MVertex*>& v2) const {
   if(_v.size() != v2.size()) return false;
   for(unsigned int i = 0; i < _v.size(); i++)
diff --git a/Geo/Chain.h b/Geo/Chain.h
index 221ee3dabadc2c75bd3b9b06a4bcac9866a7aa93..721a429df04692c1ec4389f2409d83fd871b68e4 100644
--- a/Geo/Chain.h
+++ b/Geo/Chain.h
@@ -164,6 +164,10 @@ class ElemChain : public PosetCat<ElemChain>
 
 };
 
+void findEntitiesInPhysicalGroups
+(GModel* m, const std::vector<int>& physicalGroups,
+std::vector<GEntity*>& entities);
+
 // Class to represent a chain, formal sum of elementary chains
 template <class C>
 class Chain : public VectorSpaceCat<Chain<C>, C>
@@ -176,6 +180,9 @@ private:
   // A name for the chain
   std::string _name;
 
+  Chain<C> _getTraceOrProject(const std::vector<GEntity*>& entities,
+                              bool trace) const;
+
 public:
   // Elementary chain iterators
   typedef typename std::map<ElemChain, C>::iterator ecit;
@@ -222,11 +229,22 @@ public:
   Chain<C> getBoundary() const;
 
   // Get a chain which contains elementary chains that are
-  // fully in the given physical group or elementary entities
+  // in the given physical group or elementary entities
   Chain<C> getTrace(GModel* m, int physicalGroup) const;
   Chain<C> getTrace(GModel* m, const std::vector<int>& physicalGroups) const;
   Chain<C> getTrace(const std::vector<GEntity*>& entities) const;
 
+  // Get a chain which contains elementary chains that are *not*
+  // in the given physical group or elementary entities
+  Chain<C> getProject(GModel* m, int physicalGroup) const;
+  Chain<C> getProject(GModel* m, const std::vector<int>& physicalGroups) const;
+  Chain<C> getProject(const std::vector<GEntity*>& entities) const;
+
+  // The above two methods decompose a chain c so that
+  // (c - c.getTrace(...) - c.getProject(...)).isZero() == true
+  // holds
+
+
   // Add chain to Gmsh model as a physical group,
   // elementary chains are turned into mesh elements with
   // orientation and multiplicity given by elementary chain coefficient
@@ -239,26 +257,13 @@ public:
 template <class C>
 Chain<C>::Chain(GModel* m, int physicalGroup)
 {
+  std::vector<int> groups(1, physicalGroup);
   std::vector<GEntity*> entities;
-  std::map<int, std::vector<GEntity*> > groups[4];
-  m->getPhysicalGroups(groups);
-  std::map<int, std::vector<GEntity*> >::iterator it;
-
-  for(int j = 0; j < 4; j++){
-    it = groups[j].find(physicalGroup);
-    if(it != groups[j].end()){
-      _dim = j;
-      std::vector<GEntity*> physicalGroup = it->second;
-      for(unsigned int k = 0; k < physicalGroup.size(); k++){
-        entities.push_back(physicalGroup.at(k));
-      }
-    }
-  }
-  if(entities.empty()) {
-    Msg::Error("Physical group %d does not exist", physicalGroup);
-  }
+  findEntitiesInPhysicalGroups(m, groups, entities);
+
   for(unsigned int i = 0; i < entities.size(); i++) {
     GEntity* e = entities.at(i);
+    _dim = e->dim();
     for(unsigned int j = 0; j < e->getNumMeshElements(); j++) {
       this->addMeshElement(e->getMeshElement(j));
     }
@@ -356,37 +361,36 @@ Chain<C> Chain<C>::getTrace(GModel* m, int physicalGroup) const
   return this->getTrace(m, groups);
 }
 
+template <class C>
+Chain<C> Chain<C>::getProject(GModel* m, int physicalGroup) const
+{
+  std::vector<int> groups(1, physicalGroup);
+  return this->getProject(m, groups);
+}
+
 template <class C>
 Chain<C> Chain<C>::getTrace(GModel* m,
                             const std::vector<int>& physicalGroups) const
 {
-  std::map<int, std::vector<GEntity*> > groups[4];
-  m->getPhysicalGroups(groups);
-  std::map<int, std::vector<GEntity*> >::iterator it;
   std::vector<GEntity*> entities;
-  for(unsigned int i = 0; i < physicalGroups.size(); i++){
-    bool found = false;
-    for(int j = 0; j < 4; j++){
-      it = groups[j].find(physicalGroups.at(i));
-      if(it != groups[j].end()){
-        found = true;
-        std::vector<GEntity*> physicalGroup = it->second;
-        for(unsigned int k = 0; k < physicalGroup.size(); k++){
-          entities.push_back(physicalGroup.at(k));
-        }
-      }
-    }
-    if(!found) {
-      Msg::Error("Physical group %d does not exist",
-                 physicalGroups.at(i));
-    }
-  }
+  findEntitiesInPhysicalGroups(m, physicalGroups, entities);
   if(entities.empty()) return Chain<C>();
-  return getTrace(entities);
+  return this->_getTraceOrProject(entities, true);
 }
 
 template <class C>
-Chain<C> Chain<C>::getTrace(const std::vector<GEntity*>& entities) const
+Chain<C> Chain<C>::getProject(GModel* m,
+                              const std::vector<int>& physicalGroups) const
+{
+  std::vector<GEntity*> entities;
+  findEntitiesInPhysicalGroups(m, physicalGroups, entities);
+  if(entities.empty()) return Chain<C>();
+  return this->_getTraceOrProject(entities, false);
+}
+
+template <class C>
+Chain<C> Chain<C>::_getTraceOrProject
+(const std::vector<GEntity*>& entities, bool trace) const
 {
   Chain<C> result;
   for(cecit it = _elemChains.begin(); it != _elemChains.end(); it++) {
@@ -397,11 +401,24 @@ Chain<C> Chain<C>::getTrace(const std::vector<GEntity*>& entities) const
         break;
       }
     }
-    if(inDomain) result.addElemChain(it->first, it->second);
+    if(inDomain && trace) result.addElemChain(it->first, it->second);
+    if(!inDomain && !trace) result.addElemChain(it->first, it->second);
   }
   return result;
 }
 
+template <class C>
+Chain<C> Chain<C>::getTrace(const std::vector<GEntity*>& entities) const
+{
+  return this->_getTraceOrProject(entities, true);
+}
+
+template <class C>
+Chain<C> Chain<C>::getProject(const std::vector<GEntity*>& entities) const
+{
+  return this->_getTraceOrProject(entities, false);
+}
+
 template <class C>
 void Chain<C>::addMeshElement(MElement* e, C coeff)
 {
diff --git a/Geo/Homology.h b/Geo/Homology.h
index 7489cb5743981af90cc05da6f5a1728ace07b0cf..1a997a72c45ad02a21e1739c2166ceb72192b7cb 100644
--- a/Geo/Homology.h
+++ b/Geo/Homology.h
@@ -100,7 +100,7 @@ class Homology
 	   std::vector<int> physicalSubdomain,
            std::vector<int> physicalIm,
            bool saveOrig=true,
-	   int combine=2, bool omit=true, bool smoothen=true,
+	   int combine=3, bool omit=true, bool smoothen=true,
            int heuristic=1);
   ~Homology();
 
diff --git a/Plugin/HomologyComputation.cpp b/Plugin/HomologyComputation.cpp
index 5cde0ceb8883d8b7723abdc4e3a386825676dfe8..78d0ba427c2e273c5b50e4583bfcc84654d6ae4b 100644
--- a/Plugin/HomologyComputation.cpp
+++ b/Plugin/HomologyComputation.cpp
@@ -24,7 +24,7 @@ StringXNumber HomologyComputationOptions_Number[] = {
   {GMSH_FULLRC, "CohomologyPhysicalGroupsBegin", NULL, -1.},
   {GMSH_FULLRC, "CreatePostProcessingViews", NULL, 1.},
   {GMSH_FULLRC, "ReductionOmit", NULL, 1.},
-  {GMSH_FULLRC, "ReductionCombine", NULL, 2.},
+  {GMSH_FULLRC, "ReductionCombine", NULL, 3.},
   {GMSH_FULLRC, "PostProcessSimplify", NULL, 1.},
   {GMSH_FULLRC, "ReductionHeuristic", NULL, 1.}
 };
diff --git a/Plugin/HomologyPostProcessing.cpp b/Plugin/HomologyPostProcessing.cpp
index fd0d912896799a655f938bcdb11959441b025d01..aa9f95bf1ddd2e5366fb6179ba8d6781c17d7908 100644
--- a/Plugin/HomologyPostProcessing.cpp
+++ b/Plugin/HomologyPostProcessing.cpp
@@ -26,6 +26,7 @@ StringXString HomologyPostProcessingOptions_String[] = {
   {GMSH_FULLRC, "PhysicalGroupsOfOperatedChains", NULL, "1, 2"},
   {GMSH_FULLRC, "PhysicalGroupsOfOperatedChains2", NULL, ""},
   {GMSH_FULLRC, "PhysicalGroupsToTraceResults", NULL, ""},
+  {GMSH_FULLRC, "PhysicalGroupsToProjectResults", NULL, ""},
   {GMSH_FULLRC, "NameForResultChains", NULL, "c"},
 };
 
@@ -54,7 +55,8 @@ std::string GMSH_HomologyPostProcessingPlugin::getHelp() const
     "Results a new basis for the homology space such that the incidence matrix of the new basis and the basis of the cohomology space is the identity matrix.\n\n"
 
     "Options:\n"
-    "'PhysicalGroupsToTraceResults': Trace the resulting cochains to the given physical groups.\n"
+    "'PhysicalGroupsToTraceResults': Trace the resulting (co)chains to the given physical groups.\n"
+    "'PhysicalGroupsToProjectResults': Project the resulting (co)chains to the complement of the given physical groups.\n"
     "'NameForResultChains': Post-processing view name prefix for the results.\n"
     "'ApplyBoundaryOperatorToResults': Apply boundary operator to the resulting chains.\n";
 
@@ -139,8 +141,9 @@ PView *GMSH_HomologyPostProcessingPlugin::execute(PView *v)
   std::string matrixString = HomologyPostProcessingOptions_String[0].def;
   std::string opString1 = HomologyPostProcessingOptions_String[1].def;
   std::string opString2 = HomologyPostProcessingOptions_String[2].def;
-  std::string cname = HomologyPostProcessingOptions_String[4].def;
+  std::string cname = HomologyPostProcessingOptions_String[5].def;
   std::string traceString = HomologyPostProcessingOptions_String[3].def;
+  std::string projectString = HomologyPostProcessingOptions_String[4].def;
   int bd = (int)HomologyPostProcessingOptions_Number[0].def;
 
   GModel* m = GModel::current();
@@ -216,6 +219,8 @@ PView *GMSH_HomologyPostProcessingPlugin::execute(PView *v)
 
   std::vector<int> tracePhysicals;
   if(!parseStringOpt(3, tracePhysicals)) return 0;
+  std::vector<int> projectPhysicals;
+  if(!parseStringOpt(4, projectPhysicals)) return 0;
 
   std::vector<Chain<int> > curBasis;
   for(unsigned int i = 0; i < basisPhysicals.size(); i++){
@@ -285,8 +290,15 @@ PView *GMSH_HomologyPostProcessingPlugin::execute(PView *v)
               dim, traceString.c_str());
     for(unsigned int i = 0; i < newBasis.size(); i++)
       newBasis.at(i) = newBasis.at(i).getTrace(m, tracePhysicals);
-    ElemChain::clearVertexCache();
   }
+  if(!projectPhysicals.empty()) {
+    Msg::Info("Taking projection of result %d-chains to the complement of the domain %s", dim, projectString.c_str());
+    for(unsigned int i = 0; i < newBasis.size(); i++)
+      newBasis.at(i) = newBasis.at(i).getProject(m, projectPhysicals);
+  }
+  if(!tracePhysicals.empty() || !projectPhysicals.empty())
+    ElemChain::clearVertexCache();
+
 
   for(unsigned int i = 0; i < newBasis.size(); i++) {
     std::string dims = convertInt(newBasis.at(i).getDim());