Skip to content

Commit 039d3d3

Browse files
committed
Add instructions on how to use this Julia package from Python
1 parent 3e4c618 commit 039d3d3

File tree

6 files changed

+220
-1
lines changed

6 files changed

+220
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ Manifest.toml
2525

2626
# Jupyter
2727
.ipynb_checkpoints/
28+
29+
# Conda
30+
.CondaPkg/

interoperability/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Interoperability
2+
3+
**Table of Contents**
4+
- [Interoperability](#interoperability)
5+
- [Python and Julia in harmony](#python-and-julia-in-harmony)
6+
- [Calling Julia code from Python code](#calling-julia-code-from-python-code)
7+
8+
## Python and Julia in harmony
9+
10+
Call Python code from Julia and Julia code from Python via a symmetric interface.
11+
All details can be found in https://github.com/JuliaPy/PythonCall.jl.
12+
13+
### Calling Julia code from Python code
14+
15+
First, install the `juliacall` Python module with:
16+
```bash
17+
pip install juliacall
18+
```
19+
20+
Then, in your Python script, import the `juliacall` module with:
21+
```python
22+
from juliacall import Main as jl
23+
```
24+
25+
Next, navigate into the `interoperability` folder:
26+
```bash
27+
cd /path/to/TORA.jl/interoperability
28+
```
29+
30+
Run the setup script to install the example dependencies:
31+
```bash
32+
python python_setup.py
33+
```
34+
35+
Finally, run the example script with:
36+
```bash
37+
python python_calls_julia.py
38+
```
39+
40+
Use `CTRL + C` at any point to stop running the example code.
41+
42+
The example script has comments explaining what is going on, and can be used as the basis for using the Julia framework from a Python environment for interfacing with a real robot (or with a simulator).
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python3
2+
3+
import numpy as np
4+
import time
5+
6+
from juliacall import Main as jl, convert as jlconvert
7+
8+
9+
def init():
10+
"""
11+
Initialize the Julia environment, load the TORA.jl package, and create a robot model.
12+
"""
13+
# Load package dependencies
14+
jl.seval("using MeshCat")
15+
jl.seval("using TORA")
16+
17+
# Call the greet() function from the TORA.jl package
18+
jl.TORA.greet()
19+
20+
jl_vis = jl.MeshCat.Visualizer()
21+
# jl.open(jl_vis) # NOT WORKING
22+
23+
jl_robot = jl.TORA.create_robot_franka("panda_arm", jl_vis)
24+
25+
return jl_robot
26+
27+
28+
def prepare_initial_guess(jl_problem, jl_robot, py_q):
29+
# Start by assuming zero positions, velocities, and torques for all the knots of the trajectory
30+
py_initial_qs = np.zeros((jl_problem.num_knots, jl_robot.n_q))
31+
py_initial_vs = np.zeros((jl_problem.num_knots, jl_robot.n_v))
32+
py_initial_τs = np.zeros((jl_problem.num_knots, jl_robot.n_τ))
33+
34+
# To improve the initial guess for the robot joint positions, we can take
35+
# the current robot configuration and simply repeat it for all the knots
36+
py_initial_qs = np.tile(py_q, (jl_problem.num_knots, 1))
37+
38+
# Concatenate the initial guess
39+
py_initial_guess = np.concatenate((py_initial_qs, py_initial_vs, py_initial_τs), axis=1)
40+
41+
# Flatten the numpy array
42+
py_initial_guess = py_initial_guess.flatten()
43+
44+
# Truncate the torques at the last knot of the trajectory from the initial guess
45+
py_initial_guess = py_initial_guess[: -jl_robot.n_τ]
46+
47+
# Convert the numpy array to a Julia array
48+
jl_initial_guess = jlconvert(jl.Vector[jl.Float64], py_initial_guess)
49+
50+
return jl_initial_guess
51+
52+
53+
def main():
54+
jl.println("Hello from Julia!")
55+
56+
jl_robot = init()
57+
58+
trajectory_discretisation = 100 # in Hertz
59+
trajectory_duration = 2.0 # in seconds
60+
61+
trajectory_num_knots = int(trajectory_discretisation * trajectory_duration) + 1 # in units (number of knots)
62+
trajectory_dt = 1 / trajectory_discretisation # in seconds
63+
64+
# Options for the Ipopt numerical solver
65+
ipopt_options = jl.Dict()
66+
ipopt_options["hessian_approximation"] = "limited-memory" # L-BFGS
67+
ipopt_options["max_cpu_time"] = 2.0 # in seconds
68+
ipopt_options["mu_strategy"] = "adaptive" # monotone, adaptive, quality-function
69+
# ipopt_options["acceptable_compl_inf_tol"] = 0.1
70+
# ipopt_options["acceptable_constr_viol_tol"] = 0.1
71+
# ipopt_options["acceptable_dual_inf_tol"] = 1.0
72+
ipopt_options["acceptable_iter"] = 1
73+
ipopt_options["acceptable_tol"] = 1.0
74+
# ipopt_options["print_level"] = 3 # default: 5
75+
76+
# Starting configuration of the robot
77+
py_q_start = [0, 0, 0, -np.pi / 2, 0, np.pi, 0]
78+
79+
done = False
80+
81+
while not done:
82+
# Create a problem instance
83+
jl_problem = jl.TORA.Problem(jl_robot, trajectory_num_knots, trajectory_dt)
84+
85+
# Get the current robot state
86+
# (but for this example, let's use a nominal configuration and random velocities)
87+
py_q = py_q_start
88+
py_v = np.random.rand(jl_robot.n_v)
89+
90+
# Constrain the initial joint positions and velocities to the current state of the robot
91+
jl.TORA.fix_joint_positions_b(jl_problem, jl_robot, 1, py_q)
92+
jl.TORA.fix_joint_velocities_b(jl_problem, jl_robot, 1, py_v)
93+
94+
# Constrain the final joint velocities to zero
95+
jl.TORA.fix_joint_velocities_b(jl_problem, jl_robot, jl_problem.num_knots, np.zeros(jl_robot.n_v))
96+
97+
# Define the end-effector position that we want to achieve
98+
py_eff_pos = [0.5, 0.2, 0.1]
99+
100+
# Constrain the end-effector position at the last knot of the trajectory
101+
jl.TORA.constrain_ee_position_b(jl_problem, jl_problem.num_knots, py_eff_pos)
102+
103+
# Show a summary of the problem instance (this can be commented out)
104+
jl.TORA.show_problem_info(jl_problem)
105+
106+
# Prepare the initial guess for the solver
107+
jl_initial_guess = prepare_initial_guess(jl_problem, jl_robot, py_q)
108+
109+
jl_cpu_time, jl_x, jl_solver_log = jl.TORA.solve_with_ipopt(
110+
jl_problem,
111+
jl_robot,
112+
initial_guess=jl_initial_guess,
113+
user_options=ipopt_options,
114+
use_inv_dyn=True,
115+
minimise_τ=False,
116+
)
117+
118+
# Unpack the solution `x` into joint positions, velocities, and torques
119+
jl_qs, jl_vs, jl_τs = jl.TORA.unpack_x(jl_x, jl_robot, jl_problem)
120+
121+
# Convert the Julia arrays to numpy arrays
122+
py_qs = np.array(jl_qs)
123+
py_vs = np.array(jl_vs)
124+
py_τs = np.array(jl_τs)
125+
126+
# Do something with the result (e.g., control the real robot)
127+
# (...)
128+
129+
# Sleep for 1 second before the next iteration
130+
time.sleep(1)
131+
132+
133+
if __name__ == "__main__":
134+
main()

interoperability/python_setup.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env python3
2+
3+
# Import the Main class from the juliacall module
4+
from juliacall import Main as jl
5+
6+
# Import the Pkg module
7+
jl.seval("import Pkg")
8+
9+
# Check the status of the installed packages
10+
jl.seval("Pkg.status()")
11+
12+
# Add package dependencies
13+
jl.seval('Pkg.add("MeshCat")')
14+
15+
# Develop the local package of TORA.jl
16+
jl.seval('Pkg.develop(path="../../TORA.jl")')
17+
18+
# Update the package dependencies
19+
jl.seval("Pkg.update()")
20+
21+
# Import the TORA.jl package
22+
jl.seval("using TORA")
23+
24+
# Test the TORA.jl package by calling the greet() function
25+
jl.TORA.greet()

src/TORA.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function __init__()
4444
@require KNITRO = "67920dd8-b58e-52a8-8622-53c4cffbe346" include("./transcription/knitro.jl")
4545
end
4646

47-
greet() = print("Hello World!")
47+
greet() = println("Hello World!")
4848

4949
# Artifacts (robot meshes, URDF files, etc.)
5050
include("../dev/artifacts.jl")
@@ -60,6 +60,7 @@ include("./constraints/dynamics.jl")
6060
include("./constraints/end_effector.jl")
6161
include("./transcription/ipopt.jl")
6262
include("./plots.jl")
63+
include("./utils.jl")
6364

6465
# Code to "exercise" the package - see https://julialang.github.io/PrecompileTools.jl/stable/
6566
include("./precompile.jl")

src/utils.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
function unpack_x(x::Vector, robot::Robot, problem::Problem)
2+
nₓ = robot.n_q + robot.n_v + robot.n_τ # dimension of each mesh point
3+
4+
# Helper ranges
5+
ind_q = hcat([range(1 + (i * nₓ) , length=robot.n_q) for i = (1:problem.num_knots ) .- 1]...)
6+
ind_v = hcat([range(1 + (i * nₓ) + robot.n_q , length=robot.n_v) for i = (1:problem.num_knots ) .- 1]...)
7+
ind_τ = hcat([range(1 + (i * nₓ) + robot.n_q + robot.n_v, length=robot.n_τ) for i = (1:problem.num_knots - 1) .- 1]...)
8+
9+
qs = x[ind_q]
10+
vs = x[ind_v]
11+
τs = x[ind_τ]
12+
13+
return qs, vs, τs
14+
end

0 commit comments

Comments
 (0)