diff --git a/include/gnss_utils/gnss_utils.h b/include/gnss_utils/gnss_utils.h
index 06a0d310ee6006809bea5a03aeed5feddd8eb85e..d6429ef7f3ea39318ce1248285ea0d6373677248 100644
--- a/include/gnss_utils/gnss_utils.h
+++ b/include/gnss_utils/gnss_utils.h
@@ -10,8 +10,8 @@
 #include <eigen3/Eigen/Geometry>
 #include <eigen3/Eigen/Sparse>
 
-#include "observations.h"
-#include "navigation.h"
+#include "gnss_utils/observations.h"
+#include "gnss_utils/navigation.h"
 
 extern "C"
 {
@@ -53,9 +53,16 @@ namespace GNSSUtils
   int estposOwn(const obsd_t *obs, int n, const double *rs, const double *dts,
                 const double *vare, const int *svh, const nav_t *nav,
                 const prcopt_t *opt, sol_t *sol, double *azel, int *vsat,
-                double *resp, char *msg)
+                double *resp, char *msg);
 
   Eigen::Vector3d ecefToLatLon(const Eigen::Vector3d & _ecef);
+
+  double computeSatElevation(const Eigen::Vector3d& receiver_ecef, const Eigen::Vector3d& sat_ecef);
+
+  void computeSatellitesPositions(const Observations& obs,
+                                  const Navigation& nav,
+                                  const prcopt_t& opt,
+                                  std::map<unsigned char,Eigen::Vector3d>& sats_pos);
 }
 
 #endif
diff --git a/include/gnss_utils/observations.h b/include/gnss_utils/observations.h
index e84820c2ab4dc86ef80ee79d06acc59da5047812..3e7f56e1b116f1d5680680d78f728cdd9e00c8dc 100644
--- a/include/gnss_utils/observations.h
+++ b/include/gnss_utils/observations.h
@@ -2,6 +2,7 @@
 #define OBSERVATIONS_H
 
 #include <vector>
+#include <map>
 #include <iostream>
 #include <memory>
 
@@ -14,43 +15,123 @@ extern "C"
 
 namespace GNSSUtils
 {
-  class Observations
-  {
+class Observations
+{
     public:
-      // Constructor & Destructor
-      Observations();
-      ~Observations();
+        // Constructor & Destructor
+        Observations();
+        ~Observations();
+
+        // Public objects
+
+        // Public methods
+
+
+        /* - Observations -  */
 
-      // Public objects
+        void clearObservations();
 
-      // Public methods
+        void addObservation(const obsd_t & obs);
 
+        void removeObservationByIdx(const int& _idx);
+        void removeObservationBySat(const int& _sat);
 
-      /* - Observations -  */
+        std::vector<obsd_t>& getObservations();
+        const std::vector<obsd_t> & getObservations() const;
 
-      void clearObservations();
+        obsd_t& getObservationBySat(const unsigned char& sat_number);
+        const obsd_t& getObservationBySat(const unsigned char& sat_number) const;
 
-      void pushObservation(obsd_t & obs);
+        obsd_t& getObservationByIdx(const int& idx);
+        const obsd_t& getObservationByIdx(const int& idx) const;
 
-      void reserveObservations(unsigned int n);
+        obsd_t* data();
+        const obsd_t* data() const;
 
-      const std::vector<obsd_t> & getObservations() const;
+        size_t size() const;
 
-      void print(obsd_t & _obs);
-      void print(int _obs_idx);
-      void print();
+        bool hasSatellite(const unsigned char& i) const;
 
+        void print(obsd_t & _obs);
+        void printBySat(const int& _sat);
+        void printByIdx(const int& _idx);
+        void print();
 
+        static void findCommonObservations(const Observations& obs_1, const Observations& obs_2,
+                                           Observations& common_obs_1, Observations& common_obs_2);
 
     private:
-      // Private objects
-      // rtklib-like attribute to represent the different observation msgs for a given epoch
-      std::vector<obsd_t> obs_vector_;
+        // Private objects
+        std::map<unsigned char,int> sat_2_idx_; //< key: corresponding sat number, value: idx in obs_ vector
+        std::map<int,unsigned char> idx_2_sat_; //< key: idx in obs_ vector, value: corresponding sat number
+        std::vector<obsd_t> obs_; //< vector of RTKLIB observations for a given epoch
+
+        // Private methods
+};
+
+inline void Observations::removeObservationByIdx(const int& _idx)
+{
+    obs_.erase(obs_.begin() + _idx);
+    sat_2_idx_.erase(idx_2_sat_.at(_idx));
+    idx_2_sat_.erase(_idx);
+}
+
+inline void Observations::removeObservationBySat(const int& _sat)
+{
+    obs_.erase(obs_.begin() + sat_2_idx_.at(_sat));
+    idx_2_sat_.erase(sat_2_idx_.at(_sat));
+    sat_2_idx_.erase(_sat);
+}
+
+inline const std::vector<obsd_t>& Observations::getObservations() const
+{
+    return this->obs_;
+}
 
+inline std::vector<obsd_t>& Observations::getObservations()
+{
+    return this->obs_;
+}
 
-      // Private methods
+inline obsd_t& Observations::getObservationBySat(const unsigned char& sat_number)
+{
+    return obs_.at(sat_2_idx_.at(sat_number));
+}
 
+inline const obsd_t& Observations::getObservationBySat(const unsigned char& sat_number) const
+{
+    return obs_.at(sat_2_idx_.at(sat_number));
+}
+
+inline obsd_t& Observations::getObservationByIdx(const int& idx)
+{
+    return obs_.at(idx);
+}
+
+inline const obsd_t& Observations::getObservationByIdx(const int& idx) const
+{
+    return obs_.at(idx);
+}
+
+inline obsd_t* Observations::data()
+{
+    return obs_.data();
+}
+
+inline const obsd_t* Observations::data() const
+{
+    return obs_.data();
+}
+
+inline size_t Observations::size() const
+{
+    return obs_.size();
+}
+
+inline bool Observations::hasSatellite(const unsigned char& i) const
+{
+    return sat_2_idx_.count(i) != 0;
+}
 
-  };
 }
 #endif
