Skip to content
Closed
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
1 change: 1 addition & 0 deletions docs/api/newton.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ newton
eval_ik
eval_jacobian
eval_mass_matrix
reset_state

.. rubric:: Constants

Expand Down
2 changes: 2 additions & 0 deletions newton/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
eval_ik,
eval_jacobian,
eval_mass_matrix,
reset_state,
)
Comment on lines +64 to 65
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Confirm API generation script exists
fd -i "generate_api.py"

# Check whether docs already reference the new public symbol
if [ -d docs ]; then
  rg -n --iglob "*.rst" --iglob "*.md" '\breset_state\b' docs || true
fi

# Confirm the symbol is exported in package surfaces
rg -n --iglob "*.py" '"reset_state"|reset_state' newton/__init__.py newton/_src/sim/__init__.py

Repository: newton-physics/newton

Length of output: 504


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Examine what the generate_api.py script does
head -50 docs/generate_api.py

# Find generated API doc files
find docs -type f \( -name "*.rst" -o -name "*.md" \) | head -20

# Check for API reference files that would list public symbols
find docs -type f -name "*api*" -o -name "*reference*" | head -20

# Look for generated auto-API documentation
find docs -type f \( -name "*.rst" \) -exec grep -l "reset_state\|newton\.reset_state" {} \;

Repository: newton-physics/newton

Length of output: 2281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the generated API doc for the main newton module
cat docs/api/newton.rst

Repository: newton-physics/newton

Length of output: 1430


Run docs/generate_api.py to include the new reset_state export in the API documentation.

The symbol was added to the public API in newton/__init__.py but is missing from the generated API reference file (docs/api/newton.rst). Running the script will ensure the documentation stays in sync with the code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/__init__.py` around lines 64 - 65, The public API now exports the
symbol reset_state from newton/__init__.py but the generated API docs are out of
sync; run the documentation generator (docs/generate_api.py) to regenerate
docs/api/newton.rst so reset_state appears in the API reference, then commit the
updated docs file along with the code change.


__all__ += [
Expand All @@ -78,6 +79,7 @@
"eval_ik",
"eval_jacobian",
"eval_mass_matrix",
"reset_state",
]

# ==================================================================================
Expand Down
15 changes: 15 additions & 0 deletions newton/_src/sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,19 @@
"eval_ik",
"eval_jacobian",
"eval_mass_matrix",
"reset_state",
]


def reset_state(model: Model, state: State, eval_fk: bool = True) -> None:
"""Reset a state to the model's initial configuration.

Convenience wrapper for :meth:`Model.reset_state`. See that method for
full documentation.

Args:
model: The model whose initial configuration to restore.
state: The state object to reset.
eval_fk: Whether to re-evaluate forward kinematics.
"""
model.reset_state(state, eval_fk=eval_fk)
42 changes: 42 additions & 0 deletions newton/_src/sim/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,48 @@ def state(self, requires_grad: bool | None = None) -> State:

return s

def reset_state(self, state: State, eval_fk: bool = True) -> None:
"""
Reset a :class:`State` to this model's initial configuration in-place.

Copies the model's initial position and velocity arrays into ``state``
and zeroes all force arrays. Unlike :meth:`state`, this reuses the
existing GPU allocations -- no new arrays are created.

Args:
state: The state object to reset (must have been created by this model).
eval_fk: If True and the model has joints, re-evaluate forward
kinematics so that :attr:`State.body_q` and :attr:`State.body_qd`
are consistent with the restored joint coordinates.
"""
if self.particle_count:
wp.copy(state.particle_q, self.particle_q)
wp.copy(state.particle_qd, self.particle_qd)
state.particle_f.zero_()

if self.body_count:
wp.copy(state.body_q, self.body_q)
wp.copy(state.body_qd, self.body_qd)
state.body_f.zero_()
if getattr(state, "body_q_prev", None) is not None:
wp.copy(state.body_q_prev, self.body_q)
if getattr(state, "body_qdd", None) is not None:
state.body_qdd.zero_()
if getattr(state, "body_parent_f", None) is not None:
state.body_parent_f.zero_()

if self.joint_count:
wp.copy(state.joint_q, self.joint_q)
wp.copy(state.joint_qd, self.joint_qd)
mujoco_ns = getattr(state, "mujoco", None)
if mujoco_ns is not None and getattr(mujoco_ns, "qfrc_actuator", None) is not None:
mujoco_ns.qfrc_actuator.zero_()

if eval_fk and self.joint_count:
from .articulation import eval_fk as _eval_fk # noqa: PLC0415

_eval_fk(self, self.joint_q, self.joint_qd, state)

Comment on lines +852 to +893
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset registered custom STATE attributes too.

state() clones all attributes registered with assignment == STATE, but reset_state() only restores the built-in fields. Any model that keeps extra per-state Warp arrays on State will carry their last runtime values across reset, so this new public API does not actually return the full state to its initial configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/sim/model.py` around lines 852 - 893, reset_state currently
restores only built-in fields while state() also clones attributes registered
with assignment == STATE; modify reset_state to iterate the same registration
list/state-attribute registry used by state() and for each registered STATE
attribute copy the model's initial value into the provided State (use wp.copy
for Warp arrays and preserve zero_/in-place semantics for buffers), i.e. locate
the registry used by state() and for each entry set state.<attr_name> = copy of
self.<attr_name> (or call .zero_() where appropriate) before running eval_fk so
custom per-state Warp arrays are reset as well.

