virtualmodelcontrol 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. virtualmodelcontrol/__init__.py +75 -0
  2. virtualmodelcontrol/_version.py +24 -0
  3. virtualmodelcontrol/compiler.py +158 -0
  4. virtualmodelcontrol/control/__init__.py +5 -0
  5. virtualmodelcontrol/control/controller.py +76 -0
  6. virtualmodelcontrol/core/__init__.py +24 -0
  7. virtualmodelcontrol/core/params.py +244 -0
  8. virtualmodelcontrol/core/registry.py +54 -0
  9. virtualmodelcontrol/core/signals.py +46 -0
  10. virtualmodelcontrol/core/space.py +162 -0
  11. virtualmodelcontrol/core/symbolic.py +67 -0
  12. virtualmodelcontrol/core/units.py +34 -0
  13. virtualmodelcontrol/dynamics.py +149 -0
  14. virtualmodelcontrol/mechanisms/__init__.py +67 -0
  15. virtualmodelcontrol/mechanisms/components/__init__.py +33 -0
  16. virtualmodelcontrol/mechanisms/components/base.py +71 -0
  17. virtualmodelcontrol/mechanisms/components/dissipation.py +50 -0
  18. virtualmodelcontrol/mechanisms/components/inertance.py +56 -0
  19. virtualmodelcontrol/mechanisms/components/sources.py +63 -0
  20. virtualmodelcontrol/mechanisms/components/storage.py +272 -0
  21. virtualmodelcontrol/mechanisms/coordinates/__init__.py +24 -0
  22. virtualmodelcontrol/mechanisms/coordinates/base.py +117 -0
  23. virtualmodelcontrol/mechanisms/coordinates/frames.py +62 -0
  24. virtualmodelcontrol/mechanisms/coordinates/joints.py +51 -0
  25. virtualmodelcontrol/mechanisms/coordinates/ops.py +146 -0
  26. virtualmodelcontrol/mechanisms/coordinates/references.py +43 -0
  27. virtualmodelcontrol/mechanisms/mechanism.py +88 -0
  28. virtualmodelcontrol/models/__init__.py +22 -0
  29. virtualmodelcontrol/models/actuation.py +196 -0
  30. virtualmodelcontrol/models/assembly.py +154 -0
  31. virtualmodelcontrol/models/continuum/__init__.py +5 -0
  32. virtualmodelcontrol/models/continuum/pcc.py +120 -0
  33. virtualmodelcontrol/models/kinematic.py +41 -0
  34. virtualmodelcontrol/models/rigid/__init__.py +6 -0
  35. virtualmodelcontrol/models/rigid/couplings.py +64 -0
  36. virtualmodelcontrol/models/rigid/poe.py +95 -0
  37. virtualmodelcontrol/py.typed +0 -0
  38. virtualmodelcontrol/robots/__init__.py +5 -0
  39. virtualmodelcontrol/robots/adapt.py +98 -0
  40. virtualmodelcontrol/robots/helyx.py +79 -0
  41. virtualmodelcontrol/sim/__init__.py +7 -0
  42. virtualmodelcontrol/sim/model_plant.py +79 -0
  43. virtualmodelcontrol/sim/plant.py +40 -0
  44. virtualmodelcontrol/sim/run.py +73 -0
  45. virtualmodelcontrol/system.py +55 -0
  46. virtualmodelcontrol-0.1.0.dist-info/METADATA +82 -0
  47. virtualmodelcontrol-0.1.0.dist-info/RECORD +49 -0
  48. virtualmodelcontrol-0.1.0.dist-info/WHEEL +4 -0
  49. virtualmodelcontrol-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,73 @@
