diff --git a/sidechain/pymod/CMakeLists.txt b/sidechain/pymod/CMakeLists.txt
index 5abd39150d025d662c9681cffab7f3fe6bb1a5a0..d08fe422596d6e81cdf570715e973360b5c05c0f 100644
--- a/sidechain/pymod/CMakeLists.txt
+++ b/sidechain/pymod/CMakeLists.txt
@@ -11,6 +11,7 @@ export_connector.cc
 export_graph.cc
 export_disulfid.cc
 export_rotamer_density.cc
+export_rotamer_cruncher.cc
 wrap_sidechain.cc
 )
 
diff --git a/sidechain/pymod/export_rotamer_cruncher.cc b/sidechain/pymod/export_rotamer_cruncher.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c4c8e9a91a9ca303e4cae620f24ac829a4a5354a
--- /dev/null
+++ b/sidechain/pymod/export_rotamer_cruncher.cc
@@ -0,0 +1,41 @@
+#include <boost/python.hpp>
+#include <boost/python/register_ptr_to_python.hpp>
+#include <promod3/sidechain/rotamer_cruncher.hh>
+
+
+using namespace boost::python;
+using namespace promod3::sidechain;
+
+
+namespace{
+
+  void wrap_Initialize(RotamerCruncherPtr cruncher, const String& seqres, 
+                       const boost::python::list& weights){
+    std::vector<Real> v_weights;
+    for(int i = 0; i < boost::python::len(weights); ++i){
+      v_weights.push_back(boost::python::extract<Real>(weights[i]));
+    }
+    cruncher->Initialize(seqres,v_weights);
+  }
+  void wrap_UpdateRotamerProbabilities(RotamerCruncherPtr cruncher, boost::python::list rotamers,
+                                       uint seqres_pos, Real sigma){
+    std::vector<RotamerLibEntryPtr> v_rotamers;
+    for(int i = 0; i < boost::python::len(rotamers); ++i){
+      v_rotamers.push_back(boost::python::extract<RotamerLibEntryPtr>(rotamers[i]));
+    }
+    cruncher->UpdateRotamerProbabilities(v_rotamers,seqres_pos,sigma);
+  }
+}
+
+void export_RotamerCruncher()
+{
+  class_<RotamerCruncher>("RotamerCruncher", init<>())
+    .def("Initialize", &wrap_Initialize)
+    .def("AddStructure", &RotamerCruncher::AddStructure)
+    .def("MergeDensities", &RotamerCruncher::MergeDensities)
+    .def("GetExtractedProbability",&RotamerCruncher::GetExtractedProbability,(arg("seqres_pos"),arg("chi1"),arg("chi2")=std::numeric_limits<Real>::quiet_NaN()))
+    .def("UpdateRotamerProbabilities", &wrap_UpdateRotamerProbabilities)
+  ;  
+
+  register_ptr_to_python<RotamerCruncherPtr>();
+}
diff --git a/sidechain/pymod/export_rotamer_density.cc b/sidechain/pymod/export_rotamer_density.cc
index 65cda779d623fad7aa7ce02c57b9a99e7f8c2026..86f692070a003c95598b0f8886e6af2e80147c50 100644
--- a/sidechain/pymod/export_rotamer_density.cc
+++ b/sidechain/pymod/export_rotamer_density.cc
@@ -43,6 +43,16 @@ namespace{
     return RotamerDensity2DPtr(new RotamerDensity2D(num_bins));
   }
 
+  Real wrap_GetPOne(RotamerDensity1DPtr p, Real chi1){
+    Real chi_angles[] = {chi1};
+    return p->GetP(chi_angles);
+  }
+
+  Real wrap_GetPTwo(RotamerDensity2DPtr p, Real chi1, Real chi2){
+    Real chi_angles[] = {chi1, chi2};
+    return p->GetP(chi_angles);
+  }
+
 
 }
 
