Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion agent_context/topics/simulation-system/physics-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,36 @@ Newton `solver_cfg=None` preserves DexSim's scene-aware `AutoSolverCfg`.
The concrete solver is resolved from the complete Spawn scene, then exposed
through `NewtonPhysicsBackend.solver_type`. Do not guess a solver-specific robot
preset while it is unresolved. Gradient mode requires an explicit
`semi_implicit` solver. Read exact solver options/defaults in
`semi_implicit` solver or a direct/named provider supporting the requested native
gradient mode. Functional Scene calls additionally require its functional
dynamics service. Python callers pass DexSim's `ProviderNewtonSolverCfg` with a
provider class/zero-argument factory directly through `solver_cfg`; this path
does not inspect installed metadata. DexSim creates fresh instances per model
build. Installed `dexsim.newton_solvers` names optionally pass through to DexSim's
`RegisteredNewtonSolverCfg`; missing SDK capabilities fail at selection or
provider loading rather than breaking ordinary backend imports. Read exact solver options/defaults in
`cfg/simulation.py` and the installed DexSim config types; do not substitute a
hardcoded fallback when an expected DexSim API is absent.

`physics/solvers/simple.py` owns the optional spring-slider provider;
`examples/simple_newton_solver/` demonstrates it through the public
`SimulationManager.spawn_result` Scene. The installed SDK must include the
direct-factory and service APIs. The example uses direct factory configuration;
the `simple` entry point remains optional for name-based configurations.
DexSim owns physical state, clocks and publication; the provider
owns its model-scoped parameters and functional transition. Independent
rollout uses `publish=False`; publishing advances the live clock once and must
not be paired with a manager update for the same transition. Scene functional
reset selects arena names, including the separate global `default` arena.

Articulation initialization also calls `reset(clear_dynamics=False)` without a
later coordinated clear. Preserve its position hold targets and mimic target
propagation when the solver accepts them. Query the finalized public
`Scene.dynamics_capabilities().supported_control_fields`; only an explicit
exclusion of `joint_target_q` suppresses that command. Released SDKs predating
extensions retain their unrestricted target behavior. Extension SDKs missing
the query require an upgrade instead of guessing support from solver names.

`collision_cfg` owns the external Newton contact pipeline. Its
`update_interval=None` updates at the first local solver substep; positive `k`
updates at `0, k, 2k, ...`, reusing contacts between updates. `collision_cfg=None`
Expand Down
15 changes: 15 additions & 0 deletions docs/source/api_reference/public_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,21 @@ embodichain.lab.sim.physics.newton

NewtonPhysicsBackend

embodichain.lab.sim.physics.solvers.simple
------------------------------------------

.. currentmodule:: embodichain.lab.sim.physics.solvers.simple

The optional ``simple`` entry point provides a spring-slider example of native
and functional Newton stepping. It requires a DexSim build exporting
``FunctionalDynamics``; ordinary backend imports do not load this provider.
See ``examples/simple_newton_solver/README.md`` for installation, supported
models, gradients and Scene publication/reset semantics.

.. autosummary::

SimpleSolverProvider

embodichain.lab.sim.motion.planners.base_planner
------------------------------------------------

Expand Down
44 changes: 33 additions & 11 deletions embodichain/lab/sim/cfg/physics.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,10 @@ class NewtonPhysicsCfg(PhysicsBackendCfg):
requires_grad: bool = False
"""Whether to finalize the Newton model with differentiable state enabled.

EmbodiChain currently requires the Semi-implicit solver for this mode and
disables CUDA graph capture when gradients are enabled.
Requires Semi-implicit or a direct/named provider supporting the requested
gradient mode. The provider validates native support during model creation;
Scene functional calls additionally require its functional dynamics service.
CUDA graph capture is disabled when gradients are enabled.
"""

use_cuda_graph: bool = True
Expand Down Expand Up @@ -349,6 +351,17 @@ class NewtonPhysicsCfg(PhysicsBackendCfg):
parameters accepted by that DexSim solver config. If omitted, EmbodiChain
preserves DexSim's scene-aware ``AutoSolverCfg`` default. A DexSim build
exporting ``AutoSolverCfg`` is required; no concrete-solver fallback is used.

Installed ``dexsim.newton_solvers`` entry-point names are also accepted by
DexSim builds with the named-extension API. For example,
``{"solver_type": "simple", "stiffness": 2.0}`` selects the spring-slider
demonstration. Extension names are exact; options pass to their provider.

