Skip to content

Commit e0a423a

Browse files
authored
Merge pull request #145 from pockerman/feature_142_lunar_lander_environment
Feature 142 lunar lander environment
2 parents a021a26 + 60a9d38 commit e0a423a

File tree

20 files changed

+1274
-736
lines changed

20 files changed

+1274
-736
lines changed

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ FILE(GLOB SRCS src/rlenvs/*.cpp
147147
src/rlenvs/utils/trajectory/*.cpp
148148
)
149149

150-
ADD_LIBRARY(rlenvscpplib SHARED ${SRCS})
150+
ADD_LIBRARY(rlenvscpplib SHARED ${SRCS}
151+
src/rlenvs/envs/gymnasium/box2d/lunar_lander_env.h
152+
src/rlenvs/envs/gymnasium/box2d/lunar_lander_env.cpp
153+
examples/box2d/box2d_example.cpp)
151154

152155
SET_TARGET_PROPERTIES(rlenvscpplib PROPERTIES LINKER_LANGUAGE CXX)
153156
INSTALL(TARGETS rlenvscpplib DESTINATION ${CMAKE_INSTALL_PREFIX})

docs/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ sphinx==5.0.2
22
breathe==4.35.0
33
exhale==0.3.7
44
sphinx_rtd_theme==3.0.2
5+
6+

examples/CMakeLists.txt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@ ADD_SUBDIRECTORY(example_10)
4545
ADD_SUBDIRECTORY(example_11)
4646
ADD_SUBDIRECTORY(example_12)
4747
ADD_SUBDIRECTORY(example_13)
48+
ADD_SUBDIRECTORY(box2d)
4849

49-
#IF(RLENVSCPP_WEBOTS)
50-
# ADD_SUBDIRECTORY(webots/world_1)
51-
#ENDIF()
50+
IF(RLENVSCPP_WEBOTS)
51+
ADD_SUBDIRECTORY(webots/world_1)
52+
ENDIF()
5253

5354

5455
# We follow a different approach when working with
5556
# Ray so we don't compile the example with CMake
56-
#IF(RLENVSCPP_RAY)
57-
# ADD_SUBDIRECTORY(ray/ray_example_2)
58-
#ENDIF()
57+
IF(RLENVSCPP_RAY)
58+
ADD_SUBDIRECTORY(ray/ray_example_2)
59+
ENDIF()

examples/box2d/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CMAKE_MINIMUM_REQUIRED(VERSION 3.20)
2+
3+
SET(EXECUTABLE box2d_example)
4+
SET(SOURCE ${EXECUTABLE}.cpp)
5+
6+
ADD_EXECUTABLE(${EXECUTABLE} ${SOURCE})
7+
TARGET_LINK_LIBRARIES(${EXECUTABLE} rlenvscpplib)
8+
9+
IF(RLENVSCPP_WEBOTS)
10+
TARGET_LINK_LIBRARIES(${EXECUTABLE} CppController)
11+
ENDIF()

examples/box2d/box2d_example.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Created by alex on 7/5/25.
3+
//
4+
5+
#include "rlenvs/envs/gymnasium/box2d/lunar_lander_env.h"
6+
#include "rlenvs/envs/api_server/apiserver.h"
7+
8+
#include <unordered_map>
9+
#include <iostream>
10+
11+
namespace box2d_example
12+
{
13+
const std::string SERVER_URL = "http://0.0.0.0:8001/api";
14+
using rlenvscpp::envs::RESTApiServerWrapper;
15+
using rlenvscpp::envs::gymnasium::LunarLanderDiscreteEnv;
16+
}
17+
18+
int main()
19+
{
20+
using namespace box2d_example;
21+
22+
RESTApiServerWrapper server(SERVER_URL, true);
23+
24+
LunarLanderDiscreteEnv env(server);
25+
26+
std::unordered_map<std::string, std::any> options;
27+
options["wind_power"] = std::any(static_cast<rlenvscpp::real_t>(10.0));
28+
options["enable_wind"] = std::any(static_cast<bool>(true));
29+
options["gravity"] = std::any(static_cast<rlenvscpp::real_t>(-9.86));
30+
options["turbulence_power"] = std::any(static_cast<rlenvscpp::real_t>(1.5));
31+
env.make("v3", options);
32+
33+
std::cout<<"Is environment created? "<<env.is_created()<<std::endl;
34+
std::cout<<"Is environment alive? "<<env.is_alive()<<std::endl;
35+
std::cout<<"Number of valid actions? "<<env.n_actions()<<std::endl;
36+
37+
auto time_step = env.reset();
38+
std::cout<<"Time step: "<<time_step<<std::endl;
39+
40+
time_step = env.step(1);
41+
std::cout<<"Time step: "<<time_step<<std::endl;
42+
43+
auto copy = env.make_copy(1);
44+
time_step = copy.reset();
45+
std::cout<<"Time step: "<<time_step<<std::endl;
46+
47+
env.close();
48+
copy.close();
49+
50+
}

examples/example_13/example_13.cpp

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
1-
/**
2-
* This example utilises the ```TensorboardServer``` class to log values of interest when running
3-
* an experiment. We can monitor the experimet using <a href="https://www.tensorflow.org/tensorboard">tensorboard</a>.
4-
* The ```TensorboardServer``` class is a simple wrapper that exposes three functions
5-
*
6-
* - ```add_scalar```
7-
* - ```add_scalars```
8-
* - ```add_text```
9-
*
10-
* We will use ```add_scalar``` and ```add_text```. In order to run this example, fire up the server using the ```torchboard_server/start_uvicorn.sh```.
11-
* The server listens at port 8002. You can change this however you want just make sure that the port is not used and also update the
12-
* variable ```TORCH_SERVER_HOST``` in the code below accordingly. Note that the implementation uses
13-
* <a href="https://pytorch.org/docs/stable/_modules/torch/utils/tensorboard/writer.html#SummaryWriter">SummaryWriter</a> class.
14-
* Thus you will need to have PyTorch installed on the machine that you run the server.
15-
*/
16-
17-
181
#include "rlenvs/rlenvs_types_v2.h"
192
#include "rlenvs/utils/geometry/geom_point.h"
203
#include "rlenvs/utils/geometry/mesh/mesh.h"

rest_api/gymnasium_envs/box2d/__init__.py

Whitespace-only changes.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
"""The Acrobot environment from gymnasium:
2+
for more information check: https://gymnasium.farama.org/environments/classic_control/acrobot/
3+
4+
"""
5+
import gymnasium as gym
6+
from typing import Any
7+
from loguru import logger
8+
from fastapi import APIRouter, Body, status
9+
from fastapi.responses import JSONResponse
10+
from fastapi import HTTPException
11+
from time_step_response import TimeStep, TimeStepType
12+
13+
lunar_lander_discrete_router = APIRouter(prefix="/gymnasium/lunar-lander-discrete-env",
14+
tags=["Lunar Lander Discrete API"])
15+
16+
ENV_NAME = "LunarLander"
17+
18+
# the environments to create
19+
envs = {
20+
0: None
21+
}
22+
23+
# actions that the environment accepts
24+
ACTIONS_SPACE = {0: "do nothing",
25+
1: "fire left orientation engine",
26+
2: "fire main engine",
27+
3: "fire right orientation engine"
28+
}
29+
30+
31+
@lunar_lander_discrete_router.get("/action-space")
32+
async def get_action_space() -> JSONResponse:
33+
return JSONResponse(status_code=status.HTTP_200_OK,
34+
content={"action_space": ACTIONS_SPACE})
35+
36+
37+
@lunar_lander_discrete_router.get("/is-alive")
38+
async def get_is_alive(cidx: int) -> JSONResponse:
39+
global envs
40+
if cidx in envs:
41+
env = envs[cidx]
42+
43+
if env is None:
44+
return JSONResponse(status_code=status.HTTP_200_OK,
45+
content={"result": False})
46+
else:
47+
return JSONResponse(status_code=status.HTTP_200_OK,
48+
content={"result": True})
49+
else:
50+
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,
51+
content={"message": f"Environment {ENV_NAME} and index {cidx} has not been created"})
52+
53+
54+
@lunar_lander_discrete_router.post("/close")
55+
async def close(cidx: int) -> JSONResponse:
56+
global envs
57+
if cidx in envs:
58+
env = envs[cidx]
59+
if env is not None:
60+
envs[cidx].close()
61+
envs[cidx] = None
62+
logger.info(f'Closed environment {ENV_NAME} and index {cidx}')
63+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED,
64+
content={"message": f"Environment {ENV_NAME} and index {cidx} is closed"})
65+
66+
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,
67+
content={"message": f"Environment {ENV_NAME} and index {cidx} has not been created"})
68+
69+
70+
@lunar_lander_discrete_router.post("/make")
71+
async def make(version: str = Body(default="v3"), cidx: int = Body(...),
72+
options: dict[str, Any] = Body(default={'gravity': -10.0, 'enable_wind': False,
73+
'wind_power': 15.0, 'turbulence_power': 1.5})) -> JSONResponse:
74+
if version == 'v1' or version == 'v2':
75+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
76+
detail='Environment version v1 for `LunarLander` '
77+
'is deprecated. Please use `LunarLander-v3` instead.')
78+
79+
global envs
80+
env_type = f"{ENV_NAME}-{version}"
81+
if cidx in envs:
82+
env = envs[cidx]
83+
84+
if env is not None:
85+
envs[cidx].close()
86+
87+
try:
88+
env = gym.make(env_type, continuous=False, **options)
89+
envs[cidx] = env
90+
except Exception as e:
91+
logger.error('An exception was raised')
92+
logger.opt(exception=e).info("Logging exception traceback")
93+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
94+
detail=str(e))
95+
else:
96+
try:
97+
env = gym.make(env_type)
98+
envs[cidx] = env
99+
except Exception as e:
100+
logger.error('An exception was raised')
101+
logger.opt(exception=e).info("Logging exception traceback")
102+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
103+
detail=str(e))
104+
105+
logger.info(f'Created environment {ENV_NAME} and index {cidx}')
106+
return JSONResponse(status_code=status.HTTP_201_CREATED,
107+
content={"result": True})
108+
109+
110+
@lunar_lander_discrete_router.post("/reset")
111+
async def reset(seed: int = Body(default=42), cidx: int = Body(...),
112+
options: dict[str, Any] = Body(default={})) -> JSONResponse:
113+
"""Reset the environment
114+
115+
:return:
116+
"""
117+
118+
global envs
119+
if cidx in envs:
120+
env = envs[cidx]
121+
122+
if env is not None:
123+
124+
if len(options) != 0:
125+
observation, info = env.reset(seed=seed, options=options)
126+
else:
127+
observation, info = env.reset(seed=seed)
128+
observation = [float(val) for val in observation]
129+
step = TimeStep(observation=observation,
130+
reward=0.0,
131+
step_type=TimeStepType.FIRST,
132+
info=info,
133+
discount=1.0)
134+
logger.info(f'Reset environment {ENV_NAME} and index {cidx}')
135+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED,
136+
content={"time_step": step.model_dump()})
137+
138+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
139+
detail={"message": f"Environment {ENV_NAME} is not initialized."
140+
" Have you called make()?"})
141+
142+
143+
@lunar_lander_discrete_router.post("/step")
144+
async def step(action: int = Body(...), cidx: int = Body(...)) -> JSONResponse:
145+
if action not in ACTIONS_SPACE:
146+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
147+
detail=f"Action {action} not in {list(ACTIONS_SPACE.keys())}")
148+
149+
global envs
150+
if cidx in envs:
151+
env = envs[cidx]
152+
153+
if env is not None:
154+
logger.info(f"Stepping in environment {ENV_NAME} with action={action}")
155+
observation, reward, terminated, truncated, info = env.step(action)
156+
observation = [float(val) for val in observation]
157+
158+
step_type = TimeStepType.MID
159+
if terminated:
160+
step_type = TimeStepType.LAST
161+
162+
if info is not None:
163+
info['truncated'] = truncated
164+
165+
step = TimeStep(observation=observation,
166+
reward=reward,
167+
step_type=step_type,
168+
info=info,
169+
discount=1.0)
170+
171+
logger.info(f'Step in environment {ENV_NAME} and index {cidx}')
172+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED,
173+
content={"time_step": step.model_dump()})
174+
175+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
176+
detail=f"Environment {ENV_NAME} is not initialized. Have you called make()?")
177+
178+
179+
@lunar_lander_discrete_router.post("/sync")
180+
async def sync(cidx: int = Body(...), options: dict[str, Any] = Body(default={})) -> JSONResponse:
181+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED,
182+
content={"message": "OK"})