@@ -51,26 +61,39 @@ namespace{
 void export_RotamerDensity()
 {  
 
-  class_<RotamerDensity1D>("RotamerDensity1D", no_init)
+  class_<RotamerDensity, boost::noncopyable>("RotamerDensity", no_init);
+  register_ptr_to_python<RotamerDensityPtr>();
+
+  class_<RotamerDensity1D,bases<RotamerDensity> >("RotamerDensity1D", no_init)
     .def("__init__",make_constructor(&WrapRotamerDensity1DInit))
     .def("__init__",make_constructor(&WrapRotamerDensity1DListInit))
     .def("Normalize",&RotamerDensity1D::Normalize)
     .def("AddRotamer",&RotamerDensity1D::AddRotamer,(arg("rot")))
     .def("GetNumRotamers",&RotamerDensity1D::GetNumRotamers)
     .def("BinsPerDimension",&RotamerDensity1D::BinsPerDimension)
-    .def("GetP",&RotamerDensity1D::GetP,(arg("chi1")))
+    .def("Bins",&RotamerDensity1D::Bins)
+    .def("ApplyFactor",&RotamerDensity1D::ApplyFactor)
+    .def("GetP",&wrap_GetPOne,(arg("chi1")))
+    .def("MergeProbabilities",&RotamerDensity1D::MergeProbabilities)
+    .def("Clear",&RotamerDensity1D::Clear)
+    //.def("Data",&RotamerDensity1D::Data)
   ;
 
   register_ptr_to_python<RotamerDensity1DPtr>();
 
-  class_<RotamerDensity2D>("RotamerDensity2D", no_init)
+  class_<RotamerDensity2D,bases<RotamerDensity> >("RotamerDensity2D", no_init)
     .def("__init__",make_constructor(&WrapRotamerDensity2DInit))
     .def("__init__",make_constructor(&WrapRotamerDensity2DListInit))
     .def("Normalize",&RotamerDensity2D::Normalize)
     .def("AddRotamer",&RotamerDensity2D::AddRotamer,(arg("rot")))
     .def("GetNumRotamers",&RotamerDensity2D::GetNumRotamers)
     .def("BinsPerDimension",&RotamerDensity2D::BinsPerDimension)
-    .def("GetP",&RotamerDensity2D::GetP,(arg("chi1"),arg("chi2")))
+    .def("Bins",&RotamerDensity2D::Bins)
+    .def("ApplyFactor",&RotamerDensity2D::ApplyFactor)
+    .def("GetP",&wrap_GetPTwo,(arg("chi1"),arg("chi2")))
+    .def("MergeProbabilities",&RotamerDensity2D::MergeProbabilities)
+    .def("Clear",&RotamerDensity2D::Clear)
+    //.def("Data",&RotamerDensity2D::Data)
   ;
 
   register_ptr_to_python<RotamerDensity2DPtr>();
diff --git a/sidechain/pymod/wrap_sidechain.cc b/sidechain/pymod/wrap_sidechain.cc
index eef99c233225fb6d169ee50af192ce56e07561f7..1b6263248a8cba799ed59189d72b6e2a785131c9 100644
--- a/sidechain/pymod/wrap_sidechain.cc
+++ b/sidechain/pymod/wrap_sidechain.cc
@@ -13,6 +13,7 @@ void export_Connector();
 void export_Graph();
 void export_Disulfid();
 void export_RotamerDensity();
+void export_RotamerCruncher();
 
 using namespace boost::python;
 
@@ -30,4 +31,6 @@ BOOST_PYTHON_MODULE(_sidechain)
   export_Graph();
   export_Disulfid();
   export_RotamerDensity();
+  export_RotamerCruncher();
 }
+
diff --git a/sidechain/src/CMakeLists.txt b/sidechain/src/CMakeLists.txt
index 68f5a732305f4d21432fbc55ac888e0e27bb7c4a..5a23a8382b123e3d1706594e2c8205f53eea578b 100644
--- a/sidechain/src/CMakeLists.txt
+++ b/sidechain/src/CMakeLists.txt
@@ -20,6 +20,7 @@ sidechain_connector.hh
 settings.hh
 disulfid.hh
 rotamer_density.hh
+rotamer_cruncher.hh
 )
 
 set(SIDECHAIN_SOURCES
@@ -42,6 +43,7 @@ sidechain_connector.cc
 disulfid.cc
 rotamer_id.cc
 rotamer_density.cc
+rotamer_cruncher.cc
 )
 
 module(NAME sidechain HEADERS ${SIDECHAIN_HEADERS} SOURCES ${SIDECHAIN_SOURCES} DEPENDS_ON
diff --git a/sidechain/src/rotamer_cruncher.cc b/sidechain/src/rotamer_cruncher.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2e1947cd83c60072324b9f0b4b70d632e9ea8d46
--- /dev/null
+++ b/sidechain/src/rotamer_cruncher.cc
@@ -0,0 +1,416 @@
+
+#include <promod3/sidechain/rotamer_cruncher.hh>
+
+namespace{
+
+  Real _bin_size = 0.0872664625997;
+  Real _margin = 0.349065850399;
+  int _num_sampling_points = 9;
+
+}
+
+namespace promod3{ namespace sidechain{
+
+RotamerCruncher::RotamerCruncher():readonly_(false) { std::cout<<"construct rotamer cruncher "<<42<<std::endl;}
+
+void RotamerCruncher::Initialize(const String& seqres, const std::vector<Real>& c_weights){
+  if(readonly_){
+    throw promod3::Error("Cannot reinitialize readonly RotamerCruncher!");
+  }
+  std::cout<<"start with initialization"<<std::endl;
+  seqres_ = seqres;
+  c_weights_ = c_weights;
+  dimensionality_.clear();
+  extracted_rotamers_.clear();
+  extracted_rotamers_.resize(seqres_.size());
+  for(uint i = 0; i < seqres_.size(); ++i){
+    extracted_rotamers_[i].resize(c_weights_.size());
+  }
+
+  for(uint i = 0; i < seqres_.size(); ++i){
+    switch(seqres_[i]){
+      case 'S':{
+        dimensionality_.push_back(0);
+        break;
+      }
+      case 'V':{
+        dimensionality_.push_back(0);
+        break;
+      }
+      case 'T':{
+        dimensionality_.push_back(0);
+        break;
+      }
+      case 'C':{
+        dimensionality_.push_back(0);
+        break;
+      }
+      default:{
+        dimensionality_.push_back(1);
+        break;
+      }
+    }
+  }
+}
+
+void RotamerCruncher::Save(const String& filename){
+
+  std::ofstream out_stream(filename.c_str(), std::ios::binary);
+  uint32_t seqres_size = seqres_.size();
+  out_stream.write(reinterpret_cast<char*>(&seqres_size),4);
+  out_stream.write(reinterpret_cast<char*>(&seqres_[0]),seqres_size);
+  for(uint i = 0; i < merged_rot_densities_.size(); ++i){ 
+    uint32_t density_size = merged_rot_densities_[i]->Bins()*sizeof(double);
+    out_stream.write(reinterpret_cast<char*>(&density_size),4);
+    double* merged_density_data = merged_rot_densities_[i]->Data();
+    out_stream.write(reinterpret_cast<char*>(merged_density_data),density_size);
+  }
+
+
+}
+
+RotamerCruncherPtr RotamerCruncher::Load(const String& filename){
+
+  std::ifstream in_stream(filename.c_str(), std::ios::binary);
+  uint32_t seqres_size;
+  in_stream.read(reinterpret_cast<char*>(&seqres_size),4);
+  String seqres(seqres_size,'X');
+  in_stream.read(reinterpret_cast<char*>(&seqres[0]),seqres_size);
+
+  RotamerCruncherPtr loaded_cruncher(new RotamerCruncher);
+  std::vector<Real> empty_vec;
+  loaded_cruncher->Initialize(seqres,empty_vec);
+  for(uint i = 0; i < seqres_size; ++i){
+    uint32_t density_size;
+    in_stream.read(reinterpret_cast<char*>(&density_size),4);
+    if(loaded_cruncher->dimensionality_[i] == 0){
+      loaded_cruncher->merged_rot_densities_.push_back(RotamerDensityPtr(new RotamerDensity1D(density_size)));
+    }
+    else{
+      loaded_cruncher->merged_rot_densities_.push_back(RotamerDensityPtr(new RotamerDensity2D(sqrt(density_size))));
+    }
+    double* merged_density_data = loaded_cruncher->merged_rot_densities_.back()->Data();
+    in_stream.read(reinterpret_cast<char*>(merged_density_data),density_size);
+  }
+  loaded_cruncher->readonly_ = true;
+  
+
+
+  return loaded_cruncher;
+}
+
+
+void RotamerCruncher::AddStructure(const ost::mol::ChainHandle& chain, const ost::seq::AlignmentHandle& aln, uint c){
+  if(readonly_){
+    throw promod3::Error("Cannot add structure to readonly RotamerCruncher!");
+  }
+
+  if(c >= c_weights_.size()){
+    throw promod3::Error("Invalid cluster index!");
+  }
+  int seqres_pos = 0;
+  ost::seq::ConstSequenceHandle seqres_seq = aln.GetSequence(0);
+  if(seqres_seq.GetGaplessString() != seqres_){
+    throw promod3::Error("Seqres and target sequence from the alignment are not the same!");
+  }
+  ost::seq::ConstSequenceHandle templ_seq = aln.GetSequence(1);
+  ost::mol::ResidueHandleList res_list = chain.GetResidueList();
+  RotamerLibEntryPtr rotamer;
+  
+  // build naive cluster densities 
+  for(int i = 0; i < aln.GetLength(); ++i){
+    if (seqres_seq[i] == '-'){
+      continue;
+    }
+    if (templ_seq[i] == '-'){
+      ++seqres_pos;
+      continue;
+    }
+    if (seqres_seq[i] == 'A' || seqres_seq[i] == 'G'){
+      ++seqres_pos;
+      continue;
+    }
+    if(seqres_seq[i] == templ_seq[i]){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        std::cerr<<"just caught an error"<<std::endl;
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    // include similar sidechains
+    else if(seqres_seq[i] == 'N' && templ_seq[i] == 'D'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+      // not sure if this works ...
+      rotamer->chi2 -= M_PI;
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'D' && templ_seq[i] == 'N'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'R' && templ_seq[i] == 'K'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'K' && templ_seq[i] == 'R'){
+      
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'S' && templ_seq[i] == 'T'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'T' && templ_seq[i] == 'S'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'Y' && templ_seq[i] == 'F'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'F' && templ_seq[i] == 'Y'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'E' && templ_seq[i] == 'Q'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+    else if(seqres_seq[i] == 'Q' && templ_seq[i] == 'E'){
+      int residue_index = templ_seq.GetResidueIndex(i);
+      ost::mol::ResidueHandle residue = res_list[residue_index];
+      try{
+        rotamer = RotamerLibEntry::FromResidue(residue);
+      } catch(promod3::Error& e){
+        ++seqres_pos;
+        continue;
+      }
+      extracted_rotamers_[seqres_pos][c].push_back(*rotamer);
+    }
+
+
+    ++seqres_pos;
+  }
+}
+void RotamerCruncher::MergeDensities(){
+  // normalize and apply weight to cluster densities
+  if(readonly_){
+    throw promod3::Error("Cannot merge densities of readonly RotamerCruncher!");
+  }
+  merged_rot_densities_.clear();
+
+  for(uint i = 0; i < extracted_rotamers_.size(); ++i){
+    if(dimensionality_[i] == 0){
+      merged_rot_densities_.push_back(RotamerDensityPtr(new RotamerDensity1D(72)));
+    }
+    else if(dimensionality_[i] == 1){
+      merged_rot_densities_.push_back(RotamerDensityPtr(new RotamerDensity2D(72)));
+    }    
+  }
+
+  std::cerr<<"merging probs"<<std::endl;
+  RotamerLibEntry actual_rotamer;
+  for(uint i = 0; i < extracted_rotamers_.size(); ++i){
+    for(uint j = 0; j < extracted_rotamers_[i].size(); ++j){
+      Real p = c_weights_[j] / extracted_rotamers_[i][j].size(); 
+      for(uint k = 0; k < extracted_rotamers_[i][j].size(); ++k){
+        actual_rotamer = extracted_rotamers_[i][j][k];
+        actual_rotamer.probability = p;
+        merged_rot_densities_[i]->AddRotamer(actual_rotamer);
+      }
+    }
+  }
+
+  for(uint i = 0; i < extracted_rotamers_.size(); ++i){
+    merged_rot_densities_[i]->Normalize();
+  }
+}
+
+Real RotamerCruncher::GetExtractedProbability(uint seqres_pos, Real chi1, Real chi2){
+
+  
+  if(seqres_pos >= seqres_.size()){
+    throw promod3::Error("Invalid index observed!");
+  }
+
+  Real chi_angles[2];
+  chi_angles[0] = chi1;
+
+  if(dimensionality_[seqres_pos] == 0){
+    return merged_rot_densities_[seqres_pos]->GetP(&chi_angles[0]);
+  }
+  
+  if(chi2 != chi2){
+    throw promod3::Error("Chi2 angle must be given!");
+  }
+
+  chi_angles[1] = chi2;
+
+  return merged_rot_densities_[seqres_pos]->GetP(&chi_angles[0]);
+}
+
+void RotamerCruncher::UpdateRotamerProbabilities(std::vector<RotamerLibEntryPtr>& rotamers, uint seqres_pos, Real sigma){
+
+  std::cerr<<"start to update probs"<<std::endl;
+
+  String seqres = seqres_;
+  if(seqres_pos >= seqres.size()){
+    throw Error("Seqres_pos out of range!");
+  }
+
+  int N = merged_rot_densities_[seqres_pos]->GetNumRotamers();
+  Real sippl_prefac_one = (1.0/(N*0.05+1));
+  Real sippl_prefac_two = (N*0.05/(N*0.05+1));
+  Real chi_angles[2];
+  std::vector<Real> density_probs;
+  std::vector<Real> original_probs;
+
+  //in case of only chi1
+  if(dimensionality_[seqres_pos]==0){
+
+    for(std::vector<RotamerLibEntryPtr>::iterator i = rotamers.begin();
+        i != rotamers.end(); ++i){
+
+      Real chi1 = (*i)->chi1;
+      Real summed_prob = 0.0;
+
+      chi_angles[0] = chi1-_margin;
+
+      for(int j = 0; j < _num_sampling_points; ++j){
+        summed_prob += merged_rot_densities_[seqres_pos]->GetP(&chi_angles[0]);
+        chi_angles[0] += _bin_size;
+      }
+
+      density_probs.push_back(summed_prob);
+      original_probs.push_back((*i)->probability);
+    }
+  }
+
+  else{
+    for(std::vector<RotamerLibEntryPtr>::iterator i = rotamers.begin();
+        i != rotamers.end(); ++i){
+  
+      Real chi1 = (*i)->chi1;
+      Real chi2 = (*i)->chi2;
+      Real summed_prob = 0.0;
+  
+      chi_angles[0] = chi1-_margin;
+  
+  
+  
+      for(int j = 0; j<_num_sampling_points; ++j){
+        chi_angles[1] = chi2-_margin;
+        for(int k = 0; k<_num_sampling_points; ++k){
+          summed_prob += merged_rot_densities_[seqres_pos]->GetP(&chi_angles[0]);
+          chi_angles[1] += _bin_size;
+        }
+        chi_angles[0] += _bin_size;
+      }
+  
+      density_probs.push_back(summed_prob);
+      original_probs.push_back((*i)->probability);
+    }
+  }
+
+  Real sum = 0.0;
+
+  for(std::vector<Real>::iterator i = density_probs.begin(); i != density_probs.end(); ++i){
+    sum += (*i);
+  }
+
+  if(sum == 0.0) return;
+
+  for(uint i = 0; i < density_probs.size(); ++i){
+    density_probs[i] = density_probs[i]/sum;
+  }
+
+  std::vector<Real> sippl_probs;
+
+  for(uint i = 0; i < density_probs.size(); ++i){
+    sippl_probs.push_back(sippl_prefac_one*original_probs[i]+sippl_prefac_two*density_probs[i]);
+  }
+
+  sum = 0.0;
+  for(std::vector<Real>::iterator i = sippl_probs.begin(); i != sippl_probs.end(); ++i){
+    sum += (*i);
+  }
+
+  for(uint i = 0; i < sippl_probs.size(); ++i){
+    std::cerr<<"2D probability before updating "<<rotamers[i]->probability<<std::endl;
+    rotamers[i]->probability = sippl_probs[i]/sum;
+    std::cerr<<"2D probability after updating "<<rotamers[i]->probability<<std::endl;
+  }
+}
+
+}}//ns
diff --git a/sidechain/src/rotamer_cruncher.hh b/sidechain/src/rotamer_cruncher.hh
new file mode 100644
index 0000000000000000000000000000000000000000..dcf0453196463425d0db8d69c3f81bd735431be5
--- /dev/null
+++ b/sidechain/src/rotamer_cruncher.hh
@@ -0,0 +1,57 @@
+#ifndef PROMOD3_ROTAMER_CRUNCHER_HH
+#define PROMOD3_ROTAMER_CRUNCHER_HH
+
+#include <math.h>
+#include <vector>
+#include <limits.h>
+#include <fstream>
+
+#include <boost/shared_ptr.hpp>
+
+#include <ost/seq/alignment_handle.hh>
+#include <ost/mol/entity_handle.hh>
+#include <promod3/sidechain/tetrahedral_polytope.hh>
+#include <promod3/sidechain/pairwise_terms.hh>
+#include <promod3/sidechain/rotamer_lib_entry.hh>
+#include <promod3/sidechain/rotamer_density.hh>
+
+
+namespace promod3{ namespace sidechain{
+
+class RotamerCruncher;
+typedef boost::shared_ptr<RotamerCruncher> RotamerCruncherPtr;
+
+
+class RotamerCruncher{
+
+public:
+
+  RotamerCruncher();
+
+  void Save(const String& filename);
+
+  static RotamerCruncherPtr Load(const String& filename);
+
+  void Initialize(const String& seqres, const std::vector<Real>& c_weights);
+
+  void AddStructure(const ost::mol::ChainHandle& chain, const ost::seq::AlignmentHandle& aln, uint c);
+
+  void MergeDensities();
+
+  Real GetExtractedProbability(uint seqres_pos, Real chi1, Real chi2 = std::numeric_limits<Real>::quiet_NaN());
+
+  void UpdateRotamerProbabilities(std::vector<RotamerLibEntryPtr>& rotamers, uint seqres_pos, Real sigma);
+
+private:
+
+  String seqres_;
+  std::vector<Real> c_weights_;
+  std::vector<int> dimensionality_;
+  std::vector<std::vector<std::vector<RotamerLibEntry> > > extracted_rotamers_;
+  std::vector<RotamerDensityPtr> merged_rot_densities_;
+  bool readonly_;
+};
+
+}}//ns
+
+#endif
diff --git a/sidechain/src/rotamer_density.cc b/sidechain/src/rotamer_density.cc
index ce5752e309298cf7df93baa6da1833eb73df7ced..3806376abfba6898c04a77cc309c64478b1d778d 100644
--- a/sidechain/src/rotamer_density.cc
+++ b/sidechain/src/rotamer_density.cc
@@ -3,8 +3,7 @@
 namespace promod3{ namespace sidechain{
   
 RotamerDensity1D::RotamerDensity1D(uint num_bins):num_rotamers_(0),
-                                                  num_bins_(num_bins),
-                                                  normalized_(false){
+                                                  num_bins_(num_bins){
 
   if(num_bins_ > 1000){
     std::stringstream ss;
@@ -19,8 +18,7 @@ RotamerDensity1D::RotamerDensity1D(uint num_bins):num_rotamers_(0),
 }
 
 RotamerDensity1D::RotamerDensity1D(const std::vector<RotamerDensity1DPtr>& densities,
-                                   const std::vector<Real>& weights): num_rotamers_(0),
-                                                                      normalized_(false){
+                                   const std::vector<Real>& weights): num_rotamers_(0){
 
   if(densities.empty()){
     std::stringstream ss;
@@ -65,17 +63,18 @@ RotamerDensity1D::RotamerDensity1D(const std::vector<RotamerDensity1DPtr>& densi
 void RotamerDensity1D::Normalize(){
 
   //sum everything up
-  Real sum = 0.0;
+  float sum = 0.0;
   for(uint i = 0; i < num_bins_; ++i){
     sum += data_[i];
   }
 
   //and normalize
-  Real factor = 1.0/sum;
-  for(uint i = 0; i < num_bins_; ++i){
-    data_[i] *= factor;
+  if(sum != 0.0){
+    float factor = 1.0/sum;
+    for(uint i = 0; i < num_bins_; ++i){
+      data_[i] *= factor;
+    }
   }
-  normalized_ = true;
 }
 
 void RotamerDensity1D::AddRotamer(const RotamerLibEntry& rot){
@@ -131,7 +130,15 @@ void RotamerDensity1D::AddRotamer(const RotamerLibEntry& rot){
   ++num_rotamers_;
 }
 
-Real RotamerDensity1D::GetP(Real chi1){
+void RotamerDensity1D::ApplyFactor(Real factor){
+  for(uint i = 0; i < num_bins_; ++i){
+    data_[i] *= factor;
+  }
+}
+
+Real RotamerDensity1D::GetP(Real* chi_angles){
+
+  Real chi1 = chi_angles[0];
 
   while(chi1 < -M_PI) chi1 += 2*M_PI;
   while(chi1 >= M_PI) chi1 -= 2*M_PI;
@@ -140,9 +147,28 @@ Real RotamerDensity1D::GetP(Real chi1){
   return data_[bin];
 }
 
+void RotamerDensity1D::MergeProbabilities(RotamerDensityPtr other){
+  
+  if(this->Bins() != other->Bins()){
+    throw promod3::Error("Inconsistent size when adding Rotamer Densities!");
+  }
+
+  double* other_data = other->Data();
+  uint other_num_rotamers = other->GetNumRotamers();
+  num_rotamers_ += other_num_rotamers;
+  for(uint i = 0; i < num_bins_; ++i){
+    data_[i] += other_data[i];
+  }
+
+}
+
+void RotamerDensity1D::Clear(){
+  num_rotamers_ = 0;
+  memset(data_,0,num_bins_*sizeof(double));
+}
+
 RotamerDensity2D::RotamerDensity2D(uint num_bins):num_rotamers_(0),
-                                                  num_bins_(num_bins),
-                                                  normalized_(false){
+                                                  num_bins_(num_bins){
 
   if(num_bins > 1000){
     std::stringstream ss;
@@ -158,8 +184,7 @@ RotamerDensity2D::RotamerDensity2D(uint num_bins):num_rotamers_(0),
 }
 
 RotamerDensity2D::RotamerDensity2D(const std::vector<RotamerDensity2DPtr>& densities,
-                                   const std::vector<Real>& weights): num_rotamers_(0),
-                                                                      normalized_(false){
+                                   const std::vector<Real>& weights): num_rotamers_(0){
 
   if(densities.empty()){
     std::stringstream ss;
@@ -206,9 +231,11 @@ void RotamerDensity2D::Normalize(){
   for(uint i = 0; i < num_bins_*num_bins_; ++i){
     sum += data_[i];
   }
-  double factor = 1.0/sum;
-  for(uint i = 0; i < num_bins_*num_bins_; ++i){
-    data_[i] *= factor;
+  if(sum != 0.0){
+    double factor = 1.0/sum;
+    for(uint i = 0; i < num_bins_*num_bins_; ++i){
+      data_[i] *= factor;
+    }
   }
 }
 
@@ -287,7 +314,15 @@ void RotamerDensity2D::AddRotamer(const RotamerLibEntry& rot){
   ++num_rotamers_;
 }
 
-Real RotamerDensity2D::GetP(Real chi1, Real chi2){
+void RotamerDensity2D::ApplyFactor(Real factor){
+  for(uint i = 0; i < num_bins_*num_bins_; ++i){
+    data_[i] *= factor;
+  }
+}
+
+Real RotamerDensity2D::GetP(Real* chi_angles){
+  Real chi1 = chi_angles[0];
+  Real chi2 = chi_angles[1];
   while(chi1 < -M_PI) chi1 += 2*M_PI;
   while(chi1 >= M_PI) chi1 -= 2*M_PI;
   while(chi2 < -M_PI) chi2 += 2*M_PI;
@@ -300,4 +335,24 @@ Real RotamerDensity2D::GetP(Real chi1, Real chi2){
   return data_[bin];
 }
 
+void RotamerDensity2D::MergeProbabilities(RotamerDensityPtr other){
+  
+  if(this->Bins() != other->Bins()){
+    throw promod3::Error("Inconsistent size when adding Rotamer Densities!");
+  }
+
+  double* other_data = other->Data();
+  uint other_num_rotamers = other->GetNumRotamers();
+  num_rotamers_ += other_num_rotamers;
+  for(uint i = 0; i < num_bins_*num_bins_; ++i){
+    data_[i] += other_data[i];
+  }
+}
+
+void RotamerDensity2D::Clear(){
+  num_rotamers_ = 0;
+  memset(data_,0,num_bins_*num_bins_*sizeof(double));
+}
+
 }} //ns
+
diff --git a/sidechain/src/rotamer_density.hh b/sidechain/src/rotamer_density.hh
index 67ce145539bb475064d8b5a80467e481d585cbf3..7e024cb471c447911a43be193023be5654295d32 100644
--- a/sidechain/src/rotamer_density.hh
+++ b/sidechain/src/rotamer_density.hh
@@ -10,11 +10,44 @@ namespace promod3{ namespace sidechain{
 
 class RotamerDensity1D;
 class RotamerDensity2D;
+class RotamerDensity;
 
 typedef boost::shared_ptr<RotamerDensity1D> RotamerDensity1DPtr;
 typedef boost::shared_ptr<RotamerDensity2D> RotamerDensity2DPtr;
+typedef boost::shared_ptr<RotamerDensity> RotamerDensityPtr;
 
-class RotamerDensity1D{
+
+class RotamerDensity{
+public:
+
+  RotamerDensity() { }
+
+  virtual ~RotamerDensity() { };
+
+  virtual void Normalize() = 0;
+
+  virtual void AddRotamer(const RotamerLibEntry& rot) = 0;
+
+  virtual uint GetNumRotamers() = 0;
+
+  virtual uint BinsPerDimension() = 0;
+
+  virtual uint Bins() = 0;
+
+  virtual void ApplyFactor(Real factor) = 0;
+
+  virtual Real GetP(Real* chi_angles) = 0;
+
+  virtual void MergeProbabilities(RotamerDensityPtr other) = 0;
+
+  virtual void Clear() = 0;
+
+  virtual double* Data() = 0;
+
+};
+
+
+class RotamerDensity1D : public RotamerDensity{
 public:
 
   RotamerDensity1D(uint num_bins);
@@ -22,17 +55,28 @@ public:
   RotamerDensity1D(const std::vector<RotamerDensity1DPtr>& densities,
                    const std::vector<Real>& weights);
 
-  ~RotamerDensity1D(){ delete [] data_; }
+  virtual ~RotamerDensity1D(){ delete [] data_; }
+
+  virtual void Normalize();
+
+  virtual void AddRotamer(const RotamerLibEntry& rot);
+
+  virtual uint GetNumRotamers() { return num_rotamers_;}
+
+  virtual uint BinsPerDimension() { return num_bins_; }
+
+  virtual uint Bins() { return num_bins_;}
+
+  virtual void ApplyFactor(Real factor);
 
-  void Normalize();
+  virtual Real GetP(Real* chi_angles);
 
-  void AddRotamer(const RotamerLibEntry& rot);
+  virtual void MergeProbabilities(RotamerDensityPtr other);
 
-  uint GetNumRotamers() { return num_rotamers_;}
+  virtual void Clear();
 
-  uint BinsPerDimension() { return num_bins_; }
+  virtual double* Data() { return data_; }
 
-  Real GetP(Real chi1);
 
 
 private:
@@ -40,11 +84,10 @@ private:
   uint num_rotamers_;
   Real bin_size_;
   uint num_bins_;
-  bool normalized_;
 };
 
 
-class RotamerDensity2D{
+class RotamerDensity2D : public RotamerDensity{
 public:
 
   RotamerDensity2D(uint num_bins);
@@ -52,17 +95,27 @@ public:
   RotamerDensity2D(const std::vector<RotamerDensity2DPtr>& densities,
                    const std::vector<Real>& weights);
 
-  ~RotamerDensity2D(){ delete data_; }
+  virtual ~RotamerDensity2D(){ delete [] data_; }
 
-  void Normalize();
+  virtual void Normalize();
 
-  void AddRotamer(const RotamerLibEntry& rot);
+  virtual void AddRotamer(const RotamerLibEntry& rot);
 
-  uint GetNumRotamers() { return num_rotamers_;}
+  virtual uint GetNumRotamers() { return num_rotamers_;}
 
-  uint BinsPerDimension() { return num_bins_; }
+  virtual uint BinsPerDimension() { return num_bins_; }
 
-  Real GetP(Real chi1, Real chi2);
+  virtual uint Bins() { return num_bins_*num_bins_;}
+
+  virtual void ApplyFactor(Real factor);
+
+  virtual Real GetP(Real* chi_angles);
+
+  virtual void MergeProbabilities(RotamerDensityPtr other);
+
+  virtual void Clear();
+
+  virtual double* Data() { return data_; }
 
 
 private:
@@ -70,10 +123,9 @@ private:
   uint num_rotamers_;
   Real bin_size_;
   uint num_bins_;
-  bool normalized_;
 };
 
-
 }} //ns
 
 #endif
+