{"record":{"id":"a7188e50f0c0a4f5","repo":"microsoft/qlib","slug":"cannot-clear-memory-as-num-states-0","errorCode":null,"errorMessage":"cannot clear memory as `num_states==0`","messagePattern":"cannot clear memory as `num_states==0`","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/contrib/data/dataset.py","lineNumber":251,"sourceCode":"        obj._daily_index = self._daily_index[mask]\n        return obj\n\n    def restore_index(self, index):\n        return self._index[index]\n\n    def restore_daily_index(self, daily_index):\n        return pd.Index(self._daily_index.loc[daily_index])\n\n    def assign_data(self, index, vals):\n        if self.num_states == 0:\n            raise ValueError(\"cannot assign data as `num_states==0`\")\n        if isinstance(vals, torch.Tensor):\n            vals = vals.detach().cpu().numpy()\n        self._memory[index] = vals\n\n    def clear_memory(self):\n        if self.num_states == 0:\n            raise ValueError(\"cannot clear memory as `num_states==0`\")\n        self._memory[:] = 0\n\n    def train(self):\n        \"\"\"enable traning mode\"\"\"\n        self.batch_size, self.n_samples, self.drop_last, self.shuffle = self.params\n\n    def eval(self):\n        \"\"\"enable evaluation mode\"\"\"\n        self.batch_size = -1\n        self.n_samples = None\n        self.drop_last = False\n        self.shuffle = False\n\n    def _get_slices(self):\n        if self.batch_size < 0:  # daily sampling\n            slices = self._daily_slices.copy()\n            batch_size = -1 * self.batch_size\n        else:  # normal sampling","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/contrib/data/dataset.py#L233-L269","documentation":"clear_memory zeroes the RL dataset's state-memory tensor between epochs/episodes. Like assign_data, it is only meaningful when num_states > 0; with num_states == 0 there is no memory to clear and the call raises ValueError, signaling the dataset was configured for memoryless (supervised) use.","triggerScenarios":"An RL training loop unconditionally calling dataset.clear_memory() at episode end while the dataset was constructed with num_states=0.","commonSituations":"Shared training scripts used for both supervised and RL runs; num_states left at its default because the config key was renamed or nested incorrectly.","solutions":["Set num_states > 0 at dataset construction if state memory is required.","Condition the call: if ds.num_states > 0: ds.clear_memory().","Audit config plumbing to confirm num_states reaches the dataset constructor with the intended value."],"exampleFix":"# before\nfor ep in episodes:\n    train_episode()\n    ds.clear_memory()  # -> ValueError when num_states==0\n# after\nfor ep in episodes:\n    train_episode()\n    if ds.num_states > 0:\n        ds.clear_memory()","handlingStrategy":"validation","validationCode":"if ds.num_states > 0:\n    ds.clear_memory()\n# else: no memory allocated, nothing to clear","typeGuard":"def has_state_memory(ds) -> bool:\n    return getattr(ds, 'num_states', 0) > 0","tryCatchPattern":"try:\n    ds.clear_memory()\nexcept ValueError as e:\n    if 'num_states==0' in str(e):\n        pass  # expected for memoryless datasets\n    else:\n        raise","preventionTips":["Make memory lifecycle calls (assign_data/clear_memory) conditional on num_states > 0.","Log num_states once at dataset construction to catch config plumbing mistakes early."],"tags":["qlib","reinforcement-learning","dataset","state-validation"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}