Reinforcement Learning
stable-baselines3
deep-reinforcement-learning
agricultural-ai
weather-modelling
curriculum-learning
edge-ai
Instructions to use DHDRL/monsoon-rl with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- stable-baselines3
How to use DHDRL/monsoon-rl with stable-baselines3:
from huggingface_sb3 import load_from_hub checkpoint = load_from_hub( repo_id="DHDRL/monsoon-rl", filename="{MODEL FILENAME}.zip", ) - Notebooks
- Google Colab
- Kaggle
Update hierarchical_search.py
Browse files- hierarchical_search.py +2 -40
hierarchical_search.py
CHANGED
|
@@ -59,13 +59,7 @@ logger = logging.getLogger(__name__)
|
|
| 59 |
# ---------------------------------------------------------------------------
|
| 60 |
# Depth -> data resolution
|
| 61 |
# ---------------------------------------------------------------------------
|
| 62 |
-
|
| 63 |
-
# deepest, smallest, flagged nodes ever pay for the expensive ones.
|
| 64 |
-
# BMKG_STATION (Indonesian met agency station data) is the ideal finest-leaf
|
| 65 |
-
# source for an Indonesia deployment if a real feed is wired up -- left out
|
| 66 |
-
# of the default chain since era5_data_pipeline.py's fetchers dict (in
|
| 67 |
-
# fetch_zone_obs) doesn't currently implement a BMKG fetcher; add one there
|
| 68 |
-
# first, then just append it below.
|
| 69 |
RESOLUTION_BY_DEPTH: List[DataSource] = [
|
| 70 |
DataSource.ERA5_REANALYSIS, # depth 0-1: country / province scale
|
| 71 |
DataSource.ERA5_REANALYSIS,
|
|
@@ -95,8 +89,6 @@ class BBox:
|
|
| 95 |
return ((self.lat_min + self.lat_max) / 2.0, (self.lon_min + self.lon_max) / 2.0)
|
| 96 |
|
| 97 |
def split(self) -> Tuple["BBox", "BBox"]:
|
| 98 |
-
"""Bisect along whichever axis (lat or lon) currently spans further --
|
| 99 |
-
literally the 'top/bottom or left/right 50%' heuristic."""
|
| 100 |
lat_span = self.lat_max - self.lat_min
|
| 101 |
lon_span = self.lon_max - self.lon_min
|
| 102 |
if lat_span >= lon_span:
|
|
@@ -140,8 +132,6 @@ class SpatialNode:
|
|
| 140 |
|
| 141 |
@dataclass
|
| 142 |
class ResolvedLeaf:
|
| 143 |
-
"""One terminal result from the search -- either max_depth was hit or the
|
| 144 |
-
node's coarse read was already below the alert floor."""
|
| 145 |
node: SpatialNode
|
| 146 |
max_risk: float
|
| 147 |
alert_level: AlertLevel
|
|
@@ -157,20 +147,11 @@ def _score_node(
|
|
| 157 |
date_range: Tuple[datetime, datetime],
|
| 158 |
config_template: ForecastConfig,
|
| 159 |
) -> Tuple[float, AlertLevel, DataSource]:
|
| 160 |
-
"""Register this node's bbox as a zone (idempotent -- register_zone just
|
| 161 |
-
overwrites), fetch it at the resolution appropriate for its depth, and
|
| 162 |
-
score it with the exact same crop_risk_scorer used everywhere else in
|
| 163 |
-
this project. No RL involved -- this is Phase 1's entire decision rule.
|
| 164 |
-
"""
|
| 165 |
polygon = node.bbox.to_polygon(node.node_id)
|
| 166 |
register_zone(polygon)
|
| 167 |
|
| 168 |
cfg_dict = config_template.to_dict()
|
| 169 |
if config_template.force_data_source is None:
|
| 170 |
-
# Only apply depth-based resolution when the caller hasn't already
|
| 171 |
-
# pinned a source -- an explicit override (e.g. forcing SYNTHETIC
|
| 172 |
-
# for an offline test) must actually take effect everywhere, not
|
| 173 |
-
# get silently clobbered per-node.
|
| 174 |
source = _resolution_for_depth(node.depth)
|
| 175 |
cfg_dict["force_data_source"] = source.value
|
| 176 |
else:
|
|
@@ -193,18 +174,6 @@ def hierarchical_search(
|
|
| 193 |
recurse_floor: AlertLevel = AlertLevel.WARNING,
|
| 194 |
config_template: Optional[ForecastConfig] = None,
|
| 195 |
) -> List[ResolvedLeaf]:
|
| 196 |
-
"""Budget-constrained best-first traversal of the bisection tree.
|
| 197 |
-
|
| 198 |
-
Best-first: a max-heap on max_risk, so a hard budget cap spends its
|
| 199 |
-
fetches on the worst-looking parts of the map first. A branch stops
|
| 200 |
-
(records a ResolvedLeaf) at max_depth or once alert_level drops below
|
| 201 |
-
recurse_floor. Total fetches <= budget always, regardless of leaf count
|
| 202 |
-
-- that's what makes country scale tractable.
|
| 203 |
-
|
| 204 |
-
Default recurse_floor is WARNING (product freeze): only refine regions
|
| 205 |
-
that already clear the client-facing drought/flood bars. Pass
|
| 206 |
-
AlertLevel.WATCH or ADVISORY to recurse more aggressively.
|
| 207 |
-
"""
|
| 208 |
cfg_template = config_template or ForecastConfig()
|
| 209 |
root = SpatialNode("root", root_bbox, depth=0)
|
| 210 |
|
|
@@ -238,10 +207,6 @@ def hierarchical_search(
|
|
| 238 |
heapq.heappush(frontier, (-max_risk, next(counter), child))
|
| 239 |
|
| 240 |
if failed > 0 and failed == spent:
|
| 241 |
-
# Every single node failed -- almost certainly a systemic problem
|
| 242 |
-
# (bad config, no network at all, wrong credentials), not noise.
|
| 243 |
-
# Returning an empty/near-empty list here would look identical to
|
| 244 |
-
# "searched thoroughly, found nothing risky" -- silently wrong.
|
| 245 |
raise RuntimeError(
|
| 246 |
f"hierarchical_search: all {spent} scored nodes failed -- "
|
| 247 |
f"check config_template/credentials before trusting any result "
|
|
@@ -289,7 +254,6 @@ def _decide_with_policy(node: SpatialNode, children_contexts, model, env_factory
|
|
| 289 |
if __name__ == "__main__":
|
| 290 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 291 |
|
| 292 |
-
# Roughly Indonesia's bounding box, for a realistic worst-case leaf count.
|
| 293 |
indonesia_bbox = BBox(lat_min=-11.0, lat_max=6.0, lon_min=95.0, lon_max=141.0)
|
| 294 |
|
| 295 |
date_range = (
|
|
@@ -297,8 +261,6 @@ if __name__ == "__main__":
|
|
| 297 |
datetime(2026, 7, 31, tzinfo=timezone.utc),
|
| 298 |
)
|
| 299 |
|
| 300 |
-
# force SYNTHETIC everywhere so this self-test needs no API credentials
|
| 301 |
-
# and no network access -- purely validates the tree/budget/pruning logic.
|
| 302 |
cfg = ForecastConfig(force_data_source=DataSource.SYNTHETIC)
|
| 303 |
|
| 304 |
results = hierarchical_search(
|
|
@@ -317,4 +279,4 @@ if __name__ == "__main__":
|
|
| 317 |
for depth in sorted(by_depth):
|
| 318 |
leaves = by_depth[depth]
|
| 319 |
alerts = [l.alert_level.value for l in leaves]
|
| 320 |
-
print(f" depth {depth}: {len(leaves)} branches stopped here -- alerts: {alerts}")
|
|
|
|
| 59 |
# ---------------------------------------------------------------------------
|
| 60 |
# Depth -> data resolution
|
| 61 |
# ---------------------------------------------------------------------------
|
| 62 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
RESOLUTION_BY_DEPTH: List[DataSource] = [
|
| 64 |
DataSource.ERA5_REANALYSIS, # depth 0-1: country / province scale
|
| 65 |
DataSource.ERA5_REANALYSIS,
|
|
|
|
| 89 |
return ((self.lat_min + self.lat_max) / 2.0, (self.lon_min + self.lon_max) / 2.0)
|
| 90 |
|
| 91 |
def split(self) -> Tuple["BBox", "BBox"]:
|
|
|
|
|
|
|
| 92 |
lat_span = self.lat_max - self.lat_min
|
| 93 |
lon_span = self.lon_max - self.lon_min
|
| 94 |
if lat_span >= lon_span:
|
|
|
|
| 132 |
|
| 133 |
@dataclass
|
| 134 |
class ResolvedLeaf:
|
|
|
|
|
|
|
| 135 |
node: SpatialNode
|
| 136 |
max_risk: float
|
| 137 |
alert_level: AlertLevel
|
|
|
|
| 147 |
date_range: Tuple[datetime, datetime],
|
| 148 |
config_template: ForecastConfig,
|
| 149 |
) -> Tuple[float, AlertLevel, DataSource]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
polygon = node.bbox.to_polygon(node.node_id)
|
| 151 |
register_zone(polygon)
|
| 152 |
|
| 153 |
cfg_dict = config_template.to_dict()
|
| 154 |
if config_template.force_data_source is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
source = _resolution_for_depth(node.depth)
|
| 156 |
cfg_dict["force_data_source"] = source.value
|
| 157 |
else:
|
|
|
|
| 174 |
recurse_floor: AlertLevel = AlertLevel.WARNING,
|
| 175 |
config_template: Optional[ForecastConfig] = None,
|
| 176 |
) -> List[ResolvedLeaf]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
cfg_template = config_template or ForecastConfig()
|
| 178 |
root = SpatialNode("root", root_bbox, depth=0)
|
| 179 |
|
|
|
|
| 207 |
heapq.heappush(frontier, (-max_risk, next(counter), child))
|
| 208 |
|
| 209 |
if failed > 0 and failed == spent:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
raise RuntimeError(
|
| 211 |
f"hierarchical_search: all {spent} scored nodes failed -- "
|
| 212 |
f"check config_template/credentials before trusting any result "
|
|
|
|
| 254 |
if __name__ == "__main__":
|
| 255 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 256 |
|
|
|
|
| 257 |
indonesia_bbox = BBox(lat_min=-11.0, lat_max=6.0, lon_min=95.0, lon_max=141.0)
|
| 258 |
|
| 259 |
date_range = (
|
|
|
|
| 261 |
datetime(2026, 7, 31, tzinfo=timezone.utc),
|
| 262 |
)
|
| 263 |
|
|
|
|
|
|
|
| 264 |
cfg = ForecastConfig(force_data_source=DataSource.SYNTHETIC)
|
| 265 |
|
| 266 |
results = hierarchical_search(
|
|
|
|
| 279 |
for depth in sorted(by_depth):
|
| 280 |
leaves = by_depth[depth]
|
| 281 |
alerts = [l.alert_level.value for l in leaves]
|
| 282 |
+
print(f" depth {depth}: {len(leaves)} branches stopped here -- alerts: {alerts}")
|