Skip to content

Commit 41bee43

Browse files
authored
feat(learned_model): create package (#6395)
Signed-off-by: Maxime CLEMENT <maxime.clement@tier4.jp> Co-authored-by: Tomas Nagy <tomas@pmc.sk>
1 parent f297d06 commit 41bee43

20 files changed

+1188
-23
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
cmake_minimum_required(VERSION 3.14.4)
2+
project(learning_based_vehicle_model)
3+
4+
find_package(autoware_cmake REQUIRED)
5+
autoware_package()
6+
7+
find_package(Python3 COMPONENTS Interpreter Development)
8+
find_package(pybind11 CONFIG)
9+
10+
ament_auto_add_library(${PROJECT_NAME} SHARED
11+
DIRECTORY src
12+
)
13+
target_link_libraries(${PROJECT_NAME} pybind11::embed ${Python3_LIBRARIES})
14+
target_include_directories(${PROJECT_NAME} PRIVATE ${Python3_INCLUDE_DIRS})
15+
16+
target_compile_options(${PROJECT_NAME} PRIVATE -fvisibility=hidden)
17+
18+
install(
19+
DIRECTORY include/
20+
DESTINATION include/${PROJECT_NAME}
21+
)
22+
23+
ament_auto_package()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Learned Model
2+
3+
This is the design document for the Python learned model used in the `simple_planning_simulator` package.
4+
5+
## Purpose / Use cases
6+
7+
<!-- Required -->
8+
<!-- Things to consider:
9+
- Why did we implement this feature? -->
10+
11+
This library creates an interface between models in Python and PSIM (C++). It is used to quickly deploy learned Python models in PSIM without a need for complex C++ implementation.
12+
13+
## Design
14+
15+
<!-- Required -->
16+
<!-- Things to consider:
17+
- How does it work? -->
18+
19+
The idea behind this package is that the model we want to use for simulation consists of multiple sub-models (e.g., steering model, drive model, vehicle kinematics, etc.). These sub-models are implemented in Python and can be trainable. Each sub-model has string names for all of its inputs/outputs, which are used to create model interconnections automatically (see image below). This allows us to easily switch sub-models for better customization of the simulator.
20+
21+
![py_model_interface](./image/python_model_interface.png "PyModel interface")
22+
23+
## Assumptions / Known limits
24+
25+
<!-- Required -->
26+
27+
To use this package `python3` and `pybind11` need to be installed. The only assumption on Python sub-models is their interface.
28+
29+
```python
30+
class PythonSubmodelInterface:
31+
32+
def forward(self, action, state): # Required
33+
"""
34+
Calculate forward pass through the model and returns next_state.
35+
"""
36+
return list()
37+
38+
def get_state_names(self): # Required
39+
"""
40+
Return list of string names of the model states (outputs).
41+
"""
42+
return list()
43+
44+
def get_action_names(self): # Required
45+
"""
46+
Return list of string names of the model actions (inputs).
47+
"""
48+
return list()
49+
50+
def reset(self): # Required
51+
"""
52+
Reset model. This function is called after load_params().
53+
"""
54+
pass
55+
56+
def load_params(self, path): # Required
57+
"""
58+
Load parameters of the model.
59+
Inputs:
60+
- path: Path to a parameter file to load by the model.
61+
"""
62+
pass
63+
64+
def dtSet(self, dt): # Required
65+
"""
66+
Set dt of the model.
67+
Inputs:
68+
- dt: time step
69+
"""
70+
pass
71+
```
72+
73+
## API
74+
75+
<!-- Required -->
76+
<!-- Things to consider:
77+
- How do you use the package / API? -->
78+
79+
To successfully create a vehicle model an InterconnectedModel class needs to be set up correctly.
80+
81+
### InterconnectedModel class
82+
83+
#### `Constructor`
84+
85+
The constructor takes no arguments.
86+
87+
#### `void addSubmodel(std::tuple<std::string, std::string, std::string> model_descriptor)`
88+
89+
Add a new sub-model to the model.
90+
91+
Inputs:
92+
93+
- model_descriptor: Describes what model should be used. The model descriptor contains three strings:
94+
- The first string is a path to a python module where the model is implemented.
95+
- The second string is a path to the file where model parameters are stored.
96+
- The third string is the name of the class that implements the model.
97+
98+
Outputs:
99+
100+
- None
101+
102+
#### `void generateConnections(std::vector<char *> in_names, std::vector<char*> out_names)`
103+
104+
Generate connections between sub-models and inputs/outputs of the model.
105+
106+
Inputs:
107+
108+
- in_names: String names for all of the model inputs in order.
109+
- out_names: String names for all of the model outputs in order.
110+
111+
Outputs:
112+
113+
- None
114+
115+
#### `void initState(std::vector<double> new_state)`
116+
117+
Set the initial state of the model.
118+
119+
Inputs:
120+
121+
- new_state: New state of the model.
122+
123+
Outputs:
124+
125+
- None
126+
127+
#### `std::vector<double> updatePyModel(std::vector<double> psim_input)`
128+
129+
Calculate the next state of the model by calculating the next state of all of the sub-models.
130+
131+
Inputs:
132+
133+
- psim_input: Input to the model.
134+
135+
Outputs:
136+
137+
- next_state: Next state of the model.
138+
139+
#### `dtSet(double dt)`
140+
141+
Set the time step of the model.
142+
143+
Inputs:
144+
145+
- dt: time step
146+
147+
Outputs:
148+
149+
- None
150+
151+
### Example
152+
153+
Firstly we need to set up the model.
154+
155+
```C++
156+
InterconnectedModel vehicle;
157+
158+
// Example of model descriptors
159+
std::tuple<char*, char*, char*> model_descriptor_1 = {
160+
(char*)"path_to_python_module_with_model_class_1",
161+
(char*)nullptr, // If no param file is needed you can pass 'nullptr'
162+
(char*)"ModelClass1"
163+
};
164+
165+
std::tuple<char*, char*, char*> model_descriptor_2 = {
166+
(char*)"path_to_python_module_with_model_class_2",
167+
(char*)"/path_to/param_file",
168+
(char*)"ModelClass2" // Name of the python class. Needs to use the interface from 'Assumptions'
169+
};
170+
171+
// Create sub-models based on descriptors
172+
vehicle.addSubmodel(model_descriptor_1);
173+
vehicle.addSubmodel(model_descriptor_2);
174+
175+
// Define STATE and INPUT names of the system
176+
std::vector<char*> state_names = {(char*)"STATE_NAME_1", (char*)"STATE_NAME_2"};
177+
std::vector<char*> input_names = {(char*)"INPUT_NAME_1", (char*)"INPUT_NAME_2"};
178+
179+
// Automatically connect sub-systems with model input
180+
vehicle.generateConnections(input_names, state_names);
181+
182+
// Set the time step of the model
183+
vehicle.dtSet(dt);
184+
```
185+
186+
After the model is correctly set up, we can use it the following way.
187+
188+
```C++
189+
// Example of an model input
190+
std::vector<double> vehicle_input = {0.0, 1.0}; // INPUT_NAME_1, INPUT_NAME_2
191+
192+
// Example of an model state
193+
std::vector<double> current_state = {0.2, 0.5}; // STATE_NAME_1, STATE_NAME_2
194+
195+
// Set model state
196+
vehicle.initState(current_state);
197+
198+
// Calculate the next state of the model
199+
std::vector<double> next_state = vehicle.updatePyModel(vehicle_input);
200+
```
201+
202+
## References / External links
203+
204+
<!-- Optional -->
205+
206+
## Related issues
207+
208+
<!-- Required -->
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2024 The Autoware Foundation.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef LEARNING_BASED_VEHICLE_MODEL__INTERCONNECTED_MODEL_HPP_
16+
#define LEARNING_BASED_VEHICLE_MODEL__INTERCONNECTED_MODEL_HPP_
17+
18+
#include "learning_based_vehicle_model/model_connections_helpers.hpp"
19+
#include "learning_based_vehicle_model/simple_pymodel.hpp"
20+
#include "learning_based_vehicle_model/submodel_interface.hpp"
21+
22+
#include <dlfcn.h>
23+
#include <pybind11/embed.h>
24+
#include <pybind11/stl.h>
25+
26+
#include <algorithm>
27+
#include <memory>
28+
#include <string>
29+
#include <tuple>
30+
#include <vector>
31+
32+
namespace py = pybind11;
33+
34+
class __attribute__((visibility("default"))) InterconnectedModel
35+
{
36+
// Vector of unique names of inputs and outputs of sub-models
37+
std::vector<char *> signals_vec_names;
38+
std::vector<double> model_signals_vec;
39+
int num_signals;
40+
41+
std::vector<std::unique_ptr<SubModelInterface>> submodels;
42+
43+
// index in "map_in_to_sig_vec" is index in "py_inputs" and value in "map_in_to_sig_vec" is index
44+
// in "all_variables_names"
45+
std::vector<int> map_in_to_sig_vec;
46+
47+
// index in "map_sig_vec_to_out" is index in "py_model_outputs" and value in "map_sig_vec_to_out"
48+
// is index in "all_variables_names"
49+
std::vector<int> map_sig_vec_to_out;
50+
51+
public:
52+
py::scoped_interpreter guard{}; // start the interpreter and keep it alive
53+
54+
/**
55+
* @brief constructor
56+
*/
57+
InterconnectedModel()
58+
{
59+
// Initialize python library
60+
// cspell:ignore libpython
61+
// Manually load libpython3.10.so as we need it for python.h.
62+
dlopen("libpython3.10.so", RTLD_GLOBAL | RTLD_NOW);
63+
/*
64+
More about the line above here:
65+
https://stackoverflow.com/questions/60719987/embedding-python-which-uses-numpy-in-c-doesnt-work-in-library-dynamically-loa
66+
https://mail.python.org/pipermail/new-bugs-announce/2008-November/003322.html
67+
https://stackoverflow.com/questions/67891197/ctypes-cpython-39-x86-64-linux-gnu-so-undefined-symbol-pyfloat-type-in-embedd
68+
https://man7.org/linux/man-pages/man3/dlopen.3.html
69+
*/
70+
}
71+
72+
private:
73+
/**
74+
* @brief create a mapping between vector of signal input names from PSIM to vector of signals
75+
* @param [in] in_names vector of signal input names from PSIM
76+
*/
77+
void mapInputs(std::vector<char *> in_names);
78+
79+
/**
80+
* @brief create a mapping between vector of signal output names from PSIM to vector of signals
81+
* @param [in] out_names vector of signal output names from PSIM
82+
*/
83+
void mapOutputs(std::vector<char *> out_names);
84+
85+
/**
86+
* @brief add unique names to the vector of signal names
87+
* @param [in] names vector of signal names
88+
*/
89+
void addNamesToSigVec(const std::vector<char *> & names);
90+
91+
/**
92+
* @brief create of signal names from all sub-models and PSIM signal names
93+
*/
94+
void getSignalNames(std::vector<char *> in_names, std::vector<char *> out_names);
95+
96+
public:
97+
/**
98+
* @brief automatically create connections between PSIM and all of the sub-models
99+
* @param [in] in_names string names of inputs available from PSIM
100+
* @param [in] out_names string names of outputs required by PSIM
101+
*/
102+
void generateConnections(std::vector<char *> in_names, std::vector<char *> out_names);
103+
104+
/**
105+
* @brief add a sub-model consisting of base + error model
106+
* @param [in] submodel_desc descriptor of the sub-model
107+
*/
108+
void addSubmodel(std::tuple<std::string, std::string, std::string> submodel_desc);
109+
110+
/**
111+
* @brief set a new model state if it was changed using PSIM interface (mainly position and
112+
* orientation)
113+
* @param [in] new_state new state set by PSIM
114+
*/
115+
void initState(std::vector<double> new_state);
116+
117+
/**
118+
* @brief set time step for all the models
119+
* @param [in] dt time step
120+
*/
121+
void dtSet(double dt);
122+
123+
/**
124+
* @brief compute next step of the PSIM model using python sub-models
125+
* @param [in] psim_input vector of input values provided by PSIM
126+
*/
127+
std::vector<double> updatePyModel(std::vector<double> psim_input);
128+
};
129+
130+
#endif // LEARNING_BASED_VEHICLE_MODEL__INTERCONNECTED_MODEL_HPP_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2024 The Autoware Foundation.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef LEARNING_BASED_VEHICLE_MODEL__MODEL_CONNECTIONS_HELPERS_HPP_
16+
#define LEARNING_BASED_VEHICLE_MODEL__MODEL_CONNECTIONS_HELPERS_HPP_
17+
18+
#include <cstring>
19+
#include <vector>
20+
21+
std::vector<double> fillVectorUsingMap(
22+
std::vector<double> vector1, std::vector<double> vector2, std::vector<int> map, bool inverse);
23+
24+
std::vector<int> createConnectionsMap(
25+
std::vector<char *> connection_names_1, std::vector<char *> connection_names_2);
26+
27+
#endif // LEARNING_BASED_VEHICLE_MODEL__MODEL_CONNECTIONS_HELPERS_HPP_

0 commit comments

Comments
 (0)