diff --git a/include/gnss_utils/single_differences.h b/include/gnss_utils/single_differences.h
index 9c48af4a3390ad33e930b7274d384b5a7391a6af..3c1ee0851477856c01879781644d47da08cbef4b 100644
--- a/include/gnss_utils/single_differences.h
+++ b/include/gnss_utils/single_differences.h
@@ -8,7 +8,12 @@
 #ifndef INCLUDE_GNSS_UTILS_SINGLE_DIFFERENCES_H_
 #define INCLUDE_GNSS_UTILS_SINGLE_DIFFERENCES_H_
 
-#include "gnss_utils.h"
+#define GNSS_UTILS_SD_DEBUG 1
+
+#include <set>
+#include "gnss_utils/gnss_utils.h"
+#include "gnss_utils/observations.h"
+#include "gnss_utils/navigation.h"
 extern "C"
 {
     #include "rtklib.h"
@@ -18,7 +23,7 @@ namespace GNSSUtils
 {
     struct SingleDifferencesParams
     {
-        int common_sat_min;
+        int min_common_sats;
         int raim_n;
         double raim_min_residual;
         bool use_carrier_phase;
@@ -28,26 +33,32 @@ namespace GNSSUtils
         double sigma_atm;
         double sigma_code;
         double sigma_carrier;
+        bool use_old_nav;
+        bool use_multi_freq;
     };
 
-    bool singleDifferences(const Observations& obs_r, const Navigation& nav_r,
-                           const Observations& obs_k, const Navigation& nav_r,
+    bool singleDifferences(const Observations& obs_r, Navigation& nav_r,
+                           const Observations& obs_k, const Navigation& nav_k,
                            Eigen::Vector4d& d, Eigen::Matrix4d& cov_d,
-                           double& residual, std::list<int>& discarded_sats,
+                           double& residual, std::set<unsigned char>& discarded_sats,
                            const SingleDifferencesParams& sd_opt, const prcopt_t& opt);
 
-    bool singleDifferences(const Observations& obs_r, const Eigen::Vector3d& pos_r,
-                           const Observations& obs_k, const Navigation& nav_r,
+    bool singleDifferences(const Observations& obs_r, const Navigation& nav_r, const Eigen::Vector3d& x_r,
+                           const Observations& obs_k, const Navigation& nav_k,
                            Eigen::Vector4d& d, Eigen::Matrix4d& cov_d,
-                           double& residual, std::list<int>& discarded_sats,
+                           double& residual, std::set<unsigned char>& discarded_sats,
                            const SingleDifferencesParams& sd_opt, const prcopt_t& opt);
 
-    bool singleDifferences(const Observations& common_obs_r, const Eigen::Vector3d& pos_r,
-                           const Observations& common_obs_k, const std::map<int,Eigen::Vector3d>& common_sats_pos,
+    bool singleDifferences(const Observations& common_obs_r, const Navigation& nav_r, const std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_r, const Eigen::Vector3d& x_r,
+                           const Observations& common_obs_k, const Navigation& nav_k, const std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_k,
                            Eigen::Vector4d& d, Eigen::Matrix4d& cov_d,
-                           double& residual, std::list<int>& discarded_sats,
+                           double& residual, std::set<unsigned char>& discarded_sats,
                            const SingleDifferencesParams& sd_opt, const prcopt_t& opt);
 
+    void filterCommonSatellites(Observations& common_obs_r, std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_r,
+                                Observations& common_obs_k, std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_k,
+                                std::set<unsigned char>& discarded_sats, const Eigen::Vector3d& x_r,
+                                const SingleDifferencesParams& sd_params, const prcopt_t& opt);
 
 }
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bf6f31f6e439b57c78c3d7a9ed52da3c7462a125..fc63b6c916b1826537c58b40beef0752e8283b2c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -7,6 +7,7 @@ SET(SOURCES
     gnss_utils.cpp
     observations.cpp
     navigation.cpp
+    single_differences.cpp
     utils.cpp)
 
 SET(RTKLIB_SRC
@@ -30,6 +31,7 @@ SET(HEADERS
     ../include/gnss_utils/utils.h
     ../include/gnss_utils/observations.h
     ../include/gnss_utils/navigation.h
+    ../include/gnss_utils/single_differences.h
   ${RTKLIB_SRC_DIR}/rtklib.h)
 
 # Eigen #######
diff --git a/src/examples/gnss_utils_test.cpp b/src/examples/gnss_utils_test.cpp
index fd5589b48b65ae24c960a19b75900cd8c419cc00..829192b42efaa91ce47317b7d5faabecf9eb6a43 100644
--- a/src/examples/gnss_utils_test.cpp
+++ b/src/examples/gnss_utils_test.cpp
@@ -68,10 +68,10 @@ int createObsAndNav(Observations* observations, char* obs_path,  Navigation* nav
 
   for (int i=0; i < obs.n; i++)
   {
-    std::cout << "time: " << time_str(obs.data[i].time, 3) <<  " | sat: " << int(obs.data[i].sat) << " | rcv: " << obs.data[i].rcv <<
-               " | SNR: " << obs.data[i].SNR[0] << " | LLI: " << obs.data[i].LLI[0] << " | code: " << obs.data[i].code[0] <<
+    std::cout << "time: " << time_str(obs.data[i].time, 3) <<  " | sat: " << int(obs.data[i].sat) << " | rcv: " << int(obs.data[i].rcv) <<
+               " | SNR: " << int(obs.data[i].SNR[0]) << " | LLI: " << int(obs.data[i].LLI[0]) << " | code: " << int(obs.data[i].code[0]) <<
                  " | L: " << obs.data[i].L[0] <<  " | P: " << obs.data[i].P[0] << " | D: " << obs.data[i].D[0] << std::endl;
-    observations->pushObservation(obs.data[i]);
+    observations->addObservation(obs.data[i]);
   }
 
   free(obs.data);
@@ -191,8 +191,8 @@ int main(int argc, char *argv[])
 
   for (int i=0; i < obs.size(); i++)
   {
-    std::cout << "time: " << time_str(obs[i].time, 3) <<  " | sat: " << int(obs[i].sat) << " | rcv: " << obs[i].rcv <<
-               " | SNR: " << obs[i].SNR[0] << " | LLI: " << obs[i].LLI[0] << " | code: " << obs[i].code[0] <<
+    std::cout << "time: " << time_str(obs[i].time, 3) <<  " | sat: " << int(obs[i].sat) << " | rcv: " << int(obs[i].rcv) <<
+               " | SNR: " << int(obs[i].SNR[0]) << " | LLI: " << int(obs[i].LLI[0]) << " | code: " << int(obs[i].code[0]) <<
                  " | L: " << obs[i].L[0] <<  " | P: " << obs[i].P[0] << " | D: " << obs[i].D[0] << std::endl;
   }
 
diff --git a/src/gnss_utils.cpp b/src/gnss_utils.cpp
index 1398feceb9ee850c6801c58ace41eef0830c697e..c7b69bc3fa6cb1ac8a061477ad8ab614efa0926b 100644
--- a/src/gnss_utils.cpp
+++ b/src/gnss_utils.cpp
@@ -19,7 +19,7 @@ namespace GNSSUtils
     sol_t sol;
     sol = {{0}};
 
-    output.pos_stat = pntpos(_observations.getObservations().data(), _observations.getObservations().size(),
+    output.pos_stat = pntpos(_observations.data(), _observations.size(),
                             &(_navigation.getNavigation()),
                             &_prcopt, &sol, NULL, NULL, msg);
     
@@ -68,7 +68,7 @@ namespace GNSSUtils
     sol_t sol;
     sol = {{0}};
 
-    output.pos_stat = pntposOwn(_observations.getObservations().data(), _observations.getObservations().size(),
+    output.pos_stat = pntposOwn(_observations.data(), _observations.size(),
                               &(_navigation.getNavigation()),
                               &_prcopt, &sol, NULL, NULL, msg);
 
@@ -184,75 +184,120 @@ namespace GNSSUtils
                 const prcopt_t *opt, sol_t *sol, double *azel, int *vsat,
                 double *resp, char *msg)
   {
-      double x[NX]={0},dx[NX],Q[NX*NX],*v,*H,*var,sig;
-      int i,j,k,info,stat,nv,ns;
+//      double x[NX]={0},dx[NX],Q[NX*NX],*v,*H,*var,sig;
+//      int i,j,k,info,stat,nv,ns;
+//
+//      trace(3,"estpos  : n=%d\n",n);
+//
+//      v=mat(n+4,1); H=mat(NX,n+4); var=mat(n+4,1);
+//
+//      for (i=0;i<3;i++) x[i]=sol->rr[i];
+//
+//      for (i=0;i<MAXITR;i++) {
+//
+//          /* pseudorange residuals */
+//          nv=rescode(i,obs,n,rs,dts,vare,svh,nav,x,opt,v,H,var,azel,vsat,resp,
+//                     &ns);
+//
+//          if (nv<NX) {
+//              sprintf(msg,"lack of valid sats ns=%d",nv);
+//              break;
+//          }
+//          /* weight by variance */
+//          for (j=0;j<nv;j++) {
+//              sig=sqrt(var[j]);
+//              v[j]/=sig;
+//              for (k=0;k<NX;k++) H[k+j*NX]/=sig;
+//          }
+//          /* least square estimation */
+//          if ((info=lsq(H,v,NX,nv,dx,Q))) {
+//              sprintf(msg,"lsq error info=%d",info);
+//              break;
+//          }
+//          for (j=0;j<NX;j++) x[j]+=dx[j];
+//
+//          if (norm(dx,NX)<1E-4) {
+//              sol->type=0;
+//              sol->time=timeadd(obs[0].time,-x[3]/CLIGHT);
+//              sol->dtr[0]=x[3]/CLIGHT; /* receiver clock bias (s) */
+//              sol->dtr[1]=x[4]/CLIGHT; /* glo-gps time offset (s) */
+//              sol->dtr[2]=x[5]/CLIGHT; /* gal-gps time offset (s) */
+//              sol->dtr[3]=x[6]/CLIGHT; /* bds-gps time offset (s) */
+//              for (j=0;j<6;j++) sol->rr[j]=j<3?x[j]:0.0;
+//              for (j=0;j<3;j++) sol->qr[j]=(float)Q[j+j*NX];
+//              sol->qr[3]=(float)Q[1];    /* cov xy */
+//              sol->qr[4]=(float)Q[2+NX]; /* cov yz */
+//              sol->qr[5]=(float)Q[2];    /* cov zx */
+//              sol->ns=(unsigned char)ns;
+//              sol->age=sol->ratio=0.0;
+//
+//              /* validate solution */
+//              if ((stat=valsol(azel,vsat,n,opt,v,nv,NX,msg))) {
+//                  sol->stat=opt->sateph==EPHOPT_SBAS?SOLQ_SBAS:SOLQ_SINGLE;
+//              }
+//              free(v); free(H); free(var);
+//
+//              return stat;
+//          }
+//      }
+//      if (i>=MAXITR) sprintf(msg,"iteration divergent i=%d",i);
+//
+//      free(v); free(H); free(var);
 
-      trace(3,"estpos  : n=%d\n",n);
-
-      v=mat(n+4,1); H=mat(NX,n+4); var=mat(n+4,1);
-
-      for (i=0;i<3;i++) x[i]=sol->rr[i];
+      return 0;
+  }
 
-      for (i=0;i<MAXITR;i++) {
+Eigen::Vector3d ecefToLatLon(const Eigen::Vector3d & _ecef)
+{
+    Eigen::Vector3d pos;
+    ecef2pos(_ecef.data(), pos.data());
 
-          /* pseudorange residuals */
-          nv=rescode(i,obs,n,rs,dts,vare,svh,nav,x,opt,v,H,var,azel,vsat,resp,
-                     &ns);
+    return pos;
+}
 
-          if (nv<NX) {
-              sprintf(msg,"lack of valid sats ns=%d",nv);
-              break;
-          }
-          /* weight by variance */
-          for (j=0;j<nv;j++) {
-              sig=sqrt(var[j]);
-              v[j]/=sig;
-              for (k=0;k<NX;k++) H[k+j*NX]/=sig;
-          }
-          /* least square estimation */
-          if ((info=lsq(H,v,NX,nv,dx,Q))) {
-              sprintf(msg,"lsq error info=%d",info);
-              break;
-          }
-          for (j=0;j<NX;j++) x[j]+=dx[j];
-
-          if (norm(dx,NX)<1E-4) {
-              sol->type=0;
-              sol->time=timeadd(obs[0].time,-x[3]/CLIGHT);
-              sol->dtr[0]=x[3]/CLIGHT; /* receiver clock bias (s) */
-              sol->dtr[1]=x[4]/CLIGHT; /* glo-gps time offset (s) */
-              sol->dtr[2]=x[5]/CLIGHT; /* gal-gps time offset (s) */
-              sol->dtr[3]=x[6]/CLIGHT; /* bds-gps time offset (s) */
-              for (j=0;j<6;j++) sol->rr[j]=j<3?x[j]:0.0;
-              for (j=0;j<3;j++) sol->qr[j]=(float)Q[j+j*NX];
-              sol->qr[3]=(float)Q[1];    /* cov xy */
-              sol->qr[4]=(float)Q[2+NX]; /* cov yz */
-              sol->qr[5]=(float)Q[2];    /* cov zx */
-              sol->ns=(unsigned char)ns;
-              sol->age=sol->ratio=0.0;
-
-              /* validate solution */
-              if ((stat=valsol(azel,vsat,n,opt,v,nv,NX,msg))) {
-                  sol->stat=opt->sateph==EPHOPT_SBAS?SOLQ_SBAS:SOLQ_SINGLE;
-              }
-              free(v); free(H); free(var);
-
-              return stat;
-          }
-      }
-      if (i>=MAXITR) sprintf(msg,"iteration divergent i=%d",i);
+double computeSatElevation(const Eigen::Vector3d& receiver_ecef, const Eigen::Vector3d& sat_ecef)
+{
+    // ecef 2 geodetic
+    Eigen::Vector3d receiver_geo;
+    ecef2pos(receiver_ecef.data(), receiver_geo.data());
 
-      free(v); free(H); free(var);
+    // receiver-sat vector ecef
+    Eigen::Vector3d receiver_sat_ecef = sat_ecef-receiver_ecef;
 
-      return 0;
-  }
+    // receiver-sat vector enu (receiver ecef as origin)
+    Eigen::Vector3d receiver_sat_enu;
+    ecef2enu(receiver_geo.data(),      //geodetic position {lat,lon} (rad)
+           receiver_ecef.data(),     //vector in ecef coordinate {x,y,z}
+           receiver_sat_enu.data()); // vector in local tangental coordinate {e,n,u}
 
-  Eigen::Vector3d ecefToLatLon(const Eigen::Vector3d & _ecef)
-  {
-    double pos[3];
-    ecef2pos(&_ecef(0), pos);
+    // elevation
+    return atan2(receiver_sat_enu(2), sqrt(receiver_sat_enu(0) * receiver_sat_enu(0) + receiver_sat_enu(1) * receiver_sat_enu(1)));
+}
 
-    return Eigen::Vector3d(pos);
-  }
+void computeSatellitesPositions(const Observations& obs,
+                                const Navigation& nav,
+                                const prcopt_t& opt,
+                                std::map<unsigned char,Eigen::Vector3d>& sats_pos)
+{
+    double rs[6*obs.size()],dts[2*obs.size()],var[obs.size()];
+    int svh[obs.size()];
+
+    // compute positions
+    satposs(obs.getObservations().front().time,
+            obs.data(),
+            obs.size(),
+            &nav.getNavigation(),
+            opt.sateph,
+            rs, dts, var, svh);
+
+    // store positions
+    for (int i = 0; i < obs.size(); i++)
+    {
+        if (svh[i] < 0) // ephemeris unavailable
+            sats_pos[obs.getObservationByIdx(i).sat] = Eigen::Vector3d::Zero();
+        else
+            sats_pos[obs.getObservationByIdx(i).sat] << rs[6*i], rs[6*i+1], rs[6*i+2];
 
+    }
+}
 }
diff --git a/src/observations.cpp b/src/observations.cpp
index 4ef5448237b6e98ba5fc17f92e9f3da2cc3a6a6a..3ac4ee43fcba98225ce52878877372cbae484243 100644
--- a/src/observations.cpp
+++ b/src/observations.cpp
@@ -12,27 +12,21 @@ Observations::Observations()
 
 Observations::~Observations()
 {
-  this->obs_vector_.erase(obs_vector_.begin(), obs_vector_.end());
 }
 
 void Observations::clearObservations()
 {
-  this->obs_vector_.clear();
+  this->obs_.clear();
+  this->idx_2_sat_.clear();
+  this->sat_2_idx_.clear();
 }
 
-void Observations::pushObservation(obsd_t & obs)
+void Observations::addObservation(const obsd_t & obs)
 {
-  this->obs_vector_.push_back(obs);
-}
-
-void Observations::reserveObservations(unsigned int n)
-{
-  this->obs_vector_.reserve(n);
-}
-
-const std::vector<obsd_t> & Observations::getObservations() const
-{
-  return this->obs_vector_;
+    assert(!hasSatellite(obs.sat) && "adding an observation of a satellite already stored!");
+    this->obs_.push_back(obs);
+    idx_2_sat_[obs_.size()-1] = obs.sat;
+    sat_2_idx_[obs.sat] = obs_.size()-1;
 }
 
 void Observations::print(obsd_t & _obs)
@@ -48,15 +42,36 @@ void Observations::print(obsd_t & _obs)
   printArray("D: ", _obs.D, ARRAY_SIZE(_obs.D));
 }
 
-void Observations::print(int _obs_idx)
+void Observations::printBySat(const int& _sat_number)
+{
+  print(getObservationBySat(_sat_number));
+}
+
+void Observations::printByIdx(const int& _idx)
 {
-  print(obs_vector_[_obs_idx]);
+  print(getObservationByIdx(_idx));
 }
 
 void Observations::print()
 {
-  for (auto obs: obs_vector_)
+  for (auto obs : obs_)
   {
     print(obs);
   }
-}
\ No newline at end of file
+}
+
+void Observations::findCommonObservations(const Observations& obs_1, const Observations& obs_2,
+                                          Observations& common_obs_1, Observations& common_obs_2)
+{
+    // clear and reserve
+    common_obs_1.clearObservations();
+    common_obs_2.clearObservations();
+
+    // iterate 1
+    for (auto&& obs_1_ref : obs_1.getObservations())
+        if (obs_2.hasSatellite(obs_1_ref.sat))
+        {
+            common_obs_1.addObservation(obs_1_ref);
+            common_obs_2.addObservation(obs_2.getObservationBySat(obs_1_ref.sat));
+        }
+}
diff --git a/src/single_differences.cpp b/src/single_differences.cpp
index 0ce3affcc8adff3275321b89afee5a2f5eb0e846..dfb97f36b3b571dab24f16c15147817abc806448 100644
--- a/src/single_differences.cpp
+++ b/src/single_differences.cpp
@@ -5,57 +5,481 @@
  *      Author: jvallve
  */
 
-#include "single_differences.h"
+#include "gnss_utils/single_differences.h"
 
 namespace GNSSUtils
 {
 
-bool singleDifferences(const Observations& obs_r, const Navigation& nav_r,
+bool singleDifferences(const Observations& obs_r, Navigation& nav_r,
                        const Observations& obs_k, const Navigation& nav_k,
                        Eigen::Vector4d& d, Eigen::Matrix4d& cov_d,
-                       double& residual, std::list<int>& discarded_sats,
+                       double& residual, std::set<unsigned char>& discarded_sats,
                        const SingleDifferencesParams& sd_params, const prcopt_t& opt)
 {
-    // TODO: COMPUTE RECEIVER POSITION AT t_r
-    Eigen::Vector3d pos_r(Eigen::Vector3d::Zero());
-
     // COMPUTE SINGLE DIFFERENCES
-    return singleDifferences(obs_r, pos_r,
+    return singleDifferences(obs_r, nav_r, computePos(obs_r,nav_r,opt).pos,
                              obs_k, nav_k,
                              d, cov_d,
                              residual, discarded_sats,
                              sd_params, opt);
 }
 
-bool singleDifferences(const Observations& obs_r, const Eigen::Vector3d& pos_r,
+bool singleDifferences(const Observations& obs_r, const Navigation& nav_r, const Eigen::Vector3d& x_r,
                        const Observations& obs_k, const Navigation& nav_k,
                        Eigen::Vector4d& d, Eigen::Matrix4d& cov_d,
-                       double& residual, std::list<int>& discarded_sats,
+                       double& residual, std::set<unsigned char>& discarded_sats,
                        const SingleDifferencesParams& sd_params, const prcopt_t& opt)
 {
-    // TODO: FIND COMMON SATELLITES
+    // FIND COMMON SATELLITES OBSERVATIONS
     Observations common_obs_r, common_obs_k;
+    Observations::findCommonObservations(obs_r, obs_k, common_obs_r, common_obs_k);
+    int n_common_sats = common_obs_r.getObservations().size();
+
+    // COMPUTE COMMON SATELLITES POSITION
+    std::map<unsigned char, Eigen::Vector3d> common_sats_pos_r, common_sats_pos_k;
+    computeSatellitesPositions(common_obs_r, nav_r, opt, common_sats_pos_r);
+    computeSatellitesPositions(common_obs_k, sd_params.use_old_nav ? nav_r : nav_k, opt, common_sats_pos_k);
 
-    // TODO: COMPUTE COMMON SATELLITES POSITION
-    std::map<int,Eigen::Vector3d> common_sats_pos;
+    // FILTER SATELLITES (ALREADY DISCARDED, CORRUPTED DATA, WRONG POSITION, CONSTELLATION, ELEVATION and SNR)
+    filterCommonSatellites(common_obs_r, common_sats_pos_r,
+                           common_obs_k, common_sats_pos_k,
+                           discarded_sats, x_r,
+                           sd_params, opt);
 
     // COMPUTE SINGLE DIFFERENCES
-    return singleDifferences(common_obs_r, pos_r,
-                             common_obs_k, common_sats_pos,
+    return singleDifferences(common_obs_r, nav_r, common_sats_pos_r, x_r,
+                             common_obs_k, nav_k, common_sats_pos_k,
                              d, cov_d,
                              residual, discarded_sats,
                              sd_params, opt);
 }
 
-bool singleDifferences(const Observations& common_obs_r, const Eigen::Vector3d& pos_r,
-                       const Observations& common_obs_k, const std::map<int,Eigen::Vector3d>& common_sats_pos,
+bool singleDifferences(const Observations& common_obs_r, const Navigation& nav_r, const std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_r, const Eigen::Vector3d& x_r,
+                       const Observations& common_obs_k, const Navigation& nav_k, const std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_k,
                        Eigen::Vector4d& d, Eigen::Matrix4d& cov_d,
-                       double& residual, std::list<int>& discarded_sats,
+                       double& residual, std::set<unsigned char>& excluded_sats,
                        const SingleDifferencesParams& sd_params, const prcopt_t& opt)
 {
+    assert(common_obs_r.size() == common_obs_k.size());
+    assert(common_obs_r.size() == common_sats_pos_r.size());
+    assert(common_obs_k.size() == common_sats_pos_k.size());
+
+    if (!excluded_sats.empty())
+    {
+        printf("SD: WARNING! excluded_sats list is not empty. It is an output parameter of the RAIM excluded sats. Clearing the list.");
+        excluded_sats.clear();
+    }
+
+    double tr(common_obs_r.getObservations().front().time.time + common_obs_r.getObservations().front().time.sec);
+    double tk(common_obs_k.getObservations().front().time.time + common_obs_k.getObservations().front().time.sec);
+
+    int required_n_sats(std::max(sd_params.min_common_sats + sd_params.raim_n, 4 + sd_params.raim_n));
+    int n_common_sats = common_sats_pos_k.size();
+
+    if(n_common_sats < required_n_sats)
+    {
+        #if GNSS_UTILS_SD_DEBUG == 1
+            printf("SD: NOT ENOUGH COMMON SATS");
+            printf("SD: %i common sats available, %i sats are REQUIRED. [ SKIPPING SD ]",
+                   n_common_sats,
+                   required_n_sats);
+        #endif
+        return false;
+    }
+
+    // MULTI-FREQUENCY
+    std::map<int,std::pair<int,int>> row_2_sat_freq;
+    int row = 0;
+    for (auto obs_idx = 0; obs_idx < common_obs_k.size(); obs_idx++)
+    {
+        auto&& obs_r = common_obs_r.getObservationByIdx(obs_idx);
+        auto&& obs_k = common_obs_k.getObservationByIdx(obs_idx);
+
+        const int& sat_number = obs_k.sat;
+
+        if(sd_params.use_carrier_phase)
+        {
+            // L1/E1
+            if (std::abs(obs_r.L[0]) > 1e-12 and
+                std::abs(obs_k.L[0]) > 1e-12)
+            {
+                row_2_sat_freq[row] = std::make_pair(sat_number,0);
+                row++;
+            }
+            if (!sd_params.use_multi_freq)
+                continue;
+
+            // L2 (GPS/GLO/QZS)
+            int sys=satsys(sat_number,NULL);
+            if (NFREQ >= 2 and (sys&(SYS_GPS|SYS_GLO|SYS_QZS)) and
+                std::abs(obs_r.L[1]) > 1e-12 and
+                std::abs(obs_k.L[1]) > 1e-12)
+            {
+                row_2_sat_freq[row] = std::make_pair(sat_number,1);
+                row++;
+            }
+            // E5 (GAL)
+            if (NFREQ >= 3 and (sys&SYS_GAL) and
+                std::abs(obs_r.L[2]) > 1e-12 and
+                std::abs(obs_k.L[2]) > 1e-12)
+            {
+                row_2_sat_freq[row] = std::make_pair(sat_number,2);
+                row++;
+            }
+        }
+        else
+            // L1/E1
+            if (std::abs(obs_r.P[0]) > 1e-12 and
+                std::abs(obs_k.P[0]) > 1e-12)
+            {
+                row_2_sat_freq[row] = std::make_pair(sat_number,0);
+                row++;
+            }
+    }
+    int n_differences = row_2_sat_freq.size();
+
+    // Initial guess
+    Eigen::Vector4d d_0(d);
+
+    #if GNSS_UTILS_SD_DEBUG == 1
+        std::cout << "Receiver at t_r: " << x_r.transpose() << std::endl;
+        std::cout << "d initial guess: " << d_0.transpose() << std::endl;
+    #endif
+
+    // FILL SATELLITES POSITIONS AND MEASUREMENTS =======================================================================
+    Eigen::Matrix<double, Eigen::Dynamic, 4> A(Eigen::Matrix<double, Eigen::Dynamic, 4>::Zero(n_differences,4));
+    Eigen::VectorXd r(Eigen::VectorXd::Zero(n_differences));
+    Eigen::VectorXd drho_m(Eigen::VectorXd::Zero(n_differences));
+    Eigen::Matrix<double, 3, Eigen::Dynamic> s_k(Eigen::Matrix<double, 3, Eigen::Dynamic>::Zero(3,n_differences));
+    Eigen::Matrix<double, 3, Eigen::Dynamic> s_r(Eigen::Matrix<double, 3, Eigen::Dynamic>::Zero(3,n_differences));
+    int sat_i = 0;
+
+    for (auto row_sat_freq_it : row_2_sat_freq)
+    {
+        const int& row = row_sat_freq_it.first;
+        const int& sat_number = row_sat_freq_it.second.first;
+        const int& freq = row_sat_freq_it.second.second;
+
+        auto obs_r = common_obs_r.getObservationBySat(sat_number);
+        auto obs_k = common_obs_k.getObservationBySat(sat_number);
+
+        // excluded satellite
+        if (excluded_sats.count(sat_number) != 0)
+            continue;
+
+        // Satellite's positions
+        s_r.col(row) = common_sats_pos_r.at(sat_number);
+        s_k.col(row) = common_sats_pos_k.at(sat_number);
+
+        if(sd_params.use_carrier_phase)
+            drho_m(row) = (obs_r.L[freq] * nav_r.getNavigation().lam[sat_number][freq]) -
+                          (obs_k.L[freq] * nav_k.getNavigation().lam[sat_number][freq]);
+
+        else
+            drho_m(row) = obs_r.P[freq] - obs_k.P[freq];
+
+        if(!sd_params.relinearize_jacobian)
+        {
+            // Unit vectors from recerivers to satellites
+            Eigen::Vector3d u_k = (s_k.col(row) - x_r - d.head(3)).normalized();
+
+            // Fill Jacobian matrix
+            A(row, 0) = u_k(0);
+            A(row, 1) = u_k(1);
+            A(row, 2) = u_k(2);
+            A(row, 3) = -1.0;
+        }
+    }
+
+    // LEAST SQUARES SOLUTION =======================================================================
+    for (int j = 0; j < sd_params.max_iterations; j++)
+    {
+        // fill A and r
+        for (int row = 0; row < A.rows(); row++)
+        {
+            // Evaluate r
+            r(row) = drho_m(row) - (s_k.col(row) - x_r - d.head(3)).norm() + (s_r.col(row) - x_r).norm() - d(3);
+
+            // Evaluate A
+            if (sd_params.relinearize_jacobian)
+            {
+                // Unit vectors from rcvrs to sats
+                Eigen::Vector3d u_k = (s_k.col(row) - x_r - d.head(3)).normalized();
+
+                // Fill Jacobian matrix
+                A(row, 0) = u_k(0);
+                A(row, 1) = u_k(1);
+                A(row, 2) = u_k(2);
+                A(row, 3) = -1.0;
+            }
+        }
+
+        // Solve
+        cov_d = (A.transpose()*A).inverse();
+        d -= cov_d*A.transpose()*r;
+
+        // wrong solution
+        if(d.array().isNaN().any())
+        {
+            std::cout << "SD: LSM DID NOT WORK. NAN values appeared. ABORTING!!" << std::endl;
+            return false;
+        }
+
+        // residual
+        //residual = sqrt((r - A * d).squaredNorm() / A.rows());
+        residual = (r + A * d).norm();
+        //std::cout << "residual = " << residual << std::endl;
+        //std::cout << "SD2: r-A*d = " << (r + A * d).transpose() << std::endl;
+
+        // RAIM ====================================== (only at first iteration)
+        //TODO: RAIM WITH SATELLITES, not ROWS
+        if(j==0 and sd_params.raim_n > 0 and residual > sd_params.raim_min_residual)
+        {
+            int worst_sat_row = -1;
+            double best_residual = 1e12;
+            Eigen::Vector4d best_d;
+            int n_removed_rows = 1;
+
+            // remove some satellites
+            while(excluded_sats.size() < sd_params.raim_n)
+            {
+                auto A_raim = A;
+                auto r_raim = r;
+
+                // solve removing each satellite
+                for (int row_removed = 0; row_removed < A_raim.rows(); row_removed++)
+                {
+                    // Multi-freq: some rows for the same satellite
+                    n_removed_rows = 1;
+                    if (sd_params.use_multi_freq)
+                        while (row_removed + n_removed_rows < A_raim.rows() and row_2_sat_freq.at(row_removed+n_removed_rows) == row_2_sat_freq.at(row_removed))
+                            n_removed_rows++;
+
+                    // remove satellite rows
+                    for (auto r = 0; r < n_removed_rows; r++)
+                    {
+                        A_raim.row(row_removed+r) = Eigen::Vector4d::Zero().transpose();
+                        r_raim(row_removed+r) = 0; // not necessary
+                    }
+
+                    // solve
+                    Eigen::Vector4d d_raim = d_0-(A_raim.transpose()*A_raim).inverse()*A_raim.transpose()*r_raim;
+
+                    // no NaN
+                    if (!d_raim.array().isNaN().any())
+                    {
+                        // residual
+                        residual = (r - A * d_raim).norm() / A.rows(); //FIXME: evaluate with XX_raim or not? I think next line is the good one
+                        //residual = sqrt((r_raim - A_raim * d_raim).squaredNorm() / A_raim.rows());
+    
+                        // store if best residual
+                        if (residual < best_residual)
+                        {
+                            worst_sat_row = row_removed;
+                            best_residual = residual;
+                            best_d = d_raim;
+                        }
+                    }
+                    // restore initial A and r
+                    for (auto row = 0; row < n_removed_rows; row++)
+                    {
+                        A_raim.row(row_removed+row) = A.row(row_removed+row);
+                        r_raim(row_removed+row) = r(row_removed+row);
+                    }
+                    row_removed += (n_removed_rows-1);
+                }
+
+                // No successful RAIM solution
+                if(worst_sat_row == -1)
+                {
+                    printf("SD: LS after RAIM DID NOT WORK. NAN values appeared. ABORTING!!");
+                    return false;
+                }
+
+                // store removed sat
+                excluded_sats.insert(row_2_sat_freq[worst_sat_row].first);
+
+                // decrease n_common_sats
+                n_differences=-n_removed_rows;
+                n_common_sats--;
+
+                // Remove selected satellite from problem
+                auto A_aux = A;
+                auto r_aux = r;
+                auto drho_m_aux = drho_m;
+                auto s_r_aux = s_r;
+                auto s_k_aux = s_k;
+                A.conservativeResize(n_differences, Eigen::NoChange);
+                r.conservativeResize(n_differences);
+                drho_m.conservativeResize(n_differences);
+                s_r.conservativeResize(Eigen::NoChange, n_differences);
+                s_k.conservativeResize(Eigen::NoChange, n_differences);
+
+                int back_elements_changing = A.rows() - worst_sat_row - n_removed_rows;
+                if (back_elements_changing != 0) //if last satelite removed, conservative resize did the job
+                {
+                    A       .bottomRows(back_elements_changing)  = A_aux     .bottomRows(back_elements_changing);
+                    s_r     .rightCols(back_elements_changing)   = s_r_aux   .rightCols(back_elements_changing);
+                    s_k     .rightCols(back_elements_changing)   = s_k_aux   .rightCols(back_elements_changing);
+                    r       .tail(back_elements_changing)        = r_aux     .tail(back_elements_changing);
+                    drho_m  .tail(back_elements_changing)        = drho_m_aux.tail(back_elements_changing);
+                }
+
+                // apply the RAIM solution
+                d = best_d;
+                cov_d = (A.transpose()*A).inverse();
+
+                #if GNSS_UTILS_SD_DEBUG == 1
+                    std::cout << "SD After RAIM iteration" << std::endl;
+                    std::cout << "\tExcluded sats : ";
+                    for (auto dsat : excluded_sats)
+                        std::cout << (int)dsat << " ";
+                    std::cout << std::endl;
+                    std::cout << "\tCommon sats   : " << n_common_sats << std::endl;
+                    std::cout << "\tRows          : " << n_differences << std::endl;
+                    std::cout << "\tInitial guess = " << d_0.transpose() << " (" << d_0.head<3>().norm() << "m)" << std::endl;
+                    std::cout << "\tSolution      = " << d.transpose()   << " (" << d.head<3>().norm()   << "m)" << std::endl;
+                    std::cout << "\tClock offset  = " << d(3) << std::endl;
+                    std::cout << "\tResidual      = " << residual << std::endl;
+                #endif
+            }
+
+            #if GNSS_UTILS_SD_DEBUG == 1
+                std::cout << "SD After RAIM " << std::endl;
+                std::cout << "\tExcluded sats : ";
+                for (auto dsat : excluded_sats)
+                    std::cout << (int)dsat << " ";
+                std::cout << std::endl;
+                std::cout << "\tCommon sats   : " << n_common_sats << std::endl;
+                std::cout << "\tRows          : " << n_differences << std::endl;
+                std::cout << "\tInitial guess = " << d_0.transpose() << " (" << d_0.head<3>().norm() << "m)" << std::endl;
+                std::cout << "\tSolution      = " << d.transpose()   << " (" << d.head<3>().norm()   << "m)" << std::endl;
+                std::cout << "\tClock offset  = " << d(3) << std::endl;
+                std::cout << "\tResidual      = " << residual << std::endl;
+            #endif
+        }
+
+        #if GNSS_UTILS_SD_DEBUG == 1
+            std::cout << "SD iteration " << j << std::endl;
+            std::cout << "\tExcluded sats : ";
+            for (auto dsat : excluded_sats)
+                std::cout << (int)dsat << " ";
+            std::cout << std::endl;
+            std::cout << "\tCommon sats   : " << n_common_sats << std::endl;
+            std::cout << "\tRows          : " << n_differences << std::endl;
+            std::cout << "\tInitial guess = " << d_0.transpose() << " (" << d_0.head<3>().norm() << "m)" << std::endl;
+            std::cout << "\tSolution      = " << d.transpose()   << " (" << d.head<3>().norm()   << "m)" << std::endl;
+            std::cout << "\tClock offset  = " << d(3) << std::endl;
+            std::cout << "\tResidual      = " << residual << std::endl;
+        #endif
+    }
+
+    // weight covariance with the measurement noise (proportional to time)
+    double sq_sigma_SD = (tk-tr) * sd_params.sigma_atm * sd_params.sigma_atm +
+                          2      * (sd_params.use_carrier_phase ?
+                                          sd_params.sigma_carrier * sd_params.sigma_carrier :
+                                          sd_params.sigma_code    * sd_params.sigma_code);
+    cov_d *= sq_sigma_SD;
+    //residual = (r - A * d).norm() / A.rows();
+
+    // Computing error on measurement space
+    for(int row = 0; row < n_differences; row++)
+    {
+        r(row) = drho_m(row)
+                 - (s_k.col(row) - x_r - d.head(3)).norm()
+                 + (s_r.col(row) - x_r).norm()
+                 - d(3);
+    }
+    residual = sqrt(r.squaredNorm() / r.size());
+
     return true;
 }
 
+void filterCommonSatellites(Observations& common_obs_r, std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_r,
+                            Observations& common_obs_k, std::map<unsigned char,Eigen::Vector3d>& common_sats_pos_k,
+                            std::set<unsigned char>& discarded_sats, const Eigen::Vector3d& x_r,
+                            const SingleDifferencesParams& sd_params, const prcopt_t& opt)
+{
+    assert(common_obs_r.size() == common_obs_k.size());
+    assert(common_obs_r.size() == common_sats_pos_r.size());
+    assert(common_obs_r.size() == common_sats_pos_k.size());
+
+    for (int obs_i = 0; obs_i < common_obs_r.size(); obs_i++)
+    {
+        auto&& obs_r = common_obs_r.getObservationByIdx(obs_i);
+        auto&& obs_k = common_obs_k.getObservationByIdx(obs_i);
+        assert(obs_r.sat == obs_k.sat && "common satellites observations have to be ordered equally");
+        const int& sat_number = obs_r.sat;
+
+
+        bool discard = false;
+
+        // already discarded sats
+        if (discarded_sats.count(sat_number) != 0)
+            discard = true;
+        else
+        {
+            // wrong data (satellite is not included in the discarded list)
+            if (sd_params.use_carrier_phase and (std::abs(obs_r.L[0]) < 1e-12 or std::abs(obs_k.L[0]) < 1e-12))
+            {
+                discard = true;
+            }
+            else if (!sd_params.use_carrier_phase and (std::abs(obs_r.P[0]) < 1e-12 or std::abs(obs_k.L[0]) < 1e-12))
+            {
+                discard = true;
+            }
+            else
+            {
+                // bad satellite position (satellite is not included in the discarded list)
+                if (common_sats_pos_r[sat_number].isApprox(Eigen::Vector3d::Zero()) or
+                    common_sats_pos_r[sat_number].isApprox(Eigen::Vector3d::Zero()))
+                {
+                    discard = true;
+                }
+                else
+                {
+                    // constellation
+                    int sys=satsys(obs_r.sat,NULL);
+                    if (!(sys & opt.navsys))
+                    {
+                        discarded_sats.insert(sat_number);
+                        discard = true;
+                    }
+                    else
+                    {
+                        // check both elevations
+                        double elevation = std::min(computeSatElevation(x_r, common_sats_pos_r[sat_number]),
+                                                    computeSatElevation(x_r, common_sats_pos_k[sat_number]));
+                        if (elevation < opt.elmin)
+                        {
+                            discarded_sats.insert(sat_number);
+                            discard = true;
+                        }
+                        else
+                        {
+                            // snr TODO: multifrequency (2nd param and 3rd idx)
+                            if (testsnr(0, 0, elevation, obs_r.SNR[0] * 0.25, &opt.snrmask) == 1)
+                            {
+                                discarded_sats.insert(sat_number);
+                                discard = true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // remove from vectors
+        if (discard)
+        {
+            common_obs_r.removeObservationByIdx(obs_i);
+            common_obs_k.removeObservationByIdx(obs_i);
+            common_sats_pos_r.erase(sat_number);
+            common_sats_pos_k.erase(sat_number);
+        }
+    }
+}
+
 }
 
 
diff --git a/src/utils.cpp b/src/utils.cpp
index 3e41a0e2461e417c8e3ce267963f681d9ca6614e..c967e3f7c26ea944279401f68777fb99b486a789 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -29,7 +29,7 @@ void print(std::string & _msg)
     std::cout << _name << ": [";
     for (int ii=0; ii<size; ++ii)
     {
-      std::cout << (unsigned)_array[ii];
+      std::cout << (int)(_array[ii]);
       if (ii==size-1) 
         std::cout << "] \n";
       else
@@ -63,4 +63,4 @@ void print(std::string & _msg)
     }
   }
 
-}
\ No newline at end of file
+}