Index: trunk/BNC/src/bnchelp.html
===================================================================
--- trunk/BNC/src/bnchelp.html	(revision 11060)
+++ trunk/BNC/src/bnchelp.html	(revision 11061)
@@ -7843,4 +7843,43 @@
     - despite the fact, that the delivered code biases of an AC may contain contributions from other biases, also phase
     biases.
+  </p>
+  <p>
+    <b>Consistency of code biases and clocks:</b>
+    This procedure requires that the code biases of an AC are consistent with its clocks. An AC is free to estimate
+    its clocks from signals other than the reference signals; its code biases of the reference signals must then
+    refer to exactly these clocks, i.e. they have to be estimated together with them or aligned to the same clock
+    datum. If an AC combines its clocks with code biases from another source (e.g. a SINEX Bias file of another
+    analysis), the ionosphere-free linear combination B_IF of these biases does not belong to the clocks. The clocks
+    are then shifted per satellite by the wrong amount before the combination.
+  </p>
+  <p>
+    In the combination (method 'Filter') such errors are absorbed by the satellite-specific clock offsets estimated
+    for each AC, which consequently follow B_IF of that AC from satellite to satellite. BNC uses this to check the
+    consistency, separately for each AC and system:
+  </p>
+  <ul>
+    <li>Every 5 minutes (the first check 5 minutes after the start), the AC's satellite offsets of the current epoch
+      are regressed against its B_IF.</li>
+    <li>Only satellites with code biases for both reference signals are used; at least 5 satellites are required.</li>
+    <li>B_IF must vary noticeably between the satellites (standard deviation of at least 3 cm). Otherwise, e.g. for
+      GPS where B_IF of most ACs is close to zero, an inconsistency is not detectable and the check is skipped.</li>
+    <li>An inconsistency is reported if the offsets clearly follow B_IF: regression slope at least 0.3 and correlation
+      coefficient at least 0.7. Since the error is shared between the inconsistent AC and the others, a slope of about
+      0.5 is to be expected with two ACs; with more ACs it approaches 1. The ACs with consistent biases show no
+      correlation with their own B_IF.</li>
+  </ul>
+  <p>
+    The result of each positive check is written to the combination logfile, a warning to the BNC logfile at most once
+    per hour per AC and system, e.g.
+  </p>
+  <pre>
+bncComb: BKG E code biases (C1C/C5Q) seem inconsistent with its clocks: satellite offsets follow the bias
+combination B_IF (27 satellites, B_IF spread 0.084 m, slope 0.60, correlation 0.88)
+</pre>
+  <p>
+    The AC is still used in the combination, its offsets absorb most of the error. Nevertheless, the code biases
+    sent out by this AC should be corrected by its provider: either biases estimated together with the clocks
+    should be used, or the external biases have to be aligned to the clocks per satellite (b' = b - B_IF of the
+    reference signals, SSR sign convention). The check is not applied with the 'Single-Epoch' method.
   </p>
   <p>
Index: trunk/BNC/src/combination/bnccomb.cpp
===================================================================
--- trunk/BNC/src/combination/bnccomb.cpp	(revision 11060)
+++ trunk/BNC/src/combination/bnccomb.cpp	(revision 11061)
@@ -899,4 +899,10 @@
           _newCorr->_satCodeBiasIF += codeCoeff * codeBiasesRefSig[frqType];
         }
+        if (codeBiasesRefSig.size() == 2) {
+          _acBiasIF[acName][prnStr.mid(0,3)] = _newCorr->_satCodeBiasIF;
+        }
+        else {
+          _acBiasIF[acName].remove(prnStr.mid(0,3));
+        }
         _newCorr->_satCodeBias._bias.clear();
       }
@@ -1079,4 +1085,7 @@
       out.flush();
     }
+    if (_method == filter) {
+      checkBiasConsistency(epoTime, sys, out);
+    }
     printResults(epoTime, out, masterCorr);
     dumpResults(epoTime, masterCorr);
