diff --git a/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/04_control.rst b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/04_control.rst new file mode 100644 index 000000000..6a39a797e --- /dev/null +++ b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/04_control.rst @@ -0,0 +1,20 @@ +.. _target_bf_control: +Closed-loop Control +=================== + +... + +**Learn more** + +.. toctree:: + :maxdepth: 2 + :glob: + + control/* + + +**Cross Reference** + +- :ref:`API Reference BF-Control ` +- :ref:`API Reference BF-Control Pool Objects ` +- :ref:`BF-Systems - Basics of State-based Systems ` diff --git a/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/.gitkeep b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system.drawio b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system.drawio new file mode 100644 index 000000000..5ecd76dbd --- /dev/null +++ b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system.drawio @@ -0,0 +1 @@ +7VlLc9o6GP01XtIxfmGWQNJ20TtzZ1i0WQpbNmply5VFgPz6K9kSlixDTAiZduausD49/Z2joyPj+Kvi8IWCavsPSSF2PDc9OP6D43lePA/4j4gc28jU86M2klOUylgXWKMXKIOujO5QCmujISMEM1SZwYSUJUyYEQOUkr3ZLCPYnLUCObQC6wRgO/odpWwro9No3lV8hSjfyqljb9ZWFEA1lm9Sb0FK9lrIf3T8FSWEtU/FYQWxyJ7KS9vv85na08IoLNmYDsXLGhSTzfOL+xUWi+j3cv8dTUQHMcwzwDv5xitSMkpEz/WxZrCQq2dHlRJKdmUKxaiu4y/3W8TgugKJqN1zFvDYlhWYl6b8EYMNxEuQ/MqbbiuCCeVVJSl5+2XG55KYe2IwgFFe8gKGGX+n5TOkDHEsFjLMSKU6yeWIKTKEsRrX8fwsy7wk4fGav8YvqNWk0SYKI15jp05mU0wID1pIpvILJAVk9MibqNpIwiqJ7bmyvO9YEs1lbKsRxFd8AJKZ+WnsDjz+IPG7AsuZBWWioIywSOeG8qe8SSygCGx4BvvYwpSzXxYJZVuSkxLgxy66NNHv2nwjApwGkJ+QsaOEFewYMRnBc06PP/TCkxjsU6iKDwc5eFs6ypLGlWkwjlkaSeABsR/aszYnL3VTioKa8SxNarKjCbwAhVIqQHPILm2/tp3I+hjSTdxPUTwL204UYsDQs6lTQ6SSw/1LEH+N01hxYBLY4mW7eNmro+aCUnDUmlWiQX1hHre3UeKeTL3SPgiM9vyhXUGvt1oOybIaMqe/l065fPv2UhJhKSWG9DaN7AlYCmCcDQpYlMRwkw1JoLY3YtFTnTXT91G70OuBGNhqd1JEXe3ie4lddE7sMM/6H6V3fd066Z8meU+6HF7SPw3zy/r3du3yRmpXcLV2ebcJ1/23+Fkz1JDK1XmlkSj6vRMergFoUjdIL3iDaVwdGiBUvSLje9iqv08yggGD9LGSEY73R5DfHGxR/0hz5Do3mSNldzqL86Q7nEG7c42herO6BCPVZXqduvwF4jL/Mw6fdCFuxR2uPPIZiTdppqg5MEy1SDCoa5SosGx2UR/G4v8qrje6W6/nIv1onLt9N7PoWmLDaVWdpuvT4JvYeyZ06i6c8Bxzh2nfhguUpi1LID91GnvTgigtOR88XDrhw413JwvqS+y2zofTlxm5Pkf/+DG8k2ex7xvgKTG4kRPBzBx1bg5wv50fWFzg5wwoxJFebuqq2U/uQGhFigpMrnQd4RnXMW5KCtjQwYcxqmr4ui0BddV+hMvQQcjWRdcRvsnHnN0F1n55b0fje2duzJqjiT/S0EztS9DEwo6/HDNBMnOr9rwGhAyNF6AhVphH1+jPgNNotNe4Ajm/h9xsZiE3ZEX9uyFnf6tzvOX/2A1g581N2faD8F7Y8WL3Ob4V/+5fDf/xPw== \ No newline at end of file diff --git a/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system.drawio.png b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system.drawio.png new file mode 100644 index 000000000..ccd17ed5a Binary files /dev/null and b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system.drawio.png differ diff --git a/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system_with_integrator.drawio b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system_with_integrator.drawio new file mode 100644 index 000000000..be82424d5 --- /dev/null +++ b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system_with_integrator.drawio @@ -0,0 +1 @@ +7VrbcqM4EP0aHp0CcTF+jJ1kZqtmt6YqUzW7jzIIrIlAjJBjO1+/AiSMLHDwJZet2qegRmohnaOj7nYsd5FtvzBYrP6kMSIWsOOt5d5ZAIBw5ok/lWXXWBzgBo0lZTiWtr3hEb8gabSldY1jVGodOaWE40I3RjTPUcQ1G2SMbvRuCSX6rAVMkWF4jCAxrT9xzFfS6gSz/YuvCKcrOXUIps2LDKrOciXlCsZ00zG595a7YJTy5inbLhCpdk/tSzPuYeBt+2EM5XzMgIe/oh/ej3iZP8Gtn/Iv0yzMJtLLMyRrueAFzTmj1cDHXclRJj+e79SOMLrOY1Q5tS13vllhjh4LGFVvN4IEwrbiGREtRzwSuERkDqOntB62oIQy8Sqnueg/T8RcEnJQOYMEp7loEJSIJc2fEeNYQHErzZwWapD8nGqKBBOi/FrATZIERJGwl2IZT6jzJg6WgR+IN3LNwjvaDm6m00IkyI1ohjjbiS5qQCBRlbwGtmxv9iSZKuRXHX64ygglMdPW9x478SDh64cye3mE2WT5/GJ/Rdlt8Hu++YknUwPKSEEZkGo7l0w8pfXGQobhUuzgIbYoFuSXTcr4iqY0h+R+b53r6O/7fKMVODUgvxDnOwkrXHOqM0JsMNv9XY2/8VXzH+mubtxttdZOtjpccbxxzGpJ0g+uWDldswgd2VOlOJCliB/rJ9Wg2r8x9JnYNw7wmzEMEcjxs643feyQ3r5TLJbRugo9nYkGwZqPl6O6+qAcqY40SUrEDR62X38+NQdVhiB2mb4cHP4YojDpPfxBFKJl0icfHV6F1Ugl006rFD3MMcRjEGofHCiFZypFqyZdpQjfSiiCIaEgYtc/lVYcnvkh7XDGaEcH8+Pacb5cgJFy4Z2sFuAyrXj7Iw6Gj7ggld3lVYdEwe91Ff7UAE3KGulb0cEJi20NhHqvyHiNkOS/JxleT3DxvpIRGugKXqGUQa5LxghovQFoe4KVC7x9igjHubFFYNCNcW78MHxFq+rWd8SwQA6xgeAHbTHv6KBotTIonveeq4Zy/D4B02ykBILTJFDES6ELtFOhAt4rx0/tYVMumjV/aPzkjw/tkch5zZjqXeP6A8afGNf3U9v+eGp7Y3OBU5n96e/22eeI/eLbqp6zx1VYHnC1knqKUgDDVY+IwLLEkTLLbkev57H4v4rrhXrUVhJUPheMy+eulqvZhtgIWhXtdIc0+FadPR06VcaJxB5XV5hRyMlwHDcsQeIur+/pBsR6mnqd/tzy766d9h9jd085SNYU5fdZ3bJd/0mehq6r31HOVe4ob6p7nekO3u7kewYXxD0DsyqizpdlUZ8nu8e0oFkBJyfGcv5ALDduShGL9l18hOCiRK9nBbAsmvJxgreVbB0N+v2z0ojBU2Ccl2snFC4YqBF1EorwPfMJx6xBTAzsxOK4DpK+t+rMd4CQpvEC1McK/eoaXcF2gtGxxgnIuQfITacGcn2ZoPtmyJllZgvM/8euBzsw02Xb9fyPxQ6YdVgDuGqnirPk7UzROvmanQafTcxmxrb+0VRHrnUD9t1sH1jz8q2RP9WNjMBOOFofhTEwo2Iz1jAylA4C5wqTVvZu8+wxP5i1ifToCtGJcF2vGDT657MO/H4P/Mp2Ybg9CXWFaXOi0SUh5ejg9p5cLZcTzf1/DjTd9/+A4d7/Cw== \ No newline at end of file diff --git a/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system_with_integrator.drawio.png b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system_with_integrator.drawio.png new file mode 100644 index 000000000..cd2949323 Binary files /dev/null and b/doc/rtd/content/02_basic_functions/mlpro_bf/sub/layer3_application_support/control/images/control_system_with_integrator.drawio.png differ diff --git a/doc/rtd/content/03_machine_learning/mlpro_oa/sub/05_oa_control.rst b/doc/rtd/content/03_machine_learning/mlpro_oa/sub/05_oa_control.rst new file mode 100644 index 000000000..914e1ee06 --- /dev/null +++ b/doc/rtd/content/03_machine_learning/mlpro_oa/sub/05_oa_control.rst @@ -0,0 +1,22 @@ +.. _target_oa_control: +Online Adaptive Control +======================= + +Further descriptions coming soon... + + +**Learn more** + +.. toctree:: + :maxdepth: 2 + :glob: + + oa_control/* + + +**Cross Reference** + +- :ref:`Howtos OA-Control ` +- :ref:`API Reference OA-Control ` +- :ref:`API Reference OA-Control Pool Objects ` +- :ref:`BF-Control - Basics of Closed-loop Control ` diff --git a/doc/rtd/content/03_machine_learning/mlpro_oa/sub/oa_control/.gitkeep b/doc/rtd/content/03_machine_learning/mlpro_oa/sub/oa_control/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/99_appendices/appendix1/sub/mlpro_bf/layer3_application_support/04_control.rst b/doc/rtd/content/99_appendices/appendix1/sub/mlpro_bf/layer3_application_support/04_control.rst new file mode 100644 index 000000000..820e5cc6a --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix1/sub/mlpro_bf/layer3_application_support/04_control.rst @@ -0,0 +1,8 @@ +Closed-loop Control +=================== + +.. toctree:: + :maxdepth: 1 + :glob: + + control/* diff --git a/doc/rtd/content/99_appendices/appendix1/sub/mlpro_bf/layer3_application_support/control/.gitkeep b/doc/rtd/content/99_appendices/appendix1/sub/mlpro_bf/layer3_application_support/control/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/99_appendices/appendix1/sub/mlpro_oa/03_control.rst b/doc/rtd/content/99_appendices/appendix1/sub/mlpro_oa/03_control.rst new file mode 100644 index 000000000..36e13baaf --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix1/sub/mlpro_oa/03_control.rst @@ -0,0 +1,9 @@ +.. _target_howto_oa_control: +Online Adaptive Control +======================= + +.. toctree:: + :maxdepth: 1 + :glob: + + control/* diff --git a/doc/rtd/content/99_appendices/appendix1/sub/mlpro_oa/control/.gitkeep b/doc/rtd/content/99_appendices/appendix1/sub/mlpro_oa/control/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math-Geometry_class_diagram.drawio.bkp b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math-Geometry_class_diagram.drawio.bkp deleted file mode 100644 index dcaf36aa1..000000000 --- a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math-Geometry_class_diagram.drawio.bkp +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math_class_diagram.drawio.bkp b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math_class_diagram.drawio.bkp deleted file mode 100644 index 283ce4ee4..000000000 --- a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math_class_diagram.drawio.bkp +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math_class_diagram.drawio.dtmp b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math_class_diagram.drawio.dtmp deleted file mode 100644 index 86c7e93ab..000000000 --- a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/.$MLPro-BF-Math_class_diagram.drawio.dtmp +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio index 8f46c6f03..ee1e48938 100644 --- a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio +++ b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio.png b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio.png index 4bb8b23c1..13e6e1fab 100644 Binary files a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio.png and b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer2_mathematics/images/MLPro-BF-Math-Geometry_class_diagram.drawio.png differ diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/05_control.rst b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/05_control.rst new file mode 100644 index 000000000..8f8901218 --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/05_control.rst @@ -0,0 +1,12 @@ +.. _target_api_bf_control: +BF-CONTROL - Closed-loop Control +================================ + +.. image:: images/MLPro-BF-Control_class_diagram.drawio.png + :scale: 50% + +.. automodule:: mlpro.bf.control.basics + :members: + :undoc-members: + :private-members: + :show-inheritance: \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Control_class_diagram.drawio b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Control_class_diagram.drawio new file mode 100644 index 000000000..c17cf55db --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Control_class_diagram.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Control_class_diagram.drawio.png b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Control_class_diagram.drawio.png new file mode 100644 index 000000000..ea51339ab Binary files /dev/null and b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Control_class_diagram.drawio.png differ diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio index 2aff1e498..e03c75d13 100644 --- a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio +++ b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio.png b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio.png index fdc54dd85..6fe1174a5 100644 Binary files a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio.png and b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_bf/layer3_application_support/images/MLPro-BF-Systems_class_diagram.drawio.png differ diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/03_control.rst b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/03_control.rst new file mode 100644 index 000000000..b8ed57bc5 --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/03_control.rst @@ -0,0 +1,12 @@ +.. _target_api_oa_control: +OA Control +========== + +.. image:: control/images/MLPro-OA-Control.drawio.png + :scale: 50% + +.. automodule:: mlpro.oa.control.basics + :members: + :undoc-members: + :private-members: + :show-inheritance: \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/control/images/MLPro-OA-Control_class_diagram.drawio b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/control/images/MLPro-OA-Control_class_diagram.drawio new file mode 100644 index 000000000..72daef064 --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/control/images/MLPro-OA-Control_class_diagram.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/control/images/MLPro-OA-Control_class_diagram.drawio.png b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/control/images/MLPro-OA-Control_class_diagram.drawio.png new file mode 100644 index 000000000..6e37052ce Binary files /dev/null and b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/control/images/MLPro-OA-Control_class_diagram.drawio.png differ diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/streams/images/.$MLPro-OA-Stream-Processing_class_diagram.drawio.bkp b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/streams/images/.$MLPro-OA-Stream-Processing_class_diagram.drawio.bkp deleted file mode 100644 index f1a02d7be..000000000 --- a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/streams/images/.$MLPro-OA-Stream-Processing_class_diagram.drawio.bkp +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/streams/images/.$MLPro-OA-Stream-Processing_class_diagram.drawio.dtmp b/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/streams/images/.$MLPro-OA-Stream-Processing_class_diagram.drawio.dtmp deleted file mode 100644 index fe1d2e850..000000000 --- a/doc/rtd/content/99_appendices/appendix2/sub/core/mlpro_oa/streams/images/.$MLPro-OA-Stream-Processing_class_diagram.drawio.dtmp +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_bf/03_bf_control.rst b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_bf/03_bf_control.rst new file mode 100644 index 000000000..82d1284ac --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_bf/03_bf_control.rst @@ -0,0 +1,9 @@ +.. _target_pool_bf_control: +BF-CONTROL - Closed-loop Control +================================ + +.. toctree:: + :maxdepth: 2 + :glob: + + bf_control/* \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_bf/bf_control/.gitkeep b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_bf/bf_control/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/10_streams.rst b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/10_streams.rst index 215ec7ef0..cfee65612 100644 --- a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/10_streams.rst +++ b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/10_streams.rst @@ -1,6 +1,6 @@ .. _target_api_pool_oa_streams: OA Stream Processing -=============== +==================== .. toctree:: :maxdepth: 2 diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/30_control.rst b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/30_control.rst new file mode 100644 index 000000000..73c22358c --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/30_control.rst @@ -0,0 +1,9 @@ +.. _target_api_pool_oa_control: +OA Control +========== + +.. toctree:: + :maxdepth: 2 + :glob: + + control/* \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/.gitkeep b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/.gitkeep b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/.$MLPro-OA-Control-RL-PID-Controller_class_diagram.drawio.bkp b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/.$MLPro-OA-Control-RL-PID-Controller_class_diagram.drawio.bkp new file mode 100644 index 000000000..c21951c4f --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/.$MLPro-OA-Control-RL-PID-Controller_class_diagram.drawio.bkp @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/.gitkeep b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/MLPro-OA-Control-RL-PID-Controller_architecture.drawio b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/MLPro-OA-Control-RL-PID-Controller_architecture.drawio new file mode 100644 index 000000000..082117b75 --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/MLPro-OA-Control-RL-PID-Controller_architecture.drawio @@ -0,0 +1 @@ +7Vpdd+I2EP01nNM+kGNZ2MaPIZB02zRNl+ymu29aLIxaYVFbBNxfXxlL+EMOGIhx2LN5iTWWZGnmzszViA68ma/vQrSY/c48TDum4a07cNgxTcuEhviXSOJUAkxopxI/JJ6UZYIx+Q9LoRzoL4mHo0JHzhjlZFEUTlgQ4AkvyFAYslWx25TR4lcXyMeaYDxBVJc+E4/PUqkJ1TaSF79g4s/Up22rl76ZI9VbbiWaIY+tciI46sCbkDGePs3XN5gm6lOKef4QP9P7f+y7X/+M/kWfBr89PXzuppPdHjJku4cQB/zoqSMffiSjh/6nP6Knp+c5uDMg7MJ06hdEl1JhH+9F+/HDsHvDAh4ySnEot89jpdSQLQMPJ/MaHThYzQjH4wWaJG9XAkdCNuNzKlpAPE7FPBIXoK/acq7k/QsOOREGu6bED4SMs2QCJFsUT8WWB3KZoitelwy7RylgaykBcszmmIexGCdn2RpX4Rs4aXuVgQUYqtMsBxRHIRxJhPrbyTMjiAdphwNs4mr6xp7AtGyykM+YzwJER5l0ULRI1ueeJdrc6PlvzHksDYGWnBWtJJQYxn/J8ZvGl6RxBRyoBMN1/vUwzrcecUjE7gVWUqGHotlmOVUIiDgK+XXi3EIyoSiKyESJbwnNluSpTgELcCqR740tJhLdHIEIoV+2DCd4hx2kfcWqfMz3+ZCOsBBTxMlLcXFVYNkMFTtFca7DgpGAR7mZHxNBBlwIisA1eyXvP6y/eEhXkMF2u5XjkWxo0eV8gaWhgAGBWwwYfUsPGE5FvACNxQtT0/JPd4gEHVNMZzx8Tf9//vpzG2GlbKOmfVZRjH0+a1QbubbPnmQwoBksTbqMkkl8iT5RSqK9vtG2TygvzesYr1DotZpbQTG3Wnsy6868KVNiPmkaxaSZZdYsb4Iz+GCvpg++Aqrz+GDvVXzYNCGc30Rysv3k6XYZTDhhwQU6pg3enWNamt6vq7Wrs7+cNouqL+u2SBPrss014Rv+e2UAU7YlBbZd2c78NGnEuUaZ/zae58xqw+cMa1XYVckO46YamQTQKbFJ6F5ZxVnSLciBO4gpUEx0x1xpKNHmeiua6miYHAm46Oz0JEhWpwBnXw44Cssnoa8tUJWP4gIXx2LKrj7rnAtPAOjQ+XGCf+sT/Kkn7opIBJ3duKmLv55bEdIcN/uDtfB4aE3A6uufdaydK90/pJnKANB5wJgjjttwnAul2UCe+/fz7FcKVBICXePKtXpWAQZdaB0T0w8FLHCs4nddd3cVq2vu7F/EajZarYdNpxFuJuTb7Yb87Cz5Jf/u+zpY1kZ8KxXZg8HsQKM+mN8MqHq1cBxHHM81/L7/4y2wS8cQYNY83zZ3eQP0G7UhphxdoHq7oFQ+AO2XD4Betxljvtgypx/HtXMd1zR0HM2Wu+b+asIbMeSuabRwDSYg9QrZNZKfM5iDAK/aoA+XSgOcmjSgdyINOO1WzmjDpI0wwuMP+FlR1cqVVFW5tbqcWig8vBsstUMpjdJ5R/GWupSy1L+h8KbXT2V4K1+lDJbT6UXe+rulGnWjFymimf2oLDVS9uM8OPof \ No newline at end of file diff --git a/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/MLPro-OA-Control-RL-PID-Controller_class_diagram.drawio b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/MLPro-OA-Control-RL-PID-Controller_class_diagram.drawio new file mode 100644 index 000000000..c9b7b1fd8 --- /dev/null +++ b/doc/rtd/content/99_appendices/appendix2/sub/pool/mlpro_oa/control/controllers/images/MLPro-OA-Control-RL-PID-Controller_class_diagram.drawio @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/drafts_Amerik/basic_control.py b/drafts_Amerik/basic_control.py new file mode 100644 index 000000000..d34682c56 --- /dev/null +++ b/drafts_Amerik/basic_control.py @@ -0,0 +1,151 @@ +import gymnasium as gym +import numpy as np +import matplotlib.pyplot as plt +from stable_baselines3 import DDPG +from stable_baselines3.common.noise import NormalActionNoise, OrnsteinUhlenbeckActionNoise +import random + + +from mlpro.bf.math.basics import Log +from mlpro.bf.mt import Async, Log, Task, Workflow +from mlpro.bf.streams import * + +from mlpro.bf.math import * +from mlpro.bf.streams.basics import InstDict, StreamShared +from mlpro.bf.various import * +from datetime import datetime, timedelta +from mlpro.bf.control.controllers.pid_controller import PIDController +from mlpro.bf.control.basics import CTRLError + + + +env = gym.make("Pendulum-v1",render_mode="human") + +def Random_games(): + + action_size = env.action_space.shape[0] + + for episode in range(10): + env.reset() + + while True: + env.render() + + action = np.random.uniform(-1.0,1.0,size=action_size) + + next_state,reward, done, info,_ = env.step(action) + print(f'Next state{len(next_state)}\n',f'reward:{reward}') + + if done: + break +def dpg_algorithm(): + # The noise objects for DDPG + n_actions = env.action_space.shape[-1] + action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.1 * np.ones(n_actions)) + + model = DDPG("MlpPolicy", env, action_noise=action_noise, verbose=1) + model.learn(total_timesteps=10000, log_interval=10) + vec_env = model.get_env() + + obs = vec_env.reset() + while True: + action, _states = model.predict(obs) + obs, rewards, dones, info = vec_env.step(action) + env.render() +def main(): + + # Parameter + setpoint = 22.0 # Sollwert in °C + initial_temp = 15.0 # Starttemperatur in °C + ambient_temp = 15.0 # Außentemperatur in °C + time_step = 1 # Zeitintervall in Minuten + total_time =5000 # Gesamte Simulationszeit in Minuten + + # PID-Koeffizienten + Kp = 10.0 # Proportionaler Koeffizient + Ti = 100.01 # Integraler Koeffizient + Td = 250.0 # Differenzieller Koeffizient + + # Heizwendel Parameter + coil_mass = 10.0 # Masse der Heizwendel (kg) + specific_heat_coil = 0.5 # Spezifische Wärmekapazität der Heizwendel (J/(kg*K)) + coil_temp = initial_temp # Anfangstemperatur der Heizwendel + heat_transfer_coeff = 0.1 # Wärmeübertragungskoeffizient (W/K) + + # Initialisierung + time = np.arange(0, total_time + time_step, time_step) + temperature = np.zeros_like(time,dtype=float) + setpoints = np.zeros_like(time,dtype=float) + temperature[0] = initial_temp + setpoints[0]= setpoint + # PID-Regler Variablen + integral = 0 + previous_error = 0 + + + # Simulation + for i in range(1, len(time)): + + #if i%600 ==0: + # setpoint+= random.randint(-2,2) + error = setpoint - temperature[i - 1] + integral += error * time_step + derivative = (error - previous_error) / time_step + + # PID-Regler Berechnung + control_signal = Kp * error + (Kp/Ti)*integral + Kp*Td * derivative + + # Begrenzung der Steuergröße und Normierung auf 0 bis 1 + control_signal = np.clip(control_signal, 0, 100) / 100 + + # Heizwendel-Erwärmung + power_input = control_signal * 100 # z.B. in Watt + coil_temp += (power_input - heat_transfer_coeff * (coil_temp - ambient_temp)) / (coil_mass * specific_heat_coil) * time_step + print(f"difference:{coil_temp-ambient_temp}",f"Power:{power_input}") + + # Wärmeübertragung zum Raum + heating_power = heat_transfer_coeff * (coil_temp - temperature[i - 1]) + + + # Temperaturänderung des Raums + delta_temp = heating_power * time_step / 60 + temperature[i] = temperature[i - 1] + delta_temp + + # Temperaturveränderung durch Umgebung + temperature[i] += (ambient_temp - temperature[i]) * 0.01 + + # Update der PID-Variablen + previous_error = error + setpoints[i]+=setpoint + + # Plotten der Ergebnisse + plt.plot(time, temperature, label='Raumtemperatur') + plt.plot(time,setpoints, color='r', linestyle='--', label='Sollwert') + plt.xlabel('Zeit (Minuten)') + plt.ylabel('Temperatur (°C)') + plt.title('Temperaturregelung mit normiertem PID-Algorithmus') + plt.legend() + plt.grid(True) + plt.show() + +def create_instance(): + p_set = Set() + elem = Element(p_set=p_set) + elem.set_values([1,2,3,4]) + inst = Instance(p_feature_data=elem) + print(inst.get_feature_data().get_values()) + + +pid = PIDController(12,10,2.5) +p_set = Set() +elem = Element(p_set=p_set) +elem.set_values([12]) +error = CTRLError(elem) + +for i in range(2): + print(pid.compute_action(error)) + elem = Element(p_set=p_set) + elem.set_values([11]) + error = CTRLError(elem) + + diff --git a/drafts_Amerik/pid2.py b/drafts_Amerik/pid2.py new file mode 100644 index 000000000..5120def95 --- /dev/null +++ b/drafts_Amerik/pid2.py @@ -0,0 +1,322 @@ + +from mlpro.bf.math.basics import Log +from mlpro.bf.mt import Async, Log, Task, Workflow +from mlpro.bf.streams import * + +from mlpro.bf.math import * +from mlpro.bf.streams.basics import InstDict, StreamShared +from mlpro.bf.various import * +from datetime import datetime +import matplotlib.pyplot as plt +from mlpro.bf.various import Log + + +class Link(Task): + + + def __init__(self, p_id=None, p_name: str = None, p_range_max: int = Async.C_RANGE_THREAD, p_autorun=..., p_class_shared=None, p_visualize: bool = False, p_logging=Log.C_LOG_ALL, **p_kwargs): + super().__init__(p_id, p_name, p_range_max, p_autorun, p_class_shared, p_visualize, p_logging, **p_kwargs) + + + + def _run(self, **p_kwargs): + print('Task 1') + #control_signal=self._so.get_setpoint(self.get_id()) + setpoint= self._so.get_setpoint(self.get_id()) + act_value= self._so.get_actual_value(self.get_id()) + self._so.set_error(setpoint-act_value,self.get_id()) + #print(setpoint-act_value) + +class PIDTask(Task): + + def __init__(self,kp: float, ki: float,kd: float, p_id=None, p_name: str = None, p_range_max: int = Async.C_RANGE_THREAD, p_autorun=..., p_class_shared=None, p_visualize: bool = False, p_logging=Log.C_LOG_ALL, **p_kwargs): + super().__init__(p_id, p_name, p_range_max, p_autorun, p_class_shared, p_visualize, p_logging, **p_kwargs) + + self.kp = kp + self.Ti = ki + self.Td = kd + self.integral = 0.0 + self.prev_error =0.0 + self.time_step = 1.0 + + def _run(self, **p_kwargs): + print('Task 2') + error = self._so.get_error(self.get_id()) + self.integral += error * self.time_step + derivative = (error - self.prev_error) / self.time_step + # PID-Regler Berechnung + control_signal = self.kp * error + (self.kp/self.Ti)*self.integral + self.kp* derivative*self.Td + # Begrenzung der Steuergröße und Normierung auf 0 bis 1 + control_signal = np.clip(control_signal, 0, 100) / 100 + self._so.set_control_signal(control_signal,self.get_id()) + #print(control_signal) + self.prev_error = error + +class ProcessTask(Task): + + def __init__(self, p_id=None, p_name: str = None, p_range_max: int = Async.C_RANGE_THREAD, p_autorun=..., p_class_shared=None, p_visualize: bool = False, p_logging=Log.C_LOG_ALL, **p_kwargs): + super().__init__(p_id, p_name, p_range_max, p_autorun, p_class_shared, p_visualize, p_logging, **p_kwargs) + + # Heizwendel Parameter + self.coil_mass = 10.0 # Masse der Heizwendel (kg) + self.specific_heat_coil = 0.5 # Spezifische Wärmekapazität der Heizwendel (J/(kg*K)) + self.coil_temp = 15 # Anfangstemperatur der Heizwendel + self.heat_transfer_coeff = 0.1 # Wärmeübertragungskoeffizient (W/K) + self.ambient_temp = 15.0 + self.time_step=1 + + def _run(self, **p_kwargs): + + print('Task 3') + control_signal = self._so.get_control_signal(self.get_id()) + act_value=self._so.get_actual_value(self.get_id()) + + + + + # Heizwendel-Erwärmung + power_input = control_signal * 100 # z.B. in Watt + + self.coil_temp += (power_input - self.heat_transfer_coeff * (self.coil_temp - self.ambient_temp)) / (self.coil_mass * self.specific_heat_coil) * self.time_step + print(f"difference:{self.coil_temp-self.ambient_temp}",f"Power:{power_input}") + + # Wärmeübertragung zum Raum + heating_power = self.heat_transfer_coeff * (self.coil_temp - act_value) + + # Temperaturänderung des Raums + delta_temp = heating_power/ 60 + act_value+=delta_temp + act_value += (self.ambient_temp-act_value)*0.01 + self._so.set_actual_value(act_value,self.get_id()) + #print(act_value) + + +class MasterShared(Shared): + + def __init__(self,p_range: int = Range.C_RANGE_PROCESS): + super().__init__(p_range) + + self.setpoint = 0.0 + self.actual_value = 0.0 + self.control_signal = 0.0 + self.error= 0.0 + self.actual_values =[] + + self.SpShared = Shared() + self.ActShared = Shared() + self.CrtlShared= Shared() + self.ErrShared = Shared() + + + + def set_setpoint(self,setpoint,p_id): + + test=self.SpShared.lock(p_id,3) + if test: + self.SpShared.clear_results() + self.SpShared.add_result(p_id,setpoint) + self.SpShared.unlock() + + + def get_setpoint(self,p_id): + + test =self.SpShared.lock(p_id,3) + if test: + dummy = list(self.SpShared.get_results().values()) + if len(dummy)>0: + self.setpoint = dummy[-1] + + self.SpShared.unlock() + + return self.setpoint + + def set_error(self,error,p_id): + + test=self.ErrShared.lock(p_id,3) + if test: + self.ErrShared.clear_results() + self.ErrShared.add_result(p_id,error) + self.ErrShared.unlock() + + + def get_error(self,p_id): + + test =self.ErrShared.lock(p_id,3) + if test: + dummy = list(self.ErrShared.get_results().values()) + if len(dummy)>0: + self.error = dummy[-1] + + self.ErrShared.unlock() + + return self.error + + def set_control_signal(self,control_signal,p_id): + + test=self.CrtlShared.lock(p_id,3) + if test: + self.CrtlShared.clear_results() + self.CrtlShared.add_result(p_id,control_signal) + + self.CrtlShared.unlock() + + + def get_control_signal(self,p_id): + + test =self.CrtlShared.lock(p_id,3) + if test: + dummy = list(self.CrtlShared.get_results().values()) + if len(dummy)>0: + self.control_signal = dummy[-1] + self.CrtlShared.unlock() + + return self.control_signal + + def set_actual_value(self,actual_value,p_id): + + test=self.ActShared.lock(p_id,3) + if test: + self.ActShared.clear_results() + self.ActShared.add_result(p_id,actual_value) + self.actual_values.append(actual_value) + self.ActShared.unlock() + + + def get_actual_value(self,p_id): + + test =self.ActShared.lock(p_id,3) + if test: + dummy = list(self.ActShared.get_results().values()) + if len(dummy)>0: + self.actual_value = dummy[-1] + + self.ActShared.unlock() + + return self.actual_value + +class RLWorkflow(Workflow): + + def __init__(self, p_name: str = None, p_range_max=Async.C_RANGE_THREAD, p_class_shared=None, p_visualize: bool = False, p_logging=Log.C_LOG_ALL, **p_kwargs): + super().__init__(p_name, p_range_max, p_class_shared, p_visualize, p_logging, **p_kwargs) + + + +class RLSecenario(ScenarioBase): + + def __init__(self, p_mode, p_id=None, p_cycle_limit=0, p_auto_setup: bool = True, p_visualize: bool = True, p_logging=Log.C_LOG_ALL): + super().__init__(p_mode, p_id, p_cycle_limit, p_auto_setup, p_visualize, p_logging) + + + def setup(self): + + """ + Specialized method to set up a stream scenario. It is automatically called by the constructor + and calls in turn the custom method _setup(). + """ + + self._workflow = self._setup( p_mode=self.get_mode(), + p_visualize=self.get_visualization(), + p_logging=Log.C_LOG_NOTHING)#self.get_log_level() ) + + + def _setup(self, p_mode, p_visualize:bool, p_logging): + + + # 2 Set up a stream workflow + wf = RLWorkflow(p_name="wf1",p_range_max=Workflow.C_RANGE_THREAD,p_class_shared=MasterShared) + + t1 = Link(p_name="t1",logging=Log.C_LOG_NOTHING) + t3 = ProcessTask(p_name="t2",logging=Log.C_LOG_NOTHING) + t2 = PIDTask(10,100,250,p_name="t3",logging=Log.C_LOG_NOTHING) + + wf._so.set_actual_value(15.0,self.get_id()) + wf._so.set_setpoint(22.0,self.get_id()) + + # 2.1 Set up and add an own custom task + wf.add_task( p_task=t1 ) + wf.add_task( p_task=t2) + wf.add_task( p_task=t3 ) + + # 3 Return stream and workflow + return wf + + def get_latency(self) -> timedelta: + return None + + + def _run_cycle(self): + + """ + Gets next instance from the stream and lets process it by the stream workflow. + + Returns + ------- + success : bool + True on success. False otherwise. + error : bool + True on error. False otherwise. + adapted : bool + True, if something within the scenario has adapted something in this cycle. False otherwise. + end_of_data : bool + True, if the end of the related data source has been reached. False otherwise. + """ + + try: + self._workflow.run( p_range=Workflow.C_RANGE_THREAD, p_wait=True) #alt p_wait=True + end_of_data = False + except StopIteration: + end_of_data = True + + return False, False, False, end_of_data + + +cycle_limit = 5000 + +logging = Log.C_LOG_NOTHING +visualize = False + +myscenario = RLSecenario( p_mode=Mode.C_MODE_SIM, + p_cycle_limit=cycle_limit, + p_visualize=visualize, + p_logging=logging ) + + +myscenario.run() +temperature=myscenario._workflow._so.actual_values +# Plotten der Ergebnisse +plt.plot([i for i in range(len(temperature))], temperature, label='Raumtemperatur') +#plt.plot(time,setpoints, color='r', linestyle='--', label='Sollwert') +plt.xlabel('Zeit (Minuten)') +plt.ylabel('Temperatur (°C)') +plt.title('Temperaturregelung mit normiertem PID-Algorithmus') +plt.legend() +plt.grid(True) +plt.show() +input('Press ENTER to exist...') + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mlpro/bf/control/__init__.py b/src/mlpro/bf/control/__init__.py new file mode 100644 index 000000000..eace87d6b --- /dev/null +++ b/src/mlpro/bf/control/__init__.py @@ -0,0 +1 @@ +from mlpro.bf.control.basics import * \ No newline at end of file diff --git a/src/mlpro/bf/control/basics.py b/src/mlpro/bf/control/basics.py new file mode 100644 index 000000000..59d2c29d1 --- /dev/null +++ b/src/mlpro/bf/control/basics.py @@ -0,0 +1,885 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.control +## -- Module : basics.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-08-31 0.0.0 DA Creation +## -- 2024-09-04 0.1.0 DA Updates on class design +## -- 2024-09-07 0.2.0 DA Classes CTRLError, Controller: design updates +## -- 2024-09-11 0.3.0 DA - class CTRLError renamed ControlError +## -- - new class ControlPanel +## -- 2024-09-27 0.4.0 DA Class ControlPanel: new parent EventManager +## -- 2024-10-04 0.5.0 DA Updates on class Controller +## -- 2024-10-06 0.6.0 DA New classes ControlTask, Operator +## -- 2024-10-07 0.7.0 DA - new method ControlShared.get_tstamp() +## -- - refactoring of class Controller +## -- 2024-10-08 0.8.0 DA Classes ControlPanel, ControlShared: refactoring +## -- 2024-10-10 0.9.0 DA - class Controller: bugfix in method compute_output() +## -- - class ControlWorkflow: method run() redefined +## -- 2024-10-13 0.10.0 DA Refactoring: changed parent of class Action to Instance +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.10.0 (2024-10-13) + +This module provides basic classes around the topic closed-loop control. + +""" + +from typing import Iterable +from datetime import datetime + +from matplotlib.figure import Figure + +from mlpro.bf.plot import PlotSettings +from mlpro.bf.various import Log, TStampType +from mlpro.bf.mt import Range, Task +from mlpro.bf.ops import Mode +from mlpro.bf.events import Event, EventManager +from mlpro.bf.math import Element, Function, MSpace +from mlpro.bf.streams import InstDict, InstTypeNew, Instance, StreamTask, StreamWorkflow, StreamShared, StreamScenario +from mlpro.bf.systems import Action, System + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlData (Instance): + """ + Root class for all types of control data. + + Parameters + ---------- + p_id : int, + Instance id. + p_value_space : MSpace + Metric space of the values. + p_values : Iterable + Values. + p_tstamp : TStampType = None + Optional time stamp. + **p_kwargs + Optional further keyword arguments. + """ + + C_TYPE = 'Control Data' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_id : int, + p_value_space : MSpace, + p_values : Iterable = None, + p_tstamp: TStampType = None, + **p_kwargs ): + + feature_element = Element( p_set = p_value_space ) + feature_element.set_values( p_values = p_values ) + + super().__init__( p_feature_data = feature_element, + p_label_data = None, + p_tstamp = p_tstamp, + **p_kwargs ) + + self.set_id( p_id = p_id ) + + +## ------------------------------------------------------------------------------------------------- + def _get_value_space(self): + return self.get_feature_data().get_related_set() + + +## ------------------------------------------------------------------------------------------------- + def _get_values(self): + return self.get_feature_data().get_values() + + +## ------------------------------------------------------------------------------------------------- + def _set_values(self, p_values): + self.get_feature_data().set_values( p_values = p_values) + + +## ------------------------------------------------------------------------------------------------- + def copy(self): + duplicate = self.__class__( p_id = self.get_id(), + p_value_space = self.value_space, + p_values = self.values, + p_tstamp=self.get_tstamp(), + p_kwargs=self._get_kwargs() ) + return duplicate + + +## ------------------------------------------------------------------------------------------------- + value_space = property( fget=_get_value_space ) + values = property( fget=_get_values, fset=_set_values ) + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class SetPoint (ControlData): + """ + Setpoint. + """ + + C_NAME = 'Setpoint' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlError (ControlData): + """ + Control error. + """ + + C_NAME = 'Control Error' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlVariable (ControlData): + """ + Output of a controller/input of a controlled system. + """ + + C_NAME = 'Control Variable' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlledVariable (ControlData): + """ + Output of a controlled system. + """ + + C_NAME = 'Controlled Variable' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlTask (StreamTask): + """ + Base class for all control tasks. + """ + + C_TYPE = 'Control Task' + +## ------------------------------------------------------------------------------------------------- + def _get_instance(self, p_inst: InstDict, p_type: type, p_remove: bool = False) -> Instance: + """ + Gets and optionally removes an instance of a particular type from the p_inst dictionary. + + Parameters + ---------- + p_inst: InstDict + Dictionary of instances. + p_type: type + Type of instance to be found. + p_remove: bool = False + If true, the found instance is removed. + """ + + inst_found : Instance = None + + for (inst_type, inst) in p_inst.values(): + if isinstance( inst, p_type): + inst_found = inst + break + + if ( p_remove ) and ( inst_found is not None ): + del p_inst[inst_found.id] + + return inst_found + + +## ------------------------------------------------------------------------------------------------- + def reset(self, p_seed = None, **p_kwargs): + self._reset( p_seed = p_seed, **p_kwargs ) + + +## ------------------------------------------------------------------------------------------------- + def _reset(self, p_seed = None, **p_kwargs): + pass + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class Operator (ControlTask): + """ + Base class for all operators. + """ + + C_TYPE = 'Operator' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_range_max=Task.C_RANGE_THREAD, + p_visualize = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs ): + + super().__init__( p_name = self.C_NAME, + p_range_max = p_range_max, + p_duplicate_data = False, + p_visualize = p_visualize, + p_logging = p_logging, + **p_kwargs ) + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class Controller (ControlTask): + """ + Template class for closed-loop controllers. + + Parameters + ---------- + p_input_space : MSpace + Input (or error) space of the controller. + p_output_space : MSpace + Output (or action) space of the controller. + p_id = None + Unique id of the controller + ... + """ + + C_TYPE = 'Controller' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_input_space : MSpace, + p_output_space : MSpace, + p_id = None, + p_name: str = None, + p_range_max = Task.C_RANGE_NONE, + p_visualize: bool = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs ): + + self._input_space : MSpace = p_input_space + self._output_space : MSpace = p_output_space + + super().__init__( p_name = p_name, + p_range_max = p_range_max, + p_duplicate_data = False, + p_visualize = p_visualize, + p_logging = p_logging, + **p_kwargs ) + + if p_id is not None: + self.id = p_id + + +## ------------------------------------------------------------------------------------------------- + def set_parameter(self, **p_param): + """ + Custom method to set/change the parameters of a specific controller implementation. + + Parameters + ---------- + **p_param + Parameters of the controller. + """ + + pass + + +## ------------------------------------------------------------------------------------------------- + def _run(self, p_inst: InstDict): + + # 1 Get control error instance + ctrl_error = self._get_instance( p_inst = p_inst, p_type = ControlError, p_remove = True ) + if ctrl_error is None: + self.log(Log.C_LOG_TYPE_E, 'Control error instance is missing!') + return + + # 2 Remove existing control variable from inst dictionary + self._get_instance( p_inst = p_inst, p_type = ControlVariable, p_remove = True ) + + # 3 Compute control output + ctrl_var = self.compute_output( p_ctrl_error = ctrl_error ) + + # 4 Complete and store new control variable + p_inst[ctrl_var.id] = (InstTypeNew, ctrl_var) + + +## ------------------------------------------------------------------------------------------------- + def compute_output( self, p_ctrl_error: ControlError ) -> ControlVariable: + """ + Computes a control variable based on an incoming control error. It creates a new control + variable and call the custom method _compute_output() to fill it with values. + + Parameters + ---------- + p_ctrl_error : ControlError + Control error. + + Returns + ------- + ControlVariable + ControlVariable. + """ + + # 1 Create new control variable + ctrl_var = ControlVariable( p_id = self.get_so().get_next_inst_id(), + p_value_space = self._output_space ) + + # 2 Call custom method to fill the new action element + self._compute_output( p_ctrl_error = p_ctrl_error, p_ctrl_var = ctrl_var ) + + # 3 Complete and return the new control variable + ctrl_var.tstamp = self.get_so().get_tstamp() + return ctrl_var + + +## ------------------------------------------------------------------------------------------------- + def _compute_output( self, p_ctrl_error : ControlError, p_ctrl_var : ControlVariable ): + """ + Custom method to compute a control output based on an incoming control error. The result needs + to be stored in the control variable element handed over. + + Parameters + ---------- + p_ctrl_error : CTRLError + Control error. + p_ctrl_var : ControlVariable + Control variable to be filled with resulting value(s). + """ + + raise NotImplementedError + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControllerFct (Controller): + """ + Wrapper class for controllers based on a mathematical function mapping an error to an action. + + Parameters + ---------- + p_fct : Function + Function object mapping a control error to an action + + See class Controller for further parameters. + """ + + C_TYPE = 'Controller Fct' + C_NAME = '' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_fct: Function, + p_name: str = None, + p_range_max = Task.C_RANGE_NONE, + p_duplicate_data: bool = False, + p_visualize: bool = False, + p_logging = Log.C_LOG_ALL, + **p_kwargs ): + + super().__init__( p_name = p_name, + p_range_max = p_range_max, + p_duplicate_data = p_duplicate_data, + p_visualize = p_visualize, + p_logging = p_logging, + **p_kwargs ) + + self._fct : Function = p_fct + + +## ------------------------------------------------------------------------------------------------- + def set_parameter(self, **p_param): + + raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def _compute_outsput(self, p_ctrl_error: ControlError, p_ctrl_var: ControlVariable): + + raise NotImplementedError + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class MultiController: # (Controller, StreamWorkflow): + """ + """ + + C_TYPE = 'Multi-Controller' + C_NAME = '' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlledSystem (ControlTask): + """ + Wrapper class for state-based systems. + """ + + C_TYPE = 'Controlled System' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_system : System, + p_name: str = None, + p_range_max=Task.C_RANGE_NONE, + p_visualize: bool = False, + p_logging = Log.C_LOG_ALL, + **p_kwargs ): + + super().__init__( p_name = p_name, + p_range_max = p_range_max, + p_duplicate_data = False, + p_visualize = p_visualize, + p_logging = p_logging, + **p_kwargs ) + + self.system : System = p_system + + +## ------------------------------------------------------------------------------------------------- + def _run(self, p_inst: InstDict ): + + # 1 Get and remove control variable and controlled variable from instance dict + ctrl_var = self._get_instance( p_inst = p_inst, p_type = ControlVariable, p_remove = True ) + ctrlled_var = self._get_instance( p_inst = p_inst, p_type = ControlledVariable, p_remove = True ) + + action = Action( p_agent_id = 0, + p_action_space = ctrl_var.get_feature_data().get_related_set(), + p_values = ctrl_var.values, + p_tstamp = ctrl_var.tstamp ) + + + # 2 Let the wrapped system process the action + if self.system.process_action( p_action = action ): + state = self.system.get_state() + ctrlled_var = ControlledVariable( p_id = self.get_so().get_next_inst_id(), + p_value_space = self.system.get_state_space(), + p_values = state.values, + p_tstamp = self.get_so().get_tstamp() ) + p_inst[ctrlled_var.id] = ( InstTypeNew, ctrlled_var) + else: + self.log(Log.C_LOG_TYPE_E, 'Processing of control variable failed!') + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlPanel (EventManager): + """ + Enables external control of a closed-loop control. + """ + + C_TYPE = 'Control Panel' + C_NAME = '????' + + C_EVENT_ID_SETPOINT_CHG = 'SETPOINT_CHG' + + +## ------------------------------------------------------------------------------------------------- + def start(self): + """ + (Re-)starts a closed-loop control. + """ + + self.log(Log.C_LOG_TYPE_S, 'Control process started') + self._start() + + +## ------------------------------------------------------------------------------------------------- + def _start(self): + """ + Custom method to (re-)start a closed-loop control. + """ + + raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def stop(self): + """ + Ends a closed-loop control. + """ + + self.log(Log.C_LOG_TYPE_S, 'Control process stopped') + self._stop() + + +## ------------------------------------------------------------------------------------------------- + def _stop(self): + """ + Custom method to end a closed-loop control. + """ + + raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def set_setpoint( self, p_values: Iterable ): + """ + Changes the setpoint values of a closed-loop control. + + Parameters + ---------- + p_values : Iterable + New setpoint values. + """ + + self.log(Log.C_LOG_TYPE_S, 'Setpoint values changed to', p_values) + self._set_setpoint( p_values = p_values ) + self._raise_event( p_event_id = self.C_EVENT_ID_SETPOINT_CHG, + p_event_object = Event( p_raising_object = self ) ) + + +## ------------------------------------------------------------------------------------------------- + def _set_setpoint( self, p_values: Iterable ): + """ + Custom method to change setpoint values. + + Parameters + ---------- + p_values : Iterable + New setpoint values. + """ + + raise NotImplementedError + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlShared (StreamShared, ControlPanel, Log): + """ + ... + """ + + C_TID_ADMIN = 'wf' + +## ------------------------------------------------------------------------------------------------- + def __init__(self, p_range: int = Range.C_RANGE_PROCESS): + StreamShared.__init__(self, p_range=p_range) + Log.__init__(self, p_logging = Log.C_LOG_NOTHING) + self._next_inst_id = 0 + + +## ------------------------------------------------------------------------------------------------- + def init(self, p_ctrlled_var_space: MSpace, p_ctrl_var_space: MSpace): + """ + Initializes the shared object with contextual information. + + Parameters + ---------- + p_ctrlled_var_space : MSpace + Controlled variable space. + p_ctrl_var_space : MSpace + Control variable space. + """ + + self._ctrlled_var_space = p_ctrlled_var_space + self._ctrl_var_space = p_ctrl_var_space + + +## ------------------------------------------------------------------------------------------------- + def reset(self, p_inst: InstDict): + pass + + +## ------------------------------------------------------------------------------------------------- + def get_next_inst_id(self) -> int: + """ + Returns the next instance id. + + Returns + ------- + int + Next instance id. + """ + + self.lock( p_tid = self.C_TID_ADMIN ) + next_id = self._next_inst_id + self._next_inst_id += 1 + self.unlock() + return next_id + + +## ------------------------------------------------------------------------------------------------- + def get_tstamp(self) -> TStampType: + + # + # pseudo-implementation + # + + return datetime.now() + #raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def _start(self): + + raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def _stop(self): + + raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def _set_setpoint(self, p_values: Iterable): + + # 0 Intro + setpoint : SetPoint = None + self.lock( p_tid = self.C_TID_ADMIN ) + + + # 1 Locate the instance dictionary for the first control task + try: + inst_admin = self._instances[self.C_TID_ADMIN] + except: + inst_admin = {} + self._instances[self.C_TID_ADMIN] = inst_admin + + + # 2 Get or create a setpoint instance + for (inst_type, inst) in inst_admin.values(): + if isinstance(inst, SetPoint): + setpoint = inst + break + + if setpoint is None: + setpoint = SetPoint( p_id = self.get_next_inst_id(), + p_value_space = self._ctrlled_var_space, + p_values = p_values, + p_tstamp = self.get_tstamp() ) + inst_admin[setpoint.id] = (InstTypeNew, setpoint) + else: + setpoint.values = p_values + setpoint.tstamp = self.get_tstamp() + + + # 3 Outro + self.unlock() + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlWorkflow (StreamWorkflow, Mode): + """ + Container class for all tasks of a control cycle. + """ + + C_TYPE = 'Control Workflow' + C_NAME = '' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_mode, + p_name: str = None, + p_range_max = Task.C_RANGE_NONE, + p_class_shared = ControlShared, + p_visualize : bool = False, + p_logging = Log.C_LOG_ALL, + **p_kwargs ): + + StreamWorkflow.__init__( self, + p_name = p_name, + p_range_max = p_range_max, + p_class_shared = p_class_shared, + p_visualize = p_visualize, + p_logging = p_logging, + **p_kwargs ) + + Mode.__init__( self, + p_mode = p_mode, + p_logging = p_logging ) + + self.get_so().switch_logging( p_logging = p_logging ) + + +## ------------------------------------------------------------------------------------------------- + def get_control_panel(self) -> ControlPanel: + return self.get_so() + + +## ------------------------------------------------------------------------------------------------- + def set_mode(self, p_mode): + + if p_mode == self._mode: return + + Mode.set_mode( self, p_mode = p_mode ) + + for task in self.tasks: + try: + task.set_mode( p_mode = p_mode ) + except: + pass + + +## ------------------------------------------------------------------------------------------------- + def add_task(self, p_task: Task, p_pred_tasks: list = None): + StreamWorkflow.add_task( self, p_task = p_task, p_pred_tasks = p_pred_tasks) + + try: + p_task.set_mode( p_mode = self._mode ) + except: + pass + + +## ------------------------------------------------------------------------------------------------- + def run( self, + p_range : int = None, + p_wait: bool = False, + p_inst : InstDict = None ): + + # 1 Execute all tasks + StreamWorkflow.run( self, p_range = p_range, p_wait = p_wait, p_inst = p_inst) + + + # 2 Add the outcomes of the final task(s) to the instance dict of the initial task + so = self.get_so() + so.lock( p_tid = ControlShared.C_TID_ADMIN ) + + for inst_id, (inst_type, inst) in so._instances[ControlShared.C_TID_ADMIN].items(): + if isinstance(inst, SetPoint): + setpoint = inst + break + + del so._instances[ControlShared.C_TID_ADMIN] + new_setpoint = setpoint.copy() + new_setpoint.id = so.get_next_inst_id() + new_setpoint.tstamp = so.get_tstamp() + so._instances[ControlShared.C_TID_ADMIN] = { new_setpoint.id : (inst_type, new_setpoint) } + + for task in self._final_tasks: + so._instances[ControlShared.C_TID_ADMIN].update(so._instances[task.id]) + + so.unlock() + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlSystem (StreamScenario): + """ + ... + """ + + C_TYPE = 'Control System' + +## ------------------------------------------------------------------------------------------------- + def setup(self): + + # 1 Setup control workflow + self._control_workflow = self._setup( p_mode=self.get_mode(), + p_visualize=self.get_visualization(), + p_logging=self.get_log_level() ) + + + ## ------------------------------------------------------------------------------------------------- + def _setup(self, p_mode, p_visualize: bool, p_logging) -> ControlWorkflow: + """ + Custom method to set up a control workflow. Create a new object of type ControlWorkflow and + add all control tasks of your scenario. + + Parameters + ---------- + p_mode + Operation mode. See Mode.C_VALID_MODES for valid values. Default = Mode.C_MODE_SIM. + p_visualize : bool + Boolean switch for visualisation. + p_logging + Log level (see constants of class Log). Default: Log.C_LOG_ALL. + + Returns + ------- + ControlCycle + Object of type ControlWorkflow. + """ + + raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def _set_mode(self, p_mode): + self._control_workflow.set_mode( p_mode = p_mode) + + +## ------------------------------------------------------------------------------------------------- + def _reset(self, p_seed): + self._control_workflow.reset( p_seed = p_seed) + + +## ------------------------------------------------------------------------------------------------- + def get_control_panel(self) -> ControlPanel: + """ + Returns + ------- + panel : ControlPanel + Object that enables the external control of a closed-loop control process. + """ + + return self._control_workflow.get_control_panel() + + +## ------------------------------------------------------------------------------------------------- + def _run_cycle(self): + + error = False + + try: + self._control_workflow.run() + except KeyError as Argument: + error = True + if Argument.args[0] == ControlShared.C_TID_ADMIN: + self.log(Log.C_LOG_TYPE_E, 'Setpoint missing') + else: + self.log(Log.C_LOG_TYPE_E, 'Control instance missing for task', Argument.args[0]) + + return False, error, False, False + + +## ------------------------------------------------------------------------------------------------- + def init_plot(self, p_figure: Figure = None, p_plot_settings: PlotSettings = None): + self._control_workflow.init_plot( p_figure = p_figure, + p_plot_settings = p_plot_settings ) + \ No newline at end of file diff --git a/src/mlpro/bf/control/controllers/__init__.py b/src/mlpro/bf/control/controllers/__init__.py new file mode 100644 index 000000000..77fc305cd --- /dev/null +++ b/src/mlpro/bf/control/controllers/__init__.py @@ -0,0 +1 @@ +from mlpro.bf.control.controllers.hunter import Hunter \ No newline at end of file diff --git a/src/mlpro/bf/control/controllers/hunter.py b/src/mlpro/bf/control/controllers/hunter.py new file mode 100644 index 000000000..df0a9dbf8 --- /dev/null +++ b/src/mlpro/bf/control/controllers/hunter.py @@ -0,0 +1,36 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.control.controllers +## -- Module : hunter.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-10 0.1.0 DA Initial implementation +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.1.0 (2024-10-10) + +This module provides a simple demo system that just cumulates a percentage part of the incoming +action to the inner state. +""" + + +import numpy as np + +from mlpro.bf.control import Controller, ControlError, ControlVariable + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class Hunter (Controller): + """ + ... + """ + + C_NAME = 'Hunter' + +## ------------------------------------------------------------------------------------------------- + def _compute_output(self, p_ctrl_error : ControlError, p_ctrl_var : ControlVariable ): + p_ctrl_var.values = np.array(p_ctrl_error.values) * (-1) \ No newline at end of file diff --git a/src/mlpro/bf/control/controllers/pid_controller.py b/src/mlpro/bf/control/controllers/pid_controller.py new file mode 100644 index 000000000..dfe17b96d --- /dev/null +++ b/src/mlpro/bf/control/controllers/pid_controller.py @@ -0,0 +1,227 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.control.controller +## -- Module : pid_controller.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-01 0.0.0 DA Creation +## -- 2024-09-19 0.0.0 ASP Implementation PIDController +## -- 2024-10-17 0.0.0 ASP Refactor PIDController +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2024-09-01) + +This module provides an implementation of a PID controller. + +Learn more: + +https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller + +""" + +from mlpro.bf.math.basics import MSpace +from mlpro.bf.mt import Log, Task +from mlpro.bf.control.basics import ControlError, Controller +from mlpro.bf.systems.basics import ActionElement +import numpy as np + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class PIDController (Controller): + + """ + PID Controller for closed-loop controllers. + + Parameters + ---------- + Kp : float + gain factor + Ti : float + settling time [s] + Tv : float + dead time [s] + disable_integral: Bool + disable integral term + disable_derivitave: Bool + disable derivitave term + enable_anti_windup: float + enable anti windup filter + + ... + """ + +## ------------------------------------------------------------------------------------------------- + def __init__(self, + p_input_space: MSpace, + p_output_space: MSpace, + Kp: float, + Ti: float = 0.0 , + Tv: float= 0.0, + disable_integral:bool = False, + disable_derivitave:bool = False, + enable_anti_windup: bool = False, + windup_limit:float =0, + p_id=None, p_name: str = None, + p_range_max=Task.C_RANGE_NONE, + p_visualize: bool = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs): + + super().__init__(p_input_space, + p_output_space, + p_id, p_name, p_range_max, + p_visualize, p_logging, + **p_kwargs) + + self.Kp = Kp + self.Ti = Ti + self.Tv = Tv + self.disable_integral = disable_integral + self.disable_derivitave = disable_derivitave + self.integral = 0.0 + self.prev_error = 0.0 + self.previous_tstamp = None + self.enable_windup = enable_anti_windup + self.windup_limit = windup_limit + self.output_limits = p_output_space.get_dims()[0].get_boundaries() + + +## ------------------------------------------------------------------------------------------------- + def set_parameter(self, **p_param): + """ + Sets/changes the parameters of the PID controller. + + Parameters + ---------- + Kp : float + gain factor + Ti : float + settling time + Tv : float + dead time + """ + + # set kp value + self.Kp = p_param['p_param']['Kp'] + # set Ti value + self.Ti = p_param['p_param']['Ti'] + #set Tv value + self.Tv =p_param['p_param']['Tv'] + + +## ------------------------------------------------------------------------------------------------- + def get_parameter_values(self)-> np.ndarray: + return np.array([self.Kp,self.Ti,self.Tv]) + + +## ------------------------------------------------------------------------------------------------- + def _compute_output(self, p_ctrl_error: ControlError, p_ctrl_var: ActionElement): + + """ + Custom method to compute and an action based on an incoming control error. The result needs + to be stored in the action element handed over. I/O values can be accessed as follows: + + SISO + ---- + Get single error value: control_error_siso = p_ctrl_error.values[0] + Set single action value: p_action_element.values[p_ae_id] = action_siso + + + Parameters + ---------- + p_ctrl_error : CTRLError + Control error. + p_ctrl_var : ActionElement + Control element to be filled with resulting control value(s). + """ + + #get control error + control_error_siso = p_ctrl_error._get_values()[0] + + #time delta + dt = 0 + + #get the time stamp + tstamp = p_ctrl_error.get_tstamp() + + #calculate time difference + if self.previous_tstamp is not None: + dt = tstamp- self.previous_tstamp + dt = dt.total_seconds() + + #propertional term + p_term = self.Kp * control_error_siso + + #integral term + i_term = 0 + + #ignore i term , if it is disabled + if not self.disable_integral: + + #calculat integral term + self.integral += control_error_siso*dt + + # anti - windup + if self.enable_windup and self.windup_limit is not None: + self.integral = max(min(self.integral, self.windup_limit), -self.windup_limit) + + #calculate i term , if Ti not zero + if self.Ti != 0: + i_term = (self.Kp/self.Ti)* self.integral + + # derivitave term + d_term =0 + + #ignore i term , if it is disabled or delta is equal zero + if dt> 0 and not self.disable_derivitave: + d_term = self.Kp*self.Tv*(control_error_siso- self.prev_error)/dt + + #compute control variable value + control_variable_siso = p_term+i_term+d_term + + #apply control variable limits + lower_bound, upper_bound = tuple(self.output_limits) + + control_variable_siso = min(max(control_variable_siso,lower_bound), upper_bound) + + # safe the current values for the next iteration + self.prev_error = control_error_siso + self.previous_tstamp = tstamp + + #set control value + p_ctrl_var._set_values([control_variable_siso]) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mlpro/bf/control/controlsystems/__init__.py b/src/mlpro/bf/control/controlsystems/__init__.py new file mode 100644 index 000000000..234b5f171 --- /dev/null +++ b/src/mlpro/bf/control/controlsystems/__init__.py @@ -0,0 +1 @@ +from mlpro.bf.control.controlsystems.basic import ControlSystemBasic \ No newline at end of file diff --git a/src/mlpro/bf/control/controlsystems/basic.py b/src/mlpro/bf/control/controlsystems/basic.py new file mode 100644 index 000000000..85ddbfd55 --- /dev/null +++ b/src/mlpro/bf/control/controlsystems/basic.py @@ -0,0 +1,110 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.control.control_scenarios +## -- Module : basic.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-04 0.1.0 DA Initial implementation +## -- 2024-10-09 0.2.0 DA Refactoring +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.2.0 (2024-10-09) + +This module provides a simplified container class for a basic synchronous control system containing + +- a controller +- a controlled system +- an optional integrator for the control variable + +""" + + +from mlpro.bf.control.basics import ControlWorkflow +from mlpro.bf.various import Log +from mlpro.bf.control import Controller, ControlledSystem, ControlSystem +from mlpro.bf.control.operators import Comparator, Integrator + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class ControlSystemBasic (ControlSystem): + """ + Simplified container class for a basic synchronous control system containing + + - a controller + - a controlled system + - an optional integrator for the control variable + + Parameters + ---------- + p_controller : Controller + Controller to be used in the control workflow + p_controlled_system : ControlledSystem + Controlled system to be used in the control workflow + p_ctrl_var_integration : bool = False + If True, an optional intrator is added to control workflow + """ + + C_TYPE = 'Control System Basic' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_controller : Controller, + p_controlled_system : ControlledSystem, + p_mode, + p_ctrl_var_integration : bool = False, + p_cycle_limit = 0, + p_visualize : bool = False, + p_logging = Log.C_LOG_ALL ): + + self._controller = p_controller + self._controlled_system = p_controlled_system + self._ctrl_var_integration = p_ctrl_var_integration + + super().__init__( p_mode = p_mode, + p_cycle_limit = p_cycle_limit, + p_visualize = p_visualize, + p_logging = p_logging ) + + +## ------------------------------------------------------------------------------------------------- + def _setup(self, p_mode, p_visualize: bool, p_logging) -> ControlWorkflow: + + # 1 Create a new control cycle + control_workflow = ControlWorkflow( p_mode = p_mode, + p_visualize = p_visualize, + p_logging = p_logging ) + + + # 2 Create and add a comparator + comparator = Comparator( p_visualize = p_visualize, p_logging = p_logging ) + control_workflow.add_task( p_task = comparator ) + + + # 3 Add the controller + control_workflow.add_task( p_task = self._controller, p_pred_tasks = [comparator] ) + + + # 4 Optionally create and add an integrator + if self._ctrl_var_integration: + integrator = Integrator( p_visualize = p_visualize, p_logging = p_logging ) + control_workflow.add_task( p_task = integrator, p_pred_tasks = [self._controller] ) + pred_sys = integrator + + else: + pred_sys = self._controller + + + # 5 Add the controlled system + control_workflow.add_task( p_task = self._controlled_system, p_pred_tasks = [pred_sys] ) + self._controlled_system.system.set_mode( p_mode = p_mode ) + + + # 6 Initialize and return the prepared control workflow + control_workflow.get_so().init( p_ctrlled_var_space = self._controlled_system.system.get_state_space(), + p_ctrl_var_space = self._controlled_system.system.get_action_space() ) + + return control_workflow \ No newline at end of file diff --git a/src/mlpro/bf/control/operators/__init__.py b/src/mlpro/bf/control/operators/__init__.py new file mode 100644 index 000000000..55e56592a --- /dev/null +++ b/src/mlpro/bf/control/operators/__init__.py @@ -0,0 +1,2 @@ +from mlpro.bf.control.operators.comparator import Comparator +from mlpro.bf.control.operators.integrator import Integrator \ No newline at end of file diff --git a/src/mlpro/bf/control/operators/comparator.py b/src/mlpro/bf/control/operators/comparator.py new file mode 100644 index 000000000..62de828a6 --- /dev/null +++ b/src/mlpro/bf/control/operators/comparator.py @@ -0,0 +1,85 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.control.operators +## -- Module : comparator.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-06 0.1.0 DA Creation and initial implementation +## -- 2024-10-08 0.2.0 DA Validation and various changes +## -- 2024-10-09 0.3.0 DA Refactoring +## -- 2024-10-13 0.4.0 DA Refactoring +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.4.0 (2024-10-13) + +This module provides an implementation of a comparator that determins the control error based on +setpoint and controlled variable (system state). + +""" + +import numpy as np + +from mlpro.bf.various import Log +from mlpro.bf.math import Element +from mlpro.bf.streams import InstDict, InstTypeNew +from mlpro.bf.control import SetPoint, ControlledVariable, ControlError, Operator + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class Comparator (Operator): + """ + The comparator computes the control error based on the current setpoint and controlled variable. + It consumes (not to say: removes) the current controlled variable and replaces it by a control error. + """ + + C_NAME = 'Comparator' + +## ------------------------------------------------------------------------------------------------- + def _run(self, p_inst: InstDict): + + # 1 Get setpoint + setpoint : SetPoint = self._get_instance( p_inst = p_inst, p_type = SetPoint, p_remove = True ) + if setpoint is None: + self.log(Log.C_LOG_TYPE_E, 'Setpoint missing!') + return + + + # 2 Get and remove current controlled variable + ctrlled_var : ControlledVariable = self._get_instance( p_inst = p_inst, p_type = ControlledVariable, p_remove = True ) + if ctrlled_var is None: + self.log(Log.C_LOG_TYPE_W, 'Controlled variable missing!') + ctrlled_var = setpoint + + + # 3 Compute control error + control_error = self.get_control_error( p_setpoint = setpoint, p_ctrlled_var = ctrlled_var ) + p_inst[control_error.id] = (InstTypeNew, control_error) + + +## ------------------------------------------------------------------------------------------------- + def get_control_error(self, p_setpoint: SetPoint, p_ctrlled_var: ControlledVariable ) -> ControlError: + """ + Returns a new control error as the difference betweeen a specified setpoint and controlled + variable. + + Parameters + ---------- + p_setpoint : SetPoint + Setpoint object. + p_ctrlled_var : ControlledVariable + Controlled variable object. + + Returns + ------- + ControlError + New control error object. + """ + + return ControlError( p_id = self.get_so().get_next_inst_id(), + p_value_space = p_ctrlled_var.value_space, + p_values = np.subtract( np.array(p_ctrlled_var.values), np.array(p_setpoint.values) ), + p_tstamp = self.get_so().get_tstamp() ) diff --git a/src/mlpro/bf/control/operators/integrator.py b/src/mlpro/bf/control/operators/integrator.py new file mode 100644 index 000000000..90d03ee62 --- /dev/null +++ b/src/mlpro/bf/control/operators/integrator.py @@ -0,0 +1,94 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.control.operators +## -- Module : integrator.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-07 0.1.0 DA Creation and initial implementation +## -- 2024-10-09 0.2.0 DA Refactoring +## -- 2024-10-13 0.3.0 DA Refactoring +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.3.0 (2024-10-13) + +This module provides an implementation of an integrator that determins the next control variable by +buffering and cumulating it. + +""" + +import numpy as np + +from mlpro.bf.math.basics import Log +from mlpro.bf.mt import Log, Task +from mlpro.bf.streams import InstDict, InstTypeNew +from mlpro.bf.control import ControlVariable, Operator + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class Integrator (Operator): + """ + Operator that determins the next control action by buffering and cumulating it. The origin action + provided by a controller is replaced. + """ + + C_NAME = 'Cumulator' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_range_max=Task.C_RANGE_THREAD, + p_visualize: bool = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs ): + + self._ctrl_var : ControlVariable = None + + super().__init__( p_range_max = p_range_max, + p_visualize = p_visualize, + p_logging = p_logging, + **p_kwargs ) + + +## ------------------------------------------------------------------------------------------------- + def _run(self, p_inst: InstDict): + + ctrl_var = self._get_instance( p_inst = p_inst, p_type = ControlVariable, p_remove = True ) + + if ctrl_var is None: + self.log(Log.C_LOG_TYPE_E, 'Control variable not found') + return + + ctrl_var_int = self.integrate( p_ctrl_var = ctrl_var ) + + p_inst[ctrl_var_int.id] = (InstTypeNew, ctrl_var_int ) + + +## ------------------------------------------------------------------------------------------------- + def integrate(self, p_ctrl_var : ControlVariable) -> ControlVariable: + """ + Numerically integrates the incoming control variable. + + Parameters + ---------- + p_ctrl_var : ControlVariable + Control variable to be cumulated. + + Returns + ------- + ControlVariable + Integrated control variable. + """ + + if self._ctrl_var is None: + self._ctrl_var = p_ctrl_var.copy() + else: + self._ctrl_var.values = np.add( np.array(self._ctrl_var.values), np.array(p_ctrl_var.values) ) + + ctrl_var_int = self._ctrl_var.copy() + ctrl_var_int.id = self.get_so().get_next_inst_id() + + return ctrl_var_int \ No newline at end of file diff --git a/src/mlpro/bf/ml/basics.py b/src/mlpro/bf/ml/basics.py index a1080caa4..96e5fa14e 100644 --- a/src/mlpro/bf/ml/basics.py +++ b/src/mlpro/bf/ml/basics.py @@ -492,7 +492,6 @@ def _add_objective(self, **p_kwargs): raise NotImplementedError - ## ------------------------------------------------------------------------------------------------- def get_accuracy(self) -> float: """ diff --git a/src/mlpro/bf/mt.py b/src/mlpro/bf/mt.py index 47dcc93ad..1cf3be41e 100644 --- a/src/mlpro/bf/mt.py +++ b/src/mlpro/bf/mt.py @@ -36,10 +36,11 @@ ## -- 2024-05-31 1.9.2 DA Class Task: new exception rule for MacOs in meth. init_plot() ## -- 2024-06-17 2.0.0 DA Class Workflow: new method get_tasks() ## -- 2024-06-18 2.1.0 DA Class Task: new parent class KWArgs +## -- 2024-10-07 2.2.0 DA Classes Task, Workflow: new method reset() ## ------------------------------------------------------------------------------------------------- """ -Ver. 2.1.0 (2024-06-18) +Ver. 2.2.0 (2024-10-07) This module provides classes for multitasking with optional interprocess communication (IPC) based on shared objects. Multitasking in MLPro combines multrithreading and multiprocessing and simplifies @@ -559,6 +560,11 @@ def get_tid(self): """ return self.get_id() + + +## ------------------------------------------------------------------------------------------------- + def reset(self, **p_kwargs): + pass ## ------------------------------------------------------------------------------------------------- @@ -910,6 +916,12 @@ def get_tasks(self) -> list: return self._tasks +## ------------------------------------------------------------------------------------------------- + def reset(self, **p_kwargs): + for task in self._tasks: + task.reset(**p_kwargs) + + ## ------------------------------------------------------------------------------------------------- def _get_plot_host_task(self, p_task : Task) -> Task: plot_host = None diff --git a/src/mlpro/bf/ops.py b/src/mlpro/bf/ops.py index edf19135f..8bdba6d94 100644 --- a/src/mlpro/bf/ops.py +++ b/src/mlpro/bf/ops.py @@ -304,6 +304,7 @@ def run_cycle(self): # 3 Update visualization + if self._visualize: self.update_plot() diff --git a/src/mlpro/bf/streams/basics.py b/src/mlpro/bf/streams/basics.py index aee0b741a..429761f25 100644 --- a/src/mlpro/bf/streams/basics.py +++ b/src/mlpro/bf/streams/basics.py @@ -68,12 +68,14 @@ ## -- 2024-06-07 2.0.2 LSB Fixing timedelta handling in ND plotting ## -- 2024-07-19 2.0.3 DA Class StreamTask: excluded non-numeric feature data from default ## -- visualization 2D,3D,ND +## -- 2024-09-11 2.1.0 DA Class Instance: new parent KWArgs +## -- 2024-10-01 2.1.1 DA Method StreamScenario.__init__(): simplification ## ------------------------------------------------------------------------------------------------- """ -Ver. 2.0.3 (2024-07-19) +Ver. 2.1.1 (2024-10-01) -This module provides classes for standardized stream processing. +This module provides classes for standardized data stream processing. """ import datetime @@ -83,7 +85,7 @@ from typing import Dict, Tuple from mlpro.bf.math.basics import * -from mlpro.bf.various import * +from mlpro.bf.various import Id, TStamp, KWArgs from mlpro.bf.ops import Mode, ScenarioBase from mlpro.bf.plot import PlotSettings from mlpro.bf.math import Dimension, Element @@ -113,7 +115,7 @@ class Label (Dimension): pass ## ------------------------------------------------------------------------------------------------- ## ------------------------------------------------------------------------------------------------- -class Instance (Id, TStamp): +class Instance (Id, TStamp, KWArgs): """ Instance class to store the current instance and the corresponding labels of the stream @@ -141,8 +143,7 @@ def __init__( self, self._feature_data = p_feature_data self._label_data = p_label_data TStamp.__init__(self, p_tstamp=p_tstamp) - # Id.__init__(self, p_id = 0) - self._kwargs = p_kwargs.copy() + KWArgs.__init__(self, **p_kwargs) ## ------------------------------------------------------------------------------------------------- @@ -173,7 +174,7 @@ def set_label_data(self, p_label_data:Element): ## ------------------------------------------------------------------------------------------------- def get_kwargs(self): - return self._kwargs + return self._get_kwargs() ## ------------------------------------------------------------------------------------------------- @@ -181,7 +182,7 @@ def copy(self): duplicate = self.__class__( p_feature_data=self.get_feature_data().copy(), p_label_data=self.get_label_data(), p_tstamp=self.get_tstamp(), - p_kwargs=self._kwargs ) + p_kwargs=self._get_kwargs() ) duplicate.id = self.id return duplicate @@ -390,7 +391,9 @@ def _omit_instance(self, p_inst:Instance) -> bool: Parameters ---------- - p_inst : Instance + p_inst : Instancep_set = {} +p_set[0] = tuple([1,Instance(12)]) +print(p_set) An input instance to be filtered. Returns @@ -1225,8 +1228,14 @@ def _update_plot_2d( self, # 5 Update of ax limits if ax_limits_changed: - p_settings.axes.set_xlim( self._plot_2d_xmin, self._plot_2d_xmax ) - p_settings.axes.set_ylim( self._plot_2d_ymin, self._plot_2d_ymax ) + try: + p_settings.axes.set_xlim( self._plot_2d_xmin, self._plot_2d_xmax ) + except: + pass + try: + p_settings.axes.set_ylim( self._plot_2d_ymin, self._plot_2d_ymax ) + except: + pass ## ------------------------------------------------------------------------------------------------- @@ -1655,10 +1664,6 @@ def __init__( self, p_visualize:bool=False, p_logging=Log.C_LOG_ALL ): - self._stream : Stream = None - self._iterator : Stream = None - self._workflow : StreamWorkflow = None - ScenarioBase.__init__( self, p_mode, p_cycle_limit=p_cycle_limit, diff --git a/src/mlpro/bf/systems/basics.py b/src/mlpro/bf/systems/basics.py index 444d1623d..1d97fb697 100644 --- a/src/mlpro/bf/systems/basics.py +++ b/src/mlpro/bf/systems/basics.py @@ -45,23 +45,29 @@ ## -- 2023-05-01 2.0.0 LSB New class MultiSystem ## -- 2024-05-14 2.0.1 SY Migration from MLPro to MLPro-Int-MuJoCo ## -- 2024-05-24 2.1.0 DA Class State: removed parent class TStamp +## -- 2024-09-07 2.2.0 DA - Class ActionElement: new property values +## -- - Renamed Class Controller to SAGateway +## -- - Renamed method System.add_controller to add_sagateway +## -- 2024-09-09 2.3.0 DA Class Action: parent TSTamp replaced by Instance +## -- 2024-09-11 2.4.0 DA - code review and documentation +## -- - new method State.get_kwargs() +## -- 2024-10-06 2.5.0 DA New property attribute State.value ## ------------------------------------------------------------------------------------------------- """ -Ver. 2.1.0 (2024-05-24) +Ver. 2.5.0 (2024-10-06) This module provides models and templates for state based systems. """ from time import sleep -from typing import List import numpy as np from mlpro.bf.mt import Range from mlpro.bf.streams.basics import Instance -from mlpro.bf.various import TStamp, ScientificObject, Persistent +from mlpro.bf.various import ScientificObject, Persistent from mlpro.bf.data import * from mlpro.bf.plot import Plottable, PlotSettings from matplotlib.figure import Figure @@ -71,6 +77,7 @@ + ## ------------------------------------------------------------------------------------------------- ## ------------------------------------------------------------------------------------------------- class State(Instance, Element): @@ -92,6 +99,8 @@ class State(Instance, Element): This optional flag labels the state as a final error state. Default=False. p_timeout : bool This optional flag signals that the cycle limit of an episode has been reached. Default=False. + p_kwargs : dict + Further optional named parameters. """ ## ------------------------------------------------------------------------------------------------- @@ -177,6 +186,11 @@ def set_feature_data(self, p_feature_data: Element): self.set_values(p_feature_data.get_values()) +## ------------------------------------------------------------------------------------------------- + def get_kwargs(self): + return self._get_kwargs() + + ## ------------------------------------------------------------------------------------------------- def copy(self): """ @@ -209,6 +223,9 @@ def copy(self): return copied_state +## ------------------------------------------------------------------------------------------------- + values = property( fget=Element.get_values, fset=Element.set_values) + @@ -222,16 +239,16 @@ class ActionElement (Element): ---------- p_action_space : Set Related action space. - p_weight : float + p_weight : float = 1.0 Weight of action element. Default = 1.0. """ ## ------------------------------------------------------------------------------------------------- def __init__( self, p_action_space : Set, - p_weight : float = 1.0): + p_weight : float = 1.0 ): - super().__init__(p_action_space) + Element.__init__(self, p_action_space) self.set_weight(p_weight) @@ -241,21 +258,24 @@ def get_weight(self): ## ------------------------------------------------------------------------------------------------- - def set_weight(self, p_weight): + def set_weight(self, p_weight : float): self.weight = p_weight +## ------------------------------------------------------------------------------------------------- + values = property( fget=Element.get_values, fset=Element.set_values) + + ## ------------------------------------------------------------------------------------------------- ## ------------------------------------------------------------------------------------------------- -class Action(ElementList, TStamp): +class Action(Instance, ElementList): """ - Objects of this class represent actions of (multi-)agents. Every element - of the internal list is related to an agent, and its partial subsection. - Action values for the first agent can be added while object instantiation. - Action values of further agents can be added by using method self.add_elem(). + Objects of this class represent actions of (multi-)agents. Every element of the internal list is + related to an agent, and its partial subsection. Action values for the first agent can be added + while object instantiation. Action values of further agents can be added by using method self.add_elem(). Parameters ---------- @@ -271,15 +291,18 @@ class Action(ElementList, TStamp): def __init__( self, p_agent_id = 0, p_action_space : Set = None, - p_values: np.ndarray = None ): + p_values: np.ndarray = None, + p_tstamp : TStampType = None ): ElementList.__init__(self) - TStamp.__init__(self) + action_elem = None - if (p_action_space is not None) and (p_values is not None): - e = ActionElement(p_action_space) - e.set_values(p_values) - self.add_elem(p_agent_id, e) + if ( p_action_space is not None ) and ( p_values is not None ): + action_elem = ActionElement(p_action_space) + action_elem.set_values(p_values) + self.add_elem(p_agent_id, action_elem) + + Instance.__init__( self, p_feature_data=action_elem, p_tstamp = p_tstamp ) ## ------------------------------------------------------------------------------------------------- @@ -504,20 +527,20 @@ class Actuator (Dimension): ## ------------------------------------------------------------------------------------------------- ## ------------------------------------------------------------------------------------------------- -class Controller (EventManager): +class SAGateway (EventManager): """ - Template for a controller that enables access to sensors and actuators. + Template for a gateway implementation that enables access to sensors and actuators. Parameters ---------- p_id - Unique id of the controller. + Unique id of the gateway. p_name : str - Optional name of the controller. + Optional name of the gateway. p_logging Log level (see class Log for more details). Default = Log.C_LOG_ALL. p_kwargs : dict - Further keyword arguments specific to the controller. + Further keyword arguments specific to the gateway. Attributes ---------- @@ -525,7 +548,7 @@ class Controller (EventManager): Event that is raised on a communication error """ - C_TYPE = 'Controller' + C_TYPE = 'SAGateway' C_EVENT_COMM_ERROR = 'COMM_ERROR' ## ------------------------------------------------------------------------------------------------- @@ -546,7 +569,7 @@ def __init__(self, p_id, p_name:str = '', p_logging : bool = Log.C_LOG_ALL, **p_ ## ------------------------------------------------------------------------------------------------- def reset(self) -> bool: """ - Resets the controller by calling custom method _reset(). + Resets the gateway by calling custom method _reset(). Returns ------- @@ -580,7 +603,7 @@ def _reset(self) -> bool: ## ------------------------------------------------------------------------------------------------- def add_sensor(self, p_sensor : Sensor): """ - Adds a sensor to the controller. + Adds a sensor to the gateway. Parameters ---------- @@ -666,7 +689,7 @@ def _get_sensor_value(self, p_id): ## ------------------------------------------------------------------------------------------------- def add_actuator(self, p_actuator : Actuator): """ - Adds an actuator to the controller. + Adds an actuator to the gateway. Parameters ---------- @@ -1070,11 +1093,26 @@ class System (FctSTrans, FctSuccess, FctBroken, Task, Mode, Plottable, Persisten Parameters ---------- - p_mode + p_id + Optional external id + p_name : str + Optional name of the task. Default is None. + p_range_max : int + Maximum range of asynchonicity. See class Range. Default is Range.C_RANGE_THREAD. + p_autorun : int + On value C_AUTORUN_RUN method run() is called imediately during instantiation. + On vaule C_AUTORUN_LOOP method run_loop() is called. + Value C_AUTORUN_NONE (default) causes an object instantiation without starting further + actions. + p_class_shared = None + Optional class for a shared object (class Shared or a child class of Shared) + p_mode = Mode.C_MODE_SIM Mode of the system. Possible values are Mode.C_MODE_SIM(default) or Mode.C_MODE_REAL. - p_latency : timedelta + p_latency : timedelta = None Optional latency of the system. If not provided, the internal value of constant C_LATENCY is used by default. + p_t_step : timedelta = None + ... p_fct_strans : FctSTrans Optional external function for state transition. p_fct_success : FctSuccess @@ -1084,15 +1122,15 @@ class System (FctSTrans, FctSuccess, FctBroken, Task, Mode, Plottable, Persisten p_mujoco_file Path to XML file for MuJoCo model. p_frame_skip : int - Frame to be skipped every step. Default = 1. - p_state_mapping - State mapping if the MLPro state and MuJoCo state have different naming. - p_action_mapping - Action mapping if the MLPro action and MuJoCo action have different naming. + MuJoCo only: frame to be skipped every step. Default = 1. + p_state_mapping = None + MuJoCo only: state mapping if the MLPro state and MuJoCo state have different naming. + p_action_mapping = None + MuJoCo only: action mapping if the MLPro action and MuJoCo action have different naming. p_use_radian : bool - Use radian if the action and the state based on radian unit. Default = True. + MuJoCo only: use radian if the action and the state based on radian unit. Default = True. p_camera_conf : tuple - Default camera configuration on MuJoCo Simulation (xyz position, elevation, distance). + MuJoCo only: default camera configuration on MuJoCo Simulation (xyz position, elevation, distance). p_visualize : bool Boolean switch for env/agent visualisation. Default = False. p_logging @@ -1153,10 +1191,10 @@ def __init__( self, self._state = None self._prev_state = None self._last_action = None - self._controllers = [] + self._gateways = [] self._mapping_actions = {} self._mapping_states = {} - self._t_step = p_t_step + self._t_step = p_t_step if p_mujoco_file is not None: try: @@ -1220,7 +1258,8 @@ def __init__( self, self._registered_on_so = False - ## ------------------------------------------------------------------------------------------------- + +## ------------------------------------------------------------------------------------------------- @staticmethod def setup_spaces(): """ @@ -1415,7 +1454,7 @@ def reset(self, p_seed=None) -> None: self._state.set_initial(True) if self.get_mode() == Mode.C_MODE_REAL: - for con in self._controllers: con.reset() + for con in self._gateways: con.reset() self._import_state() @@ -1435,31 +1474,31 @@ def _reset(self, p_seed=None) -> None: ## ------------------------------------------------------------------------------------------------- - def add_controller(self, p_controller : Controller, p_mapping : list) -> bool: + def add_gateway(self, p_gateway : SAGateway, p_mapping : list) -> bool: """ - Adds a controller and a related mapping of states and actions to sensors and actuators. + Adds a sensor/actuator-gateway and a related mapping of states and actions to sensors and actuators. Parameters ---------- - p_controller : Controller - Controller object to be added. + p_gateway : SAGateway + gateway object to be added. p_mapping : list A list of mapping tuples following the syntax ( [Type = 'S' or 'A'], [Name of state/action] [Name of sensor/actuator] ) Returns ------- successful : bool - True, if controller and related mapping was added successfully. False otherwise. + True, if gateway and related mapping was added successfully. False otherwise. """ # 0 Preparation states = self._state_space actions = self._action_space - sensors = p_controller.get_sensors() - actuators = p_controller.get_actuators() + sensors = p_gateway.get_sensors() + actuators = p_gateway.get_actuators() mapping_int = [] successful = True - self.log(Log.C_LOG_TYPE_I, 'Adding controller "' + p_controller.get_name() + '"...') + self.log(Log.C_LOG_TYPE_I, 'Adding gateway "' + p_gateway.get_name() + '"...') # 1 Check/conversion of mapping entries @@ -1502,21 +1541,21 @@ def add_controller(self, p_controller : Controller, p_mapping : list) -> bool: raise ParamError('Type "' + entry[0] + '" not valid!') if not successful: - self.log(Log.C_LOG_TYPE_E, 'Controller "' + p_controller.get_name() + '" could not be added') + self.log(Log.C_LOG_TYPE_E, 'SA-Gateway "' + p_gateway.get_name() + '" could not be added') return False - # 2 Takeover of mapping entries and controller + # 2 Takeover of mapping entries and gateway for entry in mapping_int: if entry[0] == 'S': - self._mapping_states[entry[3]] = ( p_controller, entry[4] ) + self._mapping_states[entry[3]] = ( p_gateway, entry[4] ) self.log(Log.C_LOG_TYPE_I, 'State component "' + entry[1] + '" assigned to sensor "' + entry[2] +'"') else: - self._mapping_actions[entry[3]] = ( p_controller, entry[4] ) + self._mapping_actions[entry[3]] = ( p_gateway, entry[4] ) self.log(Log.C_LOG_TYPE_I, 'Action component "' + entry[1] + '" assigned to actuator "' + entry[2] +'"') - self._controllers.append(p_controller) - self.log(Log.C_LOG_TYPE_I, 'Controller "' + p_controller.get_name() + '" added successfully') + self._gateways.append(p_gateway) + self.log(Log.C_LOG_TYPE_I, 'SA-Gateway "' + p_gateway.get_name() + '" added successfully') return True @@ -1600,7 +1639,7 @@ def process_action(self, p_action: Action, p_t_step: timedelta = None) -> bool: else: result = self._process_action(p_action) - self._prev_state = state + self._prev_state = state self._last_action = p_action if result: @@ -1806,7 +1845,7 @@ def _import_state(self) -> bool: try: mapping = self._mapping_states[state_dim_id] except: - self.log(Log.C_LOG_TYPE_E, 'State component "' + state_dim_name + '" not assigned to a controller/sensor') + self.log(Log.C_LOG_TYPE_E, 'State component "' + state_dim_name + '" not assigned to a gateway/sensor') successful = False else: sensor_value = mapping[0].get_sensor_value( p_id = mapping[1] ) @@ -1844,7 +1883,7 @@ def _export_action(self, p_action: Action) -> bool: mapping = self._mapping_actions[action_dim_id] except: action_dim_name = action_elem.get_related_set().get_dim(action_dim_id).get_name() - self.log(Log.C_LOG_TYPE_E, 'Action component "' + action_dim_name + '" not assigned to a controller/sensor') + self.log(Log.C_LOG_TYPE_E, 'Action component "' + action_dim_name + '" not assigned to a gateway/sensor') successful = False else: actuator_value = action_elem.get_value(p_dim_id=action_dim_id) @@ -2089,6 +2128,7 @@ def _reset(self, p_seed=None) -> None: for system in self._subsystems: system.reset(p_seed = p_seed) + ## ------------------------------------------------------------------------------------------------- def get_subsystem_ids(self): return self._subsystem_ids @@ -2224,7 +2264,7 @@ def compute_success(self, p_state: State) -> bool: class DemoScenario(ScenarioBase): """ - Demo Scenario Class to demonstrate systems, inherits from the ScenarioBase. + Demo Scenario Class to demonstrate systems. Parameters ---------- @@ -2248,9 +2288,9 @@ class DemoScenario(ScenarioBase): Log level (see constants of class Log). Default: Log.C_LOG_ALL. """ - C_NAME = 'Demo System Scenario' - C_ACTION_RANDOM = 'random' - C_ACTION_CONSTANT = 'constant' + C_NAME = 'Demo System Scenario' + C_ACTION_RANDOM = 'random' + C_ACTION_CONSTANT = 'constant' ## ------------------------------------------------------------------------------------------------- def __init__(self, diff --git a/src/mlpro/bf/systems/pool/__init__.py b/src/mlpro/bf/systems/pool/__init__.py index 7a25d6882..0143b1f3d 100644 --- a/src/mlpro/bf/systems/pool/__init__.py +++ b/src/mlpro/bf/systems/pool/__init__.py @@ -1 +1,2 @@ -from mlpro.bf.systems.pool.doublependulum import DoublePendulumSystemS4, DoublePendulumSystemS7 \ No newline at end of file +from mlpro.bf.systems.pool.doublependulum import DoublePendulumSystemS4, DoublePendulumSystemS7 +from mlpro.bf.systems.pool.fox import Fox \ No newline at end of file diff --git a/src/mlpro/bf/systems/pool/cartPole.py b/src/mlpro/bf/systems/pool/cartPole.py new file mode 100644 index 000000000..d1cec60da --- /dev/null +++ b/src/mlpro/bf/systems/pool/cartPole.py @@ -0,0 +1,114 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.systems.pool +## -- Module : fox.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-10 0.1.0 DA Initial implementation +## -- 2024-10-13 0.2.0 DA Random state jump +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.2.0 (2024-10-13) + +This module provides a simple demo system that just cumulates a percentage part of the incoming +action to the inner state. +""" + +import numpy as np +from mlpro.bf.various import Log +from mlpro.bf.ops import Mode +from mlpro.bf.mt import Task +from mlpro.bf.math import Dimension, MSpace, ESpace +from mlpro.bf.systems import State, Action, System +import gymnasium as gym +import math + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class CartPole (System): + """ + ... + """ + + C_NAME = 'Cart Pole' + C_BOUNDARIES = [0,1] + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_id=None, + p_name = None, + p_num_dim: int = 1, + p_range_max = Task.C_RANGE_NONE, + p_visualize = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs ): + + super().__init__( p_id = p_id, + p_name = p_name, + p_range_max = p_range_max, + p_mode = Mode.C_MODE_SIM, + p_visualize = p_visualize, + p_logging = p_logging ) + + + self._env = gym.make('CartPole-v1', render_mode="human") + self._observation=None + self._state_space, self._action_space = self._setup_spaces(p_num_dim=p_num_dim) + + +## ------------------------------------------------------------------------------------------------- + def _setup_spaces(self, p_num_dim: int): + + state_action_space : MSpace = ESpace() + + for i in range(p_num_dim): + state_action_space.add_dim( p_dim = Dimension( p_name_short = 'var ' + str(i), + p_base_set = Dimension.C_BASE_SET_R, + p_boundaries = self.C_BOUNDARIES ) ) + + return state_action_space, state_action_space + + + +## ------------------------------------------------------------------------------------------------- + def _radians_to_degrees(self, p_state_value: float): + p_degree = p_state_value * (180 / math.pi) + return p_degree + + + +## ------------------------------------------------------------------------------------------------- + def _reset(self, p_seed=None): + self._observation = self._env.reset()[0] + + + + +## ------------------------------------------------------------------------------------------------- + def _simulate_reaction(self, p_state: State, p_action: Action, p_step = None): + + self._env.render() + + p_action_value = p_action.get_feature_data().get_values()[0] + + + if p_action_value > 0: + p_action_value = 1 # move right + else: + p_action_value = 0 # move left + + + # Führe die Aktion in der Umgebung aus + self._observation,*_ = self._env.step(p_action_value) + + #create new state + new_state = State( p_state_space = self.get_state_space()) + #calculate angle of the pendelum + new_state.values = self._radians_to_degrees(self._observation[2]) + + return new_state \ No newline at end of file diff --git a/src/mlpro/bf/systems/pool/fox.py b/src/mlpro/bf/systems/pool/fox.py new file mode 100644 index 000000000..211a4fda9 --- /dev/null +++ b/src/mlpro/bf/systems/pool/fox.py @@ -0,0 +1,103 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.systems.pool +## -- Module : fox.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-10 0.1.0 DA Initial implementation +## -- 2024-10-13 0.2.0 DA Random state jump +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.2.0 (2024-10-13) + +This module provides a simple demo system that just cumulates a percentage part of the incoming +action to the inner state. +""" + + +import random + +import numpy as np + +from mlpro.bf.various import Log +from mlpro.bf.ops import Mode +from mlpro.bf.mt import Task +from mlpro.bf.math import Dimension, MSpace, ESpace +from mlpro.bf.systems import State, Action, System + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class Fox (System): + """ + ... + """ + + C_NAME = 'Fox' + C_BOUNDARIES = [-10,10] + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_id=None, + p_name = None, + p_num_dim: int = 1, + p_delay: float = 0.8, + p_thr_jump: float = 0.1, + p_range_max = Task.C_RANGE_NONE, + p_visualize = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs ): + + super().__init__( p_id = p_id, + p_name = p_name, + p_range_max = p_range_max, + p_mode = Mode.C_MODE_SIM, + p_visualize = p_visualize, + p_logging = p_logging ) + + self._action_factor: float = min(1, max(0, 1 - p_delay)) + self._thr_jump: float = p_thr_jump + self._state_space, self._action_space = self._setup_spaces(p_num_dim=p_num_dim) + + +## ------------------------------------------------------------------------------------------------- + def _setup_spaces(self, p_num_dim: int): + + state_action_space : MSpace = ESpace() + + for i in range(p_num_dim): + state_action_space.add_dim( p_dim = Dimension( p_name_short = 'var ' + str(i), + p_base_set = Dimension.C_BASE_SET_R, + p_boundaries = self.C_BOUNDARIES ) ) + + return state_action_space, state_action_space + + +## ------------------------------------------------------------------------------------------------- + def _jump_away(self, p_state: State): + p_state.set_initial( p_initial=True ) + p_state.values = [random.uniform( self.C_BOUNDARIES[0], self.C_BOUNDARIES[1] ) for _ in range(self.get_state_space().get_num_dim())] + + +## ------------------------------------------------------------------------------------------------- + def _reset(self, p_seed=None): + random.seed( p_seed ) + new_state = State( p_state_space = self.get_state_space(), p_initial = True ) + self._jump_away( p_state = new_state ) + self._set_state( p_state = new_state ) + + +## ------------------------------------------------------------------------------------------------- + def _simulate_reaction(self, p_state: State, p_action: Action, p_step = None): + + agent_id = p_action.get_agent_ids()[0] + new_state = State( p_state_space = self.get_state_space()) + new_state.values = np.array(p_state.values) + np.array(p_action.get_elem(p_id=agent_id).get_values()) * self._action_factor + + if np.linalg.norm( new_state.values ) <= self._thr_jump: self._jump_away( p_state=new_state) + + return new_state \ No newline at end of file diff --git a/src/mlpro/oa/control/__init__.py b/src/mlpro/oa/control/__init__.py new file mode 100644 index 000000000..8b4175433 --- /dev/null +++ b/src/mlpro/oa/control/__init__.py @@ -0,0 +1,2 @@ +from mlpro.oa.control.basics import * +from mlpro.oa.control.controlsystems.basic import OAControlSystemBasic \ No newline at end of file diff --git a/src/mlpro/oa/control/basics.py b/src/mlpro/oa/control/basics.py new file mode 100644 index 000000000..b2ea63ae4 --- /dev/null +++ b/src/mlpro/oa/control/basics.py @@ -0,0 +1,246 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.oa.control +## -- Module : basics.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-12 0.0.0 DA Creation +## -- 2024-09-16 0.1.0 DA Initial implementation of class OAController +## -- 2024-09-19 0.2.0 DA Completion of classes and their parents +## -- 2024-09-27 0.3.0 DA New method OAController hdl_setpoint_changed +## -- 2024-10-04 0.3.1 DA Bugfix in OAController.__init__() +## -- 2024-10-09 0.4.0 DA Refactoring +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.4.0 (2024-10-09) + +This module provides basic classes around the topic online-adaptive closed-loop control. + +""" + + +from mlpro.bf.systems import State, Action +from mlpro.bf.control import * +from mlpro.bf.ml import Model, Training, TrainingResults +from mlpro.bf.streams import InstDict + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAController (Controller, Model): + """ + Template class for online-adaptive closed-loop controllers. Please implement methods _compute_action() + and _adapt() in child classes. + + Parameters + ---------- + p_ada : bool = True + Boolean switch for adaptivitiy. Default = True. + p_name : str = None + Optional name of the task. Default is None. + p_range_max : int = Task.C_RANGE_THREAD + Maximum range of asynchonicity. See class Range. Default is Task.C_RANGE_PROCESS. + p_visualize : bool = False + Boolean switch for visualisation. Default = False. + p_logging = Log.C_LOG_ALL + Log level (see constants of class Log). Default: Log.C_LOG_ALL + p_kwargs : dict + Further optional named parameters. + """ + + C_TYPE = 'OA Controller' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_input_space : MSpace, + p_output_space : MSpace, + p_ada: bool = True, + p_id = None, + p_name: str = None, + p_range_max = Task.C_RANGE_NONE, + p_visualize: bool = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs ): + + Controller.__init__( self, + p_input_space = p_input_space, + p_output_space = p_output_space, + p_id = p_id, + p_name = p_name, + p_range_max = p_range_max, + p_visualize = p_visualize, + p_logging = False, + **p_kwargs ) + + Model.__init__( self, + p_ada = p_ada, + p_visualize = p_visualize, + p_logging = p_logging ) + + +## ------------------------------------------------------------------------------------------------- + def _run(self, p_inst: InstDict): + """ + Computes the next action based on the current control error and adapts on further contextual + information like setpoint, state, action. + """ + + # 0 Intro + setpoint : SetPoint = None + ctrlled_var : State = None + ctrl_error : ControlError = None + + + # 1 Determine the contextual data for action computation + ctrl_error = self._get_instance( p_inst = p_inst, p_type = ControlError ) + + if ctrl_error is None: + raise Error( 'Control error not found. Please check control workflow.') + + + # 2 Compute the next action + ctrl_var = self.compute_output( p_ctrl_error = ctrl_error ) + + + # 3 Adapt + self.adapt( p_ctrl_error = ctrl_error, + p_ctrl_var = ctrl_var ) + + +## ------------------------------------------------------------------------------------------------- + def _adapt( self, + p_ctrl_error: ControlError, + p_ctrl_var: ControlVariable ) -> bool: + """ + Specialized custom method for online adaptation in closed-loop control systems. + + Parameters + ---------- + p_ctrl_error : ControlError + Control error. + p_ctrl_var : ControlVariable + Control variable. + """ + + raise NotImplementedError + + +## ------------------------------------------------------------------------------------------------- + def hdl_setpoint_changed(self, p_event_id:str, p_event_object:Event): + """ + Handler method to be registered for event ControlPanel.C_EVNET_ID_SETPOINT_CHG. Turns off + the adaptation for one cycle. + """ + + raise NotImplementedError + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAMultiController: # (MultiController, Model): + """ + """ + + C_TYPE = 'OA Multi-Controller' + C_NAME = '' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlledSystem: # (ControlledSystem, Model): + """ + Wrapper class for online-adaptive state-based systems. + """ + + C_TYPE = 'OA Controlled System' + C_NAME = '' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlPanel: # (ControlPanel): + """ + Enables external control of a closed-loop control. + """ + + C_TYPE = 'OA Control Panel' + C_NAME = '????' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlShared: # (ControlShared, OAControlPanel): + """ + ... + """ + + pass + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlWorkflow: # (ControlWorkflow, Model): + """ + Container class for all tasks of a control workflow. + """ + + C_TYPE = 'OA Control Workflow' + C_NAME = '' + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlSystem: # (ControlSystem, Model): + """ + ... + """ + + pass + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlTrainingResults (TrainingResults): + """ + ... + """ + + pass + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlTraining (Training): + """ + ... + """ + + pass \ No newline at end of file diff --git a/src/mlpro/oa/control/controllers/__init__.py b/src/mlpro/oa/control/controllers/__init__.py new file mode 100644 index 000000000..bc8e5272f --- /dev/null +++ b/src/mlpro/oa/control/controllers/__init__.py @@ -0,0 +1,3 @@ +from mlpro.oa.control.controllers.wrapper_fct import OAControllerFct +from mlpro.oa.control.controllers.wrapper_rl import OAControllerRL +from mlpro.oa.control.controllers.oa_pid_controller import RLPID \ No newline at end of file diff --git a/src/mlpro/oa/control/controllers/oa_pid_controller.py b/src/mlpro/oa/control/controllers/oa_pid_controller.py new file mode 100644 index 000000000..6ca81ed23 --- /dev/null +++ b/src/mlpro/oa/control/controllers/oa_pid_controller.py @@ -0,0 +1,199 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.oa.control.controllers +## -- Module : oa_pid_controller.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-01 0.0.0 DA Creation +## -- 2024-09-26 0.0.0 ASP Implementation RLPID, RLPIDOffPolicy +## -- 2024-10-17 0.0.0 ASP -Refactoring class RLPID +## -- -change class name RLPIDOffPolicy to OffPolicyRLPID +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2024-09-01) + +This module provides an implementation of a OA PID controller. + +""" + +from mlpro.bf.control.controllers.pid_controller import PIDController +from mlpro.bf.ml.basics import * +from mlpro.rl import Policy,SARSElement +from mlpro.bf.control import ControlVariable, ControlledVariable + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class RLPID(Policy): + """ + Policy class for closed loop control + + Parameters + ---------- + p_pid_controller : PIDController, + Instance of PIDController + p_policy : Policy + Policy algorithm + """ + +## ------------------------------------------------------------------------------------------------- + def __init__(self, + p_observation_space: MSpace, + p_action_space: MSpace, + p_pid_controller:PIDController , + p_policy:Policy, + p_id=None, + p_buffer_size: int = 1, + p_ada: bool = True, + p_visualize: bool = False, + p_logging=Log.C_LOG_ALL ): + + super().__init__(p_observation_space, p_action_space, p_id, p_buffer_size, p_ada, p_visualize, p_logging) + + self._pid_controller = p_pid_controller + self._policy = p_policy + self._crtl_variable_old = None #None + self._action_space = p_action_space + + +## ------------------------------------------------------------------------------------------------- + def _init_hyperparam(self, **p_par): + + # 1 Create a dispatcher hyperparameter tuple for the RLPID policy + self._hyperparam_tuple = HyperParamDispatcher(p_set=self._hyperparam_space) + + # 2 Extend RLPID policy's hp space and tuple from policy + try: + self._hyperparam_space.append( self._policy.get_hyperparam().get_related_set(), p_new_dim_ids=False) + self._hyperparam_tuple.add_hp_tuple(self._policy.get_hyperparam()) + except: + pass + +## ------------------------------------------------------------------------------------------------- + def get_hyperparam(self) -> HyperParamTuple: + return self._policy.get_hyperparam() + + +## ------------------------------------------------------------------------------------------------- + def _update_hyperparameters(self) -> bool: + return self._policy._update_hyperparameters() + + +## ------------------------------------------------------------------------------------------------- + def _adapt(self, p_sars_elem: SARSElement) -> bool: + """ + Parameters: + p_sars_elem:SARSElement + Element of a SARSBuffer + """ + + is_adapted = False + + #get SARS Elements + p_state,p_crtl_variable,p_reward,p_state_new=tuple(p_sars_elem.get_data().values()) + + + if self._crtl_variable_old is not None: + + # create a new SARS + p_sars_elem_new = SARSElement(p_state=p_state, + p_action=self._crtl_variable_old, + p_reward=p_reward, + p_state_new=p_state_new) + + #adapt own policy + is_adapted = self._policy._adapt(p_sars_elem_new) + + # compute new action with new error value (second s of Sars element) + self._crtl_variable_old=self._policy.compute_action(p_obs=p_state_new) + + #get the pid paramter values + pid_values = self._crtl_variable_old.get_feature_data().get_values() + + #set paramter pid + self._pid_controller.set_parameter(p_param={"Kp":pid_values[0], + "Ti":pid_values[1], + "Tv":pid_values[2]}) + else: + #compute new action with new error value (second s of Sars element) + self._crtl_variable_old = self._policy.compute_action(p_obs=p_state_new) + + return is_adapted + + +## ------------------------------------------------------------------------------------------------- + def compute_action(self, p_obs: ControlledVariable) -> ControlVariable: + + #get action + control_variable=self._pid_controller.compute_output(p_ctrl_error=p_obs) + + #return action + return control_variable + + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OffPolicyRLPID(Policy): + """ + OFF Policy class for closed loop control + + Parameters + ---------- + p_pid_controller : PIDController, + Instance of PIDController + """ + + def __init__(self, p_observation_space: MSpace, + p_action_space: MSpace, + pid_controller:PIDController + ,p_id=None, + p_buffer_size: int = 1, + p_ada: bool = True, + p_visualize: bool = False, + p_logging=Log.C_LOG_ALL ): + + super().__init__(p_observation_space, + p_action_space, + p_id, + p_buffer_size, + p_ada, p_visualize, p_logging) + + self._pid_controller = pid_controller + self._action_space = p_action_space.get_dim(p_id=0) + + +## ------------------------------------------------------------------------------------------------- + def _init_hyperparam(self, **p_par): + + # 1 add dim (Kp,Tn,Tv) in hp space + self._hyperparam_space.add_dim( self._action_space.get_dim(p_id=0)) + self._hyperparam_space.add_dim(self._action_space.get_dim(p_id=1)) + self._hyperparam_space.add_dim(self._action_space.get_dim(p_id=2)) + + # # 2- create hp tuple from hp space + self._hyperparam_tuple = HyperParamTuple( p_set=self._hyperparam_space ) + + # 3- set hp tuple values + self._hyperparam_tuple.set_values(self._pid_controller.get_parameter_values()) + + +## ------------------------------------------------------------------------------------------------- + def _adapt(self, p_sars_elem: SARSElement) -> bool: + return False + + +## ------------------------------------------------------------------------------------------------- + def compute_action(self, p_obs: ControlledVariable) -> ControlVariable: + + #compute control variable + control_variable=self._pid_controller.compute_output(p_ctrl_error=p_obs) + + return control_variable + diff --git a/src/mlpro/oa/control/controllers/wrapper_fct.py b/src/mlpro/oa/control/controllers/wrapper_fct.py new file mode 100644 index 000000000..323faf3f7 --- /dev/null +++ b/src/mlpro/oa/control/controllers/wrapper_fct.py @@ -0,0 +1,46 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.oa.control.controllers +## -- Module : wrapper_fct.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-19 0.0.0 DA Creation +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2024-09-19) + +This module provides a wrapper class for MLPro's adaptive functions. + +""" + + +from mlpro.oa.control import OAController + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControllerFct (OAController): + """ + Wrapper class for controllers based on an online-adaptive function mapping an error to an action. + + Parameters + ---------- + p_fct : Function + Function object mapping a control error to an action + + See class Controller for further parameters. + """ + + C_TYPE = 'OA Controller Fct' + C_NAME = '' + +## ------------------------------------------------------------------------------------------------- + def switch_adaptivity(self, p_ada: bool): + try: + self._fct.switch_adaptivity(p_ada) + except: + pass diff --git a/src/mlpro/oa/control/controllers/wrapper_rl.py b/src/mlpro/oa/control/controllers/wrapper_rl.py new file mode 100644 index 000000000..418602dd7 --- /dev/null +++ b/src/mlpro/oa/control/controllers/wrapper_rl.py @@ -0,0 +1,148 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.oa.control.controller +## -- Module : wrapper_rl.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-19 0.1.0 DA Initial implementation of class OAControllerRL +## -- 2024-10-09 0.2.0 DA Refactoring +## -- 2024-10-13 0.3.0 DA Refactoring +## -- 2024-10-16 0.3.1 DA/ASP Bugfix in method OAControllerRL._adapt() +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.3.1 (2024-10-16) + +This module provides a wrapper class for MLPro's RL policies. + +""" + + +from mlpro.bf.various import Log +from mlpro.bf.mt import Task +from mlpro.bf.control import ControlError, ControlVariable +from mlpro.oa.control import OAController + +from mlpro.rl import Action, State, SARSElement, FctReward, Policy + + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControllerRL (OAController): + """ + Wrapper class for online-adaptive closed-loop controllers reusing reinforcement learning objects + like a policy and a reward function. + + Parameters + ---------- + p_rl_policy : Policy + RL policy object. + p_rl_fct_reward : FctReward + RL reward function. + p_ada : bool + Boolean switch for adaptivitiy. Default = True. + p_name : str + Optional name of the task. Default is None. + p_range_max : int + Maximum range of asynchonicity. See class Range. Default is Range.C_RANGE_PROCESS. + p_visualize : bool + Boolean switch for visualisation. Default = False. + p_logging + Log level (see constants of class Log). Default: Log.C_LOG_ALL + p_kwargs : dict + Further optional named parameters. + """ + + C_TYPE = 'OA Controller RL' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_rl_policy : Policy, + p_rl_fct_reward : FctReward, + p_ada : bool = True, + p_name: str = None, + p_range_max=Task.C_RANGE_THREAD, + p_visualize: bool = False, + p_logging=Log.C_LOG_ALL, + **p_kwargs ): + + self._rl_policy : Policy = p_rl_policy + self._rl_fct_reward : FctReward = p_rl_fct_reward + self._state_old : State = None + self._action_old : Action = None + + super().__init__( p_ada = p_ada, + p_name = p_name, + p_range_max = p_range_max, + p_visualize = p_visualize, + p_logging = p_logging, + **p_kwargs ) + + +## ------------------------------------------------------------------------------------------------- + def switch_adaptivity(self, p_ada: bool): + super().switch_adaptivity(p_ada=p_ada) + self._rl_policy.switch_adaptivity(p_ada=p_ada) + + +## ------------------------------------------------------------------------------------------------- + def compute_output( self, p_ctrl_error: ControlError ) -> ControlVariable: + + # 1 Convert control error to RL state + state = State( p_state_space = p_ctrl_error.value_space ) + state.values = p_ctrl_error.values + state.id = p_ctrl_error.id + state.tstamp = p_ctrl_error.tstamp + + # 2 Let the RL policy compute th next action + action = self._rl_policy.compute_action( p_obs = state ) + + # 3 Convert RL action to control variable and return + return ControlVariable( p_id = self.get_so().get_next_inst_id(), + p_value_space = action.get_feature_data().get_related_set(), + p_values = action.get_feature_data().get_values(), + p_tstamp = self.get_so().get_tstamp() ) + + +## ------------------------------------------------------------------------------------------------- + + def _adapt(self, p_ctrl_error: ControlError, p_ctrl_var: ControlVariable) -> bool: + + # 0 Intro + adapted = False + + # 1 Convert control error to RL state + state_new = State( p_state_space = p_ctrl_error.value_space ) + state_new.id = p_ctrl_error.id + state_new.tstamp = p_ctrl_error.tstamp + state_new.values = p_ctrl_error.values + + # 2 Adaptation from the second cycle on + if self._state_old is None: + self._state_old = state_new + return False + + # 3 Call reward function + reward = self._rl_fct_reward.compute_reward( p_state_old = self._state_old, p_state_new = state_new ) + + # 4 Setup SARS element + sars_elem = SARSElement( p_state = self._state_old, + p_action = self._action_old, + p_reward = reward, + p_state_new = state_new ) + + # 5 Trigger adaptation of the RL policy + adapted = self._rl_policy.adapt( p_sars_elem = sars_elem ) + + # 6 Buffering of new state and action + self._state_old = state_new + self._action_old = Action( p_agent_id = self.id, + p_action_space = p_ctrl_var.value_space, + p_values = p_ctrl_var.values, + p_tstamp = p_ctrl_var.tstamp ) + + # 7 Outro + return adapted \ No newline at end of file diff --git a/src/mlpro/oa/control/controlsystems/__init__.py b/src/mlpro/oa/control/controlsystems/__init__.py new file mode 100644 index 000000000..a8953cd8d --- /dev/null +++ b/src/mlpro/oa/control/controlsystems/__init__.py @@ -0,0 +1 @@ +from mlpro.oa.control.controlsystems.basic import OAControlSystemBasic \ No newline at end of file diff --git a/src/mlpro/oa/control/controlsystems/basic.py b/src/mlpro/oa/control/controlsystems/basic.py new file mode 100644 index 000000000..3da711bec --- /dev/null +++ b/src/mlpro/oa/control/controlsystems/basic.py @@ -0,0 +1,55 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.oa.control.control_scenarios +## -- Module : basic.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-07 0.1.0 DA Initial implementation +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.1.0 (2024-10-07) + +This module provides a simplified container class for a basic synchronous oa control scenario containing + +- a controller +- an oa control system +- an optional action cumulator + +""" + + +from mlpro.bf.various import Log +from mlpro.bf.control import ControlPanel, ControlWorkflow +from mlpro.bf.control.controlsystems import ControlSystemBasic +from mlpro.oa.control import OAControlSystem + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class OAControlSystemBasic: # (OAControlSystem, ControlSystemBasic): + """ + Simplified container class for a basic synchronous control system containing + + - a controller + - an oa control system + - an optional action incrementer + """ + + C_TYPE = 'OA Control Cycle Basic' + C_NAME = '' + +## ------------------------------------------------------------------------------------------------- + def _setup(self, p_mode, p_visualize: bool, p_logging) -> ControlWorkflow: + + control_workflow = ControlSystemBasic._setup(self, p_mode, p_visualize, p_logging) + + try: + control_workflow.get_control_panel().register_event_handler( p_event_id = ControlPanel.C_EVENT_ID_SETPOINT_CHG, + p_event_handler = self._controller.hdl_setpoint_changed ) + except: + self.log(Log.C_LOG_TYPE_W, 'Controller is not online adaptive') + + return control_workflow \ No newline at end of file diff --git a/src/mlpro/oa/streams/tasks/clusteranalyzers/basics.py b/src/mlpro/oa/streams/tasks/clusteranalyzers/basics.py index 1add9bf01..6ef956849 100644 --- a/src/mlpro/oa/streams/tasks/clusteranalyzers/basics.py +++ b/src/mlpro/oa/streams/tasks/clusteranalyzers/basics.py @@ -36,16 +36,21 @@ ## -- - new method _get_cluster_relations() ## -- - new method get_cluster_influences() ## -- 2024-06-16 1.2.1 DA Bugfix in ClusterAnalyzer.align_cluster_properties() +## -- 2024-08-20 1.3.0 DA Raising of events Cluster.C_CLUSTER_ADDED, Cluster.C_CLUSTER_REMOVED +## -- 2024-08-21 1.3.1 DA Resolved name collision of class mlpro.bf.events.Event ## ------------------------------------------------------------------------------------------------- """ -Ver. 1.2.1 (2024-06-16) +Ver. 1.3.1 (2024-08-21) This module provides a template class for online cluster analysis. """ +from typing import List, Tuple from matplotlib.figure import Figure + +from mlpro.bf.events import Event as MLProEvent from mlpro.bf.math.properties import * from mlpro.bf.mt import PlotSettings from mlpro.bf.streams import Instance, InstDict @@ -54,7 +59,6 @@ from mlpro.oa.streams import OATask from mlpro.bf.math.normalizers import Normalizer from mlpro.oa.streams.tasks.clusteranalyzers.clusters import Cluster, ClusterId -from typing import List, Tuple @@ -247,6 +251,10 @@ def _add_cluster(self, p_cluster:Cluster) -> bool: if self.get_visualization(): p_cluster.init_plot( p_figure=self._figure, p_plot_settings=self.get_plot_settings() ) + self._raise_event( p_event_id = self.C_EVENT_CLUSTER_ADDED, + p_event_object = MLProEvent( p_raising_object = self, + p_cluster = p_cluster ) ) + ## ------------------------------------------------------------------------------------------------- def _remove_cluster(self, p_cluster:Cluster): @@ -262,6 +270,10 @@ def _remove_cluster(self, p_cluster:Cluster): p_cluster.remove_plot(p_refresh=True) del self._clusters[p_cluster.id] + self._raise_event( p_event_id = self.C_EVENT_CLUSTER_REMOVED, + p_event_object = MLProEvent( p_raising_object = self, + p_cluster = p_cluster ) ) + ## ------------------------------------------------------------------------------------------------- def _get_cluster_relations( self, diff --git a/stepResponse.png b/stepResponse.png new file mode 100644 index 000000000..05f0e13aa Binary files /dev/null and b/stepResponse.png differ diff --git a/templates/new_howto/howto_bf_control_controllers_pid_controller.py b/templates/new_howto/howto_bf_control_controllers_pid_controller.py new file mode 100644 index 000000000..5ed435401 --- /dev/null +++ b/templates/new_howto/howto_bf_control_controllers_pid_controller.py @@ -0,0 +1,97 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.examples +## -- Module : howto_bf_zz_999_description.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2023-01-01 0.0.0 FN Creation +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2023-01-01) + +This module demonstrates ... + +You will learn: + +1) How to implement a P, PI and PID Controller + +2) How to use a PID Controller without and with anti wind up mechanism + +3) How to use a controller to create a control signal and set new pid parameters + +""" + + +from mlpro.bf.control.controllers.pid_controller import PIDController +from mlpro.bf.control.basics import CTRLError, Controller +from mlpro.bf.math.basics import Log +from mlpro.bf.mt import Log, Task +from mlpro.bf.various import Log +from mlpro.bf.math import * +from mlpro.bf.systems import Action, ActionElement + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class MyPIDController (PIDController): + """ + This class demonstrates how to ... + """ + + # needed for proper logging (see class mlpro.bf.various.Log) + C_TYPE = 'Demo' + C_NAME = 'PID Control' + +## ------------------------------------------------------------------------------------------------- + def __init__(self, Kp: float, Ti: float, Tv: float, disable_integral: bool = False, disable_derivitave: bool = False, enable_windup: bool = False, windup_limit: float = 0, output_limits: tuple = ..., p_name: str = None, p_range_max=Task.C_RANGE_THREAD, p_duplicate_data: bool = False, p_visualize: bool = False, p_logging=Log.C_LOG_ALL, **p_kwargs): + super().__init__(Kp, Ti, Tv, disable_integral, disable_derivitave, enable_windup, windup_limit, output_limits, p_name, p_range_max, p_duplicate_data, p_visualize, p_logging, **p_kwargs) + + +# 1 Preparation of demo test +if __name__ == '__main__': + + + # 1.Parameters for demo mode + Kp = 12 + Ti = 10 + Tv = 2.5 + element = Element(Set()) + print(element.get_dim_ids()) + element.set_values([12]) + crtl_error = CTRLError(element,p_tstamp=datetime.now()) + + action_element= ActionElement(Set()) + + + # 2. create diffrent instances of the class pid_controller + # create an instance of a P-Controller + p_controller = PIDController(Kp=Kp,disable_integral=True, disable_derivitave=True) + # create an instance of a PI-Controller + pi_controller = PIDController(Kp=True, Ti=10,disable_derivitave=True) + # create an instance of a PID-Controller without anti wind up mechanism + pid_controller = PIDController(Kp=True, Ti=10,Tv=Tv) + # create an instance of a PID-Controller with anti wind up mechanism + pid_controller_antiWindUp = PIDController(Kp=True, Ti=10,Tv=Tv,enable_anti_windup=True,windup_limit=100,output_limits=(0,100)) + + + # 3. run compute action + p_controller._compute_action(crtl_error,action_element) + p_controller._compute_action(crtl_error,action_element) + pi_controller._compute_action(crtl_error,action_element) + pid_controller._compute_action(crtl_error,action_element) + pid_controller_antiWindUp._compute_action(crtl_error,action_element) + + + # 4. set new pid parameter + p_controller.set_parameter(Kp= 6) + pi_controller.set_parameter(Kp= 6,Ti=4) + pid_controller.set_parameter(Kp=6,Ti=4,Tv=12) + + + + + + diff --git a/test/howtos/bf/control/howto_bf_control_001_basic_control_loop.py b/test/howtos/bf/control/howto_bf_control_001_basic_control_loop.py new file mode 100644 index 000000000..8e7a28fe8 --- /dev/null +++ b/test/howtos/bf/control/howto_bf_control_001_basic_control_loop.py @@ -0,0 +1,104 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.bf.control +## -- Module : howto_bf_control_001_basic_control_loop.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-11 0.0.0 DA Creation +## -- 2024-10-09 0.1.0 DA Refactoring +## -- 2024-10-13 0.2.0 DA Refactoring +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.2.0 (2024-10-13) + +Let's play fox and hunter! + +You will learn: + +1) How to ... + +2) How to ... + +3) How to ... + +""" + +import numpy as np + +from mlpro.bf.various import Log +from mlpro.bf.plot import PlotSettings +from mlpro.bf.ops import Mode + +from mlpro.bf.control import ControlledSystem +from mlpro.bf.control.controlsystems import ControlSystemBasic +from mlpro.bf.systems.pool import Fox +from mlpro.bf.control.controllers import Hunter + + + + + + +# 1 Preparation of demo/unit test mode +if __name__ == '__main__': + # 1.1 Parameters for demo mode + cycle_limit = 500 + num_dim = 2 + logging = Log.C_LOG_ALL + visualize = True + step_rate = 1 + +else: + # 1.2 Parameters for internal unit test + cycle_limit = 5 + num_dim = 2 + logging = Log.C_LOG_NOTHING + visualize = False + step_rate = 1 + + +# 2 Init control scenario + +# 2.1 Control system +mycontrolledsystem = ControlledSystem( p_system = Fox( p_num_dim = num_dim ), + p_name = 'Fox', + p_visualize = visualize, + p_logging = logging ) + +# 2.2 Controller +mycontroller = Hunter( p_input_space = mycontrolledsystem.system.get_state_space(), + p_output_space = mycontrolledsystem.system.get_action_space(), + p_name = 'Hunter', + p_visualize = visualize, + p_logging = logging ) + +# 2.3 Basic control system +mycontrolsystem = ControlSystemBasic( p_mode = Mode.C_MODE_SIM, + p_controller = mycontroller, + p_controlled_system = mycontrolledsystem, + p_ctrl_var_integration = False, + p_cycle_limit = cycle_limit, + p_visualize = visualize, + p_logging = logging ) + + +# 3 Set initial setpoint values and reset the controlled system +mycontrolsystem.get_control_panel().set_setpoint( p_values = np.zeros(shape=(num_dim)) ) +mycontrolledsystem.system.reset( p_seed = 1 ) + + +# 4 Run some control cycles +if __name__ == '__main__': + mycontrolsystem.init_plot( p_plot_settings=PlotSettings( p_view = PlotSettings.C_VIEW_ND, + p_view_autoselect = False, + p_step_rate = step_rate, + p_plot_horizon = 100 ) ) + input('\nPlease arrange all windows and press ENTER to start control processing...') + +mycontrolsystem.run() + +if __name__ == '__main__': + input('Press ENTER to exit...') + diff --git a/test/howtos/bf/control/howto_bf_control_101_pid_controller_standalone.py b/test/howtos/bf/control/howto_bf_control_101_pid_controller_standalone.py new file mode 100644 index 000000000..b8c0d84e4 --- /dev/null +++ b/test/howtos/bf/control/howto_bf_control_101_pid_controller_standalone.py @@ -0,0 +1,80 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : test/howtos/bf +## -- Module : howto_bf_control_101_pid_controller_standalone.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-01 0.0.0 DA Creation +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2024-09-01) + +This module demonstrates ... + +You will learn: + +1) How to ... + +2) How to ... + +3) How to ... + +""" + + +from mlpro.bf.various import Log + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class MyDemo (Log): + """ + This class demonstrates how to ... + """ + + # needed for proper logging (see class mlpro.bf.various.Log) + C_TYPE = 'Demo' + C_NAME = 'Parallel Algorithm' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_logging=Log.C_LOG_ALL ): + + super().__init__( p_logging=p_logging ) + + +## ------------------------------------------------------------------------------------------------- + def execute(self): + # Log something + self.log(Log.C_LOG_TYPE_I, 'Here we go...') + + + + + + +# 1 Preparation of demo/unit test mode +if __name__ == '__main__': + # 1.1 Parameters for demo mode + cycle_limit = 200 + logging = Log.C_LOG_ALL + visualize = True + +else: + # 1.2 Parameters for internal unit test + cycle_limit = 2 + logging = Log.C_LOG_NOTHING + visualize = False + + + +# 2 Instantiate the demo objects +demo = MyDemo( p_logging = logging ) + + + +# 3 Demo actions +demo.execute() diff --git a/test/howtos/bf/control/howto_bf_control_102_pid_controller_embedded.py b/test/howtos/bf/control/howto_bf_control_102_pid_controller_embedded.py new file mode 100644 index 000000000..acd849fef --- /dev/null +++ b/test/howtos/bf/control/howto_bf_control_102_pid_controller_embedded.py @@ -0,0 +1,104 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : test/howtos/bf +## -- Module : howto_bf_control_102_pid_controller_embedded.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-01 0.0.0 DA Creation +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2024-09-01) + +This module demonstrates ... + +You will learn: + +1) How to ... + +2) How to ... + +3) How to ... + +""" + + + +from mlpro.bf.various import Log +from mlpro.bf.plot import PlotSettings +from mlpro.bf.ops import Mode + +from mlpro.bf.control import ControlledSystem +from mlpro.bf.control.controlsystems import ControlSystemBasic +from mlpro.bf.systems.pool.cartPole import CartPole +from mlpro.bf.control.controllers.pid_controller import PIDController + + + + + + +# 1 Preparation of demo/unit test mode +if __name__ == '__main__': + # 1.1 Parameters for demo mode + cycle_limit = 500 + num_dim = 1 + logging = Log.C_LOG_ALL + visualize = False + step_rate = 1 + +else: + # 1.2 Parameters for internal unit test + cycle_limit = 5 + num_dim = 1 + logging = Log.C_LOG_NOTHING + visualize = False + step_rate = 1 + + +# 2 Init control scenario + +# 2.1 Control system +mycontrolledsystem = ControlledSystem( p_system = CartPole(p_id=num_dim), + p_name = 'Cart Pole', + p_visualize = visualize, + p_logging = logging ) + +# 2.2 Controller +mycontroller = PIDController( p_input_space = mycontrolledsystem.system.get_state_space(), + p_output_space = mycontrolledsystem.system.get_action_space(),Kp=1.33,Ti=1.33,Tv=1.99, + p_name = 'PID Controller', + p_visualize = visualize, + p_logging = logging ) + + + + +# 2.3 Basic control system +mycontrolsystem = ControlSystemBasic( p_mode = Mode.C_MODE_SIM, + p_controller = mycontroller, + p_controlled_system = mycontrolledsystem, + p_ctrl_var_integration = False, + p_cycle_limit = cycle_limit, + p_visualize = visualize, + p_logging = logging ) + + +# 3 Set initial setpoint values and reset the controlled system +mycontrolsystem.get_control_panel().set_setpoint( p_values = [0]) +mycontrolledsystem.system.reset( p_seed = 1 ) + + +# 4 Run some control cycles +if __name__ == '__main__': + mycontrolsystem.init_plot( p_plot_settings=PlotSettings( p_view = PlotSettings.C_VIEW_ND, + p_view_autoselect = False, + p_step_rate = step_rate, + p_plot_horizon = 100 ) ) + input('\nPlease arrange all windows and press ENTER to start control processing...') + +mycontrolsystem.run() + +if __name__ == '__main__': + input('Press ENTER to exit...') diff --git a/test/howtos/bf/control/howto_bf_control_103_pid_controller_cascaded.py b/test/howtos/bf/control/howto_bf_control_103_pid_controller_cascaded.py new file mode 100644 index 000000000..b576e6655 --- /dev/null +++ b/test/howtos/bf/control/howto_bf_control_103_pid_controller_cascaded.py @@ -0,0 +1,80 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : test/howtos/bf +## -- Module : howto_bf_control_103_pid_controller_cascaded.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-09-01 0.0.0 DA Creation +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2024-09-01) + +This module demonstrates ... + +You will learn: + +1) How to ... + +2) How to ... + +3) How to ... + +""" + + +from mlpro.bf.various import Log + + + +## ------------------------------------------------------------------------------------------------- +## ------------------------------------------------------------------------------------------------- +class MyDemo (Log): + """ + This class demonstrates how to ... + """ + + # needed for proper logging (see class mlpro.bf.various.Log) + C_TYPE = 'Demo' + C_NAME = 'Parallel Algorithm' + +## ------------------------------------------------------------------------------------------------- + def __init__( self, + p_logging=Log.C_LOG_ALL ): + + super().__init__( p_logging=p_logging ) + + +## ------------------------------------------------------------------------------------------------- + def execute(self): + # Log something + self.log(Log.C_LOG_TYPE_I, 'Here we go...') + + + + + + +# 1 Preparation of demo/unit test mode +if __name__ == '__main__': + # 1.1 Parameters for demo mode + cycle_limit = 200 + logging = Log.C_LOG_ALL + visualize = True + +else: + # 1.2 Parameters for internal unit test + cycle_limit = 2 + logging = Log.C_LOG_NOTHING + visualize = False + + + +# 2 Instantiate the demo objects +demo = MyDemo( p_logging = logging ) + + + +# 3 Demo actions +demo.execute() diff --git a/test/howtos/bf/howto_bf_mt_002_tasks_and_workflows.py b/test/howtos/bf/howto_bf_mt_002_tasks_and_workflows.py index ae4671118..bff6c1e9f 100644 --- a/test/howtos/bf/howto_bf_mt_002_tasks_and_workflows.py +++ b/test/howtos/bf/howto_bf_mt_002_tasks_and_workflows.py @@ -79,6 +79,8 @@ def __init__( self, ## ------------------------------------------------------------------------------------------------- def _run(self, **p_kwargs): + print('Hello World') + tid = self.get_tid() # 1 Dummy implementation to simulate a busy sub-task diff --git a/test/howtos/bf/howto_bf_streams_101_basics.py b/test/howtos/bf/howto_bf_streams_101_basics.py index 277f05bef..21e87671c 100644 --- a/test/howtos/bf/howto_bf_streams_101_basics.py +++ b/test/howtos/bf/howto_bf_streams_101_basics.py @@ -47,7 +47,7 @@ class MyTask (StreamTask): ## ------------------------------------------------------------------------------------------------- def _run(self, p_inst : InstDict): - pass + print('Hello World') @@ -111,6 +111,7 @@ def _setup(self, p_mode, p_visualize: bool, p_logging): if __name__ == '__main__': myscenario.init_plot() + myscenario.run() input('Press ENTER to start stream processing...') myscenario.run() diff --git a/test/howtos/bf/howto_bf_systems_010_systems_controllers_actuators_sensors.py b/test/howtos/bf/howto_bf_systems_010_systems_controllers_actuators_sensors.py index 17b839a11..d5cabc159 100644 --- a/test/howtos/bf/howto_bf_systems_010_systems_controllers_actuators_sensors.py +++ b/test/howtos/bf/howto_bf_systems_010_systems_controllers_actuators_sensors.py @@ -8,10 +8,11 @@ ## -- 2022-12-05 1.0.0 DA Creation ## -- 2022-12-09 1.1.0 DA Simplification ## -- 2023-04-19 1.1.1 LSB Renamed the module +## -- 2024-09-09 1.2.0 DA Refactoring ## ------------------------------------------------------------------------------------------------- """ -Ver. 1.1.1 (2023-04-19) +Ver. 1.2.0 (2024-09-09) This module demonstrates the principles of using classes System, Controller, Actuator and Sensor. To this regard we assume a custom system with two state and action components and a custom controller @@ -36,12 +37,12 @@ -class MyController (Controller): +class MySAGateway (SAGateway): C_NAME = 'Dummy' def _reset(self) -> bool: - self.log(Log.C_LOG_TYPE_S, 'Pseudo-reset of the controller') + self.log(Log.C_LOG_TYPE_S, 'Pseudo-reset of the sa-gateway') return True @@ -100,26 +101,26 @@ def _reset(self, p_seed=None) -> None: sys = MySystem( p_latency=latency, p_logging=logging ) -# 2 Instantiate and configure own controller -con = MyController( p_id=0, p_name='2x2', p_logging=logging ) +# 2 Instantiate and configure own sensor/actuator gateway +sagw = MySAGateway( p_id=0, p_name='2x2', p_logging=logging ) s1 = Sensor(p_name_short='Sensor 1') s2 = Sensor(p_name_short='Sensor 2') a1 = Actuator(p_name_short='Actuator 1') a2 = Actuator(p_name_short='Actuator 2') -con.add_sensor( p_sensor=s1 ) -con.add_sensor( p_sensor=s2 ) -con.add_actuator( p_actuator=a1 ) -con.add_actuator( p_actuator=a2 ) +sagw.add_sensor( p_sensor=s1 ) +sagw.add_sensor( p_sensor=s2 ) +sagw.add_actuator( p_actuator=a1 ) +sagw.add_actuator( p_actuator=a2 ) -# 3 Add controller to system and assign sensors to states and actuators to actions -sys.add_controller( p_controller=con, - p_mapping=[ ( 'S', 'State 1', 'Sensor 1' ), - ( 'S', 'State 2', 'Sensor 2' ), - ( 'A', 'Action 1', 'Actuator 1' ), - ( 'A', 'Action 2', 'Actuator 2') ] ) +# 3 Add gateway to system and assign sensors to states and actuators to actions +sys.add_gateway( p_gateway=sagw, + p_mapping=[ ( 'S', 'State 1', 'Sensor 1' ), + ( 'S', 'State 2', 'Sensor 2' ), + ( 'A', 'Action 1', 'Actuator 1' ), + ( 'A', 'Action 2', 'Actuator 2') ] ) # 4 Switch system to real mode diff --git a/test/howtos/oa/control/howto_oa_control_001_basic_control_loop.py.off b/test/howtos/oa/control/howto_oa_control_001_basic_control_loop.py.off new file mode 100644 index 000000000..7fab7f6f4 --- /dev/null +++ b/test/howtos/oa/control/howto_oa_control_001_basic_control_loop.py.off @@ -0,0 +1,102 @@ +## ------------------------------------------------------------------------------------------------- +## -- Project : MLPro - The integrative middleware framework for standardized machine learning +## -- Package : mlpro.oa.control +## -- Module : howto_oa_control_001_basic_control_loop.py +## ------------------------------------------------------------------------------------------------- +## -- History : +## -- yyyy-mm-dd Ver. Auth. Description +## -- 2024-10-13 0.0.0 DA Creation based on howto_bf_control_001_basic_control_loop.py +## ------------------------------------------------------------------------------------------------- + +""" +Ver. 0.0.0 (2024-10-13) + +Let's play fox and hunter! + +You will learn: + +1) How to ... + +2) How to ... + +3) How to ... + +""" + +import numpy as np + +from mlpro.bf.various import Log +from mlpro.bf.plot import PlotSettings +from mlpro.bf.ops import Mode + +from mlpro.oa.control import OAControlledSystem +from mlpro.oa.control.controlsystems import OAControlSystemBasic +from mlpro.bf.systems.pool import Fox +from mlpro.bf.control.controllers import Hunter + + + + + + +# 1 Preparation of demo/unit test mode +if __name__ == '__main__': + # 1.1 Parameters for demo mode + cycle_limit = 500 + num_dim = 2 + logging = Log.C_LOG_ALL + visualize = True + step_rate = 1 + +else: + # 1.2 Parameters for internal unit test + cycle_limit = 5 + num_dim = 2 + logging = Log.C_LOG_NOTHING + visualize = False + step_rate = 1 + + +# 2 Init control scenario + +# 2.1 Control system +mycontrolledsystem = OAControlledSystem( p_system = Fox( p_num_dim = num_dim ), + p_name = '"Fox"', + p_visualize = visualize, + p_logging = logging ) + +# 2.2 Controller +mycontroller = Hunter( p_input_space = mycontrolledsystem.system.get_state_space(), + p_output_space = mycontrolledsystem.system.get_action_space(), + p_name = '"Hunter"', + p_visualize = visualize, + p_logging = logging ) + +# 2.3 Basic control system +mycontrolsystem = OAControlSystemBasic( p_mode = Mode.C_MODE_SIM, + p_controller = mycontroller, + p_controlled_system = mycontrolledsystem, + p_ctrl_var_integration = False, + p_cycle_limit = cycle_limit, + p_visualize = visualize, + p_logging = logging ) + + +# 3 Set initial setpoint values and reset the controlled system +mycontrolsystem.get_control_panel().set_setpoint( p_values = np.zeros(shape=(num_dim)) ) +mycontrolledsystem.system.reset( p_seed = 1 ) + + +# 4 Run some control cycles +if __name__ == '__main__': + mycontrolsystem.init_plot( p_plot_settings=PlotSettings( p_view = PlotSettings.C_VIEW_ND, + p_view_autoselect = False, + p_step_rate = step_rate, + p_plot_horizon = 100 ) ) + input('\nPlease arrange all windows and press ENTER to start control processing...') + +mycontrolsystem.run() + +if __name__ == '__main__': + input('Press ENTER to exit...') + diff --git a/test/test_oa_pid_controller.py b/test/test_oa_pid_controller.py new file mode 100644 index 000000000..177badd31 --- /dev/null +++ b/test/test_oa_pid_controller.py @@ -0,0 +1,256 @@ +from mlpro.bf.control.controllers.pid_controller import PIDController +from mlpro.bf.control.basics import ControlError, SetPoint +from mlpro.bf.ml.basics import * +from mlpro.oa.control.controllers import RLPID,wrapper_rl +import gymnasium as gym +from stable_baselines3 import A2C, PPO, DQN, DDPG, SAC +from mlpro_int_sb3.wrappers import WrPolicySB32MLPro +from mlpro.rl.models import * +from mlpro.rl.models_env import Reward +import datetime +from mlpro.bf.control import ControlVariable, ControlledVariable + +import math +matplotlib.use('TkAgg') +import matplotlib.pyplot as plt + + +def radians_to_degrees(radians): + degrees = radians * (180 / math.pi) + return degrees + + + +class MyReward(FctReward): + + def __init__(self, p_logging=Log.C_LOG_ALL): + self._reward = Reward(p_value=0) + self._reward_value =0 + self.error_streak_counter=0 + super().__init__(p_logging) + + + + def _compute_reward(self, p_state_old: ControlledVariable = None, p_state_new: ControlledVariable= None) -> Reward: + + #get old error + error_old = p_state_old.get_feature_data().get_values()[0] + + #get new error + error_new = p_state_new.get_feature_data().get_values()[0] + + + # Berechnung der Ableitung des Fehlers (Fehleränderungsrate) + error_derivative = error_new - error_old + + # Inverser Reward basierend auf dem neuen Fehler (kleiner Fehler = größerer Reward) + epsilon = 1e-6 # Kleine Zahl, um Division durch Null zu vermeiden + reward = 20 / (abs(error_new) + epsilon) + + # 1. Bestrafung für große Fehler (Abweichung vom Sollwert) + penalty_threshold = 3.0 # Schwellenwert für zu große Fehler + if abs(error_new) > penalty_threshold: + reward -= (abs(error_new) - penalty_threshold) # Bestrafung für große Fehler + + # 2. Bestrafung für starkes Überschwingen (Wenn der Fehler das Sollwert überschreitet) + if (error_old > 0 and error_new < 0) or (error_old < 0 and error_new > 0): + reward -= 5.0 # Bestrafung für Überschwingen (besonders stark bestraft) + + # 3. Bestrafung für starke Schwankungen in der Fehleränderung (Oszillationen) + derivative_threshold = 3 # Schwellenwert für starke Fehleränderungen + if abs(error_derivative) > derivative_threshold: + reward -= 2.0 * abs(error_derivative) # Bestrafung für starke Änderungen + + # 4. Exponentielle Belohnung für kleine Fehler (schnelles Erreichen des Sollwerts) + k = 20 # Tuning-Parameter für exponentielle Bestrafung + reward += math.exp(-0.3 * (error_new*error_new))*k + + # 5. Zusätzliche Belohnung für konstant kleinen Fehler über Zeit + # Schwellenwert für "kleinen Fehler" + small_error_threshold = 1 # Definiere, was als "kleiner Fehler" gilt + streak_threshold = 10 # Anzahl der aufeinanderfolgenden Schritte für zusätzliche Belohnung + + # Prüfe, ob der Fehler klein ist + if abs(error_new) < small_error_threshold: + self.error_streak_counter += 1 # Zähler erhöhen, wenn der Fehler klein ist + else: + self.error_streak_counter = 0 # Zähler zurücksetzen, wenn der Fehler größer wird + + # Wenn der Fehler für mehrere Schritte konstant klein war, zusätzliche Belohnung + if self.error_streak_counter >= streak_threshold: + reward += 10 # Zusätzliche Belohnung, wenn der Fehler konstant klein bleibt + # Optional: Zähler zurücksetzen, wenn der maximale Bonus erreicht ist + # self.error_streak_counter = 0 + reward =min(reward,30) + + self._reward.set_overall_reward(reward) + + return self._reward + +action_space = MSpace() +p_obs = MSpace() +dim1 = Dimension('Kp',p_boundaries=[0.1,100]) +dim2 = Dimension('Tn',p_unit='second',p_boundaries=[0,100]) +dim3= Dimension('Tv',p_unit='second',p_boundaries=[0,100]) +action_space.add_dim(dim1) +action_space.add_dim(dim2) +action_space.add_dim(dim3) +observation_space = MSpace() +error_dim = Dimension('error',p_boundaries=[-100,100]) +setpoint_dim = Dimension('setpoint',p_boundaries=[-100,100]) +observation_space.add_dim(dim1) +output_dim =Dimension(p_name_short='output_dim',p_boundaries=[0,100]) +output_space=MSpace() +output_space.add_dim(p_dim=output_dim) +pid = PIDController(Kp=13,p_input_space=MSpace(),p_output_space=output_space,Ti=23,Tv=34) + +# PPO +policy_sb3 = PPO( + policy="MlpPolicy", + n_steps=100, + env=None, + _init_setup_model=False, + device="cpu",learning_rate=0.003,seed=42) +cycle_limit=200 +poliy_wrapper = WrPolicySB32MLPro(p_sb3_policy=policy_sb3, + p_cycle_limit=cycle_limit, + p_observation_space=observation_space, + p_action_space=action_space) + +rl_pid_policy = RLPID(p_observation_space=observation_space, + p_action_space=action_space, + p_pid_controller = pid, + p_policy=poliy_wrapper) + + +setpoint_space = Set() +setpoint_space.add_dim(p_dim=setpoint_dim) +setpoint = SetPoint(p_id=0,p_value_space=setpoint_space,p_values=[0],p_tstamp=datetime.datetime.now()) + + + +error_space = Set() +error_space.add_dim(p_dim=error_dim) +control_error = ControlError(p_id=0,p_value_space=error_space,p_values=[0],p_tstamp=datetime.datetime.now()) +oa_controller=wrapper_rl.OAControllerRL(p_input_space=MSpace(),p_output_space=MSpace(),p_rl_policy=rl_pid_policy,p_rl_fct_reward=MyReward()) + + + +# Daten für das Plotten sammeln +angles = [] +actions = [] +rewards = [] +gain_values=[] +integral_values=[] +deritave_values=[] +errors =[] +total_reward = 0 + + + + +#training loop +for k in range(5): + env = gym.make('CartPole-v1', render_mode="human") + observation = env.reset()[0] + for t in range(cycle_limit): + + env.render() + # get obs values + cart_position, cart_velocity, pole_angle, pole_velocity = observation + #convert rad in ° + actual_angle = radians_to_degrees(pole_angle) + #calculate error + control_error.get_feature_data().set_value(error_space.get_dim_ids()[0],actual_angle-setpoint.get_feature_data().get_values()[0]) + control_error.set_tstamp(datetime.datetime.now()) + + + oa_controller._adapt(p_ctrl_error=control_error,p_ctrl_var=ControlVariable(p_id=0,p_value_space=MSpace())) + + + control_variable=oa_controller.compute_output(control_error) + output= control_variable._get_values()[0] + + # Aktion umsetzen (nach links oder rechts) + if output > 0: + output = 1 # Bewegung nach rechts + else: + output = 0 # Bewegung nach links + + # Führe die Aktion in der Umgebung aus + observation, reward, done, *_ = env.step(output) + total_reward += oa_controller._rl_fct_reward._reward.get_overall_reward() + # Daten sammeln + angles.append(actual_angle) + actions.append(output) + rewards.append(oa_controller._rl_fct_reward._reward.get_overall_reward()) + gain,integral,deritave = tuple(oa_controller._rl_policy._pid_controller.get_parameter_values()) + gain_values.append(gain) + integral_values.append(integral) + deritave_values.append(deritave) + errors.append(control_error._get_values()[0]) + + if done: + break + + env.close() + +# Plotten der Ergebnisse +time_steps = range(len(angles)) + +plt.figure(figsize=(12, 8)) + +plt.subplot(3, 1, 1) +plt.plot(time_steps, angles, label='Pole Angle') +plt.xlabel('Time Step') +plt.ylabel('Angle (radians)') +plt.legend() + +plt.subplot(3, 1, 2) +plt.plot(time_steps, actions, label='ControlVariable') +plt.xlabel('Time Step') +plt.ylabel('ControlVariable (0=left, 1=right)') +plt.legend() + +plt.subplot(3, 1, 3) +plt.plot(time_steps, rewards, label='Reward') +plt.xlabel('Time Step') +plt.ylabel('Reward') +plt.legend() + +plt.subplot(3, 1, 1) +plt.plot(time_steps, gain_values, label='Gain') +plt.xlabel('Time Step') +plt.ylabel('-') +plt.legend() + +plt.subplot(3, 1, 1) +plt.plot(time_steps,integral_values, label='integral') +plt.xlabel('Time Step') +plt.ylabel('seconds') +plt.legend() + +plt.subplot(3, 1, 1) +plt.plot(time_steps, deritave_values, label='deritives') +plt.xlabel('Time Step') +plt.ylabel('seconds') +plt.legend() + +plt.subplot(3, 1, 1) +plt.plot(time_steps, errors, label='errors') +plt.xlabel('Time Step') +plt.ylabel('°') +plt.legend() + +plt.tight_layout() +plt.show() + +print(f'Total Reward: {total_reward}') + + + + + + + +