rest_api/requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Box2D==2.3.10
2+
fastapi==0.115.9
3+
fastapi-cli==0.0.7
4+
gymnasium==1.0.0
5+
swig==4.3.1

rest_api/rlenvs_rest_api_app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from gymnasium_envs.classic_control.pendulum_env_api import pendulum_router
1313
from gymnasium_envs.classic_control.acrobot_env_api import acrobot_router
1414
from gymnasium_envs.classic_control.v.acrobot_v_env_api import acrobot_v_router
15+
from gymnasium_envs.box2d.lunar_lander_env_discrete_api import lunar_lander_discrete_router
1516
from gdrl.gym_walk_env.gym_walk_env_api import gym_walk_env_router
1617

1718
from gym_pybullet_drones_env.quadrotor_sim_env_api import FOUND_GYM_PYBULLET
@@ -49,6 +50,8 @@
4950
app.include_router(pendulum_router, prefix=BASE_URL)
5051
app.include_router(acrobot_router, prefix=BASE_URL)
5152
app.include_router(acrobot_v_router, prefix=BASE_URL)
53+
app.include_router(lunar_lander_discrete_router, prefix=BASE_URL)
54+
5255
if FOUND_GYM_PYBULLET:
5356
app.include_router(quadrotor_sim_router, prefix=BASE_URL)
5457

0 commit comments

Comments
 (0)