@@ -36,6 +36,11 @@ using CarrierPhaseBase = GnssMeasurementBase;
3636 *
3737 * where dt_u, dt_s are in seconds and ambiguity is in meters.
3838 *
39+ * @note The ambiguity is estimated as a *float* (continuous `double`) in the
40+ * factor graph; GTSAM never optimizes it as an integer. This factor yields
41+ * the float estimate and covariance; integer ambiguity fixing (e.g. LAMBDA)
42+ * is a separate step performed outside the graph.
43+ *
3944 * @ingroup navigation
4045 */
4146class GTSAM_EXPORT CarrierPhaseFactor
@@ -112,6 +117,208 @@ class GTSAM_EXPORT CarrierPhaseFactor
112117template <>
113118struct traits <CarrierPhaseFactor> : public Testable<CarrierPhaseFactor> {};
114119
120+ /* *
121+ * Undifferenced (raw) PPP carrier phase factor.
122+ *
123+ * Models a single raw carrier phase (in meters, with the satellite-side SSR
124+ * corrections including the phase bias already folded into the measurement) and
125+ * carries the receiver clock, zenith wet tropo, slant ionosphere and the
126+ * integer ambiguity as state variables:
127+ *
128+ * error = geodist(sat, rcv) + c*(dt_u - dt_s)
129+ * + m_w * ZTD_wet - mu_f * I_slant + lambda * N - measuredPhase
130+ *
131+ * Compared with the pseudorange model, the ionospheric term is *advanced*
132+ * (negative sign). The ambiguity N is in cycles and lambda is the wavelength
133+ * [m/cycle], so the integer structure is preserved for ambiguity resolution.
134+ * The wet mapping function and iono coefficient are held constant per factor.
135+ *
136+ * @note The ambiguity N is represented as a continuous `double` (in cycles)
137+ * and is estimated as a *float* ambiguity in the factor graph; GTSAM never
138+ * optimizes it as an integer. This factor provides the float estimate and its
139+ * covariance. Integer ambiguity fixing is a separate step performed outside
140+ * the graph, e.g. with the LAMBDA method, using the float estimate and
141+ * covariance.
142+ *
143+ * Keys: [pos, clock, ztd, slant-iono, ambiguity].
144+ *
145+ * @ingroup navigation
146+ */
147+ class GTSAM_EXPORT UndifferencedCarrierPhaseFactor
148+ : public NoiseModelFactorN<Point3, double , double , double , double >,
149+ private CarrierPhaseBase {
150+ private:
151+ typedef NoiseModelFactorN<Point3, double , double , double , double > Base;
152+ double tropoMap_ = 0.0 ; // /< Tropospheric wet mapping function (constant).
153+ double ionoCoeff_ = 1.0 ; // /< Slant-iono coefficient mu_f (constant).
154+ double lambda_ = 1.0 ; // /< Wavelength [m/cycle] for the ambiguity term.
155+
156+ public:
157+ using Base::evaluateError;
158+ typedef std::shared_ptr<UndifferencedCarrierPhaseFactor> shared_ptr;
159+ typedef UndifferencedCarrierPhaseFactor This;
160+
161+ UndifferencedCarrierPhaseFactor () : CarrierPhaseBase{0.0 , Point3 (0 , 0 , 0 ), 0.0 } {}
162+ virtual ~UndifferencedCarrierPhaseFactor () = default ;
163+
164+ /* *
165+ * @param receiverPositionKey Receiver Point3 ECEF position node.
166+ * @param receiverClockBiasKey Receiver clock bias node [s].
167+ * @param tropoZenithWetKey Tropospheric zenith wet delay node [m].
168+ * @param slantIonoKey Slant ionospheric delay node (this sat) [m].
169+ * @param ambiguityKey Ambiguity node [cycles].
170+ * @param measuredCarrierPhaseMeters SSR-corrected carrier phase [m].
171+ * @param satellitePosition Satellite ECEF position [m].
172+ * @param tropoWetMapping Wet mapping function m_w at predicted elevation.
173+ * @param ionoCoefficient Ionospheric coefficient mu_f (+1 on L1).
174+ * @param lambda Wavelength [m/cycle].
175+ * @param satelliteClockBias Satellite clock bias [s].
176+ * @param model 1-D noise model.
177+ */
178+ UndifferencedCarrierPhaseFactor (
179+ Key receiverPositionKey, Key receiverClockBiasKey, Key tropoZenithWetKey,
180+ Key slantIonoKey, Key ambiguityKey, double measuredCarrierPhaseMeters,
181+ const Point3& satellitePosition, double tropoWetMapping,
182+ double ionoCoefficient, double lambda, double satelliteClockBias = 0.0 ,
183+ const SharedNoiseModel& model = noiseModel::Unit::Create(1 ));
184+
185+ gtsam::NonlinearFactor::shared_ptr clone () const override {
186+ return std::static_pointer_cast<gtsam::NonlinearFactor>(
187+ gtsam::NonlinearFactor::shared_ptr (new This (*this )));
188+ }
189+
190+ void print (const std::string& s = " " , const KeyFormatter& keyFormatter =
191+ DefaultKeyFormatter) const override ;
192+ bool equals (const NonlinearFactor& expected,
193+ double tol = 1e-9 ) const override ;
194+
195+ Vector evaluateError (const Point3& receiverPosition,
196+ const double & receiverClockBias,
197+ const double & tropoZenithWet, const double & slantIono,
198+ const double & ambiguity, OptionalMatrixType HreceiverPos,
199+ OptionalMatrixType HreceiverClockBias,
200+ OptionalMatrixType HtropoZenithWet,
201+ OptionalMatrixType HslantIono,
202+ OptionalMatrixType Hambiguity) const override ;
203+
204+ inline double tropoMapping () const { return tropoMap_; }
205+ inline double ionoCoefficient () const { return ionoCoeff_; }
206+ inline double wavelength () const { return lambda_; }
207+
208+ private:
209+ #if GTSAM_ENABLE_BOOST_SERIALIZATION
210+ friend class boost ::serialization::access;
211+ template <class ARCHIVE >
212+ void serialize (ARCHIVE & ar, const unsigned int /* version*/ ) {
213+ ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP (Base);
214+ ar& BOOST_SERIALIZATION_NVP (measurement_);
215+ ar& BOOST_SERIALIZATION_NVP (satPos_);
216+ ar& BOOST_SERIALIZATION_NVP (satClkBias_);
217+ ar& BOOST_SERIALIZATION_NVP (tropoMap_);
218+ ar& BOOST_SERIALIZATION_NVP (ionoCoeff_);
219+ ar& BOOST_SERIALIZATION_NVP (lambda_);
220+ }
221+ #endif
222+ };
223+
224+ // / traits
225+ template <>
226+ struct traits <UndifferencedCarrierPhaseFactor>
227+ : public Testable<UndifferencedCarrierPhaseFactor> {};
228+
229+ /* *
230+ * Undifferenced (raw) PPP carrier phase factor with lever-arm correction.
231+ *
232+ * Like UndifferencedCarrierPhaseFactor but keys on a body Pose3 with a lever arm to
233+ * the antenna and an optional ecef_T_nav transform.
234+ * Keys: [pose, clock, ztd, slant-iono, ambiguity].
235+ *
236+ * @ingroup navigation
237+ */
238+ class GTSAM_EXPORT UndifferencedCarrierPhaseFactorArm
239+ : public NoiseModelFactorN<Pose3, double , double , double , double >,
240+ private CarrierPhaseBase {
241+ private:
242+ typedef NoiseModelFactorN<Pose3, double , double , double , double > Base;
243+ gnss::LeverArm arm_;
244+ double tropoMap_ = 0.0 ;
245+ double ionoCoeff_ = 1.0 ;
246+ double lambda_ = 1.0 ;
247+
248+ public:
249+ using Base::evaluateError;
250+ typedef std::shared_ptr<UndifferencedCarrierPhaseFactorArm> shared_ptr;
251+ typedef UndifferencedCarrierPhaseFactorArm This;
252+
253+ UndifferencedCarrierPhaseFactorArm ()
254+ : CarrierPhaseBase{0.0 , Point3 (0 , 0 , 0 ), 0.0 } {}
255+ virtual ~UndifferencedCarrierPhaseFactorArm () = default ;
256+
257+ // / Construct with an ECEF pose key.
258+ UndifferencedCarrierPhaseFactorArm (
259+ Key poseKey, Key receiverClockBiasKey, Key tropoZenithWetKey,
260+ Key slantIonoKey, Key ambiguityKey, double measuredCarrierPhaseMeters,
261+ const Point3& satellitePosition, const Point3& leverArm,
262+ double tropoWetMapping, double ionoCoefficient, double lambda,
263+ double satelliteClockBias = 0.0 ,
264+ const SharedNoiseModel& model = noiseModel::Unit::Create(1 ));
265+
266+ // / Construct with a local nav-frame pose key + ecef_T_nav.
267+ UndifferencedCarrierPhaseFactorArm (
268+ Key poseKey, Key receiverClockBiasKey, Key tropoZenithWetKey,
269+ Key slantIonoKey, Key ambiguityKey, double measuredCarrierPhaseMeters,
270+ const Point3& satellitePosition, const Point3& leverArm,
271+ const Pose3& ecef_T_nav, double tropoWetMapping, double ionoCoefficient,
272+ double lambda, double satelliteClockBias = 0.0 ,
273+ const SharedNoiseModel& model = noiseModel::Unit::Create(1 ));
274+
275+ gtsam::NonlinearFactor::shared_ptr clone () const override {
276+ return std::static_pointer_cast<gtsam::NonlinearFactor>(
277+ gtsam::NonlinearFactor::shared_ptr (new This (*this )));
278+ }
279+
280+ void print (const std::string& s = " " , const KeyFormatter& keyFormatter =
281+ DefaultKeyFormatter) const override ;
282+ bool equals (const NonlinearFactor& expected,
283+ double tol = 1e-9 ) const override ;
284+
285+ Vector evaluateError (const Pose3& pose, const double & receiverClockBias,
286+ const double & tropoZenithWet, const double & slantIono,
287+ const double & ambiguity, OptionalMatrixType H_pose,
288+ OptionalMatrixType HreceiverClockBias,
289+ OptionalMatrixType HtropoZenithWet,
290+ OptionalMatrixType HslantIono,
291+ OptionalMatrixType Hambiguity) const override ;
292+
293+ inline const Point3& leverArm () const { return arm_.b ; }
294+ inline const std::optional<Pose3>& ecefTnav () const { return arm_.ecef_T_nav ; }
295+ inline double tropoMapping () const { return tropoMap_; }
296+ inline double ionoCoefficient () const { return ionoCoeff_; }
297+ inline double wavelength () const { return lambda_; }
298+
299+ private:
300+ #if GTSAM_ENABLE_BOOST_SERIALIZATION
301+ friend class boost ::serialization::access;
302+ template <class ARCHIVE >
303+ void serialize (ARCHIVE & ar, const unsigned int /* version*/ ) {
304+ ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP (Base);
305+ ar& BOOST_SERIALIZATION_NVP (measurement_);
306+ ar& BOOST_SERIALIZATION_NVP (satPos_);
307+ ar& BOOST_SERIALIZATION_NVP (satClkBias_);
308+ ar& boost::serialization::make_nvp (" bL_" , arm_.b );
309+ ar& boost::serialization::make_nvp (" ecef_T_nav_" , arm_.ecef_T_nav );
310+ ar& BOOST_SERIALIZATION_NVP (tropoMap_);
311+ ar& BOOST_SERIALIZATION_NVP (ionoCoeff_);
312+ ar& BOOST_SERIALIZATION_NVP (lambda_);
313+ }
314+ #endif
315+ };
316+
317+ // / traits
318+ template <>
319+ struct traits <UndifferencedCarrierPhaseFactorArm>
320+ : public Testable<UndifferencedCarrierPhaseFactorArm> {};
321+
115322/* *
116323 * Carrier phase factor with lever arm correction.
117324 *
0 commit comments