py-feedback-controller 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. py_feedback_controller-0.1.0/LICENSE +21 -0
  2. py_feedback_controller-0.1.0/MANIFEST.in +1 -0
  3. py_feedback_controller-0.1.0/PKG-INFO +5 -0
  4. py_feedback_controller-0.1.0/README.md +0 -0
  5. py_feedback_controller-0.1.0/include/pyfc/fc_exceptions.hpp +24 -0
  6. py_feedback_controller-0.1.0/include/pyfc/feedbackcontroller.hpp +36 -0
  7. py_feedback_controller-0.1.0/include/pyfc/math/fc_math.hpp +29 -0
  8. py_feedback_controller-0.1.0/include/pyfc/pid/autooptpid.hpp +58 -0
  9. py_feedback_controller-0.1.0/include/pyfc/pid/pidcontroller.hpp +52 -0
  10. py_feedback_controller-0.1.0/include/pyfc/timer/timer.hpp +19 -0
  11. py_feedback_controller-0.1.0/setup.cfg +4 -0
  12. py_feedback_controller-0.1.0/setup.py +28 -0
  13. py_feedback_controller-0.1.0/src/py_feedback_controller.egg-info/PKG-INFO +5 -0
  14. py_feedback_controller-0.1.0/src/py_feedback_controller.egg-info/SOURCES.txt +33 -0
  15. py_feedback_controller-0.1.0/src/py_feedback_controller.egg-info/dependency_links.txt +1 -0
  16. py_feedback_controller-0.1.0/src/py_feedback_controller.egg-info/top_level.txt +1 -0
  17. py_feedback_controller-0.1.0/src/pyfc/__init__.py +11 -0
  18. py_feedback_controller-0.1.0/src/pyfc/_core.cpp +58 -0
  19. py_feedback_controller-0.1.0/src/pyfc/_core.pyi +101 -0
  20. py_feedback_controller-0.1.0/src/pyfc/fc_exceptions.cpp +13 -0
  21. py_feedback_controller-0.1.0/src/pyfc/feedbackcontroller.cpp +39 -0
  22. py_feedback_controller-0.1.0/src/pyfc/math/fc_math.cpp +101 -0
  23. py_feedback_controller-0.1.0/src/pyfc/pid/__init__.py +4 -0
  24. py_feedback_controller-0.1.0/src/pyfc/pid/autooptpid.cpp +153 -0
  25. py_feedback_controller-0.1.0/src/pyfc/pid/pidcontroller.cpp +124 -0
  26. py_feedback_controller-0.1.0/src/pyfc/timer/__init__.py +4 -0
  27. py_feedback_controller-0.1.0/src/pyfc/timer/timer.cpp +23 -0
  28. py_feedback_controller-0.1.0/test/test.py +46 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrik-J
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ recursive-include include *.hpp *.h
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-feedback-controller
3
+ Version: 0.1.0
4
+ License-File: LICENSE
5
+ Dynamic: license-file
File without changes
@@ -0,0 +1,24 @@
1
+ #ifndef FC_EXCEPTIONS_HPP
2
+ #define FC_EXCEPTIONS_HPP
3
+
4
+ #include <exception>
5
+
6
+ class FeedbackControllerException : public std::exception {
7
+ public:
8
+ FeedbackControllerException(const char* msg);
9
+ virtual const char* what() const throw();
10
+
11
+ private:
12
+ const char* msg;
13
+ };
14
+
15
+ class VectorMathException : public std::exception {
16
+ public:
17
+ VectorMathException(const char* msg);
18
+ virtual const char* what() const throw();
19
+
20
+ private:
21
+ const char* msg;
22
+ };
23
+
24
+ #endif
@@ -0,0 +1,36 @@
1
+ #ifndef FEEDBACKCONTROLLER_HPP
2
+ #define FEEDBACKCONTROLLER_HPP
3
+
4
+ #include <sstream>
5
+
6
+ #include "fc_exceptions.hpp"
7
+ #include "fc_math.hpp"
8
+
9
+ enum FCType {
10
+ NotDefined,
11
+ PID,
12
+ AutoOptPID
13
+ };
14
+
15
+ class FeedbackController {
16
+ public:
17
+ FeedbackController();
18
+ FeedbackController(FCType type);
19
+ FeedbackController(FCType type, double setpoint);
20
+
21
+ virtual double requestLoop(double input) = 0;
22
+ virtual void init() = 0;
23
+
24
+ void setSetpoint(double setpoint);
25
+ double getSetpoint() const;
26
+ FCType getType() const;
27
+
28
+ friend std::ostream& operator<<(std::ostream& strm, const FeedbackController& fc);
29
+
30
+ protected:
31
+ FCType type;
32
+ double setpoint = 0.0;
33
+ bool intialized = false;
34
+ };
35
+
36
+ #endif
@@ -0,0 +1,29 @@
1
+ #ifndef FC_MATH_HPP
2
+ #define FC_MATH_HPP
3
+
4
+ #include <vector>
5
+ #include <cmath>
6
+ #include <random>
7
+
8
+ #include "fc_exceptions.hpp"
9
+
10
+ using DoubleVector = std::vector<double>;
11
+
12
+ DoubleVector operator+(DoubleVector& v1, DoubleVector& v2);
13
+ DoubleVector operator-(DoubleVector& v1, DoubleVector& v2);
14
+ double operator*(DoubleVector& v1, DoubleVector& v2);
15
+ DoubleVector operator*(DoubleVector& v, double& d);
16
+ DoubleVector operator*(double& d, DoubleVector& v);
17
+ DoubleVector operator/(DoubleVector& v, double& d);
18
+
19
+ DoubleVector& operator+=(DoubleVector& v1, DoubleVector& v2);
20
+ DoubleVector& operator-=(DoubleVector& v1, DoubleVector& v2);
21
+ DoubleVector& operator*=(DoubleVector& v, double& d);
22
+ DoubleVector& operator/=(DoubleVector& v, double& d);
23
+
24
+ double abs(DoubleVector& v);
25
+ double sign(double d);
26
+
27
+ DoubleVector randomVector(unsigned int length, double mean = 5.0, double std_dev = 2.0);
28
+
29
+ #endif
@@ -0,0 +1,58 @@
1
+ #ifndef AUTOOPTPID_HPP
2
+ #define AUTOOPTPID_HPP
3
+
4
+ #include "feedbackcontroller.hpp"
5
+ #include "pidcontroller.hpp"
6
+
7
+ class AutoOptimizingPID : public FeedbackController {
8
+ static inline const unsigned int MAX_STORED = 3;
9
+
10
+ public:
11
+ AutoOptimizingPID();
12
+ AutoOptimizingPID(DoubleVector initialParams, double setpoint, double lr = 1e-3);
13
+ AutoOptimizingPID(double setpoint, double lr = 1e-3);
14
+ ~AutoOptimizingPID();
15
+
16
+ double requestLoop(double input) override;
17
+ void init() override;
18
+
19
+ void setLearningRate(double lr);
20
+ double getLearningRate();
21
+
22
+ void setCaps(double time_integral, double derivative);
23
+ DoubleVector getCaps();
24
+
25
+ PIDController getAsPIDController();
26
+
27
+ private:
28
+ // param 0: proportional gain
29
+ // param 1: integral gain
30
+ // param 2: derivative gain
31
+ DoubleVector params;
32
+
33
+ // learning rate for gradient descent
34
+ double lr = 1e-3;
35
+
36
+ // storage for integral and derivative
37
+ double* last_points;
38
+ double* last_times;
39
+ unsigned int last_input_index = 0;
40
+
41
+ // time integral
42
+ double time_integral = 0.0;
43
+ double ti_cap = 0.0; // to prevent overshooting
44
+
45
+ // derivative
46
+ double derivative = 0.0;
47
+ double dv_cap = 0.0; // to prevent overshooting
48
+
49
+ // timer for time steps
50
+ Timer timer;
51
+
52
+ // functions
53
+ void integrate();
54
+ void differentiate();
55
+ void optimize();
56
+ };
57
+
58
+ #endif
@@ -0,0 +1,52 @@
1
+ #ifndef PIDCONTROLLER_HPP
2
+ #define PIDCONTROLLER_HPP
3
+
4
+ #include "feedbackcontroller.hpp"
5
+ #include "timer.hpp"
6
+
7
+ class PIDController : public FeedbackController {
8
+
9
+ static inline const unsigned int MAX_STORED = 5;
10
+
11
+ public:
12
+ PIDController();
13
+ PIDController(DoubleVector params, double setpoint);
14
+ ~PIDController();
15
+
16
+ double requestLoop(double input) override;
17
+ void init() override;
18
+
19
+ void setCaps(double time_integral, double derivative);
20
+ DoubleVector getCaps();
21
+
22
+ void setGains(DoubleVector gains);
23
+ DoubleVector getGains();
24
+
25
+ private:
26
+ // param 0: proportional gain
27
+ // param 1: integral gain
28
+ // param 2: derivative gain
29
+ DoubleVector params;
30
+
31
+ // storage for integral and derivative
32
+ double* last_points;
33
+ double* last_times;
34
+ unsigned int last_input_index = 0;
35
+
36
+ // time integral
37
+ double time_integral = 0.0;
38
+ double ti_cap = 0.0; // to prevent overshooting
39
+
40
+ // derivative
41
+ double derivative = 0.0;
42
+ double dv_cap = 0.0; // to prevent overshooting
43
+
44
+ // timer for time steps
45
+ Timer timer;
46
+
47
+ // functions
48
+ void integrate();
49
+ void differentiate();
50
+ };
51
+
52
+ #endif
@@ -0,0 +1,19 @@
1
+ #ifndef TIMER_HPP
2
+ #define TIMER_HPP
3
+
4
+ #include <chrono>
5
+
6
+ class Timer {
7
+ public:
8
+ Timer();
9
+
10
+ void start();
11
+ uint64_t currentMillis();
12
+ uint64_t currentMicros();
13
+
14
+ private:
15
+ std::chrono::steady_clock::time_point start_time;
16
+
17
+ };
18
+
19
+ #endif
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ from pybind11.setup_helpers import Pybind11Extension, build_ext
2
+ from setuptools import setup, find_packages
3
+
4
+ ext_modules = [
5
+ Pybind11Extension("pyfc._core",
6
+ ["src/pyfc/fc_exceptions.cpp",
7
+ "src/pyfc/feedbackcontroller.cpp",
8
+ "src/pyfc/math/fc_math.cpp",
9
+ "src/pyfc/pid/pidcontroller.cpp",
10
+ "src/pyfc/pid/autooptpid.cpp",
11
+ "src/pyfc/timer/timer.cpp",
12
+ "src/pyfc/_core.cpp"],
13
+ include_dirs=[
14
+ "include",
15
+ "include/pyfc",
16
+ "include/pyfc/math",
17
+ "include/pyfc/timer",
18
+ "include/pyfc/pid"],
19
+ cxx_std=17),
20
+ ]
21
+
22
+ setup(
23
+ name="py-feedback-controller",
24
+ version="0.1.0",
25
+ packages=find_packages(where="src"),
26
+ package_dir={"": "src"},
27
+ ext_modules=ext_modules,
28
+ cmdclass={"build_ext": build_ext})
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-feedback-controller
3
+ Version: 0.1.0
4
+ License-File: LICENSE
5
+ Dynamic: license-file
@@ -0,0 +1,33 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ setup.py
5
+ include/pyfc/fc_exceptions.hpp
6
+ include/pyfc/feedbackcontroller.hpp
7
+ include/pyfc/math/fc_math.hpp
8
+ include/pyfc/pid/autooptpid.hpp
9
+ include/pyfc/pid/pidcontroller.hpp
10
+ include/pyfc/timer/timer.hpp
11
+ src/pyfc/_core.cpp
12
+ src/pyfc/fc_exceptions.cpp
13
+ src/pyfc/feedbackcontroller.cpp
14
+ src/pyfc/math/fc_math.cpp
15
+ src/pyfc/pid/autooptpid.cpp
16
+ src/pyfc/pid/pidcontroller.cpp
17
+ src/pyfc/timer/timer.cpp
18
+ src/py_feedback_controller.egg-info/PKG-INFO
19
+ src/py_feedback_controller.egg-info/SOURCES.txt
20
+ src/py_feedback_controller.egg-info/dependency_links.txt
21
+ src/py_feedback_controller.egg-info/top_level.txt
22
+ src/pyfc/__init__.py
23
+ src/pyfc/_core.cpp
24
+ src/pyfc/_core.pyi
25
+ src/pyfc/fc_exceptions.cpp
26
+ src/pyfc/feedbackcontroller.cpp
27
+ src/pyfc/math/fc_math.cpp
28
+ src/pyfc/pid/__init__.py
29
+ src/pyfc/pid/autooptpid.cpp
30
+ src/pyfc/pid/pidcontroller.cpp
31
+ src/pyfc/timer/__init__.py
32
+ src/pyfc/timer/timer.cpp
33
+ test/test.py
@@ -0,0 +1,11 @@
1
+ from pyfc import pid, timer
2
+ from pyfc._core import VectorMathException, FeedbackControllerException
3
+
4
+ __all__ = [
5
+ "pid",
6
+ "timer",
7
+ "VectorMathException",
8
+ "FeedbackControllerException"
9
+ ]
10
+
11
+ __version__ = "0.1.0"
@@ -0,0 +1,58 @@
1
+ #include <pybind11/pybind11.h>
2
+ #include <pybind11/stl.h>
3
+
4
+ #include "feedbackcontroller.hpp"
5
+ #include "timer/timer.hpp"
6
+ #include "pid/pidcontroller.hpp"
7
+ #include "pid/autooptpid.hpp"
8
+ #include "fc_exceptions.hpp"
9
+
10
+ namespace py = pybind11;
11
+
12
+ PYBIND11_MODULE(_core, m) {
13
+ m.doc() = "Bindings for the PyFC module.";
14
+
15
+ // exceptions
16
+ static py::exception<FeedbackControllerException> fc_exc(m, "FeedbackControllerException");
17
+ static py::exception<VectorMathException> vm_exc(m, "VectorMathException");
18
+
19
+
20
+ // Timer class
21
+ py::class_<Timer>(m, "Timer")
22
+ .def(py::init(), "Create an instance of the Timer class")
23
+ .def("start", &Timer::start, "Set the start time of the timer.")
24
+ .def("millis", &Timer::currentMillis, "Get the milliseconds since the timer was started")
25
+ .def("micros", &Timer::currentMicros, "Get the microseconds since the timer was started");
26
+
27
+ // base FeedbackController class
28
+ py::class_<FeedbackController>(m, "FeedbackController")
29
+ .def("setSetpoint", &FeedbackController::setSetpoint, py::arg("setpoint"), "Set the setpoint of the feedback controller.")
30
+ .def("getSetpoint", &FeedbackController::getSetpoint, "Get the setpoint of the feedback controller.")
31
+ .def("__repr__", [](const FeedbackController &self) {
32
+ std::ostringstream oss;
33
+ oss << self;
34
+ return oss.str();
35
+ });
36
+
37
+ // PIDController class
38
+ py::class_<PIDController, FeedbackController>(m, "PIDController")
39
+ .def(py::init<DoubleVector, double>(), py::arg("params"), py::arg("setpoint"))
40
+ .def("requestLoop", &PIDController::requestLoop, py::arg("input"), "Request a single loop of the PID controller.")
41
+ .def("init", &PIDController::init, "Initialize the PID controller")
42
+ .def("setCaps", &PIDController::setCaps, py::arg("time_integral"), py::arg("derivative"), "Set the upper and lower caps of the integral and derivative.")
43
+ .def("getCaps", &PIDController::getCaps, "Get the upper and lower caps of the integral and derivative.")
44
+ .def("setGains", &PIDController::setGains, py::arg("gains"),"Set the parameter gains of the PID controller.")
45
+ .def("getGains", &PIDController::getGains, "Get the parameter gains of the PID controller.");
46
+
47
+ // AutoOptimizingPID class
48
+ py::class_<AutoOptimizingPID, FeedbackController>(m, "AutoOptimizingPID")
49
+ .def(py::init<DoubleVector, double, double>(), py::arg("params"), py::arg("setpoint"), py::arg("lr") = 1e-3)
50
+ .def(py::init<double, double>(), py::arg("setpoint"), py::arg("lr") = 1e-3)
51
+ .def("requestLoop", &AutoOptimizingPID::requestLoop, py::arg("input"), "Request a single loop of the auto-optimizing PID controller.")
52
+ .def("init", &AutoOptimizingPID::init, "Initialize the auto-optimizing PID controller")
53
+ .def("setCaps", &AutoOptimizingPID::setCaps, py::arg("time_integral"), py::arg("derivative"), "Set the upper and lower caps of the integral and derivative.")
54
+ .def("getCaps", &AutoOptimizingPID::getCaps, "Get the upper and lower caps of the integral and derivative.")
55
+ .def("setLearningRate", &AutoOptimizingPID::setLearningRate, py::arg("lr"), "Set the learning rate of the gradient descent algorithm.")
56
+ .def("getLearningRate", &AutoOptimizingPID::getLearningRate, "Get the learning rate of the gradient descent algorithm.")
57
+ .def("getAsPID", &AutoOptimizingPID::getAsPIDController, "Get the auto-optimizing PID controller as a simple PID controller.");
58
+ };
@@ -0,0 +1,101 @@
1
+ """
2
+ Bindings for the PyFC module.
3
+ """
4
+ from __future__ import annotations
5
+ import collections.abc
6
+ import typing
7
+ __all__: list[str] = ['AutoOptimizingPID', 'FeedbackController', 'FeedbackControllerException', 'PIDController', 'Timer', 'VectorMathException']
8
+ class AutoOptimizingPID(FeedbackController):
9
+ @typing.overload
10
+ def __init__(self, params: collections.abc.Sequence[typing.SupportsFloat | typing.SupportsIndex], setpoint: typing.SupportsFloat | typing.SupportsIndex, lr: typing.SupportsFloat | typing.SupportsIndex = 0.001) -> None:
11
+ ...
12
+ @typing.overload
13
+ def __init__(self, setpoint: typing.SupportsFloat | typing.SupportsIndex, lr: typing.SupportsFloat | typing.SupportsIndex = 0.001) -> None:
14
+ ...
15
+ def getAsPID(self) -> PIDController:
16
+ """
17
+ Get the auto-optimizing PID controller as a simple PID controller.
18
+ """
19
+ def getCaps(self) -> list[float]:
20
+ """
21
+ Get the upper and lower caps of the integral and derivative.
22
+ """
23
+ def getLearningRate(self) -> float:
24
+ """
25
+ Get the learning rate of the gradient descent algorithm.
26
+ """
27
+ def init(self) -> None:
28
+ """
29
+ Initialize the auto-optimizing PID controller
30
+ """
31
+ def requestLoop(self, input: typing.SupportsFloat | typing.SupportsIndex) -> float:
32
+ """
33
+ Request a single loop of the auto-optimizing PID controller.
34
+ """
35
+ def setCaps(self, time_integral: typing.SupportsFloat | typing.SupportsIndex, derivative: typing.SupportsFloat | typing.SupportsIndex) -> None:
36
+ """
37
+ Set the upper and lower caps of the integral and derivative.
38
+ """
39
+ def setLearningRate(self, lr: typing.SupportsFloat | typing.SupportsIndex) -> None:
40
+ """
41
+ Set the learning rate of the gradient descent algorithm.
42
+ """
43
+ class FeedbackController:
44
+ def __repr__(self) -> str:
45
+ ...
46
+ def getSetpoint(self) -> float:
47
+ """
48
+ Get the setpoint of the feedback controller.
49
+ """
50
+ def setSetpoint(self, setpoint: typing.SupportsFloat | typing.SupportsIndex) -> None:
51
+ """
52
+ Set the setpoint of the feedback controller.
53
+ """
54
+ class FeedbackControllerException(Exception):
55
+ pass
56
+ class PIDController(FeedbackController):
57
+ def __init__(self, params: collections.abc.Sequence[typing.SupportsFloat | typing.SupportsIndex], setpoint: typing.SupportsFloat | typing.SupportsIndex) -> None:
58
+ ...
59
+ def getCaps(self) -> list[float]:
60
+ """
61
+ Get the upper and lower caps of the integral and derivative.
62
+ """
63
+ def getGains(self) -> list[float]:
64
+ """
65
+ Get the parameter gains of the PID controller.
66
+ """
67
+ def init(self) -> None:
68
+ """
69
+ Initialize the PID controller
70
+ """
71
+ def requestLoop(self, input: typing.SupportsFloat | typing.SupportsIndex) -> float:
72
+ """
73
+ Request a single loop of the PID controller.
74
+ """
75
+ def setCaps(self, time_integral: typing.SupportsFloat | typing.SupportsIndex, derivative: typing.SupportsFloat | typing.SupportsIndex) -> None:
76
+ """
77
+ Set the upper and lower caps of the integral and derivative.
78
+ """
79
+ def setGains(self, gains: collections.abc.Sequence[typing.SupportsFloat | typing.SupportsIndex]) -> None:
80
+ """
81
+ Set the parameter gains of the PID controller.
82
+ """
83
+ class Timer:
84
+ def __init__(self) -> None:
85
+ """
86
+ Create an instance of the Timer class
87
+ """
88
+ def micros(self) -> int:
89
+ """
90
+ Get the microseconds since the timer was started
91
+ """
92
+ def millis(self) -> int:
93
+ """
94
+ Get the milliseconds since the timer was started
95
+ """
96
+ def start(self) -> None:
97
+ """
98
+ Set the start time of the timer.
99
+ """
100
+ class VectorMathException(Exception):
101
+ pass
@@ -0,0 +1,13 @@
1
+ #include "fc_exceptions.hpp"
2
+
3
+ FeedbackControllerException::FeedbackControllerException(const char* msg) : msg(msg) {};
4
+
5
+ const char* FeedbackControllerException::what() const throw() {
6
+ return this->msg;
7
+ };
8
+
9
+ VectorMathException::VectorMathException(const char* msg) : msg(msg) {};
10
+
11
+ const char* VectorMathException::what() const throw() {
12
+ return this->msg;
13
+ };
@@ -0,0 +1,39 @@
1
+ #include "feedbackcontroller.hpp"
2
+
3
+ FeedbackController::FeedbackController() : type(FCType::NotDefined) {};
4
+
5
+ FeedbackController::FeedbackController(FCType type) : type(type) {};
6
+
7
+ FeedbackController::FeedbackController(FCType type, double setpoint) : type(type), setpoint(setpoint) {};
8
+
9
+ void FeedbackController::setSetpoint(double setpoint) {
10
+ this->setpoint = setpoint;
11
+ };
12
+
13
+ double FeedbackController::getSetpoint() const {
14
+ return this->setpoint;
15
+ };
16
+
17
+ FCType FeedbackController::getType() const {
18
+ return this->type;
19
+ };
20
+
21
+ std::ostream& operator<<(std::ostream& strm, const FeedbackController& fc) {
22
+ strm << "Feedback controller of type ";
23
+ switch (fc.getType()) {
24
+ case FCType::PID:
25
+ strm << "'PID'";
26
+ break;
27
+ case FCType::AutoOptPID:
28
+ strm << "'Auto-Optimizing PID'";
29
+ break;
30
+ case FCType::NotDefined:
31
+ strm << "'Undefined'";
32
+ break;
33
+ default:
34
+ strm << "'Unknown'";
35
+ break;
36
+ }
37
+ strm << " with setpoint " << fc.getSetpoint() << std::endl;
38
+ return strm;
39
+ };
@@ -0,0 +1,101 @@
1
+ #include "fc_math.hpp"
2
+
3
+ DoubleVector operator+(DoubleVector& v1, DoubleVector& v2) {
4
+ if (v1.size() != v2.size())
5
+ throw VectorMathException("Vector of unequal size cannot be used in a mathematical operation!");
6
+ DoubleVector v;
7
+ for (unsigned int i = 0; i < v1.size(); i++)
8
+ v.push_back(v1[0] + v2[0]);
9
+ return v;
10
+ };
11
+
12
+ DoubleVector operator-(DoubleVector& v1, DoubleVector& v2) {
13
+ if (v1.size() != v2.size())
14
+ throw VectorMathException("Vector of unequal size cannot be used in a mathematical operation!");
15
+ DoubleVector v;
16
+ for (unsigned int i = 0; i < v1.size(); i++)
17
+ v.push_back(v1[0] - v2[0]);
18
+ return v;
19
+ };
20
+
21
+ double operator*(DoubleVector& v1, DoubleVector& v2) {
22
+ if (v1.size() != v2.size())
23
+ throw VectorMathException("Vector of unequal size cannot be used in a mathematical operation!");
24
+ double scalar = 0.0;
25
+ for (unsigned int i = 0; i < v1.size(); i++)
26
+ scalar += v1[0] + v2[0];
27
+ return scalar;
28
+ };
29
+
30
+ DoubleVector operator*(DoubleVector& v, double& d) {
31
+ DoubleVector v_scaled;
32
+ for (unsigned int i = 0; i < v.size(); i++)
33
+ v_scaled.push_back(v[0] * d);
34
+ return v_scaled;
35
+ };
36
+
37
+ DoubleVector operator*(double& d, DoubleVector& v) {
38
+ return v * d;
39
+ };
40
+
41
+ DoubleVector operator/(DoubleVector& v, double& d) {
42
+ double s = 1/d;
43
+ return v * s;
44
+ };
45
+
46
+ DoubleVector& operator+=(DoubleVector& v1, DoubleVector& v2) {
47
+ if (v1.size() != v2.size())
48
+ throw VectorMathException("Vector of unequal size cannot be used in a mathematical operation!");
49
+ for (unsigned int i = 0; i < v1.size(); i++)
50
+ v1[i] += v2[i];
51
+ return v1;
52
+ };
53
+
54
+ DoubleVector& operator-=(DoubleVector& v1, DoubleVector& v2) {
55
+ if (v1.size() != v2.size())
56
+ throw VectorMathException("Vector of unequal size cannot be used in a mathematical operation!");
57
+ for (unsigned int i = 0; i < v1.size(); i++)
58
+ v1[i] -= v2[i];
59
+ return v1;
60
+ };
61
+
62
+ DoubleVector& operator*=(DoubleVector& v, double& d) {
63
+ for (unsigned int i = 0; i < v.size(); i++)
64
+ v[i] *= d;
65
+ return v;
66
+ };
67
+
68
+ DoubleVector& operator/=(DoubleVector& v, double& d) {
69
+ for (unsigned int i = 0; i < v.size(); i++)
70
+ v[i] /= d;
71
+ return v;
72
+ };
73
+
74
+ double abs(DoubleVector& v) {
75
+ double sum = 0.0;
76
+ for (unsigned int i = 0; i < v.size(); i++)
77
+ sum += std::pow(v[i], 2.0);
78
+ return std::pow(sum, 0.5);
79
+ };
80
+
81
+ double sign(double d) {
82
+ if (d >= 0.0)
83
+ return 1.0;
84
+ else
85
+ return -1.0;
86
+ };
87
+
88
+ DoubleVector randomVector(unsigned int length, double mean, double std_dev) {
89
+ DoubleVector v(length, 0.0);
90
+
91
+ std::random_device rd{};
92
+ std::mt19937 gen{rd()};
93
+ std::normal_distribution d{mean, std_dev};
94
+
95
+ auto random = [&d, &gen]{ return d(gen); };
96
+
97
+ for (unsigned int i = 0; i < length; i++)
98
+ v[i] = random();
99
+
100
+ return v;
101
+ };
@@ -0,0 +1,4 @@
1
+ from .._core import PIDController, AutoOptimizingPID
2
+
3
+ # Re-export classes
4
+ __all__ = ["PIDController", "AutoOptimizingPID"]
@@ -0,0 +1,153 @@
1
+ #include "autooptpid.hpp"
2
+
3
+ AutoOptimizingPID::AutoOptimizingPID() : FeedbackController(FCType::AutoOptPID) {};
4
+
5
+ AutoOptimizingPID::AutoOptimizingPID(DoubleVector initialParams, double setpoint, double lr) : FeedbackController(FCType::AutoOptPID, setpoint) {
6
+ this->params = initialParams;
7
+ this->lr = lr;
8
+ };
9
+
10
+ AutoOptimizingPID::AutoOptimizingPID(double setpoint, double lr) : FeedbackController(FCType::AutoOptPID, setpoint) {
11
+ this->lr = lr;
12
+ };
13
+
14
+ AutoOptimizingPID::~AutoOptimizingPID() {
15
+ delete[] this->last_points;
16
+ delete[] this->last_times;
17
+ };
18
+
19
+ double AutoOptimizingPID::requestLoop(double input) {
20
+ if (!this->intialized)
21
+ throw FeedbackControllerException("Feedback controller was not initialized! Use .init()");
22
+
23
+ // get the difference to use in the PID loop
24
+ double diff = input - this->setpoint;
25
+
26
+ // get the next index
27
+ unsigned int lii = (this->last_input_index + 1) % AutoOptimizingPID::MAX_STORED;
28
+
29
+ // store the new point
30
+ this->last_points[lii] = diff;
31
+ this->last_times[lii] = this->timer.currentMicros();
32
+ this->last_input_index = lii;
33
+
34
+ // sum of all three parts
35
+ double sum = 0.0;
36
+
37
+ // proportional
38
+ sum += this->params[0] * diff;
39
+
40
+ // integral
41
+ this->integrate();
42
+ sum += this->params[1] * this->time_integral;
43
+
44
+ // derivative
45
+ this->differentiate();
46
+ sum += this->params[2] * this->derivative;
47
+
48
+ // prior to the next step, optimize the params
49
+ this->optimize();
50
+
51
+ // return the sum
52
+ return sum;
53
+ };
54
+
55
+ void AutoOptimizingPID::init() {
56
+ this->timer = Timer();
57
+ this->timer.start();
58
+ this->last_points = new double[AutoOptimizingPID::MAX_STORED] {0.0};
59
+ this->last_times = new double[AutoOptimizingPID::MAX_STORED] {0.0};
60
+ this->last_input_index = 0;
61
+
62
+ if (this->params.size() == 0)
63
+ this->params = randomVector(3);
64
+ this->intialized = true;
65
+ };
66
+
67
+ void AutoOptimizingPID::setLearningRate(double lr) {
68
+ this->lr = lr;
69
+ };
70
+
71
+ double AutoOptimizingPID::getLearningRate() {
72
+ return this->lr;
73
+ };
74
+
75
+ void AutoOptimizingPID::setCaps(double time_integral, double derivative) {
76
+ this->ti_cap = time_integral;
77
+ this->dv_cap = derivative;
78
+ };
79
+
80
+ DoubleVector AutoOptimizingPID::getCaps() {
81
+ return {this->ti_cap, this->dv_cap};
82
+ };
83
+
84
+ PIDController AutoOptimizingPID::getAsPIDController() {
85
+ PIDController pid(this->params, this->setpoint);
86
+ return pid;
87
+ };
88
+
89
+ void AutoOptimizingPID::integrate() {
90
+ double dt = this->last_times[this->last_input_index] - this->last_times[(AutoOptimizingPID::MAX_STORED + this->last_input_index - 1) % AutoOptimizingPID::MAX_STORED];
91
+ double dI = (this->last_points[this->last_input_index] + this->last_points[(AutoOptimizingPID::MAX_STORED + this->last_input_index - 1) % AutoOptimizingPID::MAX_STORED]) / 2.0;
92
+
93
+ this->time_integral += dt * dI;
94
+
95
+ if (std::abs(this->time_integral) > this->ti_cap)
96
+ this->time_integral = sign(this->time_integral) * this->ti_cap;
97
+ };
98
+
99
+ void AutoOptimizingPID::differentiate() {
100
+ // three-point central difference formula
101
+ unsigned int lii = this->last_input_index;
102
+ unsigned int li = (AutoOptimizingPID::MAX_STORED + lii - 1) % AutoOptimizingPID::MAX_STORED;
103
+ unsigned int l = (AutoOptimizingPID::MAX_STORED + li - 1) % AutoOptimizingPID::MAX_STORED;
104
+
105
+ // t_i+1 - t-i
106
+ double h1 = this->last_times[lii] - this->last_times[li];
107
+
108
+ // t_i - t_i-1
109
+ double h2 = this->last_times[li] - this->last_times[l];
110
+
111
+ // simple fail-safe, if dt = 0
112
+ if (h1 == 0)
113
+ h1 = 1e-20;
114
+ if (h2 == 0)
115
+ h2 = 1e-20;
116
+
117
+ // y_i
118
+ double I0 = this->last_points[lii];
119
+
120
+ // y_i-1
121
+ double I1 = this->last_points[li];
122
+
123
+ // y_i-2
124
+ double I2 = this->last_points[l];
125
+
126
+ double coeff0 = (2 * h1 + h2)/(h1 * (h1 + h2));
127
+ double coeff1 = (h1 + h2)/(h1 * h2);
128
+ double coeff2 = h1/(h2 * (h1 + h2));
129
+
130
+ this->derivative = coeff0 * I0 + coeff1 * I1 + coeff2 * I2;
131
+
132
+ if (std::abs(this->derivative) > this->dv_cap)
133
+ this->derivative = sign(this->derivative) * this->dv_cap;
134
+ };
135
+
136
+ void AutoOptimizingPID::optimize() {
137
+ // determine the error between the setpoint and the input
138
+ double diff = this->last_points[this->last_input_index];
139
+ double error = 0.5*std::pow(diff, 2);
140
+
141
+ // gradient
142
+ DoubleVector grad;
143
+
144
+ // the derivatives wrt. to each parameter
145
+ grad.push_back(diff);
146
+ grad.push_back(this->time_integral);
147
+ grad.push_back(this->derivative);
148
+
149
+ grad *= diff;
150
+
151
+ // gradient descent
152
+ this->params -= this->lr * grad;
153
+ };
@@ -0,0 +1,124 @@
1
+ #include "pidcontroller.hpp"
2
+
3
+ PIDController::PIDController() : FeedbackController(FCType::PID) {};
4
+
5
+ PIDController::PIDController(DoubleVector params, double setpoint) : FeedbackController(FCType::PID, setpoint) {
6
+ this->params = params;
7
+ };
8
+
9
+ PIDController::~PIDController() {
10
+ delete[] this->last_points;
11
+ delete[] this->last_times;
12
+ };
13
+
14
+ double PIDController::requestLoop(double input) {
15
+ if (this->params.size() != 3)
16
+ throw FeedbackControllerException("PID params were not set!");
17
+
18
+ if (!this->intialized)
19
+ throw FeedbackControllerException("Feedback controller was not initialized! Use .init()");
20
+
21
+ // get the difference to use in the PID loop
22
+ double diff = input - this->setpoint;
23
+
24
+ // get the next index
25
+ unsigned int lii = (this->last_input_index + 1) % PIDController::MAX_STORED;
26
+
27
+ // store the new point
28
+ this->last_points[lii] = diff;
29
+ this->last_times[lii] = this->timer.currentMicros();
30
+ this->last_input_index = lii;
31
+
32
+ // sum of all three partsw
33
+ double sum = 0.0;
34
+
35
+ // proportional
36
+ sum += this->params[0] * diff;
37
+
38
+ // integral
39
+ this->integrate();
40
+ sum += this->params[1] * this->time_integral;
41
+
42
+ // derivative
43
+ this->differentiate();
44
+ sum += this->params[2] * this->derivative;
45
+
46
+ // return the sum
47
+ return sum;
48
+ };
49
+
50
+ void PIDController::integrate() {
51
+ double dt = this->last_times[this->last_input_index] - this->last_times[(PIDController::MAX_STORED + this->last_input_index - 1) % PIDController::MAX_STORED];
52
+ double dI = (this->last_points[this->last_input_index] + this->last_points[(PIDController::MAX_STORED + this->last_input_index - 1) % PIDController::MAX_STORED])/2.0;
53
+
54
+ this->time_integral += dt * dI;
55
+
56
+ if (abs(this->time_integral) > this->ti_cap)
57
+ this->time_integral = sign(this->time_integral) * this->ti_cap;
58
+ };
59
+
60
+ void PIDController::differentiate() {
61
+ // three-point central difference formula
62
+ unsigned int lii = this->last_input_index;
63
+ unsigned int li = (PIDController::MAX_STORED + lii - 1) % PIDController::MAX_STORED;
64
+ unsigned int l = (PIDController::MAX_STORED + li - 1) % PIDController::MAX_STORED;
65
+
66
+ // t_i+1 - t-i
67
+ double h1 = this->last_times[lii] - this->last_times[li];
68
+
69
+ // t_i - t_i-1
70
+ double h2 = this->last_times[li] - this->last_times[l];
71
+
72
+ // simple fail-safe, if dt = 0
73
+ if (h1 == 0)
74
+ h1 = 1e-20;
75
+ if (h2 == 0)
76
+ h2 = 1e-20;
77
+
78
+ // y_i
79
+ double I0 = this->last_points[lii];
80
+
81
+ // y_i-1
82
+ double I1 = this->last_points[li];
83
+
84
+ // y_i-2
85
+ double I2 = this->last_points[l];
86
+
87
+ double coeff0 = (2 * h1 + h2)/(h1 * (h1 + h2));
88
+ double coeff1 = (h1 + h2)/(h1 * h2);
89
+ double coeff2 = h1/(h2 * (h1 + h2));
90
+
91
+ this->derivative = coeff0 * I0 + coeff1 * I1 + coeff2 * I2;
92
+
93
+ if (abs(this->derivative) > this->dv_cap)
94
+ this->derivative = sign(this->derivative) * this->dv_cap;
95
+ };
96
+
97
+ void PIDController::init() {
98
+ this->timer = Timer();
99
+ this->timer.start();
100
+ this->last_points = new double[PIDController::MAX_STORED] {0.0};
101
+ this->last_times = new double[PIDController::MAX_STORED] {0.0};
102
+ this->last_input_index = 0;
103
+
104
+ if (this->params.size() == 0)
105
+ this->params = randomVector(3);
106
+ this->intialized = true;
107
+ };
108
+
109
+ void PIDController::setCaps(double time_integral, double derivative) {
110
+ this->ti_cap = time_integral;
111
+ this->dv_cap = derivative;
112
+ };
113
+
114
+ DoubleVector PIDController::getCaps() {
115
+ return {this->ti_cap, this->dv_cap};
116
+ };
117
+
118
+ void PIDController::setGains(DoubleVector gains) {
119
+ this->params = gains;
120
+ };
121
+
122
+ DoubleVector PIDController::getGains() {
123
+ return this->params;
124
+ };
@@ -0,0 +1,4 @@
1
+ from .._core import Timer
2
+
3
+ # Re-export classes
4
+ __all__ = ["Timer"]
@@ -0,0 +1,23 @@
1
+ #include "timer.hpp"
2
+
3
+ Timer::Timer() {};
4
+
5
+ void Timer::start() {
6
+ this->start_time = std::chrono::steady_clock::now();
7
+ };
8
+
9
+ uint64_t Timer::currentMicros() {
10
+ const auto now = std::chrono::steady_clock::now();
11
+
12
+ auto diff = std::chrono::duration_cast<std::chrono::microseconds>(now - this->start_time);
13
+
14
+ return diff.count();
15
+ };
16
+
17
+ uint64_t Timer::currentMillis() {
18
+ const auto now = std::chrono::steady_clock::now();
19
+
20
+ auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - this->start_time);
21
+
22
+ return diff.count();
23
+ };
@@ -0,0 +1,46 @@
1
+ from pyfc.pid import AutoOptimizingPID
2
+
3
+ from matplotlib.animation import FuncAnimation
4
+ from matplotlib import pyplot as plt
5
+
6
+ import numpy as np
7
+
8
+ setpoint = 1.0
9
+
10
+ pid = AutoOptimizingPID(setpoint=setpoint, lr=1.0)
11
+ pid.init()
12
+
13
+ z = [0.0]
14
+ t = [0.0]
15
+ dt = 1e-3
16
+
17
+ fig, ax = plt.subplots()
18
+ line = ax.plot(t, z)[0]
19
+
20
+ def func(frame):
21
+ global setpoint
22
+ if (frame % 500) == 0:
23
+ if setpoint == 1.0:
24
+ setpoint = 2.0
25
+ elif setpoint == 2.0:
26
+ setpoint = 1.5
27
+ elif setpoint == 1.5:
28
+ setpoint = 2.5
29
+ elif setpoint == 2.5:
30
+ setpoint = 1.0
31
+
32
+ pid.setSetpoint(setpoint)
33
+
34
+ pid_out = pid.requestLoop(z[frame-1])
35
+
36
+ z.append(z[frame-1] + dt * pid_out)
37
+ t.append(t[frame-1] + dt)
38
+
39
+ line.set_xdata(t)
40
+ line.set_ydata(z)
41
+ ax.set_ylim(-0.1, 1.2*np.max(z))
42
+ ax.set_xlim(0, 1.2*np.max(t))
43
+
44
+ anim = FuncAnimation(fig, func, frames=10000, interval=1)
45
+ plt.grid()
46
+ plt.show()