| """ |
| rl/model.py |
| PyTorch model definitions: MapEncoder CNN + PathQNet MLP. |
| """ |
| from __future__ import annotations |
| import math |
| from typing import List |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| ACTIONS = ["UP", "DOWN", "LEFT", "RIGHT", "WAIT"] |
| ACTION2IDX = {a: i for i, a in enumerate(ACTIONS)} |
| CELL2IDX = {"road": 0, "building": 1, "tree": 2, "obstacle": 3, "delivery": 4, "done_del": 5, "drone": 6} |
|
|
|
|
| class MapEncoder(nn.Module): |
| """Encodes a flat cell-type grid to a latent embedding using a mini-CNN.""" |
|
|
| def __init__(self, embed_dim: int = 64): |
| super().__init__() |
| self.embed = nn.Embedding(len(CELL2IDX) + 1, 8) |
| self.conv = nn.Sequential( |
| nn.Conv2d(8, 16, 3, padding=1), nn.ReLU(), |
| nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), |
| nn.AdaptiveAvgPool2d((4, 4)), |
| ) |
| self.fc = nn.Linear(32 * 4 * 4, embed_dim) |
|
|
| def forward(self, cell_ids: torch.Tensor) -> torch.Tensor: |
| B = cell_ids.size(0) |
| x = self.embed(cell_ids) |
| x = x.permute(0, 2, 1) |
| side = int(math.ceil(math.sqrt(x.size(-1)))) |
| pad_len = side * side - x.size(-1) |
| if pad_len > 0: |
| x = F.pad(x, (0, pad_len)) |
| x = x.reshape(B, 8, side, side) |
| x = self.conv(x) |
| x = x.reshape(B, -1) |
| return F.relu(self.fc(x)) |
|
|
|
|
| class PathQNet(nn.Module): |
| """ |
| Maps (map_embed, drone_xy_norm, battery, target_xy_norm) → Q(a) for 5 actions. |
| """ |
|
|
| def __init__(self, embed_dim: int = 64): |
| super().__init__() |
| in_dim = embed_dim + 5 |
| self.map_encoder = MapEncoder(embed_dim=embed_dim) |
| self.net = nn.Sequential( |
| nn.Linear(in_dim, 128), nn.LayerNorm(128), nn.ReLU(), |
| nn.Linear(128, 128), nn.ReLU(), |
| nn.Linear(128, 64), nn.ReLU(), |
| nn.Linear(64, len(ACTIONS)), |
| ) |
|
|
| def forward( |
| self, |
| cell_ids: torch.Tensor, |
| drone_xy: torch.Tensor, |
| battery: torch.Tensor, |
| target_xy: torch.Tensor, |
| ) -> torch.Tensor: |
| map_emb = self.map_encoder(cell_ids) |
| state = torch.cat([map_emb, drone_xy, battery, target_xy], dim=-1) |
| return self.net(state) |
|
|