rlmf 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rlmf-0.1.0/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.pyo
4
+ *.pyd
5
+ *.so
6
+ *.egg
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .eggs/
11
+ .venv/
12
+ venv/
13
+ env/
14
+ .env
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .pytest_cache/
18
+ htmlcov/
19
+ .coverage
20
+ *.log
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [0.1.0] — unreleased
7
+
8
+ ### Added
9
+ - `Robot.load()` — universal entry point accepting YAML file path, YAML string, or dict
10
+ - Morphology Description Language (MDL) parser — converts YAML robot definitions into
11
+ structured `RobotModel` objects with limbs, joints, topology, and mass
12
+ - Kinematics engine — analytical 2-link IK and FK per limb
13
+ - Gait engine — `TripodGait`, `WaveGait`, `RippleGait`, `TrotGait`
14
+ - Automatic gait selection via `select_gait(family, behavior)`
15
+ - Balance engine — convex-hull support polygon, stability margin, tip-risk scoring
16
+ - Motor abstraction layer — `SimulatedDriver` + swappable hardware driver interface
17
+ - Bundled robot definitions: hexapod, quadruped, spider (octopod), centipede
18
+ - `rlmf.robots` sub-package for accessing bundled YAML definitions by Wafula Ian Elmer
19
+ - `rlmf` CLI with `describe`, `walk`, `balance`, and `robots` commands
20
+ - `py.typed` marker for PEP 561 type-checking support
21
+ - Full pytest test suite (55+ tests)
rlmf-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wafula Ian Elmer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
rlmf-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: rlmf
3
+ Version: 0.1.0
4
+ Summary: Robot Locomotion & Morphology Framework — universal legged-robot API
5
+ Project-URL: Homepage, https://github.com/Wafula_Ian01/rlmf
6
+ Project-URL: Documentation, https://github.com/Wafula_Ian01/rlmf#readme
7
+ Project-URL: Repository, https://github.com/Wafula_Ian01/rlmf.git
8
+ Project-URL: Bug Tracker, https://github.com/Wafula_Ian01/rlmf/issues
9
+ Author-email: Wafula Ian Elmer <ianwelmer01@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: gait,hexapod,inverse kinematics,locomotion,quadruped,robotics,servo
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: pyyaml>=6.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.2; extra == 'dev'
29
+ Requires-Dist: hatchling>=1.24; extra == 'dev'
30
+ Requires-Dist: mypy>=1.10; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.4; extra == 'dev'
34
+ Requires-Dist: twine>=5.1; extra == 'dev'
35
+ Provides-Extra: pi
36
+ Requires-Dist: adafruit-circuitpython-servokit>=1.3; extra == 'pi'
37
+ Requires-Dist: gpiozero>=2.0; extra == 'pi'
38
+ Requires-Dist: pigpio>=1.78; extra == 'pi'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # rlmf — Robot Locomotion & Morphology Framework
42
+
43
+ [![PyPI](https://img.shields.io/pypi/v/rlmf)](https://pypi.org/project/rlmf/)
44
+ [![Python](https://img.shields.io/pypi/pyversions/rlmf)](https://pypi.org/project/rlmf/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
46
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)]()
47
+
48
+ A universal robot body model and locomotion API.
49
+ Define any legged robot in YAML, then call `robot.walk()`.
50
+
51
+ ```python
52
+ from rlmf import Robot
53
+
54
+ robot = Robot.load("hexapod.yaml")
55
+ robot.walk()
56
+ robot.turn_left(45)
57
+ robot.climb()
58
+ robot.follow_path([(0.5, 0), (0.5, 0.5), (0, 0.5)])
59
+ ```
60
+
61
+ The API is **identical** regardless of morphology — 4-leg, 6-leg, 8-leg, 12-leg.
62
+ No gait code. No IK math. No synchronization logic. No motor coordination code.
63
+
64
+ ---
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ pip install rlmf
70
+ ```
71
+
72
+ With Raspberry Pi hardware support (PCA9685 + GPIO servos):
73
+
74
+ ```bash
75
+ pip install "rlmf[pi]"
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Quickstart
81
+
82
+ ```python
83
+ from rlmf import Robot
84
+ import rlmf.robots as robots
85
+
86
+ # Use a bundled robot definition
87
+ robot = Robot.load(robots.get("hexapod"))
88
+
89
+ # Or load your own YAML
90
+ robot = Robot.load("my_robot.yaml")
91
+
92
+ # Behaviours — same API for any morphology
93
+ robot.walk()
94
+ robot.turn_left(45)
95
+ robot.turn_right(30)
96
+ robot.climb()
97
+ robot.stop()
98
+ robot.follow_path([(0.5, 0.0), (0.5, 0.5)])
99
+
100
+ # Low-level access
101
+ robot.reach("leg_0", (0.20, 0.05, -0.10)) # IK to world target
102
+ robot.move_joint("leg_0.hip", 30.0) # direct joint control
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Morphology Description Language
108
+
109
+ Define any robot in YAML — the framework handles the rest:
110
+
111
+ ```yaml
112
+ name: HexaBot
113
+ body:
114
+ segments: [thorax]
115
+ length: 0.30
116
+ width: 0.18
117
+ height: 0.09
118
+ limbs:
119
+ - type: leg
120
+ count: 6
121
+ joints:
122
+ hip: {min: -90, max: 90}
123
+ knee: {min: 0, max: 120}
124
+ ankle: {min: -45, max: 45}
125
+ segment_lengths: [0.07, 0.12, 0.10]
126
+ physics:
127
+ mass: 1.2kg
128
+ ```
129
+
130
+ After loading, the full model is immediately introspectable:
131
+
132
+ ```python
133
+ robot.topology.family # "hexapod"
134
+ robot.topology.limb_count # 6
135
+ robot.topology.total_joints # 18
136
+ robot.mass # 1.2
137
+ robot.limbs # List[Limb]
138
+ robot.joints # Dict[str, JointDef]
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Bundled robots
144
+
145
+ ```python
146
+ import rlmf.robots as robots
147
+
148
+ robots.list_robots() # ['centipede', 'hexapod', 'quadruped', 'spider']
149
+ path = robots.get("hexapod") # Path to bundled hexapod.yaml
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Architecture
155
+
156
+ ```
157
+ User API robot.walk() / robot.turn_left() / robot.climb()
158
+
159
+ Gait Engine TripodGait / WaveGait / RippleGait / TrotGait
160
+
161
+ Kinematics solve_ik() / solve_fk() per limb
162
+
163
+ Balance Engine support polygon · stability margin · tip risk
164
+
165
+ Motor Layer SimulatedDriver / PCA9685 / your hardware
166
+ ```
167
+
168
+ ### Gait engine
169
+
170
+ | Gait | Class | Best for | Duty factor |
171
+ |------|-------|----------|-------------|
172
+ | tripod | `TripodGait` | hexapods, fast walking | 0.50 |
173
+ | wave | `WaveGait` | any, climbing | 0.83 |
174
+ | ripple | `RippleGait` | octopods, medium speed | 0.67 |
175
+ | trot | `TrotGait` | quadrupeds | 0.50 |
176
+
177
+ Gait selection is automatic — `robot.walk()` picks the right pattern for your morphology.
178
+ Override it when needed:
179
+
180
+ ```python
181
+ from rlmf import select_gait, TrotGait
182
+
183
+ gait = select_gait("hexapod", "climb") # → WaveGait
184
+ frames = robot.get_gait_frames("ripple", num_frames=60)
185
+ ```
186
+
187
+ ### Balance engine
188
+
189
+ ```python
190
+ state = robot.balance_state()
191
+
192
+ state.center_of_mass # (x, y, z) world space
193
+ state.support_polygon # convex hull of stance feet
194
+ state.stability_margin # metres to nearest polygon edge (>0 = stable)
195
+ state.is_stable # bool
196
+ state.tip_risk # 0.0 (safe) … 1.0 (falling)
197
+ ```
198
+
199
+ ### Motor abstraction
200
+
201
+ Swap the hardware driver without touching any robot code:
202
+
203
+ ```python
204
+ from rlmf import Robot, MotorAbstractionLayer
205
+ from my_hardware import MyServoDriver # your implementation
206
+
207
+ robot = Robot(
208
+ Robot.load("hexapod.yaml")._model,
209
+ motor_layer=MotorAbstractionLayer(driver=MyServoDriver()),
210
+ )
211
+ robot.walk() # drives your hardware
212
+ ```
213
+
214
+ Implement `MotorDriver` for any servo bus:
215
+
216
+ ```python
217
+ from rlmf import MotorDriver
218
+
219
+ class MyDriver(MotorDriver):
220
+ def set_angle(self, joint_id, angle_deg, speed=1.0): ...
221
+ def get_angle(self, joint_id) -> float: ...
222
+ def enable(self, joint_id): ...
223
+ def disable(self, joint_id): ...
224
+ ```
225
+
226
+ ---
227
+
228
+ ## CLI
229
+
230
+ ```bash
231
+ rlmf robots # list bundled robots
232
+ rlmf describe hexapod # print topology
233
+ rlmf describe path/to/mybot.yaml # your own robot
234
+ rlmf walk hexapod --gait wave # simulate gait, print stats
235
+ rlmf balance quadruped # print balance state
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Raspberry Pi deployment
241
+
242
+ Install with hardware extras:
243
+
244
+ ```bash
245
+ pip install "rlmf[pi]"
246
+ ```
247
+
248
+ Wire a PCA9685 to the Pi over I2C, then:
249
+
250
+ ```python
251
+ from adafruit_servokit import ServoKit
252
+ from rlmf import Robot, MotorAbstractionLayer
253
+ from rlmf.motors import MotorDriver
254
+
255
+ class PCA9685Driver(MotorDriver):
256
+ def __init__(self):
257
+ self._kit = ServoKit(channels=16)
258
+ self._map = {}
259
+
260
+ def assign(self, joint_id, channel):
261
+ self._map[joint_id] = channel
262
+
263
+ def set_angle(self, joint_id, angle_deg, speed=1.0):
264
+ ch = self._map.get(joint_id)
265
+ if ch is not None:
266
+ self._kit.servo[ch].angle = max(0, min(180, angle_deg + 90))
267
+
268
+ def get_angle(self, joint_id): return 0.0
269
+ def enable(self, joint_id): pass
270
+ def disable(self, joint_id): pass
271
+
272
+ driver = PCA9685Driver()
273
+ driver.assign("leg_0.hip", 0)
274
+ # … assign all 18 joints …
275
+
276
+ import rlmf.robots as robots
277
+ robot = Robot(
278
+ Robot.load(robots.get("hexapod"))._model,
279
+ motor_layer=MotorAbstractionLayer(driver=driver),
280
+ )
281
+ robot.walk() # moves physical servos
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Roadmap
287
+
288
+ - **v0.1** — Phase 1: 3–12 leg robots, four gait patterns, analytical IK, balance engine, motor abstraction
289
+ - **v0.2** — Phase 2: biped support, dynamic balance, weight shifting, fall recovery
290
+ - **v0.3** — Phase 3: arbitrary morphologies, terrain adaptation, path planning
291
+
292
+ ---
293
+
294
+ ## Development
295
+
296
+ ```bash
297
+ git clone https://github.com/Wafula_Ian01/rlmf
298
+ cd rlmf
299
+ pip install -e ".[dev]"
300
+ pytest
301
+ ```
302
+
303
+ ---
304
+
305
+ ## License
306
+
307
+ MIT — see [LICENSE](LICENSE).
rlmf-0.1.0/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # rlmf — Robot Locomotion & Morphology Framework
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/rlmf)](https://pypi.org/project/rlmf/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/rlmf)](https://pypi.org/project/rlmf/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)]()
7
+
8
+ A universal robot body model and locomotion API.
9
+ Define any legged robot in YAML, then call `robot.walk()`.
10
+
11
+ ```python
12
+ from rlmf import Robot
13
+
14
+ robot = Robot.load("hexapod.yaml")
15
+ robot.walk()
16
+ robot.turn_left(45)
17
+ robot.climb()
18
+ robot.follow_path([(0.5, 0), (0.5, 0.5), (0, 0.5)])
19
+ ```
20
+
21
+ The API is **identical** regardless of morphology — 4-leg, 6-leg, 8-leg, 12-leg.
22
+ No gait code. No IK math. No synchronization logic. No motor coordination code.
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install rlmf
30
+ ```
31
+
32
+ With Raspberry Pi hardware support (PCA9685 + GPIO servos):
33
+
34
+ ```bash
35
+ pip install "rlmf[pi]"
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Quickstart
41
+
42
+ ```python
43
+ from rlmf import Robot
44
+ import rlmf.robots as robots
45
+
46
+ # Use a bundled robot definition
47
+ robot = Robot.load(robots.get("hexapod"))
48
+
49
+ # Or load your own YAML
50
+ robot = Robot.load("my_robot.yaml")
51
+
52
+ # Behaviours — same API for any morphology
53
+ robot.walk()
54
+ robot.turn_left(45)
55
+ robot.turn_right(30)
56
+ robot.climb()
57
+ robot.stop()
58
+ robot.follow_path([(0.5, 0.0), (0.5, 0.5)])
59
+
60
+ # Low-level access
61
+ robot.reach("leg_0", (0.20, 0.05, -0.10)) # IK to world target
62
+ robot.move_joint("leg_0.hip", 30.0) # direct joint control
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Morphology Description Language
68
+
69
+ Define any robot in YAML — the framework handles the rest:
70
+
71
+ ```yaml
72
+ name: HexaBot
73
+ body:
74
+ segments: [thorax]
75
+ length: 0.30
76
+ width: 0.18
77
+ height: 0.09
78
+ limbs:
79
+ - type: leg
80
+ count: 6
81
+ joints:
82
+ hip: {min: -90, max: 90}
83
+ knee: {min: 0, max: 120}
84
+ ankle: {min: -45, max: 45}
85
+ segment_lengths: [0.07, 0.12, 0.10]
86
+ physics:
87
+ mass: 1.2kg
88
+ ```
89
+
90
+ After loading, the full model is immediately introspectable:
91
+
92
+ ```python
93
+ robot.topology.family # "hexapod"
94
+ robot.topology.limb_count # 6
95
+ robot.topology.total_joints # 18
96
+ robot.mass # 1.2
97
+ robot.limbs # List[Limb]
98
+ robot.joints # Dict[str, JointDef]
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Bundled robots
104
+
105
+ ```python
106
+ import rlmf.robots as robots
107
+
108
+ robots.list_robots() # ['centipede', 'hexapod', 'quadruped', 'spider']
109
+ path = robots.get("hexapod") # Path to bundled hexapod.yaml
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Architecture
115
+
116
+ ```
117
+ User API robot.walk() / robot.turn_left() / robot.climb()
118
+
119
+ Gait Engine TripodGait / WaveGait / RippleGait / TrotGait
120
+
121
+ Kinematics solve_ik() / solve_fk() per limb
122
+
123
+ Balance Engine support polygon · stability margin · tip risk
124
+
125
+ Motor Layer SimulatedDriver / PCA9685 / your hardware
126
+ ```
127
+
128
+ ### Gait engine
129
+
130
+ | Gait | Class | Best for | Duty factor |
131
+ |------|-------|----------|-------------|
132
+ | tripod | `TripodGait` | hexapods, fast walking | 0.50 |
133
+ | wave | `WaveGait` | any, climbing | 0.83 |
134
+ | ripple | `RippleGait` | octopods, medium speed | 0.67 |
135
+ | trot | `TrotGait` | quadrupeds | 0.50 |
136
+
137
+ Gait selection is automatic — `robot.walk()` picks the right pattern for your morphology.
138
+ Override it when needed:
139
+
140
+ ```python
141
+ from rlmf import select_gait, TrotGait
142
+
143
+ gait = select_gait("hexapod", "climb") # → WaveGait
144
+ frames = robot.get_gait_frames("ripple", num_frames=60)
145
+ ```
146
+
147
+ ### Balance engine
148
+
149
+ ```python
150
+ state = robot.balance_state()
151
+
152
+ state.center_of_mass # (x, y, z) world space
153
+ state.support_polygon # convex hull of stance feet
154
+ state.stability_margin # metres to nearest polygon edge (>0 = stable)
155
+ state.is_stable # bool
156
+ state.tip_risk # 0.0 (safe) … 1.0 (falling)
157
+ ```
158
+
159
+ ### Motor abstraction
160
+
161
+ Swap the hardware driver without touching any robot code:
162
+
163
+ ```python
164
+ from rlmf import Robot, MotorAbstractionLayer
165
+ from my_hardware import MyServoDriver # your implementation
166
+
167
+ robot = Robot(
168
+ Robot.load("hexapod.yaml")._model,
169
+ motor_layer=MotorAbstractionLayer(driver=MyServoDriver()),
170
+ )
171
+ robot.walk() # drives your hardware
172
+ ```
173
+
174
+ Implement `MotorDriver` for any servo bus:
175
+
176
+ ```python
177
+ from rlmf import MotorDriver
178
+
179
+ class MyDriver(MotorDriver):
180
+ def set_angle(self, joint_id, angle_deg, speed=1.0): ...
181
+ def get_angle(self, joint_id) -> float: ...
182
+ def enable(self, joint_id): ...
183
+ def disable(self, joint_id): ...
184
+ ```
185
+
186
+ ---
187
+
188
+ ## CLI
189
+
190
+ ```bash
191
+ rlmf robots # list bundled robots
192
+ rlmf describe hexapod # print topology
193
+ rlmf describe path/to/mybot.yaml # your own robot
194
+ rlmf walk hexapod --gait wave # simulate gait, print stats
195
+ rlmf balance quadruped # print balance state
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Raspberry Pi deployment
201
+
202
+ Install with hardware extras:
203
+
204
+ ```bash
205
+ pip install "rlmf[pi]"
206
+ ```
207
+
208
+ Wire a PCA9685 to the Pi over I2C, then:
209
+
210
+ ```python
211
+ from adafruit_servokit import ServoKit
212
+ from rlmf import Robot, MotorAbstractionLayer
213
+ from rlmf.motors import MotorDriver
214
+
215
+ class PCA9685Driver(MotorDriver):
216
+ def __init__(self):
217
+ self._kit = ServoKit(channels=16)
218
+ self._map = {}
219
+
220
+ def assign(self, joint_id, channel):
221
+ self._map[joint_id] = channel
222
+
223
+ def set_angle(self, joint_id, angle_deg, speed=1.0):
224
+ ch = self._map.get(joint_id)
225
+ if ch is not None:
226
+ self._kit.servo[ch].angle = max(0, min(180, angle_deg + 90))
227
+
228
+ def get_angle(self, joint_id): return 0.0
229
+ def enable(self, joint_id): pass
230
+ def disable(self, joint_id): pass
231
+
232
+ driver = PCA9685Driver()
233
+ driver.assign("leg_0.hip", 0)
234
+ # … assign all 18 joints …
235
+
236
+ import rlmf.robots as robots
237
+ robot = Robot(
238
+ Robot.load(robots.get("hexapod"))._model,
239
+ motor_layer=MotorAbstractionLayer(driver=driver),
240
+ )
241
+ robot.walk() # moves physical servos
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Roadmap
247
+
248
+ - **v0.1** — Phase 1: 3–12 leg robots, four gait patterns, analytical IK, balance engine, motor abstraction
249
+ - **v0.2** — Phase 2: biped support, dynamic balance, weight shifting, fall recovery
250
+ - **v0.3** — Phase 3: arbitrary morphologies, terrain adaptation, path planning
251
+
252
+ ---
253
+
254
+ ## Development
255
+
256
+ ```bash
257
+ git clone https://github.com/Wafula_Ian01/rlmf
258
+ cd rlmf
259
+ pip install -e ".[dev]"
260
+ pytest
261
+ ```
262
+
263
+ ---
264
+
265
+ ## License
266
+
267
+ MIT — see [LICENSE](LICENSE).