dynamicalnodes 0.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: dynamicalnodes
3
+ Version: 0.1.2
4
+ Summary: dynamicalnodes is a Python library for modeling general control systems.
5
+ Author-email: Nehal Singh Mangat <nehalsinghmangat.software@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/nehalsinghmangat/dynamicalnodes
8
+ Project-URL: Documentation, https://nehalsinghmangat.github.io/dynamicalnodes
9
+ Project-URL: Issues, https://github.com/nehalsinghmangat/dynamicalnodes/issues
10
+ Keywords: control,estimation,ros2
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy>=1.24
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-doctestplus>=1.0.0; extra == "dev"
24
+ Requires-Dist: flake8>=6.0; extra == "dev"
25
+ Requires-Dist: black>=24.0; extra == "dev"
26
+ Requires-Dist: isort>=6.0; extra == "dev"
27
+ Requires-Dist: mypy>=1.0; extra == "dev"
28
+ Requires-Dist: build>=0.10; extra == "dev"
29
+ Requires-Dist: twine>=4.0; extra == "dev"
30
+ Provides-Extra: docs
31
+ Requires-Dist: sphinx>=8.0; extra == "docs"
32
+ Requires-Dist: sphinx-rtd-theme>=1.3; extra == "docs"
33
+ Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
34
+ Requires-Dist: myst_nb; extra == "docs"
35
+ Requires-Dist: sphinx-autodoc-typehints>=1.24.0; extra == "docs"
36
+ Requires-Dist: matplotlib>=3.9; extra == "docs"
37
+ Requires-Dist: scipy>=1.11; extra == "docs"
38
+ Requires-Dist: jupyter; extra == "docs"
39
+ Requires-Dist: jaxtyping>=0.2; extra == "docs"
40
+ Requires-Dist: beartype>=0.15; extra == "docs"
41
+ Dynamic: license-file
42
+
43
+ # dynamicalnodes: Control Systems on ROS 2
44
+
45
+ > _"Give me a [control system] long enough and a [computer] on which to place it, and I shall move the world."_ - Archimedes
46
+
47
+ **dynamicalnodes** is a Python development framework that bridges the gap between control systems and their implementation onto ROS 2. Designed for hobbyists, students, and academics alike, this framework won't cure cancer, but it can do the next best thing: make robotics easier.
48
+
49
+ To get started, or to explore what this framework has to offer, click here: [dynamicalnodes Documentation](https://nehalsinghmangat.github.io/dynamicalnodes).
50
+
51
+ For a 22 second video describing this framework's intended workflow for, click here: [SAIL 2025 -- Presenting dynamicalnodes](https://youtu.be/5GbVHo6QZrw).
52
+
53
+ ---
54
+ ## Turtlebot: Whiteboard -> Python -> ROS -> World
55
+ <table>
56
+ <tr>
57
+ <td align="center">
58
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlebot.svg" alt="TurtleBot Overview" width="400"/><br/>
59
+ </td>
60
+ <td align="center">
61
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_software.gif" alt="Turtle Software" width="200"/><br/>
62
+ </td>
63
+ <td align="center">
64
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_simulation.gif" alt="Turtle Simulation" width="200"/><br/>
65
+ </td>
66
+ <td align="center">
67
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_hardware.gif" alt="Turtle Hardware" width="200"/><br/>
68
+ </td>
69
+ </tr>
70
+ </table>
71
+
72
+ <p align="center">
73
+ <img src="docs/source/_static/figure_turtlebot_pipeline/labels_for_steps_from_theory_to_hardware.png" alt="Step Labels" width="800"/>
74
+ </p>
75
+
76
+
77
+
78
+
79
+
80
+
@@ -0,0 +1,38 @@
1
+ # dynamicalnodes: Control Systems on ROS 2
2
+
3
+ > _"Give me a [control system] long enough and a [computer] on which to place it, and I shall move the world."_ - Archimedes
4
+
5
+ **dynamicalnodes** is a Python development framework that bridges the gap between control systems and their implementation onto ROS 2. Designed for hobbyists, students, and academics alike, this framework won't cure cancer, but it can do the next best thing: make robotics easier.
6
+
7
+ To get started, or to explore what this framework has to offer, click here: [dynamicalnodes Documentation](https://nehalsinghmangat.github.io/dynamicalnodes).
8
+
9
+ For a 22 second video describing this framework's intended workflow for, click here: [SAIL 2025 -- Presenting dynamicalnodes](https://youtu.be/5GbVHo6QZrw).
10
+
11
+ ---
12
+ ## Turtlebot: Whiteboard -> Python -> ROS -> World
13
+ <table>
14
+ <tr>
15
+ <td align="center">
16
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlebot.svg" alt="TurtleBot Overview" width="400"/><br/>
17
+ </td>
18
+ <td align="center">
19
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_software.gif" alt="Turtle Software" width="200"/><br/>
20
+ </td>
21
+ <td align="center">
22
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_simulation.gif" alt="Turtle Simulation" width="200"/><br/>
23
+ </td>
24
+ <td align="center">
25
+ <img src="docs/source/_static/figure_turtlebot_pipeline/turtlesim_hardware.gif" alt="Turtle Hardware" width="200"/><br/>
26
+ </td>
27
+ </tr>
28
+ </table>
29
+
30
+ <p align="center">
31
+ <img src="docs/source/_static/figure_turtlebot_pipeline/labels_for_steps_from_theory_to_hardware.png" alt="Step Labels" width="800"/>
32
+ </p>
33
+
34
+
35
+
36
+
37
+
38
+
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "dynamicalnodes"
10
- version = "0.1"
10
+ version = "0.1.2"
11
11
  description = "dynamicalnodes is a Python library for modeling general control systems."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.12"
@@ -16,6 +16,14 @@ authors = [
16
16
  { name = "Nehal Singh Mangat", email = "nehalsinghmangat.software@gmail.com" }
17
17
  ]
18
18
  keywords = ["control", "estimation", "ros2"]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Science/Research",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Scientific/Engineering",
25
+ "Topic :: Software Development :: Libraries",
26
+ ]
19
27
  dependencies = [
20
28
  "numpy>=1.24",
21
29
  ]
@@ -35,21 +43,32 @@ dev = [
35
43
  docs = [
36
44
  "sphinx>=8.0",
37
45
  "sphinx-rtd-theme>=1.3",
46
+ "sphinx-copybutton>=0.5",
38
47
  "myst_nb",
39
48
  "sphinx-autodoc-typehints>=1.24.0",
40
- "matplotlib>=3.7",
49
+ "matplotlib>=3.9",
41
50
  "scipy>=1.11",
42
51
  "jupyter",
52
+ "jaxtyping>=0.2",
53
+ "beartype>=0.15",
43
54
  ]
44
55
 
45
56
  [project.urls]
46
57
  Homepage = "https://github.com/nehalsinghmangat/dynamicalnodes"
47
58
  Documentation = "https://nehalsinghmangat.github.io/dynamicalnodes"
59
+ Issues = "https://github.com/nehalsinghmangat/dynamicalnodes/issues"
48
60
 
49
61
  [tool.pytest.ini_options]
50
62
  addopts = "--doctest-modules"
51
63
  doctest_optionflags = ["NORMALIZE_WHITESPACE", "ELLIPSIS"]
52
64
 
65
+ [tool.isort]
66
+ profile = "black"
67
+
68
+ [tool.mypy]
69
+ python_version = "3.12"
70
+ strict = true
71
+
53
72
  [tool.setuptools]
54
73
  package-dir = {"" = "src"}
55
74
 
@@ -0,0 +1,29 @@
1
+ """
2
+ dynamicalnodes: A modular Python framework for dynamical systems, estimation, and ROS2 integration.
3
+ """
4
+
5
+ from .dynamical_system import DynamicalSystem
6
+ from .rosnode import ROSNode
7
+
8
+ # Lazy import — requires a live ROS2 installation
9
+ _ROS2_MODULES = {
10
+ "ros2py_py2ros": ("ros2py_py2ros", None),
11
+ }
12
+
13
+
14
+ def __getattr__(name):
15
+ if name in _ROS2_MODULES:
16
+ module_name, attr = _ROS2_MODULES[name]
17
+ try:
18
+ import importlib
19
+ mod = importlib.import_module(f".{module_name}", package=__name__)
20
+ return mod if attr is None else getattr(mod, attr)
21
+ except ImportError as e:
22
+ raise ImportError(
23
+ f"{name} requires ROS2 dependencies. Install with: pip install dynamicalnodes[ros2]\n"
24
+ f"Original error: {e}"
25
+ ) from e
26
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
27
+
28
+
29
+ __all__ = ["DynamicalSystem", "ROSNode"]
@@ -186,6 +186,11 @@ class DynamicalSystem:
186
186
  Keyword arguments passed to f and h. Each function receives
187
187
  only the kwargs it declares in its signature via `_smart_call`.
188
188
 
189
+ A reserved key ``_state`` may be passed to inject the current
190
+ state without knowing ``f``'s first parameter name. It is
191
+ removed from kwargs before dispatch and inserted under the
192
+ correct name automatically.
193
+
189
194
  Returns
190
195
  -------
191
196
  Any
@@ -24,6 +24,7 @@ from turtlesim.msg import Pose
24
24
  from nav_msgs.msg import Odometry, Path
25
25
  from sensor_msgs.msg import (
26
26
  Imu,
27
+ NavSatFix,
27
28
  LaserScan,
28
29
  Image,
29
30
  CompressedImage,
@@ -558,6 +559,42 @@ def py2ros_imu(arr: NDArray) -> Imu:
558
559
  return msg
559
560
 
560
561
 
562
+ # ---------------------------------------------------------------------------
563
+ # sensor_msgs/NavSatFix <-> np.ndarray(3,) [latitude, longitude, altitude]
564
+ # ---------------------------------------------------------------------------
565
+
566
+ def ros2py_nav_sat_fix(msg: NavSatFix) -> NDArray:
567
+ """
568
+ NavSatFix → [latitude, longitude, altitude]
569
+
570
+ >>> from sensor_msgs.msg import NavSatFix
571
+ >>> msg = NavSatFix()
572
+ >>> msg.latitude, msg.longitude, msg.altitude = 37.5, -122.0, 10.0
573
+ >>> ros2py_nav_sat_fix(msg).tolist()
574
+ [37.5, -122.0, 10.0]
575
+ """
576
+ return np.array([msg.latitude, msg.longitude, msg.altitude], dtype=float)
577
+
578
+
579
+ def py2ros_nav_sat_fix(arr: NDArray) -> NavSatFix:
580
+ """
581
+ [latitude, longitude, altitude] → NavSatFix
582
+
583
+ >>> import numpy as np
584
+ >>> msg = py2ros_nav_sat_fix(np.array([37.5, -122.0, 10.0]))
585
+ >>> msg.latitude, msg.longitude, msg.altitude
586
+ (37.5, -122.0, 10.0)
587
+ """
588
+ flat = np.asarray(arr, dtype=float).ravel()
589
+ if flat.size != 3:
590
+ raise ValueError(f"py2ros_nav_sat_fix expected length 3, got {flat.size}")
591
+ msg = NavSatFix()
592
+ msg.latitude = float(flat[0])
593
+ msg.longitude = float(flat[1])
594
+ msg.altitude = float(flat[2])
595
+ return msg
596
+
597
+
561
598
  # ---------------------------------------------------------------------------
562
599
  # nav_msgs/Odometry <-> np.ndarray(13,)
563
600
  # (pose + twist; covariances ignored)
@@ -907,9 +944,9 @@ def py2ros_uint8_multiarray(arr: NDArray) -> UInt8MultiArray:
907
944
  # std_msgs/Float64 <-> scalar float (as 1D array)
908
945
  # ---------------------------------------------------------------------------
909
946
 
910
- def ros2py_float64(msg: Float64) -> NDArray:
911
- """Convert Float64 message to 1D numpy array with single element."""
912
- return np.array([msg.data], dtype=float)
947
+ def ros2py_float64(msg: Float64) -> float:
948
+ """Convert Float64 message to Python float."""
949
+ return float(msg.data)
913
950
 
914
951
 
915
952
  def py2ros_float64(arr: NDArray) -> Float64:
@@ -994,6 +1031,7 @@ ROS2PY_DEFAULT = {
994
1031
  AccelStamped: ros2py_accel_stamped,
995
1032
  Vector3Stamped: ros2py_vector3_stamped,
996
1033
  Imu: ros2py_imu,
1034
+ NavSatFix: ros2py_nav_sat_fix,
997
1035
  Odometry: ros2py_odometry,
998
1036
  Path: ros2py_path,
999
1037
  JointState: ros2py_joint_state,
@@ -1019,6 +1057,7 @@ PY2ROS_DEFAULT = {
1019
1057
  AccelStamped: py2ros_accel_stamped,
1020
1058
  Vector3Stamped: py2ros_vector3_stamped,
1021
1059
  Imu: py2ros_imu,
1060
+ NavSatFix: py2ros_nav_sat_fix,
1022
1061
  Odometry: py2ros_odometry,
1023
1062
  Path: py2ros_path,
1024
1063
  JointState: py2ros_joint_state,