1
+ """The run loop: one loop for simulated and real plants, with a guard and a recorder."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+
10
+ from ..core.signals import Signals
11
+
12
+ MEASUREMENTS = ("motor_position", "motor_velocity")
13
+
14
+
15
+ @dataclass
16
+ class SimClock:
17
+ """Simulated time: each step advances the plant by ``dt`` [s]."""
18
+
19
+ dt: float
20
+
21
+
22
+ @dataclass
23
+ class Guard:
24
+ """Sends zero torque when a measurement is missing, flagged invalid, NaN or Inf."""
25
+
26
+ required: tuple[str, ...] = MEASUREMENTS
27
+ trips: int = 0
28
+
29
+ def ok(self, meas: Signals) -> bool:
30
+ """True if every required measurement is valid."""
31
+ good = all(meas.is_valid(name) for name in self.required)
32
+ self.trips += not good
33
+ return good
34
+
35
+
36
+ @dataclass
37
+ class RunLog:
38
+ """Recorded signals of a run, one row per step; ``arrays`` stacks them."""
39
+
40
+ rows: dict[str, list[np.ndarray]] = field(default_factory=dict)
41
+
42
+ def append(self, **values: Any) -> None:
43
+ """Add one row of named values."""
44
+ for name, value in values.items():
45
+ self.rows.setdefault(name, []).append(np.atleast_1d(np.asarray(value, dtype=float)))
46
+
47
+ def arrays(self) -> dict[str, np.ndarray]:
48
+ """Each signal as an (n_steps, ...) array."""
49
+ return {name: np.array(rows) for name, rows in self.rows.items()}
50
+
51
+
52
+ def run(
53
+ plant: Any, controller: Any, clock: SimClock, T: float, guard: Guard | None = None
54
+ ) -> RunLog:
55
+ """Run ``controller`` on a simulated ``plant`` for ``T`` [s]; returns the recorded run.
56
+
57
+ Each step reads the plant, asks the controller for a command (zero torque if the guard
58
+ trips), writes it and advances the plant by ``clock.dt``.
59
+ """
60
+ guard = Guard() if guard is None else guard
61
+ log = RunLog()
62
+ meas = plant.read()
63
+ controller.reset(plant.t, meas)
64
+ for _ in range(round(T / clock.dt)):
65
+ meas = plant.read()
66
+ if guard.ok(meas):
67
+ cmd = controller.step(plant.t, meas)
68
+ else:
69
+ cmd = Signals(plant.t, motor_torque=np.zeros_like(plant.u))
70
+ plant.write(cmd)
71
+ log.append(t=plant.t, motor_torque=cmd["motor_torque"], **{n: meas[n] for n in meas.names})
72
+ plant.advance(clock.dt)
73
+ return log
@@ -0,0 +1,55 @@
1
+ """A robot and the virtual mechanism that controls it, as one system."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .core.params import ParamSet
8
+ from .mechanisms.components.base import Component
9
+ from .mechanisms.coordinates.joints import State
10
+ from .mechanisms.mechanism import Mechanism
11
+ from .models.actuation import Direct
12
+
13
+
14
+ class VirtualMechanismSystem:
15
+ """A robot mechanism (with a kinematic model) and the virtual mechanism attached to it.
16
+
17
+ Params are named ``<mechanism>.<name>``; a Param shared by the robot and the controller keeps
18
+ the robot's name. ``couplings`` is an optional third mechanism, treated like the controller.
19
+ """
20
+
21
+ def __init__(
22
+ self, robot: Mechanism, virtual: Mechanism, couplings: Mechanism | None = None
23
+ ) -> None:
24
+ if robot.model is None:
25
+ raise ValueError(f"robot {robot.name!r} needs a kinematic model")
26
+ self.robot = robot
27
+ self.virtual = virtual
28
+ self.couplings = couplings
29
+ self.actuation: Any = robot.actuation if robot.actuation is not None else Direct()
30
+
31
+ @property
32
+ def controllers(self) -> list[Mechanism]:
33
+ """The virtual mechanism and the couplings, if any."""
34
+ return [m for m in (self.virtual, self.couplings) if m is not None]
35
+
36
+ @property
37
+ def params(self) -> ParamSet:
38
+ """Every Param of the system, robot first."""
39
+ ps = ParamSet()
40
+ for mechanism in (self.robot, *self.controllers):
41
+ ps.merge(mechanism.params, mechanism.name)
42
+ return ps
43
+
44
+ @property
45
+ def states(self) -> list[State]:
46
+ """Virtual degrees of freedom, in order."""
47
+ return [s for m in self.controllers for s in m.states.values()]
48
+
49
+ @property
50
+ def components(self) -> list[tuple[str, Component]]:
51
+ """Controller components as (``mechanism.name``, component)."""
52
+ return [(f"{m.name}.{n}", c) for m in self.controllers for n, c in m.components.items()]
53
+
54
+ def __repr__(self) -> str:
55
+ return f"VirtualMechanismSystem({self.robot.name!r}, {self.virtual.name!r})"
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.5
2
+ Name: virtualmodelcontrol
3
+ Version: 0.1.0
4
+ Summary: Virtual Model Control: robots and controllers as mechanisms of springs, dampers and inertances, compiled with CasADi.
5
+ Project-URL: Repository, https://github.com/vigno0405/VirtualModelControl
6
+ Project-URL: Documentation, https://vigno0405.github.io/VirtualModelControl/
7
+ Author: Lorenzo Vignoli
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: casadi,control,passivity,robotics,soft robots,virtual model control
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: casadi>=3.6
18
+ Requires-Dist: numpy>=1.26
19
+ Requires-Dist: scipy>=1.11
20
+ Provides-Extra: dev
21
+ Requires-Dist: import-linter>=2.0; extra == 'dev'
22
+ Requires-Dist: mypy>=1.10; extra == 'dev'
23
+ Requires-Dist: pre-commit>=3.5; extra == 'dev'
24
+ Requires-Dist: pytest>=8; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6; extra == 'dev'
26
+ Provides-Extra: docs
27
+ Requires-Dist: myst-nb>=1.1; extra == 'docs'
28
+ Requires-Dist: pydata-sphinx-theme>=0.15; extra == 'docs'
29
+ Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
30
+ Requires-Dist: sphinx-design>=0.5; extra == 'docs'
31
+ Requires-Dist: sphinx>=7.2; extra == 'docs'
32
+ Provides-Extra: viz
33
+ Requires-Dist: matplotlib>=3.8; extra == 'viz'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # VirtualModelControl
37
+
38
+ `virtualmodelcontrol` is a Python library for Virtual Model Control (VMC). Robots and
39
+ controllers are described as mechanisms: coordinates plus springs, dampers, inertances and
40
+ sources. One CasADi model serves the real-time controller, simulation and optimization.
41
+
42
+ A collaboration between EPFL (Prof. Josie Hughes) and the University of Cambridge
43
+ (Prof. Fulvio Forni).
44
+
45
+ ## Install
46
+
47
+ Python ≥ 3.10 on Linux, macOS or Windows:
48
+
49
+ ```bash
50
+ pip install virtualmodelcontrol
51
+ ```
52
+
53
+ Then `import virtualmodelcontrol as vmc`. The latest development version installs with
54
+ `pip install "git+https://github.com/vigno0405/VirtualModelControl.git"`.
55
+
56
+ ## Develop
57
+
58
+ On Linux or macOS (Windows: see the install page in the docs):
59
+
60
+ ```bash
61
+ git clone https://github.com/vigno0405/VirtualModelControl.git
62
+ cd VirtualModelControl
63
+ python3 -m venv .venv
64
+ .venv/bin/pip install -e ".[dev,docs]"
65
+ .venv/bin/python -m pytest
66
+ ```
67
+
68
+ On Debian/Ubuntu, `python3 -m venv` needs the `python3-venv` package
69
+ (`sudo apt install python3-venv`); a conda environment works too. If ROS 2 is sourced in your
70
+ shell, prefix these commands with `env -u PYTHONPATH` so ROS's Python packages stay out of the
71
+ environment.
72
+
73
+ ## Documentation
74
+
75
+ https://vigno0405.github.io/VirtualModelControl/
76
+
77
+ To build it locally: `sphinx-build docs docs/_build/html`, then open
78
+ `docs/_build/html/index.html`.
79
+
80
+ ## Citing
81
+
82
+ See `CITATION.cff`.
@@ -0,0 +1,49 @@
1
+ virtualmodelcontrol/__init__.py,sha256=E0QJMB_Ku0DLj06SSBGgnXUImzGIstTgBXe3e6LHoGw,1505
2
+ virtualmodelcontrol/_version.py,sha256=n_5vdJsPNu7wZ57LGuRL585uvll-hiuvZUBWzdG0RQU,520
3
+ virtualmodelcontrol/compiler.py,sha256=97qhVBamIvYBDAf2HxP9WR3GeGXsVkQFWGdsvExYNzo,5876
4
+ virtualmodelcontrol/dynamics.py,sha256=YWplGRVqtyH4xKWNms9DcmHzhOs9_Ec4zwkmJ-fLDNM,5416
5
+ virtualmodelcontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ virtualmodelcontrol/system.py,sha256=gfcDmffB7xpUJphtw4Wr31M7kdjI-ruYIStRzPM8GRo,2083
7
+ virtualmodelcontrol/control/__init__.py,sha256=NmW5vQN8UcJlhDW3V4fc37LZYSnJ6XyL7E82fUSEhls,135
8
+ virtualmodelcontrol/control/controller.py,sha256=ZLh1xxjgeGQRctDcYNsG1Ku5YateKK_iolNsqC1wsfc,3128
9
+ virtualmodelcontrol/core/__init__.py,sha256=qxsDqaGE_6I2De9AGzWxPCMuT2KzGuShVfizdu1HkGY,527
10
+ virtualmodelcontrol/core/params.py,sha256=C5BqagsGLay-aSOEQqCGiuMvFVF0X0pCFu0k-YOgzWI,9108
11
+ virtualmodelcontrol/core/registry.py,sha256=8IWfZJB1cGyygET-qqJsi_nH_BdiSiLtWqu9DeYlO68,1651
12
+ virtualmodelcontrol/core/signals.py,sha256=5fT6iiOk4UBV2ZUoD1gGJMrCG8rTz6yMzc-RwOPTSd0,1617
13
+ virtualmodelcontrol/core/space.py,sha256=xG59fXBoLkA1FHGCyvrPgWty98yCxhtLFOyj4Cvy0vk,5182
14
+ virtualmodelcontrol/core/symbolic.py,sha256=FxWm6hGFNjD6X6rQSjUyI3dJaDMN2z-Bi6SXud-OgAE,2151
15
+ virtualmodelcontrol/core/units.py,sha256=XJSY1_2toVh_rSPVcH2vUfPlEsf6LGTB-fpB-hd8xnc,1006
16
+ virtualmodelcontrol/mechanisms/__init__.py,sha256=ooJV38z81RHdstT6zIbtT4kFl_VeNbIEwp_qZs0cRUU,1084
17
+ virtualmodelcontrol/mechanisms/mechanism.py,sha256=KQ8WQWkN3UE-GVwPGeDTpL4s_CgEslAv_6uzKGxYrKM,3615
18
+ virtualmodelcontrol/mechanisms/components/__init__.py,sha256=a5Fm56UFgVuhRrQgRLdE_X2KeBYNeAg8BFz_AxX_iHM,728
19
+ virtualmodelcontrol/mechanisms/components/base.py,sha256=vq3irvmneUZf8uq_gFuI1uyDWhdayZlj-3JvLNY78J0,2590
20
+ virtualmodelcontrol/mechanisms/components/dissipation.py,sha256=E0AH4T47wh6B0dUGfPS5mpDN_7LWI4nE6oL5Ouc9i44,1685
21
+ virtualmodelcontrol/mechanisms/components/inertance.py,sha256=uTO8d25WTiclbIhynvyrqjQSJOt9f5TMxKe0uUenonc,1744
22
+ virtualmodelcontrol/mechanisms/components/sources.py,sha256=-tU3o_pIG6BtH2nZyjAgwXkBwDLEfTrMQut8dRE5UjM,2309
23
+ virtualmodelcontrol/mechanisms/components/storage.py,sha256=nAe76S8L_aZtDuB_pm2BATYo2o03Tusdu2epsl-n9SU,10300
24
+ virtualmodelcontrol/mechanisms/coordinates/__init__.py,sha256=bTY7pyy9MRrol5A3EKJqh9Ra0xx532HzBCE4S7tBx4U,540
25
+ virtualmodelcontrol/mechanisms/coordinates/base.py,sha256=81WIiLpE017YX5FeiC-pHnHUdKR5kU2oE4IN9BylMDY,3661
26
+ virtualmodelcontrol/mechanisms/coordinates/frames.py,sha256=nGuLSAQowICJ_-uNx8H9krK3GXxYMKOHDRaPLir3NlU,2156
27
+ virtualmodelcontrol/mechanisms/coordinates/joints.py,sha256=aCj7nXBppYNpPWLY6O4cXTVXOUskm7iPkR36m04bd_Q,1715
28
+ virtualmodelcontrol/mechanisms/coordinates/ops.py,sha256=DpvbLbWAmddhKZpcio2gtO3cJcgtl2XIxkMZL7sudU4,4972
29
+ virtualmodelcontrol/mechanisms/coordinates/references.py,sha256=KXiT3UVtznfqgHDfM8a0AnipTw_fg7nVVlodVM8eBw0,1305
30
+ virtualmodelcontrol/models/__init__.py,sha256=A3_HwU2p5S5XuvwSaY--PRp6EJBeNmjV0tf70n1Gl7g,561
31
+ virtualmodelcontrol/models/actuation.py,sha256=PDKRUmPZloD8mq2gDZzKg-lp7kPVvhPh4i4CNmadKEc,7171
32
+ virtualmodelcontrol/models/assembly.py,sha256=QGmdDWcvUlpfC-uKaM-SCS-aal8GD-MhUNZRRK9Lb8A,6884
33
+ virtualmodelcontrol/models/kinematic.py,sha256=oKgEpk9PAqYkyoCMdkJEVhptMqUVUa1-k2MohdS4jtk,1422
34
+ virtualmodelcontrol/models/continuum/__init__.py,sha256=pkrNcvrQ93Ly86iS5lgNGN0r1c__N57aX0Uw9nDHKyM,97
35
+ virtualmodelcontrol/models/continuum/pcc.py,sha256=V0HJJWz73p1BnSXQVCbczOAEdu1fyOmMfKyhdsr9wUE,4977
36
+ virtualmodelcontrol/models/rigid/__init__.py,sha256=-THPuFfle_2vjeZe-nu2lG0xi31pztvBS2jQTkI6HEQ,133
37
+ virtualmodelcontrol/models/rigid/couplings.py,sha256=E7BO8v5kLg2mC2QxekrLpklXakzJsNRbrYatMpRUO9E,2363
38
+ virtualmodelcontrol/models/rigid/poe.py,sha256=VVWzcAcZF6fojeoNSk1SErGM8mTmUK_VL4EIvbhaL8o,3927
39
+ virtualmodelcontrol/robots/__init__.py,sha256=tdOyxcvni3revezROiWDV8eqX9X2GR5gPd_a2Olkrco,136
40
+ virtualmodelcontrol/robots/adapt.py,sha256=U0a11gV37xbW2Krn3XquAZ4fqUO_HaDO_S2by5nRpUY,3546
41
+ virtualmodelcontrol/robots/helyx.py,sha256=fagNg8iEU3evxa9AiOjLDZFkvya3gE8eKx6HXO7eh8Q,3604
42
+ virtualmodelcontrol/sim/__init__.py,sha256=khTS2enjoYrbXRfgrFTLLLVcgEwvqY9wO_ZvfWLIViI,291
43
+ virtualmodelcontrol/sim/model_plant.py,sha256=y5P7XUjk5_mlUC5DA1gXf6DOMFYkAqM6ALIVls8CL00,2805
44
+ virtualmodelcontrol/sim/plant.py,sha256=pG_e1Qn9ozAysSecMSHG9HgezRU1dyPXL49lM4kpnL4,1122
45
+ virtualmodelcontrol/sim/run.py,sha256=t4L0_KCsNO931GZAKVp2H9Q9u4wtl6K2X-sC6xmWxpg,2285
46
+ virtualmodelcontrol-0.1.0.dist-info/METADATA,sha256=fXt-VpyvUyPMxt4ruSrG2vM25Fqz5-bTZw7PRghpKws,2843
47
+ virtualmodelcontrol-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
48
+ virtualmodelcontrol-0.1.0.dist-info/licenses/LICENSE,sha256=0stuzGeBCLbI3KFHplc0nCKgO67svcYW-vIMw3I3Isw,1072
49
+ virtualmodelcontrol-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lorenzo Vignoli
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.