From b4b6d23227dfa99df4d7ee6b6eef04513abf279f Mon Sep 17 00:00:00 2001 From: LolaSegura <48759425+LolaSegura@users.noreply.github.com> Date: Fri, 3 Sep 2021 14:14:53 -0300 Subject: [PATCH] Adds python interface to PID and SemanticVersion. (#229) Signed-off-by: LolaSegura Co-authored-by: Louise Poubel --- src/python/CMakeLists.txt | 4 + src/python/PID.i | 88 +++++++++ src/python/PID_TEST.py | 305 +++++++++++++++++++++++++++++ src/python/SemanticVersion.i | 64 ++++++ src/python/SemanticVersion_TEST.py | 166 ++++++++++++++++ src/python/python.i | 2 + 6 files changed, 629 insertions(+) create mode 100644 src/python/PID.i create mode 100644 src/python/PID_TEST.py create mode 100644 src/python/SemanticVersion.i create mode 100644 src/python/SemanticVersion_TEST.py diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index be294b52d..0be042f58 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -17,7 +17,9 @@ if (SWIG_FOUND) set(swig_files Angle GaussMarkovProcess + PID Rand + SemanticVersion Vector2 Vector3 Vector4) @@ -72,8 +74,10 @@ if (PYTHONLIBS_FOUND) GaussMarkovProcess_TEST Line2_TEST Line3_TEST + PID_TEST python_TEST Rand_TEST + SemanticVersion_TEST SignalStats_TEST Vector2_TEST Vector3_TEST diff --git a/src/python/PID.i b/src/python/PID.i new file mode 100644 index 000000000..452e73045 --- /dev/null +++ b/src/python/PID.i @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * 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. + * +*/ +%module pid +%{ + #include +%} + +%include "typemaps.i" +%apply double *OUTPUT { double &_pe, double &_ie, double &_de }; + +namespace ignition +{ + namespace math + { + class PID + { + %rename("%(undercase)s", %$isfunction, %$ismember, %$not %$isconstructor) ""; + public: PID(const double _p = 0.0, + const double _i = 0.0, + const double _d = 0.0, + const double _imax = -1.0, + const double _imin = 0.0, + const double _cmdMax = -1.0, + const double _cmdMin = 0.0, + const double _cmdOffset = 0.0); + public: PID(const PID& pid) = default; + public: ~PID() = default; + public: void Init(const double _p = 0.0, + const double _i = 0.0, + const double _d = 0.0, + const double _imax = -1.0, + const double _imin = 0.0, + const double _cmdMax = -1.0, + const double _cmdMin = 0.0, + const double _cmdOffset = 0.0); + %rename(set_p_gain) SetPGain; + public: void SetPGain(const double _p); + %rename(set_i_gain) SetIGain; + public: void SetIGain(const double _i); + %rename(set_d_gain) SetDGain; + public: void SetDGain(const double _d); + %rename(set_i_max) SetIMax; + public: void SetIMax(const double _i); + %rename(set_i_min) SetIMin; + public: void SetIMin(const double _i); + public: void SetCmdMax(const double _c); + public: void SetCmdMin(const double _c); + public: void SetCmdOffset(const double _c); + %rename(p_gain) PGain; + public: double PGain() const; + %rename(i_gain) IGain; + public: double IGain() const; + %rename(d_gain) DGain; + public: double DGain() const; + %rename(i_max) IMax; + public: double IMax() const; + %rename(i_min) IMin; + public: double IMin() const; + public: double CmdMax() const; + public: double CmdMin() const; + public: double CmdOffset() const; + public: void SetCmd(const double _cmd); + public: double Cmd() const; + public: void Errors(double &_pe, double &_ie, double &_de) const; + public: void Reset(); + }; + + %extend PID { + double Update(const double error, const double dt) { + return (*$self).Update(error, std::chrono::duration(dt)); + } + } + } +} diff --git a/src/python/PID_TEST.py b/src/python/PID_TEST.py new file mode 100644 index 000000000..362397eff --- /dev/null +++ b/src/python/PID_TEST.py @@ -0,0 +1,305 @@ +# Copyright (C) 2021 Open Source Robotics Foundation +# +# 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. + +import unittest +from ignition.math import PID + + +class TestPID(unittest.TestCase): + + def test_constructor(self): + pid = PID() + + self.assertAlmostEqual(0.0, pid.p_gain()) + self.assertAlmostEqual(0.0, pid.i_gain()) + self.assertAlmostEqual(0.0, pid.d_gain()) + self.assertAlmostEqual(-1.0, pid.i_max()) + self.assertAlmostEqual(0.0, pid.i_min()) + self.assertAlmostEqual(-1.0, pid.cmd_max()) + self.assertAlmostEqual(0.0, pid.cmd_min()) + self.assertAlmostEqual(0.0, pid.cmd_offset()) + self.assertAlmostEqual(0.0, pid.cmd()) + + [pe, ie, de] = pid.errors() + self.assertAlmostEqual(pe, 0.0) + self.assertAlmostEqual(ie, 0.0) + self.assertAlmostEqual(de, 0.0) + + def test_set_values(self): + pid2 = PID(1.0, 2.1, -4.5, 10.5, 1.4, 45, -35, 1.3) + self.assertAlmostEqual(1.0, pid2.p_gain()) + self.assertAlmostEqual(2.1, pid2.i_gain()) + self.assertAlmostEqual(-4.5, pid2.d_gain()) + self.assertAlmostEqual(10.5, pid2.i_max()) + self.assertAlmostEqual(1.4, pid2.i_min()) + self.assertAlmostEqual(45, pid2.cmd_max()) + self.assertAlmostEqual(-35, pid2.cmd_min()) + self.assertAlmostEqual(1.3, pid2.cmd_offset()) + self.assertAlmostEqual(0.0, pid2.cmd()) + + # Test set*() functions + cmd = 10.4 + pid = PID() + pid.set_p_gain(pid2.p_gain()) + pid.set_i_gain(pid2.i_gain()) + pid.set_d_gain(pid2.d_gain()) + pid.set_i_max(pid2.i_max()) + pid.set_i_min(pid2.i_min()) + pid.set_cmd_max(pid2.cmd_max()) + pid.set_cmd_min(pid2.cmd_min()) + pid.set_cmd_offset(pid2.cmd_offset()) + pid.set_cmd(cmd) + + self.assertAlmostEqual(pid.p_gain(), pid2.p_gain()) + self.assertAlmostEqual(pid.i_gain(), pid2.i_gain()) + self.assertAlmostEqual(pid.d_gain(), pid2.d_gain()) + self.assertAlmostEqual(pid.i_max(), pid2.i_max()) + self.assertAlmostEqual(pid.i_min(), pid2.i_min()) + self.assertAlmostEqual(pid.cmd_max(), pid2.cmd_max()) + self.assertAlmostEqual(pid.cmd_min(), pid2.cmd_min()) + self.assertAlmostEqual(pid.cmd_offset(), pid2.cmd_offset()) + self.assertAlmostEqual(pid.cmd(), cmd) + + pid = PID(pid2) + self.assertAlmostEqual(pid.p_gain(), pid2.p_gain()) + self.assertAlmostEqual(pid.i_gain(), pid2.i_gain()) + self.assertAlmostEqual(pid.d_gain(), pid2.d_gain()) + self.assertAlmostEqual(pid.i_max(), pid2.i_max()) + self.assertAlmostEqual(pid.i_min(), pid2.i_min()) + self.assertAlmostEqual(pid.cmd_max(), pid2.cmd_max()) + self.assertAlmostEqual(pid.cmd_min(), pid2.cmd_min()) + self.assertAlmostEqual(pid.cmd_offset(), pid2.cmd_offset()) + self.assertAlmostEqual(pid.cmd(), pid2.cmd()) + + def test_equal_corner_case(self): + pid = PID(1.0, 2.1, -4.5, 10.5, 1.4, 45, -35, 1.23) + self.assertAlmostEqual(pid.p_gain(), 1.0) + self.assertAlmostEqual(pid.i_gain(), 2.1) + self.assertAlmostEqual(pid.d_gain(), -4.5) + self.assertAlmostEqual(pid.i_max(), 10.5) + self.assertAlmostEqual(pid.i_min(), 1.4) + self.assertAlmostEqual(pid.cmd_max(), 45.0) + self.assertAlmostEqual(pid.cmd_min(), -35.0) + self.assertAlmostEqual(pid.cmd_offset(), 1.23) + self.assertAlmostEqual(pid.cmd(), 0.0) + + pid = PID(pid) + self.assertAlmostEqual(pid.p_gain(), 1.0) + self.assertAlmostEqual(pid.i_gain(), 2.1) + self.assertAlmostEqual(pid.d_gain(), -4.5) + self.assertAlmostEqual(pid.i_max(), 10.5) + self.assertAlmostEqual(pid.i_min(), 1.4) + self.assertAlmostEqual(pid.cmd_max(), 45.0) + self.assertAlmostEqual(pid.cmd_min(), -35.0) + self.assertAlmostEqual(pid.cmd_offset(), 1.23) + self.assertAlmostEqual(pid.cmd(), 0.0) + + def test_update(self): + pid = PID() + pid.init(1.0, 0.1, 0.5, 10, 0, 20, -20) + + result = pid.update(5.0, 0.0) + self.assertAlmostEqual(result, 0.0) + + result = pid.update(5.0, 10.0) + self.assertAlmostEqual(result, -10.25) + + [pe, ie, de] = pid.errors() + self.assertAlmostEqual(pe, 5) + self.assertAlmostEqual(ie, 5) + self.assertAlmostEqual(de, 0.5) + + # Test max integral term + pid.set_i_max(0.2) + pid.set_i_gain(10.0) + result = pid.update(5.0, 10.0) + self.assertAlmostEqual(result, -5.2) + [pe, ie, de] = pid.errors() + self.assertAlmostEqual(pe, 5) + self.assertAlmostEqual(ie, 0.2) + self.assertAlmostEqual(de, 0.0) + + # Test min integral term + pid.set_i_max(20) + pid.set_i_min(1.4) + pid.set_i_gain(0.01) + result = pid.update(5.0, 10.0) + self.assertAlmostEqual(result, -6.4) + [pe, ie, de] = pid.errors() + self.assertAlmostEqual(pe, 5) + self.assertAlmostEqual(ie, 1.4) + self.assertAlmostEqual(de, 0.0) + + def update_test(self, _pid, _result, _error, + _dt, _p_error, _i_error, _d_error): + + self.assertAlmostEqual(_result, _pid.update(_error, _dt)) + [p_error, i_error, d_error] = _pid.errors() + self.assertAlmostEqual(p_error, _p_error) + self.assertAlmostEqual(i_error, _i_error) + self.assertAlmostEqual(d_error, _d_error) + + def test_zero_gains(self): + # controller with zero gains, no command limits + pid = PID() + + # repeat once to test derivative and integral error + self.update_test(pid, 0, 0, 0, 0, 0, 0) + self.update_test(pid, 0, 0, 0, 0, 0, 0) + + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + + self.update_test(pid, 0, 1, 1, 1, 0, 1) + self.update_test(pid, 0, 1, 1, 1, 0, 0) + self.update_test(pid, 0, -1, 1, -1, 0, -2) + self.update_test(pid, 0, -1, 1, -1, 0, 0) + self.update_test(pid, 0, 1, 1, 1, 0, 2) + self.update_test(pid, 0, 1, 1, 1, 0, 0) + + self.update_test(pid, 0, 1, 0, 1, 0, 0) + self.update_test(pid, 0, 1, 0, 1, 0, 0) + self.update_test(pid, 0, -1, 0, 1, 0, 0) + self.update_test(pid, 0, -1, 0, 1, 0, 0) + self.update_test(pid, 0, 1, 0, 1, 0, 0) + self.update_test(pid, 0, 1, 0, 1, 0, 0) + + self.update_test(pid, 0, 1, -1, 1, 0, 0) + self.update_test(pid, 0, 1, -1, 1, 0, 0) + self.update_test(pid, 0, -1, -1, -1, 0, 2) + self.update_test(pid, 0, -1, -1, -1, 0, 0) + self.update_test(pid, 0, 1, -1, 1, 0, -2) + self.update_test(pid, 0, 1, -1, 1, 0, 0) + + pid.reset() + self.update_test(pid, 0, 1, 0, 0, 0, 0) + + # cmd_max defaults to -1.0 + # setting cmd_min to -10.0, means output should now be -1.0 + # when time is non-zero + pid.set_cmd_min(-10.0) + self.assertAlmostEqual(-10.0, pid.cmd_min()) + # command hasn't been updated yet + self.assertAlmostEqual(0.0, pid.cmd()) + + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + + self.update_test(pid, -1, 1, 1, 1, 0, 1) + self.update_test(pid, -1, 1, 1, 1, 0, 0) + self.update_test(pid, -1, -1, 1, -1, 0, -2) + self.update_test(pid, -1, -1, 1, -1, 0, 0) + self.update_test(pid, -1, 1, 1, 1, 0, 2) + self.update_test(pid, -1, 1, 1, 1, 0, 0) + + pid.reset() + self.update_test(pid, 0, 1, 0, 0, 0, 0) + + # i_max defaults to -1.0 + # setting i_min to -10.0, means output should now be -1.0 + # when time is non-zero + pid.set_i_min(-10.0) + self.assertAlmostEqual(-10.0, pid.i_min()) + # i_err hasn't been updated yet + [p_err, i_err, d_err] = pid.errors() + self.assertAlmostEqual(0.0, i_err) + + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + + self.update_test(pid, -1, 1, 1, 1, -1, 1) + self.update_test(pid, -1, 1, 1, 1, -1, 0) + self.update_test(pid, -1, -1, 1, -1, -1, -2) + self.update_test(pid, -1, -1, 1, -1, -1, 0) + self.update_test(pid, -1, 1, 1, 1, -1, 2) + self.update_test(pid, -1, 1, 1, 1, -1, 0) + + pid.reset() + self.update_test(pid, 0, 1, 0, 0, 0, 0) + + pid.set_cmd_offset(-20.0) + self.assertAlmostEqual(-20.0, pid.cmd_offset()) + # cmd hasn't been updated yet + self.assertAlmostEqual(0.0, pid.cmd()) + + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, -1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + self.update_test(pid, 0, 1, 0, 0, 0, 0) + + self.update_test(pid, -10, 1, 1, 1, -1, 1) + self.update_test(pid, -10, 1, 1, 1, -1, 0) + self.update_test(pid, -10, -1, 1, -1, -1, -2) + self.update_test(pid, -10, -1, 1, -1, -1, 0) + self.update_test(pid, -10, 1, 1, 1, -1, 2) + self.update_test(pid, -10, 1, 1, 1, -1, 0) + + def test_p_control(self): + pid = PID(1) + N = 5 + for i in range(N): + self.assertAlmostEqual(-i, pid.update(i, 1.0)) + + pid.set_p_gain(2) + for i in range(N): + self.assertAlmostEqual(-2*i, pid.update(i, 1.0)) + + def test_i_control(self): + pid = PID(0, 1) + N = 5 + for i in range(N): + self.assertAlmostEqual(-(i+1), pid.update(1, 1.0)) + + pid.set_i_gain(2) + + [p_err, i_err, d_err] = pid.errors() + self.assertAlmostEqual(N, i_err) + i_0 = i_err + + # confirm that changing gain doesn't cause jumps in integral control + self.assertAlmostEqual(-i_0, pid.update(0, 1.0)) + self.assertAlmostEqual(-i_0, pid.update(0, 1.0)) + + for i in range(N): + self.assertAlmostEqual(-i_0-2*(i+1), pid.update(1, 1.0)) + + def test_d_control(self): + pid = PID(0, 0, 1) + self.assertAlmostEqual(1, pid.update(-1, 1.0)) + N = 5 + for i in range(N): + self.assertAlmostEqual(-1, pid.update(i, 1.0)) + + pid.set_d_gain(2) + self.assertAlmostEqual(10, pid.update(-1, 1.0)) + for i in range(N): + self.assertAlmostEqual(-2, pid.update(i, 1.0)) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/python/SemanticVersion.i b/src/python/SemanticVersion.i new file mode 100644 index 000000000..cb105adfd --- /dev/null +++ b/src/python/SemanticVersion.i @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * 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. + * +*/ + +%module semanticversion +%{ + #include +%} + +%include "std_string.i" + +namespace ignition +{ + namespace math + { + class SemanticVersion + { + %rename("%(undercase)s", %$isfunction, %$ismember, %$not %$isconstructor) ""; + public: SemanticVersion(); + public: explicit SemanticVersion(const std::string &_v); + public: SemanticVersion(const SemanticVersion &_copy); + public: SemanticVersion(const unsigned int _major, + const unsigned int _minor = 0, + const unsigned int _patch = 0, + const std::string &_prerelease = "", + const std::string &_build = ""); + public: ~SemanticVersion(); + public: bool Parse(const std::string &_versionStr); + public: std::string Version() const; + public: unsigned int Major() const; + public: unsigned int Minor() const; + public: unsigned int Patch() const; + public: std::string Prerelease() const; + public: std::string Build() const; + public: bool operator<(const SemanticVersion &_other) const; + public: bool operator<=(const SemanticVersion &_other) const; + public: bool operator>(const SemanticVersion &_other) const; + public: bool operator>=(const SemanticVersion &_other) const; + public: bool operator==(const SemanticVersion &_other) const; + public: bool operator!=(const SemanticVersion &_other) const; + }; + + %extend SemanticVersion { + std::string __str__() const { + std::ostringstream out; + out << *$self; + return out.str(); + } + } + } +} diff --git a/src/python/SemanticVersion_TEST.py b/src/python/SemanticVersion_TEST.py new file mode 100644 index 000000000..095568f5b --- /dev/null +++ b/src/python/SemanticVersion_TEST.py @@ -0,0 +1,166 @@ +# Copyright (C) 2021 Open Source Robotics Foundation +# +# 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. + +import unittest +from ignition.math import SemanticVersion + + +class TestSemanticVersion(unittest.TestCase): + + def test_prerelease(self): + a = SemanticVersion("0.1.0") + b = SemanticVersion("0.1.0-pr2") + + self.assertTrue(b < a) + self.assertFalse(a.prerelease()) + self.assertEqual(b.prerelease(), "pr2") + + self.assertEqual(b.major(), a.major()) + self.assertEqual(b.minor(), a.minor()) + self.assertEqual(b.patch(), a.patch()) + self.assertEqual(b.build(), a.build()) + + self.assertEqual(a.version(), "0.1.0") + self.assertEqual(b.version(), "0.1.0-pr2") + + def test_build(self): + a = SemanticVersion("0.1.0") + b = SemanticVersion("0.1.0+012345") + + self.assertTrue(b == a) + self.assertFalse(a.build()) + self.assertEqual(b.build(), "012345") + + self.assertEqual(b.major(), a.major()) + self.assertEqual(b.minor(), a.minor()) + self.assertEqual(b.patch(), a.patch()) + self.assertEqual(b.prerelease(), a.prerelease()) + + self.assertEqual(a.version(), "0.1.0") + self.assertEqual(b.version(), "0.1.0+012345") + + def test_prerelease_build(self): + a = SemanticVersion("0.1.0") + b = SemanticVersion("0.1.0-pr2") + c = SemanticVersion("0.1.0+012345") + d = SemanticVersion("0.1.0-pr2+012345") + + self.assertEqual(a.version(), "0.1.0") + self.assertEqual(b.version(), "0.1.0-pr2") + self.assertEqual(c.version(), "0.1.0+012345") + self.assertEqual(d.version(), "0.1.0-pr2+012345") + + self.assertTrue(b < a) + self.assertTrue(b < c) + self.assertTrue(b == d) + self.assertTrue(a == c) + + self.assertEqual(a.major(), b.major()) + self.assertEqual(a.minor(), b.minor()) + self.assertEqual(a.patch(), b.patch()) + self.assertFalse(a.prerelease()) + self.assertFalse(a.build()) + + self.assertEqual(b.major(), c.major()) + self.assertEqual(b.minor(), c.minor()) + self.assertEqual(b.patch(), c.patch()) + self.assertEqual(b.prerelease(), d.prerelease()) + + self.assertEqual(c.major(), d.major()) + self.assertEqual(c.minor(), d.minor()) + self.assertEqual(c.patch(), d.patch()) + self.assertEqual(c.build(), d.build()) + + self.assertEqual(d.build(), "012345") + self.assertEqual(d.prerelease(), "pr2") + + def test_sream_out(self): + a = SemanticVersion("0.1.0") + b = SemanticVersion("0.1.0-pr2") + c = SemanticVersion("0.1.0+012345") + d = SemanticVersion("0.1.0-pr2+012345") + + self.assertEqual(str(a), "0.1.0") + self.assertEqual(str(b), "0.1.0-pr2") + self.assertEqual(str(c), "0.1.0+012345") + self.assertEqual(str(d), "0.1.0-pr2+012345") + + def test_operators(self): + a = SemanticVersion("0.1.0") + b = SemanticVersion("1.0.0") + c = SemanticVersion("1.0.0") + c2 = SemanticVersion("1.0.2") + d = SemanticVersion("0.2.0") + + # check that the short form is the same as the long one + aa = SemanticVersion("0.1") + self.assertEqual(aa.version(), "0.1.0") + + # check second constructor + c2b = SemanticVersion(1, 0, 2) + self.assertTrue(c2 == c2b) + + self.assertFalse(a < a) + self.assertFalse(b < a) + self.assertTrue(a < d) + self.assertFalse(d < a) + + self.assertTrue(a < b) + self.assertTrue(a <= b) + + # equality + self.assertTrue(a != b) + self.assertTrue(b == c) + self.assertFalse(a == b) + self.assertFalse(a != a) + + # other operators + self.assertTrue(b <= c) + self.assertTrue(b >= c) + self.assertTrue(c2 > c) + self.assertFalse(c2 < c) + self.assertTrue(b == b) + + def test_assign_copy(self): + a = SemanticVersion("0.1+pr2") + b = SemanticVersion("0.2") + + aa = SemanticVersion(a) + self.assertTrue(a == aa) + aaa = SemanticVersion(aa) + self.assertTrue(a == aaa) + # change a + a = SemanticVersion(b) + # aaa unchanged + self.assertTrue(aa == aaa) + # a and aaa now different + self.assertTrue(a != aaa) + + def test_parse(self): + a = SemanticVersion() + self.assertFalse(a.parse("")) + self.assertFalse(a.parse("0.1.2+1-1")) + + def test_constructor(self): + a = SemanticVersion() + + self.assertEqual(a.major(), 0) + self.assertEqual(a.minor(), 0) + self.assertEqual(a.patch(), 0) + self.assertFalse(a.prerelease()) + self.assertFalse(a.build()) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/python/python.i b/src/python/python.i index ace8f922c..b461e5967 100644 --- a/src/python/python.i +++ b/src/python/python.i @@ -7,6 +7,8 @@ %include Vector4.i %include Line2.i %include Line3.i +%include PID.i +%include SemanticVersion.i %include SignalStats.i %include Temperature.i %include Triangle.i