feat(ccd): add dual-clock field sampling
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+60
-17
@@ -111,6 +111,7 @@ class FlowField:
|
||||
self.objects = {}
|
||||
self.action = np.zeros(0, dtype=self.DATA_TYPE)
|
||||
self.obs = np.zeros(0, dtype=self.DATA_TYPE)
|
||||
self._control_interval = None
|
||||
|
||||
initflow(
|
||||
self.flag_gpu,
|
||||
@@ -337,26 +338,55 @@ class FlowField:
|
||||
# if type == "taylor":
|
||||
# self.vortex_config[6] =
|
||||
|
||||
def run(self, num_steps: int, action_target: np.ndarray):
|
||||
if (
|
||||
action_target.size != len(self.objects)
|
||||
or action_target.dtype != self.DATA_TYPE
|
||||
):
|
||||
def _validate_run(self, num_steps: int, action_target: np.ndarray):
|
||||
if type(num_steps) is not int or num_steps < 1:
|
||||
raise ValueError("num_steps must be a positive integer")
|
||||
if action_target.size != len(self.objects) or action_target.dtype != self.DATA_TYPE:
|
||||
raise ValueError("action data type or size does not match the objects.")
|
||||
elif len(self.objects) == 0:
|
||||
if len(self.objects) == 0:
|
||||
raise ValueError("No objects have been added to the flow field.")
|
||||
|
||||
weight = 0.1
|
||||
stream = cuda.Stream()
|
||||
action_pinned = cuda.pagelocked_empty_like(self.action)
|
||||
action_pinned[:] = self.action
|
||||
obs_pinned = cuda.pagelocked_empty_like(self.obs)
|
||||
def run(self, num_steps: int, action_target: np.ndarray):
|
||||
"""Advance one complete legacy interval (original public behavior)."""
|
||||
if self._control_interval is not None:
|
||||
raise RuntimeError("run is unavailable while a control interval is active")
|
||||
self.begin_control_interval(num_steps, action_target)
|
||||
self.run_control_segment(num_steps)
|
||||
self.end_control_interval()
|
||||
|
||||
def begin_control_interval(self, total_steps: int, action_target: np.ndarray):
|
||||
"""Start one policy interval that may be split only to read/save fields."""
|
||||
if self._control_interval is not None:
|
||||
raise RuntimeError("a control interval is already active")
|
||||
self._validate_run(total_steps, action_target)
|
||||
self.error_flag[0] = 0
|
||||
cuda.memcpy_htod(self.error_flag_gpu, self.error_flag)
|
||||
self.obs[:] = 0
|
||||
for i in range(num_steps):
|
||||
action_pinned = (1 - weight) * action_pinned + weight * action_target
|
||||
cuda.memcpy_htod_async(self.action_gpu, action_pinned, stream)
|
||||
action = cuda.pagelocked_empty_like(self.action)
|
||||
action[:] = self.action
|
||||
self._control_interval = {
|
||||
"total_steps": total_steps,
|
||||
"completed_steps": 0,
|
||||
"target": action_target.copy(),
|
||||
"action": action,
|
||||
"obs_steps": cuda.pagelocked_empty((total_steps, self.obs.size), dtype=self.DATA_TYPE),
|
||||
"stream": cuda.Stream(),
|
||||
}
|
||||
|
||||
def run_control_segment(self, num_steps: int):
|
||||
"""Advance part of the active interval without resetting smoothing or obs."""
|
||||
state = self._control_interval
|
||||
if state is None:
|
||||
raise RuntimeError("no control interval is active")
|
||||
if type(num_steps) is not int or num_steps < 1:
|
||||
raise ValueError("num_steps must be a positive integer")
|
||||
if state["completed_steps"] + num_steps > state["total_steps"]:
|
||||
raise ValueError("segment exceeds the active control interval")
|
||||
stream = state["stream"]
|
||||
start = state["completed_steps"]
|
||||
for local_step in range(num_steps):
|
||||
state["action"] = 0.9 * state["action"] + 0.1 * state["target"]
|
||||
cuda.memcpy_htod_async(self.action_gpu, state["action"], stream)
|
||||
self.step(
|
||||
self.flag_gpu,
|
||||
self.ddf_gpu,
|
||||
@@ -375,13 +405,26 @@ class FlowField:
|
||||
stream=stream,
|
||||
)
|
||||
self.ddf_gpu, self.temp_gpu = self.temp_gpu, self.ddf_gpu
|
||||
cuda.memcpy_dtoh_async(obs_pinned, self.obs_gpu, stream)
|
||||
cuda.memcpy_dtoh_async(state["obs_steps"][start + local_step], self.obs_gpu, stream)
|
||||
cuda.memset_d32_async(self.obs_gpu, 0, self.obs.size, stream)
|
||||
self.obs += obs_pinned
|
||||
stream.synchronize()
|
||||
self.obs = (self.obs / num_steps).astype(self.DATA_TYPE)
|
||||
state["completed_steps"] += num_steps
|
||||
|
||||
def end_control_interval(self):
|
||||
"""Publish obs once, only at the original policy-control boundary."""
|
||||
state = self._control_interval
|
||||
if state is None:
|
||||
raise RuntimeError("no control interval is active")
|
||||
if state["completed_steps"] != state["total_steps"]:
|
||||
raise RuntimeError("cannot end a control interval before its boundary")
|
||||
self.obs[:] = 0
|
||||
for step_obs in state["obs_steps"]:
|
||||
self.obs += step_obs
|
||||
self.obs = (self.obs / state["total_steps"]).astype(self.DATA_TYPE)
|
||||
cuda.memcpy_dtoh(self.error_flag, self.error_flag_gpu)
|
||||
self.last_error_flag = int(self.error_flag[0])
|
||||
self._control_interval = None
|
||||
return self.obs
|
||||
|
||||
def has_numeric_error(self) -> bool:
|
||||
return bool(self.last_error_flag != 0)
|
||||
|
||||
Reference in New Issue
Block a user