Program Listing for File rate_limiter.hpp

Return to documentation for file (include/control_toolbox/rate_limiter.hpp)

// Copyright 2020 PAL Robotics S.L.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
 * Author: Enrique Fernández
 */

#ifndef CONTROL_TOOLBOX__RATE_LIMITER_HPP_
#define CONTROL_TOOLBOX__RATE_LIMITER_HPP_

#include <cmath>
#include <limits>

#include <algorithm>
#include <stdexcept>

namespace control_toolbox
{
template <typename T>
class RateLimiter
{
public:
  RateLimiter(
    T min_value = std::numeric_limits<T>::quiet_NaN(),
    T max_value = std::numeric_limits<T>::quiet_NaN(),
    T min_first_derivative_neg = std::numeric_limits<T>::quiet_NaN(),
    T max_first_derivative_pos = std::numeric_limits<T>::quiet_NaN(),
    T min_first_derivative_pos = std::numeric_limits<T>::quiet_NaN(),
    T max_first_derivative_neg = std::numeric_limits<T>::quiet_NaN(),
    T min_second_derivative = std::numeric_limits<T>::quiet_NaN(),
    T max_second_derivative = std::numeric_limits<T>::quiet_NaN());

  T limit(T & v, T v0, T v1, T dt);

  T limit_value(T & v);

  T limit_first_derivative(T & v, T v0, T dt);

  T limit_second_derivative(T & v, T v0, T v1, T dt);

  void set_params(
    T min_value = std::numeric_limits<T>::quiet_NaN(),
    T max_value = std::numeric_limits<T>::quiet_NaN(),
    T min_first_derivative_neg = std::numeric_limits<T>::quiet_NaN(),
    T max_first_derivative_pos = std::numeric_limits<T>::quiet_NaN(),
    T min_first_derivative_pos = std::numeric_limits<T>::quiet_NaN(),
    T max_first_derivative_neg = std::numeric_limits<T>::quiet_NaN(),
    T min_second_derivative = std::numeric_limits<T>::quiet_NaN(),
    T max_second_derivative = std::numeric_limits<T>::quiet_NaN());

private:
  // Enable/Disable value/first_derivative/second_derivative limits:
  bool has_value_limits_ = true;
  bool has_first_derivative_limits_ = true;
  bool has_second_derivative_limits_ = true;

  // value limits:
  T min_value_ = std::numeric_limits<T>::quiet_NaN();
  T max_value_ = std::numeric_limits<T>::quiet_NaN();

  // first_derivative limits:
  T min_first_derivative_neg_ = std::numeric_limits<T>::quiet_NaN();
  T max_first_derivative_pos_ = std::numeric_limits<T>::quiet_NaN();
  T min_first_derivative_pos_ = std::numeric_limits<T>::quiet_NaN();
  T max_first_derivative_neg_ = std::numeric_limits<T>::quiet_NaN();