Python callers can pass DexSim's ``ProviderNewtonSolverCfg`` with a
``provider_factory`` class or zero-argument factory and provider ``options``.
That configuration passes through directly without package discovery. The
SDK creates a fresh provider for each model build; pass a factory rather
than a live provider or solver instance.
"""

collision_cfg: NewtonCollisionPipelineCfg | Mapping[str, Any] | None = field(
Expand Down Expand Up @@ -407,13 +420,16 @@ def to_dexsim_cfg(
solver_cfg_map=solver_cfg_map,
)

if self.requires_grad and (
solver_cfg is None or solver_cfg.solver_type != "semi_implicit"
solver_name = "auto" if solver_cfg is None else str(solver_cfg.solver_type)
if (
self.requires_grad
and solver_name in solver_cfg_map
and solver_name != "semi_implicit"
):
logger.log_error(
"Newton gradient mode requires an explicit "
"solver_type='semi_implicit'; AutoSolver does not select a "
"differentiable solver."
"solver_type='semi_implicit' or a named functional extension; "
"AutoSolver does not select a differentiable solver."
)

collision_pipeline_cfg = None
Expand Down Expand Up @@ -494,11 +510,13 @@ def _normalize_newton_solver_type(solver_type: str) -> str:
"vbdsolvercfg": "vbd",
}
if key not in aliases:
logger.log_error(
f"Unsupported Newton solver type '{solver_type}'. "
"Expected one of 'auto', 'dexuni', 'mjwarp', 'xpbd', 'semi_implicit', "
"'featherstone', or 'vbd'."
)
try:
from dexsim.engine.newton_physics import normalize_newton_solver_name
except ImportError as exc:
raise ValueError(
f"Solver {solver_type!r} requires a DexSim build with named solver extensions"
) from exc
return normalize_newton_solver_name(solver_type)
return aliases[key]


Expand All @@ -525,6 +543,10 @@ def _newton_solver_cfg_to_dexsim(
or "auto"
)
normalized_solver_type = _normalize_newton_solver_type(str(configured_solver_type))
if normalized_solver_type not in solver_cfg_map:
from dexsim.engine.newton_physics import RegisteredNewtonSolverCfg

return RegisteredNewtonSolverCfg(normalized_solver_type, solver_cfg_data)
solver_cfg_type = solver_cfg_map[normalized_solver_type]
if normalized_solver_type == "dexuni":
try:
Expand Down
13 changes: 11 additions & 2 deletions embodichain/lab/sim/objects/articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
from embodichain.lab.sim.objects.backends.newton import (
_configure_newton_mimic_compliance,
_stabilize_newton_mimic_target_write,
_supports_newton_position_targets,
)
from embodichain.utils.math import (
matrix_from_euler,
Expand Down Expand Up @@ -2797,6 +2798,11 @@ def reset(
num_instances = len(local_env_ids)
self.cfg: ArticulationCfg

write_hold_targets = (
not self.body_data.is_newton_backend
or _supports_newton_position_targets(self._spawn_result)
)

self.restore_visual_material(env_ids=local_env_ids)
self._restore_default_physical_properties(local_env_ids)

Expand Down Expand Up @@ -2845,8 +2851,11 @@ def reset(
# impulse when initialized away from their equality manifold.
qpos = self._project_mimic_qpos(qpos)
self.set_qpos(qpos, target=False, env_ids=local_env_ids)
# Set drive target to hold position.
self.set_qpos(qpos, target=True, env_ids=local_env_ids)
# Initialization uses clear_dynamics=False with no subsequent clear.
# Keep hold targets and mimic propagation for position-capable solvers;
# effort-only solvers use native reset bookkeeping without a command.
if write_hold_targets:
self.set_qpos(qpos, target=True, env_ids=local_env_ids)

if clear_dynamics:
self.clear_dynamics(env_ids=local_env_ids)
Expand Down
17 changes: 17 additions & 0 deletions embodichain/lab/sim/objects/backends/newton.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ def is_newton_scene(scene: object) -> bool:
return isinstance(scene, Scene) and scene.backend == "newton"


def _supports_newton_position_targets(scene: Scene) -> bool:
"""Query the finalized solver contract while preserving legacy SDK support."""
query = getattr(scene, "dynamics_capabilities", None)
if query is None:
from dexsim.engine import newton_physics

if hasattr(newton_physics, "NewtonSolverExtension"):
raise RuntimeError(
"Newton solver extensions require a compatible DexSim SDK with "
"Scene.dynamics_capabilities() for articulation initialization."
)
# Released SDKs predating extensions accept the existing target channels.
return True
fields = query().supported_control_fields
return fields is None or "joint_target_q" in fields


def _create_newton_standalone_state_sync(
model: Any,
body_ids: Sequence[int],
Expand Down
20 changes: 20 additions & 0 deletions embodichain/lab/sim/physics/solvers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------
"""Optional Newton providers for direct factories or named package discovery."""

from __future__ import annotations

__all__: list[str] = []
Loading
Loading