@@ -137,6 +137,16 @@ namespace qlibs {
137137 return static_cast <size_t >( ( Time.value /dt ) + 0 .5_re );
138138 }
139139
140+ /* * @cond **/
141+ class ITransportDelay {
142+ public:
143+ virtual ~ITransportDelay () = default ;
144+ virtual real_t delay (const real_t xInput) noexcept = 0;
145+ virtual real_t operator ()(const real_t xInput) noexcept = 0;
146+ virtual size_t getNumberOfDelays () const noexcept = 0;
147+ };
148+ /* * @cond **/
149+
140150 /* *
141151 * @brief Delays the input by a specified amount of time. You can use this
142152 * class to simulate a time delay.
@@ -150,11 +160,12 @@ namespace qlibs {
150160 * @endcode
151161 */
152162 template <size_t numberOfDelays>
153- class transportDelay {
163+ class transportDelay : public ITransportDelay {
154164 private:
155165 real_t buf[ numberOfDelays + 1 ];
156- tdl delay ;
166+ tdl dl ;
157167 public:
168+ virtual ~transportDelay () {}
158169 /* *
159170 * @brief Constructor for the transportDelay class
160171 * @param[in] initValue The output generated by the block between the
@@ -163,17 +174,28 @@ namespace qlibs {
163174 transportDelay ( const real_t initValue = 0 .0_re )
164175 {
165176 static_assert ( numberOfDelays >= 1 , " Delay taps should be greater than 0" );
166- delay.setup ( buf, initValue);
177+ dl.setup ( buf, initValue);
178+ }
179+
180+ /* *
181+ * @brief Delays the input by a specified amount of time.
182+ * @param[in] xInput The signal to be delayed.
183+ * @return The delayed input signal
184+ */
185+ real_t delay ( const real_t xInput ) noexcept override
186+ {
187+ dl.insertSample ( xInput );
188+ return dl.getOldest ();
167189 }
190+
168191 /* *
169192 * @brief Delays the input by a specified amount of time.
170193 * @param[in] xInput The signal to be delayed.
171194 * @return The delayed input signal
172195 */
173- real_t operator ()( const real_t xInput ) noexcept
196+ real_t operator ()( const real_t xInput ) noexcept override
174197 {
175- delay.insertSample ( xInput );
176- return delay.getOldest ();
198+ return delay ( xInput );
177199 }
178200
179201 constexpr size_t getNumberOfDelays () const noexcept {
@@ -276,7 +298,7 @@ namespace qlibs {
276298 * @param[in] u A new input sample that excites (drives) the system.
277299 * @return The system's output (response) at the current time step.
278300 */
279- real_t excite ( real_t u );
301+ real_t excite ( real_t u ) noexcept ;
280302
281303 // / @copydoc excite(real_t)
282304 real_t operator ()( const real_t u ) {
@@ -721,6 +743,104 @@ namespace qlibs {
721743 bool setIntegrationMethod ( integrationMethod m );
722744 };
723745
746+ /* *
747+ * @brief A Smith Predictor implementation for compensating time delays in
748+ * control systems.
749+ * @details The Smith Predictor is a model-based feedforward control strategy
750+ * designed to improve performance in systems with significant dead time.
751+ * It estimates the delay-free output using an internal model of the process,
752+ * a delay block, and optionally an output filter model.
753+ */
754+ class smithPredictor {
755+ private:
756+ ltisys *model;
757+ ITransportDelay *modelDelay;
758+ ltisys *filter{ nullptr };
759+ real_t yp_hat;
760+ public:
761+ virtual ~smithPredictor () {}
762+ /* *
763+ * @brief Constructs a Smith Predictor with a plant model, delay model,
764+ * and optional initial output estimate.
765+ * @param[in] modelTf Reference to the LTI system model representing the
766+ * delay-free plant.
767+ * @param[in] mDelay Reference to the transport delay block modeling the
768+ * plant’s dead time.
769+ * @param[in] initialCondition Initial value for the internal output
770+ * prediction (@c yp_hat). Default is 0.0.
771+ *
772+ * @note Both the model and delay block are passed by reference and stored internally as pointers.
773+ */
774+ smithPredictor ( ltisys& modelTf,
775+ ITransportDelay& mDelay ,
776+ const real_t initialCondition = 0 .0_re )
777+ : model(&modelTf), modelDelay(&mDelay ), yp_hat(initialCondition) {}
778+
779+ /* *
780+ * @brief Constructs a Smith Predictor with a plant model, delay model,
781+ * and optional initial output estimate.
782+ * @param[in] modelTf Reference to the LTI system model representing the
783+ * delay-free plant.
784+ * @param[in] mDelay Reference to the transport delay block modeling the
785+ * plant’s dead time.
786+ * @param[in] filterTf Reference to the LTI system used as the robustness
787+ * filter.
788+ * @param[in] initialCondition Initial value for the internal output
789+ * prediction (@c yp_hat). Default is 0.0.
790+ *
791+ * @note Both the model and delay block are passed by reference and stored internally as pointers.
792+ */
793+ smithPredictor ( ltisys& modelTf,
794+ ITransportDelay& mDelay ,
795+ ltisys& filterTf,
796+ const real_t initialCondition = 0 .0_re )
797+ : model(&modelTf), modelDelay(&mDelay ), filter(&filterTf), yp_hat(initialCondition) {}
798+
799+ /* *
800+ * @brief Updates the internal prediction based on control input and
801+ * measured plant output.
802+ * @details This method should be called at each control step. It updates
803+ * the internal predicted output by propagating the input through the
804+ * internal delay-free model and adjusting the prediction using the
805+ * actual plant output.
806+ *
807+ * @param[in] ut The control input applied to the real plant.
808+ * @param[in] yt The actual measured output from the delayed plant.
809+ * @return @c true if the prediction was successfully updated, @c false otherwise.
810+ *
811+ * @note The time-step used to call this function must match the time
812+ * base used by both the plant model and the delay block.
813+ */
814+ bool updatePrediction ( const real_t ut,
815+ const real_t yt ) noexcept ;
816+
817+ /* *
818+ * @brief Retrieves the current delay-free predicted output of the system.
819+ * @return The internally computed predicted output.
820+ */
821+ real_t getPrediction () const noexcept {
822+ return yp_hat;
823+ }
824+
825+ /* *
826+ * @brief Sets an optional filter for the internal Smith Predictor model.
827+ * @details The filter is used to improve the system's robustness, attenuate
828+ * measurement noise, and ensure internal stability, particularly in the case
829+ * of unstable plant dynamics.
830+ *
831+ * @param[in] filterTf Reference to the LTI system used as the robustness
832+ * filter.
833+ * @return @c true if the filter was set successfully.
834+ *
835+ * @note The filter is applied internally to the model-based prediction
836+ * and does not affect the plant or delay block directly.
837+ */
838+ bool setFilter ( ltisys& filterTf ) noexcept {
839+ filter = &filterTf;
840+ return true ;
841+ }
842+ };
843+
724844 /* * @}*/
725845}
726846
0 commit comments