def control(self, requires_grad: bool | None = None, clone_variables: bool = True) -> Control:
"""
Create and return a new :class:`Control` object for this model.
Expand Down
29 changes: 29 additions & 0 deletions newton/_src/viewer/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ def is_paused(self) -> bool:
"""
return False

def is_reset_requested(self) -> bool:
"""Report whether a simulation reset has been requested.

The flag is set by viewer UI controls (e.g. the *R* key or
*Reset* button in :class:`ViewerGL`). Callers should check this
once per frame before stepping the simulation and call
:meth:`clear_reset_request` after handling the reset.

Returns:
bool: True when a reset has been requested.
"""
return self._reset_requested

def clear_reset_request(self) -> None:
"""Clear the reset-requested flag after the reset has been handled."""
self._reset_requested = False

def request_reset(self) -> None:
"""Request a simulation reset.

Sets the internal flag that :meth:`is_reset_requested` queries.
The next frame's run-loop iteration (or user code) should detect
the flag, perform the reset, and call :meth:`clear_reset_request`.
"""
self._reset_requested = True

def is_key_down(self, key: str | int) -> bool:
"""Default key query API. Concrete viewers can override.

Expand Down Expand Up @@ -111,6 +137,9 @@ def clear_model(self) -> None:
# Picking
self.picking_enabled = True

# Reset signal
self._reset_requested = False

# Display options
self.show_joints = False
self.show_com = False
Expand Down
5 changes: 5 additions & 0 deletions newton/_src/viewer/viewer_gl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1637,6 +1637,9 @@ def on_key_press(self, symbol: int, modifiers: int):
elif symbol == pyglet.window.key.F:
# Frame camera around model bounds
self._frame_camera_on_model()
elif symbol == pyglet.window.key.R:
# Request simulation reset
self.request_reset()
elif symbol == pyglet.window.key.ESCAPE:
# Exit with Escape key
self.renderer.close()
Expand Down Expand Up @@ -1967,6 +1970,8 @@ def _render_left_panel(self):

# Pause simulation checkbox
changed, self._paused = imgui.checkbox("Pause", self._paused)
if imgui.button("Reset"):
self.request_reset()

# Visualization Controls section
imgui.set_next_item_open(True, imgui.Cond_.appearing)
Expand Down
17 changes: 12 additions & 5 deletions newton/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ class _ExampleBrowser:
def __init__(self, viewer):
self.viewer = viewer
self.switch_target: str | None = None
self._reset_requested = False
self.callback = None
self._tree: dict[str, list[tuple[str, str]]] = {}

Expand All @@ -214,7 +213,7 @@ def _browser_ui(imgui):
imgui.tree_pop()
imgui.separator()
if imgui.button("Reset"):
self._reset_requested = True
self.viewer.request_reset()

self.callback = _browser_ui
viewer.register_ui_callback(_browser_ui, position="panel")
Expand All @@ -240,7 +239,6 @@ def switch(self, example_class):

def reset(self, example_class):
"""Reset the current example by re-creating it. Returns the new example or None."""
self._reset_requested = False
self.viewer.clear_model()
try:
parser = getattr(example_class, "create_parser", create_parser)()
Expand Down Expand Up @@ -279,8 +277,17 @@ def run(example, args):
example, example_class = browser.switch(example_class)
continue

if browser is not None and browser._reset_requested:
example = browser.reset(example_class)
if viewer.is_reset_requested():
viewer.clear_reset_request()
if hasattr(example, "reset"):
example.reset()
elif hasattr(example, "model"):
for attr in ("state_0", "state_1"):
s = getattr(example, attr, None)
if s is not None:
example.model.reset_state(s)
if hasattr(example, "sim_time"):
example.sim_time = 0.0
Comment on lines +282 to +290
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset example.control in the fallback path too.

The generic reset only rewinds state_0/state_1 and sim_time. Examples commonly allocate self.control = self.model.control() alongside those states, so any runtime edits to targets/actuation survive the reset and the next step no longer starts from the original setup.

💡 Minimal fix
             elif hasattr(example, "model"):
                 for attr in ("state_0", "state_1"):
                     s = getattr(example, attr, None)
                     if s is not None:
                         example.model.reset_state(s)
+                if getattr(example, "control", None) is not None:
+                    example.control = example.model.control()
                 if hasattr(example, "sim_time"):
                     example.sim_time = 0.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/examples/__init__.py` around lines 282 - 290, In the fallback reset
branch (where example has no reset method but has a model) also recreate/reset
the example.control to the model's default by calling example.model.control()
and assigning it to example.control; keep the existing logic that resets
example.model.reset_state for attributes "state_0" and "state_1" and resets
example.sim_time to 0.0, but add the example.control reset so runtime edits to
control don't persist across resets.

continue

if example is None:
Expand Down
Loading
Loading