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,309 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
from .engineering_request import ValidatedEngineeringRequest
|
|
8
|
+
from .robot_platform import RobotProfileRegistry
|
|
9
|
+
from .twin import (
|
|
10
|
+
ControllerModel,
|
|
11
|
+
EvidenceOrigin,
|
|
12
|
+
EvidenceValue,
|
|
13
|
+
FrameTransform,
|
|
14
|
+
GeometryKind,
|
|
15
|
+
GeometrySpec,
|
|
16
|
+
Pose3D,
|
|
17
|
+
RealityCalibrationEvidence,
|
|
18
|
+
SensorSpec,
|
|
19
|
+
ToolSpec,
|
|
20
|
+
TwinEntity,
|
|
21
|
+
TwinSpec,
|
|
22
|
+
TwinValidationReport,
|
|
23
|
+
TwinValidator,
|
|
24
|
+
UncertaintyModel,
|
|
25
|
+
evidence_summary,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TwinBuildState(str, Enum):
|
|
30
|
+
NEEDS_INFORMATION = "needs_information"
|
|
31
|
+
READY_FOR_PLANNING = "ready_for_planning"
|
|
32
|
+
READY_FOR_PHYSICS = "ready_for_physics"
|
|
33
|
+
REALITY_CALIBRATED = "reality_calibrated"
|
|
34
|
+
INVALID = "invalid"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class TwinInformationRequest:
|
|
39
|
+
code: str
|
|
40
|
+
question: str
|
|
41
|
+
reason: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class TwinBuildInputs:
|
|
46
|
+
"""Customer/site information already known to DevAgent.
|
|
47
|
+
|
|
48
|
+
The builder intentionally has no implicit dimensions, frames, TCP, physics,
|
|
49
|
+
or latency defaults. Unknown engineering-critical data remains unknown.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
robot_base_pose: Pose3D | None = None
|
|
53
|
+
entities: tuple[TwinEntity, ...] = ()
|
|
54
|
+
tool: ToolSpec | None = None
|
|
55
|
+
frames: tuple[FrameTransform, ...] = ()
|
|
56
|
+
sensors: tuple[SensorSpec, ...] = ()
|
|
57
|
+
controller: ControllerModel = ControllerModel()
|
|
58
|
+
uncertainty: UncertaintyModel = UncertaintyModel()
|
|
59
|
+
reality: RealityCalibrationEvidence = RealityCalibrationEvidence()
|
|
60
|
+
metadata: Mapping[str, Any] | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True, slots=True)
|
|
64
|
+
class TwinBuildResult:
|
|
65
|
+
twin: TwinSpec
|
|
66
|
+
validation: TwinValidationReport
|
|
67
|
+
state: TwinBuildState
|
|
68
|
+
information_requests: tuple[TwinInformationRequest, ...]
|
|
69
|
+
evidence_origins: Mapping[str, int]
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def planning_allowed(self) -> bool:
|
|
73
|
+
return self.state in {
|
|
74
|
+
TwinBuildState.READY_FOR_PLANNING,
|
|
75
|
+
TwinBuildState.READY_FOR_PHYSICS,
|
|
76
|
+
TwinBuildState.REALITY_CALIBRATED,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def physics_allowed(self) -> bool:
|
|
81
|
+
return self.state in {
|
|
82
|
+
TwinBuildState.READY_FOR_PHYSICS,
|
|
83
|
+
TwinBuildState.REALITY_CALIBRATED,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def to_dict(self) -> dict[str, Any]:
|
|
87
|
+
return {
|
|
88
|
+
"state": self.state.value,
|
|
89
|
+
"twin_id": self.twin.twin_id,
|
|
90
|
+
"twin_hash": self.validation.twin_hash,
|
|
91
|
+
"validation": self.validation.to_dict(),
|
|
92
|
+
"information_requests": [
|
|
93
|
+
{
|
|
94
|
+
"code": item.code,
|
|
95
|
+
"question": item.question,
|
|
96
|
+
"reason": item.reason,
|
|
97
|
+
}
|
|
98
|
+
for item in self.information_requests
|
|
99
|
+
],
|
|
100
|
+
"evidence_origins": dict(self.evidence_origins),
|
|
101
|
+
"planning_allowed": self.planning_allowed,
|
|
102
|
+
"physics_allowed": self.physics_allowed,
|
|
103
|
+
"physical_qualification": False,
|
|
104
|
+
"real_execution_allowed": False,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_QUESTIONS: dict[str, tuple[str, str]] = {
|
|
109
|
+
"robot_base_pose_missing": (
|
|
110
|
+
"Where is the robot base frame located in the cell?",
|
|
111
|
+
"The base transform is required to evaluate reach, collision, and path geometry.",
|
|
112
|
+
),
|
|
113
|
+
"missing_source_entity": (
|
|
114
|
+
"Please provide the source geometry and pose.",
|
|
115
|
+
"The requested pickup/source location is not represented in the twin.",
|
|
116
|
+
),
|
|
117
|
+
"missing_destination_entity": (
|
|
118
|
+
"Please provide the destination geometry and pose.",
|
|
119
|
+
"The requested destination is not represented in the twin.",
|
|
120
|
+
),
|
|
121
|
+
"missing_workpiece_entity": (
|
|
122
|
+
"Please provide the workpiece geometry and initial pose.",
|
|
123
|
+
"The part being manipulated must be represented to qualify physical motion.",
|
|
124
|
+
),
|
|
125
|
+
"entity_pose_missing": (
|
|
126
|
+
"Please provide the missing entity pose or import calibrated cell geometry.",
|
|
127
|
+
"Enabled collision geometry cannot be placed correctly without a pose.",
|
|
128
|
+
),
|
|
129
|
+
"entity_geometry_unknown": (
|
|
130
|
+
"Please provide dimensions or CAD/mesh geometry for the missing entity.",
|
|
131
|
+
"Collision and reachability qualification require usable geometry.",
|
|
132
|
+
),
|
|
133
|
+
"tool_not_specified": (
|
|
134
|
+
"Which tool or gripper is mounted on the robot?",
|
|
135
|
+
"The tool changes collision geometry, TCP, reachability, and payload behavior.",
|
|
136
|
+
),
|
|
137
|
+
"tool_tcp_missing": (
|
|
138
|
+
"What is the calibrated TCP of the selected tool?",
|
|
139
|
+
"Motion planning must use the real tool center point rather than an invented one.",
|
|
140
|
+
),
|
|
141
|
+
"tool_geometry_unknown": (
|
|
142
|
+
"Please provide tool dimensions or CAD/mesh geometry.",
|
|
143
|
+
"Unknown tool collision geometry limits geometric qualification.",
|
|
144
|
+
),
|
|
145
|
+
"physics_evidence_incomplete": (
|
|
146
|
+
"Please provide workpiece mass/contact data and controller timing/speed information for physics qualification.",
|
|
147
|
+
"Planning geometry is available, but the twin does not yet contain enough physical evidence for a physics-level claim.",
|
|
148
|
+
),
|
|
149
|
+
"possible_unit_mismatch": (
|
|
150
|
+
"Please confirm the imported dimensions and their units.",
|
|
151
|
+
"An unusually large dimension may indicate a millimetre/metre conversion error; DevAgent will not plan through it until confirmed.",
|
|
152
|
+
),
|
|
153
|
+
"unknown_pose_frame": (
|
|
154
|
+
"Please provide the missing coordinate-frame transform.",
|
|
155
|
+
"A pose references a frame that is not connected to the twin frame graph.",
|
|
156
|
+
),
|
|
157
|
+
"unknown_parent_frame": (
|
|
158
|
+
"Please provide the missing parent-frame transform.",
|
|
159
|
+
"The frame graph is incomplete and cannot be used for geometric planning.",
|
|
160
|
+
),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_RECOVERABLE_INFORMATION_CODES = frozenset(
|
|
164
|
+
{
|
|
165
|
+
"robot_base_pose_missing",
|
|
166
|
+
"missing_source_entity",
|
|
167
|
+
"missing_destination_entity",
|
|
168
|
+
"missing_workpiece_entity",
|
|
169
|
+
"entity_pose_missing",
|
|
170
|
+
"entity_geometry_unknown",
|
|
171
|
+
"tool_tcp_missing",
|
|
172
|
+
"possible_unit_mismatch",
|
|
173
|
+
"unknown_pose_frame",
|
|
174
|
+
"unknown_parent_frame",
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TwinBuilder:
|
|
180
|
+
"""Assemble a twin from explicit evidence and validate what it can claim."""
|
|
181
|
+
|
|
182
|
+
def __init__(self, robot_profiles: RobotProfileRegistry) -> None:
|
|
183
|
+
self.robot_profiles = robot_profiles
|
|
184
|
+
self.validator = TwinValidator(robot_profiles)
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _apply_request_payload(
|
|
188
|
+
request: ValidatedEngineeringRequest,
|
|
189
|
+
entities: tuple[TwinEntity, ...],
|
|
190
|
+
) -> tuple[TwinEntity, ...]:
|
|
191
|
+
"""Carry an explicitly supplied request payload into the workpiece model.
|
|
192
|
+
|
|
193
|
+
This is not a guessed physics parameter. It preserves provenance as
|
|
194
|
+
USER_DECLARED and never overwrites a more specific supplied mass.
|
|
195
|
+
Friction/contact data remain unknown until separately provided.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
if request.payload_kg is None:
|
|
199
|
+
return entities
|
|
200
|
+
output: list[TwinEntity] = []
|
|
201
|
+
for entity in entities:
|
|
202
|
+
if entity.entity_id != request.object_id:
|
|
203
|
+
output.append(entity)
|
|
204
|
+
continue
|
|
205
|
+
if entity.physics.mass_kg.value is not None:
|
|
206
|
+
output.append(entity)
|
|
207
|
+
continue
|
|
208
|
+
physics = replace(
|
|
209
|
+
entity.physics,
|
|
210
|
+
mass_kg=EvidenceValue(
|
|
211
|
+
request.payload_kg,
|
|
212
|
+
EvidenceOrigin.USER_DECLARED,
|
|
213
|
+
source_ref=request.request_id,
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
output.append(replace(entity, physics=physics))
|
|
217
|
+
return tuple(output)
|
|
218
|
+
|
|
219
|
+
def build(
|
|
220
|
+
self,
|
|
221
|
+
request: ValidatedEngineeringRequest,
|
|
222
|
+
inputs: TwinBuildInputs,
|
|
223
|
+
*,
|
|
224
|
+
twin_id: str | None = None,
|
|
225
|
+
) -> TwinBuildResult:
|
|
226
|
+
profile = self.robot_profiles.resolve(request.robot_key)
|
|
227
|
+
|
|
228
|
+
entities = self._apply_request_payload(request, tuple(inputs.entities))
|
|
229
|
+
twin = TwinSpec(
|
|
230
|
+
twin_id=twin_id or f"TWIN-{request.request_id[4:]}",
|
|
231
|
+
robot_profile_key=profile.key,
|
|
232
|
+
robot_base_pose=inputs.robot_base_pose,
|
|
233
|
+
entities=entities,
|
|
234
|
+
source_entity_id=request.source,
|
|
235
|
+
destination_entity_id=request.destination,
|
|
236
|
+
workpiece_entity_id=request.object_id,
|
|
237
|
+
tool=inputs.tool,
|
|
238
|
+
frames=tuple(inputs.frames),
|
|
239
|
+
sensors=tuple(inputs.sensors),
|
|
240
|
+
controller=inputs.controller,
|
|
241
|
+
uncertainty=inputs.uncertainty,
|
|
242
|
+
reality=inputs.reality,
|
|
243
|
+
metadata=dict(inputs.metadata or {}),
|
|
244
|
+
)
|
|
245
|
+
validation = self.validator.validate(twin, request=request)
|
|
246
|
+
|
|
247
|
+
error_codes = {issue.code for issue in validation.errors}
|
|
248
|
+
if error_codes and not error_codes.issubset(_RECOVERABLE_INFORMATION_CODES):
|
|
249
|
+
state = TwinBuildState.INVALID
|
|
250
|
+
elif error_codes:
|
|
251
|
+
state = TwinBuildState.NEEDS_INFORMATION
|
|
252
|
+
elif validation.reality_calibrated:
|
|
253
|
+
state = TwinBuildState.REALITY_CALIBRATED
|
|
254
|
+
elif validation.ready_for_physics:
|
|
255
|
+
state = TwinBuildState.READY_FOR_PHYSICS
|
|
256
|
+
elif validation.ready_for_planning:
|
|
257
|
+
state = TwinBuildState.READY_FOR_PLANNING
|
|
258
|
+
else:
|
|
259
|
+
state = TwinBuildState.NEEDS_INFORMATION
|
|
260
|
+
|
|
261
|
+
information_requests = self._questions_for(validation)
|
|
262
|
+
return TwinBuildResult(
|
|
263
|
+
twin=twin,
|
|
264
|
+
validation=validation,
|
|
265
|
+
state=state,
|
|
266
|
+
information_requests=information_requests,
|
|
267
|
+
evidence_origins=evidence_summary(twin),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def _questions_for(
|
|
272
|
+
validation: TwinValidationReport,
|
|
273
|
+
) -> tuple[TwinInformationRequest, ...]:
|
|
274
|
+
result: list[TwinInformationRequest] = []
|
|
275
|
+
seen: set[str] = set()
|
|
276
|
+
for issue in validation.issues:
|
|
277
|
+
if issue.code in seen:
|
|
278
|
+
continue
|
|
279
|
+
template = _QUESTIONS.get(issue.code)
|
|
280
|
+
if template is None:
|
|
281
|
+
continue
|
|
282
|
+
seen.add(issue.code)
|
|
283
|
+
result.append(TwinInformationRequest(issue.code, *template))
|
|
284
|
+
return tuple(result)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def conceptual_entity(
|
|
288
|
+
entity_id: str,
|
|
289
|
+
entity_type: str,
|
|
290
|
+
*,
|
|
291
|
+
pose: Pose3D | None = None,
|
|
292
|
+
) -> TwinEntity:
|
|
293
|
+
"""Explicit placeholder helper for UI/import staging.
|
|
294
|
+
|
|
295
|
+
A conceptual entity is intentionally UNKNOWN geometry and therefore cannot
|
|
296
|
+
pass geometric planning validation. It is useful for representing what the
|
|
297
|
+
user named while DevAgent asks for dimensions/CAD instead of inventing them.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
return TwinEntity(
|
|
301
|
+
entity_id=entity_id,
|
|
302
|
+
entity_type=entity_type,
|
|
303
|
+
pose=pose,
|
|
304
|
+
geometry=GeometrySpec(
|
|
305
|
+
GeometryKind.UNKNOWN,
|
|
306
|
+
EvidenceOrigin.UNKNOWN,
|
|
307
|
+
collision_geometry=False,
|
|
308
|
+
),
|
|
309
|
+
)
|