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,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
+ )