  // second_derivative limits:
  T min_second_derivative_ = std::numeric_limits<T>::quiet_NaN();
  T max_second_derivative_ = std::numeric_limits<T>::quiet_NaN();
};

template <typename T>
RateLimiter<T>::RateLimiter(
  T min_value, T max_value, T min_first_derivative_neg, T max_first_derivative_pos,
  T min_first_derivative_pos, T max_first_derivative_neg, T min_second_derivative,
  T max_second_derivative)
{
  set_params(
    min_value, max_value, min_first_derivative_neg, max_first_derivative_pos,
    min_first_derivative_pos, max_first_derivative_neg, min_second_derivative,
    max_second_derivative);
};

// Check if limits are valid, max must be specified, min defaults to -max if unspecified
template <typename T>
void RateLimiter<T>::set_params(
  T min_value, T max_value, T min_first_derivative_neg, T max_first_derivative_pos,
  T min_first_derivative_pos, T max_first_derivative_neg, T min_second_derivative,
  T max_second_derivative)
{
  min_value_ = min_value;
  max_value_ = max_value;
  min_first_derivative_neg_ = min_first_derivative_neg;
  max_first_derivative_pos_ = max_first_derivative_pos;
  min_first_derivative_pos_ = min_first_derivative_pos;
  max_first_derivative_neg_ = max_first_derivative_neg;
  min_second_derivative_ = min_second_derivative;
  max_second_derivative_ = max_second_derivative;

  if (std::isnan(max_value_))
  {
    has_value_limits_ = false;
  }
  if (std::isnan(min_value_))
  {
    min_value_ = -max_value_;
  }
  if (has_value_limits_ && min_value_ > max_value_)
  {
    throw std::invalid_argument("Invalid value limits");
  }

  if (std::isnan(max_first_derivative_pos_))
  {
    has_first_derivative_limits_ = false;
  }
  if (std::isnan(min_first_derivative_neg_))
  {
    min_first_derivative_neg_ = -max_first_derivative_pos_;
  }
  if (has_first_derivative_limits_ && min_first_derivative_neg_ > max_first_derivative_pos_)
  {
    throw std::invalid_argument("Invalid first derivative limits");
  }
  if (has_first_derivative_limits_)
  {
    if (std::isnan(max_first_derivative_neg_))
    {
      max_first_derivative_neg_ = max_first_derivative_pos_;
    }
    if (std::isnan(min_first_derivative_pos_))
    {
      min_first_derivative_pos_ = min_first_derivative_neg_;
    }
    if (has_first_derivative_limits_ && min_first_derivative_pos_ > max_first_derivative_neg_)
    {
      throw std::invalid_argument("Invalid first derivative limits");
    }
  }

  if (std::isnan(max_second_derivative_))
  {
    has_second_derivative_limits_ = false;
  }
  if (std::isnan(min_second_derivative_))
  {
    min_second_derivative_ = -max_second_derivative_;
  }
  if (has_second_derivative_limits_ && min_second_derivative_ > max_second_derivative_)
  {
    throw std::invalid_argument("Invalid second derivative limits");
  }
}

template <typename T>
T RateLimiter<T>::limit(T & v, T v0, T v1, T dt)
{
  const T tmp = v;

  limit_second_derivative(v, v0, v1, dt);
  limit_first_derivative(v, v0, dt);
  limit_value(v);

  return tmp != static_cast<T>(0.0) ? v / tmp : static_cast<T>(1.0);
}

template <typename T>
T RateLimiter<T>::limit_value(T & v)
{
  const T tmp = v;

  if (has_value_limits_)
  {
    v = std::clamp(v, min_value_, max_value_);
  }

  return tmp != static_cast<T>(0.0) ? v / tmp : static_cast<T>(1.0);
}

template <typename T>
T RateLimiter<T>::limit_first_derivative(T & v, T v0, T dt)
{
  const T tmp = v;

  if (has_first_derivative_limits_)
  {
    T dv_max, dv_min = 0;
    if (v0 > static_cast<T>(0.0))
    {
      dv_max = max_first_derivative_pos_ * dt;
      dv_min = min_first_derivative_pos_ * dt;
    }
    else if (v0 < static_cast<T>(0.0))
    {
      dv_min = min_first_derivative_neg_ * dt;
      dv_max = max_first_derivative_neg_ * dt;
    }
    else
    {
      dv_min = min_first_derivative_neg_ * dt;
      dv_max = max_first_derivative_pos_ * dt;
    }
    const T dv = std::clamp(v - v0, dv_min, dv_max);

    v = v0 + dv;
  }

  return tmp != static_cast<T>(0.0) ? v / tmp : static_cast<T>(1.0);
}

template <typename T>
T RateLimiter<T>::limit_second_derivative(T & v, T v0, T v1, T dt)
{
  const T tmp = v;

  if (has_second_derivative_limits_)
  {
    const T dv = v - v0;
    const T dv0 = v0 - v1;

    // Only limit jerk when accelerating or reverse_accelerating
    // Note: this prevents oscillating closed-loop behavior, see discussion
    // details in https://github.com/ros-controls/control_toolbox/issues/240.
    if ((dv - dv0) * (v - v0) > 0)
    {
      const T dt2 = dt * dt;

      const T da_min = min_second_derivative_ * dt2;
      const T da_max = max_second_derivative_ * dt2;

      const T da = std::clamp(dv - dv0, da_min, da_max);

      v = v0 + dv0 + da;
    }
  }

  return tmp != static_cast<T>(0.0) ? v / tmp : static_cast<T>(1.0);
}

}  // namespace control_toolbox

#endif  // CONTROL_TOOLBOX__RATE_LIMITER_HPP_