devagent-physical-engine 0.10.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 (84) hide show
  1. devagent_physical_engine/__init__.py +44 -0
  2. devagent_physical_engine/agent/__init__.py +40 -0
  3. devagent_physical_engine/agent/compiler.py +285 -0
  4. devagent_physical_engine/agent/contracts.py +129 -0
  5. devagent_physical_engine/agent/coordinator.py +108 -0
  6. devagent_physical_engine/agent/critic.py +72 -0
  7. devagent_physical_engine/agent/evidence.py +34 -0
  8. devagent_physical_engine/agent/interpreter.py +179 -0
  9. devagent_physical_engine/agent/planner.py +105 -0
  10. devagent_physical_engine/agent/recovery.py +54 -0
  11. devagent_physical_engine/agent/routing.py +76 -0
  12. devagent_physical_engine/agent/runtime.py +270 -0
  13. devagent_physical_engine/agent/semantic.py +304 -0
  14. devagent_physical_engine/agent/structured.py +423 -0
  15. devagent_physical_engine/ai_cli.py +226 -0
  16. devagent_physical_engine/cli.py +392 -0
  17. devagent_physical_engine/doctor.py +20 -0
  18. devagent_physical_engine/engineering_agent.py +243 -0
  19. devagent_physical_engine/engineering_request.py +630 -0
  20. devagent_physical_engine/execution.py +90 -0
  21. devagent_physical_engine/models.py +143 -0
  22. devagent_physical_engine/operating_envelope.py +120 -0
  23. devagent_physical_engine/optimization/__init__.py +50 -0
  24. devagent_physical_engine/optimization/benchmark.py +122 -0
  25. devagent_physical_engine/optimization/candidates.py +198 -0
  26. devagent_physical_engine/optimization/contracts.py +235 -0
  27. devagent_physical_engine/optimization/evaluator.py +107 -0
  28. devagent_physical_engine/optimization/evidence.py +53 -0
  29. devagent_physical_engine/optimization/experience.py +105 -0
  30. devagent_physical_engine/optimization/measured.py +125 -0
  31. devagent_physical_engine/optimization/optimizer.py +215 -0
  32. devagent_physical_engine/optimization/orchestrator.py +155 -0
  33. devagent_physical_engine/physical_campaign.py +413 -0
  34. devagent_physical_engine/physical_evidence.py +214 -0
  35. devagent_physical_engine/physical_motion.py +196 -0
  36. devagent_physical_engine/planning.py +80 -0
  37. devagent_physical_engine/preexecution_contract.py +65 -0
  38. devagent_physical_engine/provider_adapters/__init__.py +22 -0
  39. devagent_physical_engine/provider_adapters/anthropic.py +112 -0
  40. devagent_physical_engine/provider_adapters/common.py +187 -0
  41. devagent_physical_engine/provider_adapters/factory.py +20 -0
  42. devagent_physical_engine/provider_adapters/gemini.py +126 -0
  43. devagent_physical_engine/provider_adapters/openai.py +95 -0
  44. devagent_physical_engine/provider_qualification.py +268 -0
  45. devagent_physical_engine/providers.py +94 -0
  46. devagent_physical_engine/qualification.py +44 -0
  47. devagent_physical_engine/qualification_cli.py +195 -0
  48. devagent_physical_engine/qualification_harness.py +917 -0
  49. devagent_physical_engine/robot_platform.py +411 -0
  50. devagent_physical_engine/robots.py +76 -0
  51. devagent_physical_engine/ros2/__init__.py +35 -0
  52. devagent_physical_engine/ros2/acceptance.py +324 -0
  53. devagent_physical_engine/ros2/commands.py +175 -0
  54. devagent_physical_engine/ros2/doctor.py +116 -0
  55. devagent_physical_engine/ros2/fk_probe.py +83 -0
  56. devagent_physical_engine/ros2/frame_alignment.py +61 -0
  57. devagent_physical_engine/ros2/gazebo_world.py +125 -0
  58. devagent_physical_engine/ros2/joint_state_recorder.py +64 -0
  59. devagent_physical_engine/ros2/measured_motion.py +233 -0
  60. devagent_physical_engine/ros2/moveit_scene.py +121 -0
  61. devagent_physical_engine/ros2/preexecution.py +113 -0
  62. devagent_physical_engine/ros2/qualification.py +81 -0
  63. devagent_physical_engine/ros2/qualification_v10.py +252 -0
  64. devagent_physical_engine/ros2/scene_probe.py +219 -0
  65. devagent_physical_engine/ros2/state_validity_probe.py +125 -0
  66. devagent_physical_engine/ros2/tf_probe.py +51 -0
  67. devagent_physical_engine/ros2/trajectory.py +188 -0
  68. devagent_physical_engine/ros2/ur5e.py +59 -0
  69. devagent_physical_engine/ros2/ur5e_adapter.py +349 -0
  70. devagent_physical_engine/ros2/ur5e_v10_adapter.py +292 -0
  71. devagent_physical_engine/setup_profile.py +356 -0
  72. devagent_physical_engine/simulation.py +32 -0
  73. devagent_physical_engine/simulation_platform.py +269 -0
  74. devagent_physical_engine/trajectory_qualification.py +201 -0
  75. devagent_physical_engine/twin.py +939 -0
  76. devagent_physical_engine/twin_builder.py +309 -0
  77. devagent_physical_engine/twin_materialization.py +404 -0
  78. devagent_physical_engine/verification.py +46 -0
  79. devagent_physical_engine-0.10.0.dist-info/METADATA +315 -0
  80. devagent_physical_engine-0.10.0.dist-info/RECORD +84 -0
  81. devagent_physical_engine-0.10.0.dist-info/WHEEL +5 -0
  82. devagent_physical_engine-0.10.0.dist-info/entry_points.txt +3 -0
  83. devagent_physical_engine-0.10.0.dist-info/licenses/NOTICE +2 -0
  84. devagent_physical_engine-0.10.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, timezone