@@ -1144,5 +1153,5 @@
         if (pp->type == cmbParam::offACSat            &&
             pp->AC   == corrs(sys)[maxResIndex-1]->_acName &&
-            pp->prn  == corrs(sys)[maxResIndex-1]->_prn.mid(0,3)) {
+            pp->prn  == corrs(sys)[maxResIndex-1]->_prn) {
           QQ_sav.Row(iPar)    = 0.0;
           QQ_sav.Column(iPar) = 0.0;
@@ -1174,4 +1183,105 @@
 
   return success;
+}
+
+// Check the consistency of the AC code biases with the AC clocks
+////////////////////////////////////////////////////////////////////////////
+// An AC's clocks refer to its own clock signals; the code biases of the
+// reference signals refer them to the reference signals (B_IF, ionosphere-
+// free combination of these biases, added to the AC clocks). If the code
+// biases do not belong to the clocks (e.g. taken from another product), the
+// error B_IF is absorbed by the AC's satellite offsets. The satellite offsets
+// of each AC are therefore regressed against its B_IF every 5 minutes (from
+// 5 minutes after the start); a warning is issued (at most once per hour per
+// AC and system) if B_IF varies noticeably between the satellites and the
+// offsets clearly follow it.
+////////////////////////////////////////////////////////////////////////////
+void bncComb::checkBiasConsistency(bncTime epoTime, char sys, QTextStream& out) {
+
+  const double CHECK_INT   = 300.0;   // [s] check interval, first check after start
+  const unsigned MIN_SAT   = 5;
+  const double MIN_SPREAD  = 0.03;    // [m] minimum std. deviation of B_IF
+  const double MIN_SLOPE   = 0.3;     // 0.5 expected for 2 ACs, -> 1 for more ACs
+  const double MIN_CORR    = 0.7;
+
+  if (!_biasCheckStart[sys].valid()) {
+    _biasCheckStart[sys] = epoTime;
+    _biasCheckLast[sys]  = epoTime;
+    return;
+  }
+  if (epoTime - _biasCheckLast[sys] < CHECK_INT) {
+    return;
+  }
+  _biasCheckLast[sys] = epoTime;
+
+  QListIterator<cmbAC*> itAc(_ACs);
+  while (itAc.hasNext()) {
+    const QString& acName = itAc.next()->name;
+    if (!_acBiasIF.contains(acName)) {
+      continue;
+    }
+    const QMap<QString, double>& biasIF = _acBiasIF[acName];
+
+    // satellites of this AC in the current epoch with B_IF available
+    QVector<double> xx, yy;
+    for (int ii = 0; ii < corrs(sys).size(); ii++) {
+      const cmbCorr* corr = corrs(sys)[ii];
+      if (corr->_acName != acName || !biasIF.contains(corr->_prn.mid(0,3))) {
+        continue;
+      }
+      for (int iPar = 1; iPar <= _params[sys].size(); iPar++) {
+        const cmbParam* pp = _params[sys][iPar-1];
+        if (pp->type == cmbParam::offACSat && pp->AC == acName && pp->prn == corr->_prn) {
+          xx.push_back(biasIF[corr->_prn.mid(0,3)]);
+          yy.push_back(pp->xx);
+          break;
+        }
+      }
+    }
+    unsigned nn = xx.size();
+    if (nn < MIN_SAT) {
+      continue;
+    }
+    double mx = 0.0, my = 0.0;
+    for (unsigned ii = 0; ii < nn; ii++) {
+      mx += xx[ii] / nn;
+      my += yy[ii] / nn;
+    }
+    double sxx = 0.0, syy = 0.0, sxy = 0.0;
+    for (unsigned ii = 0; ii < nn; ii++) {
+      sxx += (xx[ii] - mx) * (xx[ii] - mx);
+      syy += (yy[ii] - my) * (yy[ii] - my);
+      sxy += (xx[ii] - mx) * (yy[ii] - my);
+    }
+    double spread = sqrt(sxx / nn);
+    if (spread < MIN_SPREAD || syy <= 0.0) {
+      continue;
+    }
+    double slope = sxy / sxx;
+    double corrCoeff = sxy / sqrt(sxx * syy);
+    if (slope < MIN_SLOPE || corrCoeff < MIN_CORR) {
+      continue;
+    }
+
+    QString refSig;
+    for (unsigned ii = 1; ii < cmbRefSig::cIF; ii++) {
+      t_frequency::type frqType = cmbRefSig::toFreq(sys, static_cast<cmbRefSig::type>(ii));
+      refSig += QString("%1C%2%3").arg(ii > 1 ? "/" : "").arg(t_frequency::toString(frqType)[1])
+                                  .arg(cmbRefSig::toAttrib(sys, static_cast<cmbRefSig::type>(ii)));
+    }
+    QString msg = QString("%1 %2 code biases (%3) seem inconsistent with its clocks: "
+                          "satellite offsets follow the bias combination B_IF "
+                          "(%4 satellites, B_IF spread %5 m, slope %6, correlation %7)")
+                  .arg(acName).arg(sys).arg(refSig).arg(nn)
+                  .arg(spread, 0, 'f', 3).arg(slope, 0, 'f', 2).arg(corrCoeff, 0, 'f', 2);
+    out << epoTime.datestr().c_str() << " " << epoTime.timestr().c_str()
+        << " " << msg << "\n";
+    QString key = acName + sys;
+    bncTime& warned = _biasCheckWarned[key];
+    if (!warned.valid() || epoTime - warned >= 3600.0) {
+      warned = epoTime;
+      emit newMessage("bncComb: " + msg.toLatin1(), true);
+    }
+  }
 }
 
Index: trunk/BNC/src/combination/bnccomb.h
===================================================================
--- trunk/BNC/src/combination/bnccomb.h	(revision 11060)
+++ trunk/BNC/src/combination/bnccomb.h	(revision 11061)
@@ -270,4 +270,9 @@
   QMap<QString, bncTime>                     _refBiasWarned; // last warning per AC and system
   QMap<char, bncTime>                        _singleACWarned; // last 'fewer than 2 ACs' message per system
+  QMap<QString, QMap<QString, double> >      _acBiasIF;       // latest B_IF of the reference signals per AC and satellite
+  QMap<char, bncTime>                        _biasCheckStart; // first combined epoch per system
+  QMap<char, bncTime>                        _biasCheckLast;  // last bias/clock consistency check per system
+  QMap<QString, bncTime>                     _biasCheckWarned; // last inconsistency warning per AC and system
+  void checkBiasConsistency(bncTime epoTime, char sys, QTextStream& out);
   bool allACsBeyond(const bncTime& epoTime) const;
   bncTime                                    _resTime;
