eg_plds_ctrl.cpp

eg_plds_ctrl.cpp #

Example PLDS Control

//===-- eg_plds_ctrl.cpp - Example PLDS Control ---------------------===//
//
// Copyright 2021 Michael Bolus
// Copyright 2021 Georgia Institute of Technology
//
// 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.
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//

#include <ldsCtrlEst>

using lds::Matrix;
using lds::Vector;
using lds::data_t;
using std::cout;

auto main() -> int {
  cout << " ********** Example Poisson LDS Control ********** \n\n";

  // Make SISO system sampled at 1kHz
  data_t dt = 1e-3;
  size_t n_u = 1;
  size_t n_x = 1;
  size_t n_y = 1;

  // no time steps for simulation.
  auto n_t = static_cast<size_t>(10.0 / dt);

  // Control variables: _reference/target output, controller gains
  // n.b., Can either use Vector (arma::Col) or std::vector
  Vector y_ref0 = Vector(n_y, arma::fill::ones) * 30.0 * dt;
  Matrix k_x =
      Matrix(n_u, n_x, arma::fill::zeros) + 1;  // gains on state error
  Matrix k_inty = Matrix(n_u, n_y, arma::fill::zeros) +
                   10;  // gains on integrated output err

  // Set control type bit mask, so controller knows what to do
  size_t control_type = lds::kControlTypeIntY;  // integral action

  // Ground-truth parameters for the controlled system
  // (stand-in for physical system to be controlled)
  Matrix a_true(n_x, n_x, arma::fill::eye);
  a_true[0] = 0.986;
  Matrix b_true(n_x, n_u, arma::fill::zeros);
  b_true[0] = 0.054;
  Vector x0_true = Vector(n_x, arma::fill::ones) * log(1 * dt);

  size_t which_m = 0;
  data_t m_low = log(1 * dt) * (1 - a_true[0]);
  data_t pr_lo2hi = 1e-3;
  data_t m_high = log(20 * dt) * (1 - a_true[0]);
  data_t pr_hi2lo = pr_lo2hi;

  Vector m0_true = Vector(n_x, arma::fill::ones) * m_low;
  // construct ground truth system to be controlled...
  lds::poisson::System controlled_system(n_u, n_x, n_y, dt);

  // Assign params.
  controlled_system.set_A(a_true);
  controlled_system.set_B(b_true);
  controlled_system.set_m(m0_true);
  controlled_system.set_x0(x0_true);
  // reset to initial conditions
  controlled_system.Reset();

  cout << ".....................................\n";
  cout << "controlled_system:\n";
  cout << ".....................................\n";
  controlled_system.Print();
  cout << ".....................................\n";

  // Create the controller
  lds::poisson::Controller controller;
  {
    // Create model used for control.
    lds::poisson::System controller_system(controlled_system);

    // for this example, assume model correct, except disturbance
    Vector m0_controller = Vector(n_x, arma::fill::ones) * m_low;
    Vector x0_controller = arma::log(y_ref0);
    controller_system.set_m(m0_controller);
    controller_system.set_x0(x0_controller);
    controller_system.Reset(); //reset to new init condition

    // adaptively re-estimate process disturbance (m)
    controller_system.do_adapt_m = true;

    // set adaptation rate by changing covariance of assumed process noise
    // acting on random-walk evolution of m
    Matrix q_m = Matrix(n_x, n_x, arma::fill::eye) * 1e-5;
    controller_system.set_Q_m(q_m);

    data_t u_lb = 0.0;
    data_t u_ub = 5.0;
    controller = std::move(
        lds::poisson::Controller(std::move(controller_system), u_lb, u_ub));
  }
  // set controller type
  controller.set_control_type(control_type);

  // set controller gains
  controller.set_Kc(k_x);
  controller.set_Kc_inty(k_inty);

  // to protect against integral windup when output is consistently above
  // target:
  data_t tau_awu(0.1);
  controller.set_tau_awu(tau_awu);

  cout << ".....................................\n";
  cout << "controller:\n";
  cout << ".....................................\n";
  controller.Print();
  cout << ".....................................\n";

  // create Matrix to save outputs in...
  Matrix y_ref = Matrix(n_y, n_t, arma::fill::zeros);
  y_ref.each_col() += y_ref0;

  // Simulated measurements
  Matrix z(n_y, n_t, arma::fill::zeros);

  // simulated control signal ([=] V)
  Matrix u(n_u, n_t, arma::fill::zeros);

  // outputs, states and gain/disturbance params
  // *_hat indicates online estimates
  Matrix y_hat(n_y, n_t, arma::fill::zeros);
  Matrix x_hat(n_x, n_t, arma::fill::zeros);
  Matrix m_hat(n_y, n_t, arma::fill::zeros);

  // *_true indicates ground truth (system being controlled)
  Matrix y_true(n_y, n_t, arma::fill::zeros);
  Matrix x_true(n_x, n_t, arma::fill::zeros);
  Matrix m_true(n_y, n_t, arma::fill::zeros);

  // set initial val
  y_hat.col(0) = controller.sys().y();
  y_true.col(0) = controlled_system.y();

  x_hat.col(0) = controller.sys().x();
  x_true.col(0) = controlled_system.x();

  m_hat.col(0) = controller.sys().m();
  m_true.col(0) = controlled_system.m();

  cout << "Starting " << n_t * dt << " sec simulation ... \n";
  auto start = std::chrono::high_resolution_clock::now();
  for (size_t t = 1; t < n_t; t++) {
    // simulate a stochastically switched disturbance
    Vector chance = arma::randu<Vector>(1);
    if (which_m == 0)  // low disturbance
    {
      if (chance[0] < pr_lo2hi) {  // switches low -> high disturbance
        m0_true = std::vector<data_t>(n_x, m_high);
        which_m = 1;
      }
    } else {                       // high disturbance
      if (chance[0] < pr_hi2lo) {  // swithces high -> low disturbance
        m0_true = std::vector<data_t>(n_x, m_low);
        which_m = 0;
      }
    }
    controlled_system.set_m(m0_true);

    // e.g., use sinusoidal reference
    data_t f = 0.5;  // freq [=] Hz
    Vector t_vec = Vector(n_y, arma::fill::ones) * t;
    y_ref.col(t) +=
        y_ref0 % arma::sin(f * 2 * lds::kPi * dt * t_vec - lds::kPi / 4);

    // Simulate the true system.
    z.col(t)=controlled_system.Simulate(u.col(t-1));

    // This method uses a steady-state solution to control problem to calculate
    // x_ref, u_ref from reference output y_ref. Notably, it does this in the
    // log-linear space (i.e., log(y)).
    //
    // Therefore, it is only applicable to regulation problems or cases where
    // reference trajectory changes slowly compared to system dynamics.
    controller.set_y_ref(y_ref.col(t));
    u.col(t)=controller.ControlOutputReference(z.col(t));

    y_true.col(t) = controlled_system.y();
    x_true.col(t) = controlled_system.x();
    m_true.col(t) = controlled_system.m();

    y_hat.col(t) = controller.sys().y();
    x_hat.col(t) = controller.sys().x();
    m_hat.col(t) = controller.sys().m();
  }

  auto finish = std::chrono::high_resolution_clock::now();
  std::chrono::duration<data_t, std::milli> sim_time_ms = finish - start;
  cout << "Finished simulation in " << sim_time_ms.count() << " ms.\n";
  cout << "(app. " << (sim_time_ms.count() / n_t) * 1e3 << " us/time-step)\n";

  // saved variables: dt, y_hat, x_hat, m_hat, z, u, y_ref, y_true,
  // x_true, m_true saving with hdf5 via armadillo
  arma::hdf5_opts::opts replace = arma::hdf5_opts::replace;

  auto dt_vec = Vector(1).fill(dt);
  dt_vec.save(arma::hdf5_name("eg_plds_ctrl.h5", "dt"));
  y_ref.save(arma::hdf5_name("eg_plds_ctrl.h5", "y_ref", replace));
  u.save(arma::hdf5_name("eg_plds_ctrl.h5", "u", replace));
  z.save(arma::hdf5_name("eg_plds_ctrl.h5", "z", replace));
  x_true.save(arma::hdf5_name("eg_plds_ctrl.h5", "x_true", replace));
  m_true.save(arma::hdf5_name("eg_plds_ctrl.h5", "m_true", replace));
  y_true.save(arma::hdf5_name("eg_plds_ctrl.h5", "y_true", replace));
  x_hat.save(arma::hdf5_name("eg_plds_ctrl.h5", "x_hat", replace));
  m_hat.save(arma::hdf5_name("eg_plds_ctrl.h5", "m_hat", replace));
  y_hat.save(arma::hdf5_name("eg_plds_ctrl.h5", "y_hat", replace));

  return 0;
}

Updated on 19 May 2022 at 17:16:05 Eastern Daylight Time