Skip to content

Commit 06e25f1

Browse files
committed
Update lunar lander API. Add dependencies for swig and box2d
1 parent 150228a commit 06e25f1

File tree

6 files changed

+146
-41
lines changed

6 files changed

+146
-41
lines changed

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ ENV PATH="/root/.local/bin:$PATH"
1313
WORKDIR /app
1414

1515
# Install system dependencies
16-
RUN apt-get update && apt-get install -y --no-install-recommends \
16+
RUN apt-get update && apt-get install -y --no-install-recommends build-essential python3-dev swig \
1717
gcc libgl1 libglib2.0-0 curl && \
1818
rm -rf /var/lib/apt/lists/*
19+
#RUN apt-get update && apt-get install -y swig python3-dev
20+
1921

2022
# Install uv (https://docs.astral.sh/uv)
2123
RUN curl -LsSf https://astral.sh/uv/install.sh | sh

api/gymnasium_envs/box2d/lunar_lander_env_continuous_api.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
44
"""
55
import sys
6-
from typing import Any
6+
from typing import Any, Annotated
77
from loguru import logger
8-
from fastapi import APIRouter, Body, status
8+
from fastapi import APIRouter, status, Depends
99
from fastapi.responses import JSONResponse
1010
from fastapi import HTTPException
11-
from api.utils.time_step_response import TimeStep, TimeStepType
11+
12+
from api.utils.make_env_request_model import MakeEnvRequestModel
13+
from api.utils.make_env_response_model import MakeEnvResponseModel
14+
from api.utils.reset_request_model import RestEnvRequestModel
15+
from api.utils.spaces.actions import ContinuousVectorAction
16+
from api.utils.time_step_response import TimeStep, TimeStepType, TimeStepResponse
1217
from api.utils.gym_env_manager import GymEnvManager
18+
from api.api_config import get_api_config, Config
1319

1420
lunar_lander_continuous_router = APIRouter(prefix="/gymnasium/lunar-lander-continuous-env",
1521
tags=["Lunar Lander Continuous API"])
@@ -24,6 +30,9 @@
2430
1: "Side engine throttle — range [-1, 1], controlling left/right orientation.",
2531
}
2632

33+
DEFAULT_OPTIONS = {'gravity': -10.0, 'enable_wind': False, 'wind_power': 15.0, 'turbulence_power': 1.5}
34+
DEFAULT_VERSION = "v3"
35+
2736

2837
@lunar_lander_continuous_router.get("/copies")
2938
async def get_n_copies() -> JSONResponse:
@@ -37,7 +46,7 @@ async def get_action_space() -> JSONResponse:
3746
content={"action_space": ACTIONS_SPACE})
3847

3948

40-
@lunar_lander_continuous_router.get("/is-alive")
49+
@lunar_lander_continuous_router.get("/{idx}/is-alive")
4150
async def get_is_alive(idx: str) -> JSONResponse:
4251
is_alive_ = manager.is_alive(idx=idx)
4352
return JSONResponse(status_code=status.HTTP_200_OK,
@@ -56,10 +65,15 @@ async def close(idx: str) -> JSONResponse:
5665
content={"message": "FAILED"})
5766

5867

59-
@lunar_lander_continuous_router.post("/make")
60-
async def make(version: str = Body(default="v3"),
61-
options: dict[str, Any] = Body(default={'gravity': -10.0, 'enable_wind': False,
62-
'wind_power': 15.0, 'turbulence_power': 1.5})) -> JSONResponse:
68+
@lunar_lander_continuous_router.post("/make", status_code=status.HTTP_201_CREATED,
69+
response_model=MakeEnvResponseModel)
70+
async def make(request: MakeEnvRequestModel,
71+
api_config: Annotated[Config, Depends(get_api_config)]) -> JSONResponse:
72+
version = request.version or DEFAULT_VERSION
73+
74+
# merge defaults with user overrides
75+
options = DEFAULT_OPTIONS | (request.options or {})
76+
6377
if version == 'v1' or version == 'v2':
6478
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
6579
detail='Environment version v1 for `LunarLander` '
@@ -70,14 +84,15 @@ async def make(version: str = Body(default="v3"),
7084
options['continuous'] = True
7185
idx = await manager.make(env_name=env_type, **options)
7286

73-
logger.info(f'Created environment {ENV_NAME} and index {idx}')
87+
if api_config.LOG_INFO:
88+
logger.info(f'Created environment {env_type} with index {idx}')
7489
return JSONResponse(status_code=status.HTTP_201_CREATED,
7590
content={"message": "OK", "idx": idx})
7691

7792

78-
@lunar_lander_continuous_router.post("/{idx}/reset")
79-
async def reset(idx: str, seed: int = Body(default=42),
80-
options: dict[str, Any] = Body(default={})) -> JSONResponse:
93+
@lunar_lander_continuous_router.post("/{idx}/reset", status_code=status.HTTP_202_ACCEPTED,
94+
response_model=TimeStepResponse)
95+
async def reset(idx: str, reset_ops: RestEnvRequestModel) -> JSONResponse:
8196
"""Reset the environment
8297
8398
:return:
@@ -88,7 +103,7 @@ async def reset(idx: str, seed: int = Body(default=42),
88103
detail={"message": "NOT_ALIVE/NOT_CREATED"})
89104

90105
try:
91-
reset_step = await manager.reset(idx=idx, seed=seed)
106+
reset_step = await manager.reset(idx=idx, seed=reset_ops.seed)
92107

93108
observation = reset_step.observation
94109
observation = [float(val) for val in observation]
@@ -107,17 +122,19 @@ async def reset(idx: str, seed: int = Body(default=42),
107122
" Have you called make()?"})
108123

109124

110-
@lunar_lander_continuous_router.post("/{idx}/step")
111-
async def step(idx: str, action: list[float] = Body(...)) -> JSONResponse:
125+
@lunar_lander_continuous_router.post("/{idx}/step", status_code=status.HTTP_202_ACCEPTED,
126+
response_model=TimeStepResponse)
127+
async def step(idx: str, action: ContinuousVectorAction,
128+
api_config: Annotated[Config, Depends(get_api_config)]) -> JSONResponse:
112129
if idx not in manager:
113130
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
114131
detail={"message": "NOT_ALIVE/NOT_CREATED. Call make/reset"})
115132

116-
if len(action) != len(ACTIONS_SPACE):
133+
if len(action.action) != len(ACTIONS_SPACE):
117134
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
118-
detail=f"Action vector size {len(action)} != 2")
135+
detail=f"Action vector size {len(action.action)} != 2")
119136

120-
step_result = await manager.step(idx=idx, action=action)
137+
step_result = await manager.step(idx=idx, action=action.action)
121138

122139
step_type = TimeStepType.MID
123140
if step_result.terminated:
@@ -135,6 +152,7 @@ async def step(idx: str, action: list[float] = Body(...)) -> JSONResponse:
135152
info=step_result.info,
136153
discount=1.0)
137154

138-
logger.info(f'Step in environment {ENV_NAME} and index {idx}')
155+
if api_config.LOG_INFO:
156+
logger.info(f'Step in environment {ENV_NAME} and index {idx}')
139157
return JSONResponse(status_code=status.HTTP_202_ACCEPTED,
140158
content={"time_step": step_.model_dump()})

api/gymnasium_envs/box2d/lunar_lander_env_discrete_api.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
44
"""
55
import sys
6-
from typing import Any
6+
from typing import Any, Annotated
77
from loguru import logger
8-
from fastapi import APIRouter, Body, status
8+
from fastapi import APIRouter, Body, status, Depends
99
from fastapi.responses import JSONResponse
1010
from fastapi import HTTPException
11-
from api.utils.time_step_response import TimeStep, TimeStepType
11+
12+
from api.utils.make_env_request_model import MakeEnvRequestModel
13+
from api.utils.make_env_response_model import MakeEnvResponseModel
14+
from api.utils.reset_request_model import RestEnvRequestModel
15+
from api.utils.spaces.actions import DiscreteAction
16+
from api.utils.time_step_response import TimeStep, TimeStepType, TimeStepResponse
1217
from api.utils.gym_env_manager import GymEnvManager
18+
from api.api_config import get_api_config, Config
1319

1420
lunar_lander_discrete_router = APIRouter(prefix="/gymnasium/lunar-lander-discrete-env",
1521
tags=["Lunar Lander Discrete API"])
@@ -26,6 +32,9 @@
2632
3: "fire right orientation engine"
2733
}
2834

35+
DEFAULT_OPTIONS = {'gravity': -10.0, 'enable_wind': False, 'wind_power': 15.0, 'turbulence_power': 1.5}
36+
DEFAULT_VERSION = "v3"
37+
2938

3039
@lunar_lander_discrete_router.get("/copies")
3140
async def get_n_copies() -> JSONResponse:
@@ -58,29 +67,35 @@ async def close(idx: str) -> JSONResponse:
5867
content={"message": "FAILED"})
5968

6069

61-
@lunar_lander_discrete_router.post("/make")
62-
async def make(version: str = Body(default="v3"),
63-
options: dict[str, Any] = Body(default={'gravity': -10.0, 'enable_wind': False,
64-
'wind_power': 15.0, 'turbulence_power': 1.5})) -> JSONResponse:
70+
@lunar_lander_discrete_router.post("/make", status_code=status.HTTP_201_CREATED,
71+
response_model=MakeEnvResponseModel)
72+
async def make(request: MakeEnvRequestModel,
73+
api_config: Annotated[Config, Depends(get_api_config)]) -> JSONResponse:
74+
version = request.version or DEFAULT_VERSION
75+
76+
# merge defaults with user overrides
77+
options = DEFAULT_OPTIONS | (request.options or {})
78+
6579
if version == 'v1' or version == 'v2':
6680
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
67-
detail='Environment version v1 for `LunarLander` '
81+
detail='Environment version v1/v2 for `LunarLander` '
6882
'is deprecated. Please use `LunarLander-v3` instead.')
6983

7084
env_type = f"{ENV_NAME}-{version}"
7185

7286
options['continuous'] = False
7387
idx = await manager.make(env_name=env_type, **options)
7488

75-
logger.info(f'Created environment {ENV_NAME} and index {idx}')
89+
if api_config.LOG_INFO:
90+
logger.info(f'Created environment {env_type} with index {idx}')
7691
return JSONResponse(status_code=status.HTTP_201_CREATED,
7792
content={"message": "OK", "idx": idx})
7893

7994

80-
@lunar_lander_discrete_router.post("/{idx}/reset")
95+
@lunar_lander_discrete_router.post("/{idx}/reset", status_code=status.HTTP_202_ACCEPTED,
96+
response_model=TimeStepResponse)
8197
async def reset(idx: str,
82-
seed: int = Body(default=42),
83-
options: dict[str, Any] = Body(default={})) -> JSONResponse:
98+
reset_ops: RestEnvRequestModel) -> JSONResponse:
8499
"""Reset the environment
85100
86101
:return:
@@ -91,7 +106,7 @@ async def reset(idx: str,
91106
detail={"message": "NOT_ALIVE/NOT_CREATED"})
92107

93108
try:
94-
reset_step = await manager.reset(idx=idx, seed=seed)
109+
reset_step = await manager.reset(idx=idx, seed=reset_ops.seed)
95110

96111
observation = reset_step.observation
97112
observation = [float(val) for val in observation]
@@ -110,17 +125,19 @@ async def reset(idx: str,
110125
" Have you called make()?"})
111126

112127

113-
@lunar_lander_discrete_router.post("/{idx}/step")
114-
async def step(idx: str, action: int = Body(...)) -> JSONResponse:
128+
@lunar_lander_discrete_router.post("/{idx}/step", status_code=status.HTTP_202_ACCEPTED,
129+
response_model=TimeStepResponse)
130+
async def step(idx: str, action: DiscreteAction,
131+
api_config: Annotated[Config, Depends(get_api_config)]) -> JSONResponse:
115132
if idx not in manager:
116133
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
117134
detail={"message": "NOT_ALIVE/NOT_CREATED. Call make/reset"})
118135

119-
if action not in ACTIONS_SPACE:
136+
if action.action not in ACTIONS_SPACE:
120137
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
121138
detail=f"Action {action} not in {list(ACTIONS_SPACE.keys())}")
122139

123-
step_result = await manager.step(idx=idx, action=action)
140+
step_result = await manager.step(idx=idx, action=action.action)
124141

125142
step_type = TimeStepType.MID
126143
if step_result.terminated:
@@ -138,6 +155,7 @@ async def step(idx: str, action: int = Body(...)) -> JSONResponse:
138155
info=step_result.info,
139156
discount=1.0)
140157

141-
logger.info(f'Step in environment {ENV_NAME} and index {idx}')
158+
if api_config.LOG_INFO:
159+
logger.info(f'Step in environment {ENV_NAME} and index {idx}')
142160
return JSONResponse(status_code=status.HTTP_202_ACCEPTED,
143161
content={"time_step": step_.model_dump()})

api/utils/spaces/actions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ class DiscreteAction(BaseModel):
77

88
class ContinuousAction(BaseModel):
99
action: float
10+
11+
12+
class ContinuousVectorAction(BaseModel):
13+
action: list[float]

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ requires-python = ">=3.12"
77
dependencies = [
88
"fastapi>=0.121.0",
99
"fastapi-cli>=0.0.14",
10-
"gymnasium>=1.2.2",
10+
"gymnasium[box2d]>=1.2.2",
1111
"loguru>=0.7.3",
1212
"six>=1.17.0",
13+
"swig>=4.4.1",
1314
"tensorboard>=2.20.0",
1415
"torch>=2.9.1",
1516
]

0 commit comments

Comments
 (0)