5
+ from hashlib import sha256
6
+ import json
7
+ from math import isfinite
8
+ from time import perf_counter
9
+ from typing import Any, Mapping, Sequence
10
+
11
+ from .physical_motion import MotionExecutionMetrics, PhysicalMotionPlan
12
+ from .robot_platform import QualificationState
13
+ from .simulation_platform import RobotSimulationAdapter
14
+ from .twin import TwinSpec
15
+
16
+
17
+ class TrajectoryQualificationError(RuntimeError):
18
+ pass
19
+
20
+
21
+ def _hash(payload: object) -> str:
22
+ text = json.dumps(
23
+ payload, sort_keys=True, separators=(",", ":"), allow_nan=False
24
+ )
25
+ return sha256(text.encode("utf-8")).hexdigest()
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class TrajectoryQualificationCase:
30
+ case_id: str
31
+ motion: PhysicalMotionPlan
32
+ reset_state: Mapping[str, Any]
33
+ max_tracking_error_rad: float
34
+
35
+ def __post_init__(self) -> None:
36
+ if not self.case_id.strip():
37
+ raise TrajectoryQualificationError("trajectory_qualification_case_id_required")
38
+ threshold = float(self.max_tracking_error_rad)
39
+ if not isfinite(threshold) or threshold <= 0:
40
+ raise TrajectoryQualificationError("trajectory_tracking_threshold_invalid")
41
+ try:
42
+ json.dumps(self.reset_state, sort_keys=True, allow_nan=False)
43
+ except (TypeError, ValueError) as exc:
44
+ raise TrajectoryQualificationError(
45
+ "trajectory_reset_state_not_json_safe"
46
+ ) from exc
47
+
48
+ def to_dict(self) -> dict[str, Any]:
49
+ return {
50
+ "case_id": self.case_id,
51
+ "motion_hash": self.motion.fingerprint,
52
+ "reset_state": dict(self.reset_state),
53
+ "max_tracking_error_rad": self.max_tracking_error_rad,
54
+ }
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class TrajectoryQualificationResult:
59
+ case: TrajectoryQualificationCase
60
+ passed: bool
61
+ metrics: MotionExecutionMetrics
62
+ failure_code: str | None
63
+ duration_ms: float
64
+
65
+ def to_dict(self) -> dict[str, Any]:
66
+ payload = {
67
+ "case": self.case.to_dict(),
68
+ "passed": self.passed,
69
+ "metrics": self.metrics.to_dict(),
70
+ "failure_code": self.failure_code,
71
+ "duration_ms": round(self.duration_ms, 6),
72
+ }
73
+ payload["evidence_hash"] = _hash(payload)
74
+ return payload
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class TrajectoryQualificationReport:
79
+ run_id: str
80
+ adapter_id: str
81
+ adapter_qualification_at_start: str
82
+ scope: str
83
+ total_cases: int
84
+ passed_cases: int
85
+ corpus_hash: str
86
+ promotion_candidate: bool
87
+ physical_qualification: bool
88
+ commissioning_qualification: bool
89
+ results: tuple[TrajectoryQualificationResult, ...]
90
+
91
+ def to_dict(self) -> dict[str, Any]:
92
+ return {
93
+ "run_id": self.run_id,
94
+ "adapter_id": self.adapter_id,
95
+ "adapter_qualification_at_start": self.adapter_qualification_at_start,
96
+ "scope": self.scope,
97
+ "total_cases": self.total_cases,
98
+ "passed_cases": self.passed_cases,
99
+ "corpus_hash": self.corpus_hash,
100
+ "promotion_candidate": self.promotion_candidate,
101
+ "physical_qualification": self.physical_qualification,
102
+ "commissioning_qualification": self.commissioning_qualification,
103
+ "real_execution_allowed": False,
104
+ "results": [item.to_dict() for item in self.results],
105
+ }
106
+
107
+
108
+ class TrajectoryRuntimeQualificationRunner:
109
+ """Qualify only the compiled-trajectory execution/measurement path."""
110
+
111
+ def run(
112
+ self,
113
+ *,
114
+ adapter: RobotSimulationAdapter,
115
+ twin: TwinSpec,
116
+ cases: Sequence[TrajectoryQualificationCase],
117
+ ) -> TrajectoryQualificationReport:
118
+ if not cases:
119
+ raise TrajectoryQualificationError("trajectory_qualification_empty")
120
+ if len(cases) > 100:
121
+ raise TrajectoryQualificationError("trajectory_qualification_case_limit")
122
+ descriptor = adapter.descriptor
123
+ if descriptor.qualification not in {
124
+ QualificationState.EXPERIMENTAL,
125
+ QualificationState.QUALIFIED,
126
+ }:
127
+ raise TrajectoryQualificationError("trajectory_adapter_not_runnable")
128
+ if twin.robot_profile_key not in descriptor.robot_profile_keys:
129
+ raise TrajectoryQualificationError("trajectory_adapter_robot_mismatch")
130
+ for case in cases:
131
+ if case.motion.robot_profile_key != twin.robot_profile_key:
132
+ raise TrajectoryQualificationError("trajectory_case_robot_mismatch")
133
+ if case.motion.twin_hash != twin.fingerprint:
134
+ raise TrajectoryQualificationError("trajectory_case_twin_hash_mismatch")
135
+
136
+ health = adapter.doctor(robot_profile_key=twin.robot_profile_key)
137
+ if not health.ready:
138
+ raise TrajectoryQualificationError(
139
+ f"trajectory_adapter_doctor_failed:{health.code}"
140
+ )
141
+
142
+ results: list[TrajectoryQualificationResult] = []
143
+ try:
144
+ adapter.prepare(twin=twin)
145
+ for case in cases:
146
+ started = perf_counter()
147
+ failure: str | None = None
148
+ try:
149
+ adapter.reset(case_id=case.case_id, state=case.reset_state)
150
+ metrics = adapter.execute_motion(motion=case.motion)
151
+ tracking = metrics.max_tracking_error_rad
152
+ passed = (
153
+ metrics.success
154
+ and tracking is not None
155
+ and tracking <= case.max_tracking_error_rad
156
+ )
157
+ if not passed:
158
+ failure = (
159
+ "tracking_measurement_missing"
160
+ if tracking is None
161
+ else "trajectory_runtime_threshold_failed"
162
+ )
163
+ except Exception as exc:
164
+ failure = f"trajectory_runner_error:{type(exc).__name__}"
165
+ metrics = MotionExecutionMetrics(
166
+ success=False,
167
+ observed_duration_s=None,
168
+ planned_duration_s=case.motion.planned_duration_s,
169
+ planned_joint_travel_rad=case.motion.joint_travel_rad,
170
+ failure_codes=(failure,),
171
+ metrics_origin="trajectory_qualification_runner",
172
+ )
173
+ passed = False
174
+ results.append(
175
+ TrajectoryQualificationResult(
176
+ case=case,
177
+ passed=passed,
178
+ metrics=metrics,
179
+ failure_code=failure,
180
+ duration_ms=(perf_counter() - started) * 1000.0,
181
+ )
182
+ )
183
+ finally:
184
+ adapter.stop()
185
+
186
+ corpus_hash = _hash([case.to_dict() for case in cases])
187
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ")
188
+ passed_cases = sum(item.passed for item in results)
189
+ return TrajectoryQualificationReport(
190
+ run_id=f"trajectory-runtime-{timestamp}",
191
+ adapter_id=descriptor.adapter_id,
192
+ adapter_qualification_at_start=descriptor.qualification.value,
193
+ scope="trajectory_runtime_only",
194
+ total_cases=len(results),
195
+ passed_cases=passed_cases,
196
+ corpus_hash=corpus_hash,
197
+ promotion_candidate=passed_cases == len(results),
198
+ physical_qualification=False,
199
+ commissioning_qualification=False,
200
+ results=tuple(results),
201
+ )