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.
- devagent_physical_engine/__init__.py +44 -0
- devagent_physical_engine/agent/__init__.py +40 -0
- devagent_physical_engine/agent/compiler.py +285 -0
- devagent_physical_engine/agent/contracts.py +129 -0
- devagent_physical_engine/agent/coordinator.py +108 -0
- devagent_physical_engine/agent/critic.py +72 -0
- devagent_physical_engine/agent/evidence.py +34 -0
- devagent_physical_engine/agent/interpreter.py +179 -0
- devagent_physical_engine/agent/planner.py +105 -0
- devagent_physical_engine/agent/recovery.py +54 -0
- devagent_physical_engine/agent/routing.py +76 -0
- devagent_physical_engine/agent/runtime.py +270 -0
- devagent_physical_engine/agent/semantic.py +304 -0
- devagent_physical_engine/agent/structured.py +423 -0
- devagent_physical_engine/ai_cli.py +226 -0
- devagent_physical_engine/cli.py +392 -0
- devagent_physical_engine/doctor.py +20 -0
- devagent_physical_engine/engineering_agent.py +243 -0
- devagent_physical_engine/engineering_request.py +630 -0
- devagent_physical_engine/execution.py +90 -0
- devagent_physical_engine/models.py +143 -0
- devagent_physical_engine/operating_envelope.py +120 -0
- devagent_physical_engine/optimization/__init__.py +50 -0
- devagent_physical_engine/optimization/benchmark.py +122 -0
- devagent_physical_engine/optimization/candidates.py +198 -0
- devagent_physical_engine/optimization/contracts.py +235 -0
- devagent_physical_engine/optimization/evaluator.py +107 -0
- devagent_physical_engine/optimization/evidence.py +53 -0
- devagent_physical_engine/optimization/experience.py +105 -0
- devagent_physical_engine/optimization/measured.py +125 -0
- devagent_physical_engine/optimization/optimizer.py +215 -0
- devagent_physical_engine/optimization/orchestrator.py +155 -0
- devagent_physical_engine/physical_campaign.py +413 -0
- devagent_physical_engine/physical_evidence.py +214 -0
- devagent_physical_engine/physical_motion.py +196 -0
- devagent_physical_engine/planning.py +80 -0
- devagent_physical_engine/preexecution_contract.py +65 -0
- devagent_physical_engine/provider_adapters/__init__.py +22 -0
- devagent_physical_engine/provider_adapters/anthropic.py +112 -0
- devagent_physical_engine/provider_adapters/common.py +187 -0
- devagent_physical_engine/provider_adapters/factory.py +20 -0
- devagent_physical_engine/provider_adapters/gemini.py +126 -0
- devagent_physical_engine/provider_adapters/openai.py +95 -0
- devagent_physical_engine/provider_qualification.py +268 -0
- devagent_physical_engine/providers.py +94 -0
- devagent_physical_engine/qualification.py +44 -0
- devagent_physical_engine/qualification_cli.py +195 -0
- devagent_physical_engine/qualification_harness.py +917 -0
- devagent_physical_engine/robot_platform.py +411 -0
- devagent_physical_engine/robots.py +76 -0
- devagent_physical_engine/ros2/__init__.py +35 -0
- devagent_physical_engine/ros2/acceptance.py +324 -0
- devagent_physical_engine/ros2/commands.py +175 -0
- devagent_physical_engine/ros2/doctor.py +116 -0
- devagent_physical_engine/ros2/fk_probe.py +83 -0
- devagent_physical_engine/ros2/frame_alignment.py +61 -0
- devagent_physical_engine/ros2/gazebo_world.py +125 -0
- devagent_physical_engine/ros2/joint_state_recorder.py +64 -0
- devagent_physical_engine/ros2/measured_motion.py +233 -0
- devagent_physical_engine/ros2/moveit_scene.py +121 -0
- devagent_physical_engine/ros2/preexecution.py +113 -0
- devagent_physical_engine/ros2/qualification.py +81 -0
- devagent_physical_engine/ros2/qualification_v10.py +252 -0
- devagent_physical_engine/ros2/scene_probe.py +219 -0
- devagent_physical_engine/ros2/state_validity_probe.py +125 -0
- devagent_physical_engine/ros2/tf_probe.py +51 -0
- devagent_physical_engine/ros2/trajectory.py +188 -0
- devagent_physical_engine/ros2/ur5e.py +59 -0
- devagent_physical_engine/ros2/ur5e_adapter.py +349 -0
- devagent_physical_engine/ros2/ur5e_v10_adapter.py +292 -0
- devagent_physical_engine/setup_profile.py +356 -0
- devagent_physical_engine/simulation.py +32 -0
- devagent_physical_engine/simulation_platform.py +269 -0
- devagent_physical_engine/trajectory_qualification.py +201 -0
- devagent_physical_engine/twin.py +939 -0
- devagent_physical_engine/twin_builder.py +309 -0
- devagent_physical_engine/twin_materialization.py +404 -0
- devagent_physical_engine/verification.py +46 -0
- devagent_physical_engine-0.10.0.dist-info/METADATA +315 -0
- devagent_physical_engine-0.10.0.dist-info/RECORD +84 -0
- devagent_physical_engine-0.10.0.dist-info/WHEEL +5 -0
- devagent_physical_engine-0.10.0.dist-info/entry_points.txt +3 -0
- devagent_physical_engine-0.10.0.dist-info/licenses/NOTICE +2 -0
- 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
|
+
)
|