Skip to content

Commit

Permalink
Replace Sleipnir with Jormungandr
Browse files Browse the repository at this point in the history
  • Loading branch information
calcmogul committed Feb 23, 2024
1 parent 2eadf91 commit 6988ff3
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 198 deletions.
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ disable=bad-inline-option,
locally-disabled,
no-else-return,
no-member,
no-name-in-module,
raw-checker-failed,
redefined-outer-name,
suppressed-message,
Expand Down
22 changes: 1 addition & 21 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,27 +91,9 @@ build/venv.stamp:
@mkdir -p $(@D)
python3 setup_venv.py
$(VENV_PIP) install -e ./bookutil
$(VENV_PIP) install frccontrol==2023.28 pylint qrcode requests robotpy-wpimath==2024.0.0b3.post1
$(VENV_PIP) install frccontrol==2023.28 sleipnirgroup-jormungandr==0.0.1.dev23 pylint qrcode requests robotpy-wpimath==2024.0.0b3.post1
@touch $@

ifeq ($(OS),Windows_NT)
$(CPP_EXE): build/%.exe: %.cpp build/venv.stamp
else
$(CPP_EXE): build/%: %.cpp build/venv.stamp
endif
@mkdir -p $(@D)
# Run CMake
cmake -B $(@D) -S $(dir $<)
# Build and run binary
cmake --build $(@D) --target $(notdir $(basename $@))
ifeq ($(OS),Windows_NT)
cd $(@D)/Debug && ./$(notdir $@)
else
cd $(@D) && ./$(notdir $@)
endif
# Convert generated CSVs to PDFs
$(abspath $(VENV_PYTHON)) ./snippets/sleipnir_csv_to_pdf.py $(@D)/*.csv

$(PY_STAMP): build/%.stamp: %.py build/venv.stamp
@mkdir -p $(@D)
cd $(@D) && $(abspath $(VENV_PYTHON)) $(abspath ./$<) --noninteractive
Expand All @@ -129,8 +111,6 @@ format:
# Format .py
find . -type f -name '*.py' -not -path './build/*' -print0 | xargs -0 python3 -m autoflake -i
find . -type f -name '*.py' -not -path './build/*' -print0 | xargs -0 python3 -m black -q
# Format .cmake
find . -type f \( -name CMakeLists.txt -o -name '*.cmake' \) -not -path './build/*' -print0 | xargs -0 python3 -m gersemi -i -q

# Run formatters and all .tex linters. The commit metadata files and files
# generated by Python scripts are dependencies because check_tex_includes.py
Expand Down
26 changes: 0 additions & 26 deletions motion-planning/CMakeLists.txt

This file was deleted.

84 changes: 0 additions & 84 deletions motion-planning/DoubleIntegratorMinimumTime.cpp

This file was deleted.

110 changes: 110 additions & 0 deletions motion-planning/double_integrator_minimum_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3

"""Solves double integrator minimum-time trajectory optimization problem with Jormungandr."""

from pathlib import Path
import re
import sys

from jormungandr.optimization import OptimizationProblem
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

from bookutil import latex

if "--noninteractive" in sys.argv:
mpl.use("svg")
plt.rc("text", usetex=True)


def main():
"""Entry point."""
T = 3.5 # s
dt = 0.005 # 5 ms
N = int(T / dt)

r = 2.0

problem = OptimizationProblem()

# 2x1 state vector with N + 1 timesteps (includes last state)
X = problem.decision_variable(2, N + 1)

# 1x1 input vector with N timesteps (input at last state doesn't matter)
U = problem.decision_variable(1, N)

# Kinematics constraint assuming constant acceleration between timesteps
for k in range(N):
p_k1 = X[0, k + 1]
v_k1 = X[1, k + 1]
p_k = X[0, k]
v_k = X[1, k]
a_k = U[0, k]

problem.subject_to(p_k1 == p_k + v_k * dt)
problem.subject_to(v_k1 == v_k + a_k * dt)

# Start and end at rest
problem.subject_to(X[:, 0] == np.array([[0.0], [0.0]]))
problem.subject_to(X[:, N] == np.array([[r], [0.0]]))

# Limit velocity
problem.subject_to(-1 <= X[1, :])
problem.subject_to(X[1, :] <= 1)

# Limit acceleration
problem.subject_to(-1 <= U)
problem.subject_to(U <= 1)

# Cost function - minimize position error
J = 0.0
for k in range(N + 1):
J += (r - X[0, k]) ** 2
problem.minimize(J)

problem.solve()

ts = [k * dt for k in range(N + 1)]

# Plot states
labels = ["Position (m)", "Velocity (m/s)"]
for i in range(X.shape[0]):
plt.figure()
ax = plt.gca()

xs = X.value()[i, :]
ax.plot(ts, xs, label=labels[i])
ax.set_xlabel("Time (s)")
ax.set_ylabel(labels[i])

unit_name = re.search(r"(\w+)(\(.*?\))?", labels[i]).group(1).lower()
figname = f"{Path(__file__).stem}_{unit_name}"

if "--noninteractive" in sys.argv:
latex.savefig(figname)
else:
plt.show()

# Plot inputs
labels = ["Acceleration (m/s²)"]
for i in range(U.shape[0]):
plt.figure()
ax = plt.gca()

us = np.concatenate((U.value()[i, :], [0.0]))
ax.plot(ts, us, label=labels[i])
ax.set_xlabel("Time (s)")
ax.set_ylabel(labels[i])

unit_name = re.search(r"(\w+)(\(.*?\))?", labels[i]).group(1).lower()
figname = f"{Path(__file__).stem}_{unit_name}"

if "--noninteractive" in sys.argv:
latex.savefig(figname)
else:
plt.show()


if __name__ == "__main__":
main()
49 changes: 30 additions & 19 deletions motion-planning/trajectory-optimization.tex
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,29 @@ \subsection{Double integrator minimum time}
acceleration. The velocity constraints are $-1 \leq x(1) \leq 1$ and the
acceleration constraints are $-1 \leq u \leq 1$.

\subsubsection{Importing required libraries}

Jormungandr is the Python version of Sleipnir and can be installed via
\texttt{pip install sleipnirgroup-jormungandr}.
\begin{code}
\begin{lstlisting}[style=customPython]
from jormungandr.optimization import OptimizationProblem
import numpy as np
\end{lstlisting}
\end{code}

\subsubsection{Initializing a problem instance}

First, we need to make a problem instance.
\begin{coderemotesubset}{cpp}
{motion-planning/DoubleIntegratorMinimumTime.cpp}{1}{17}
\begin{coderemotesubset}{Python}
{motion-planning/double_integrator_minimum_time.py}{23}{29}
\end{coderemotesubset}

\subsubsection{Creating decision variables}

Next, we need to make decision variables for our state and input.
\begin{coderemotesubset}{cpp}
{motion-planning/DoubleIntegratorMinimumTime.cpp}{19}{23}
\begin{coderemotesubset}{Python}
{motion-planning/double_integrator_minimum_time.py}{31}{35}
\end{coderemotesubset}

By convention, we use capital letters for the variables to designate
Expand All @@ -68,29 +79,29 @@ \subsubsection{Creating decision variables}
\subsubsection{Applying constraints}

Now, we need to apply dynamics constraints between timesteps.
\begin{coderemotesubset}{cpp}
{motion-planning/DoubleIntegratorMinimumTime.cpp}{25}{36}
\begin{coderemotesubset}{Python}
{motion-planning/double_integrator_minimum_time.py}{37}{46}
\end{coderemotesubset}

Next, we'll apply the state and input constraints.
\begin{coderemotesubset}{cpp}
{motion-planning/DoubleIntegratorMinimumTime.cpp}{38}{48}
\begin{coderemotesubset}{Python}
{motion-planning/double_integrator_minimum_time.py}{48}{58}
\end{coderemotesubset}

\subsubsection{Specifying a cost function}

Next, we'll create a cost function for minimizing position error.
\begin{coderemotesubset}{cpp}
{motion-planning/DoubleIntegratorMinimumTime.cpp}{50}{55}
\begin{coderemotesubset}{Python}
{motion-planning/double_integrator_minimum_time.py}{60}{64}
\end{coderemotesubset}

The cost function passed to Minimize() should produce a scalar output.

\subsubsection{Solving the problem}

Now we can solve the problem.
\begin{coderemotesubset}{cpp}
{motion-planning/DoubleIntegratorMinimumTime.cpp}{57}{57}
\begin{coderemotesubset}{Python}
{motion-planning/double_integrator_minimum_time.py}{66}{66}
\end{coderemotesubset}

The solver will find the decision variable values that minimize the cost
Expand All @@ -100,22 +111,22 @@ \subsubsection{Accessing the solution}

You can obtain the solution by querying the values of the variables like so.
\begin{code}
\begin{lstlisting}[style=customCpp]
double position = X.Value(0, 0);
double velocity = X.Value(1, 0);
double acceleration = U.Value(0);
\begin{lstlisting}[style=customPython]
position = X.value[0, 0]
velocity = X.value[1, 0]
acceleration = U.value(0)
\end{lstlisting}
\end{code}
\begin{bookfigure}
\begin{minisvg}{2}{build/motion-planning/DoubleIntegratorMinimumTimeStatesPosition}
\begin{minisvg}{2}{build/motion-planning/double_integrator_minimum_time_position}
\caption{Double integrator position}
\end{minisvg}
\hfill
\begin{minisvg}{2}{build/motion-planning/DoubleIntegratorMinimumTimeStatesVelocity}
\begin{minisvg}{2}{build/motion-planning/double_integrator_minimum_time_velocity}
\caption{Double integrator velocity}
\end{minisvg}
\hfill
\begin{minisvg}{2}{build/motion-planning/DoubleIntegratorMinimumTimeInputsAcceleration}
\begin{minisvg}{2}{build/motion-planning/double_integrator_minimum_time_acceleration}
\caption{Double integrator acceleration}
\end{minisvg}
\end{bookfigure}
Expand Down
3 changes: 1 addition & 2 deletions setup_archlinux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,4 @@ sudo pacman -Sy --needed --noconfirm \
texlive-xetex

# autoflake isn't in [extra] and we can't use an AUR helper
# gersemi isn't in [extra] or the AUR
pip3 install --user --break-system-packages autoflake gersemi
pip3 install --user --break-system-packages autoflake
3 changes: 1 addition & 2 deletions setup_macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,5 @@ sudo /Library/TeX/texbin/tlmgr install \

# Python packages
# * black (to format Python source code)
# * gersemi (to format CMake files)
# * pylint (for Python linting)
pip3 install --user autoflake black gersemi pylint qrcode requests wheel
pip3 install --user autoflake black pylint qrcode requests wheel
3 changes: 1 addition & 2 deletions setup_ubuntu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,5 @@ sudo apt-get install -y \
# The Ubuntu 22.04 packages are too old
# Python packages
# * black (to format Python source code)
# * gersemi (to format CMake files)
# * pylint (for Python linting)
pip3 install --user autoflake black gersemi pylint qrcode
pip3 install --user autoflake black pylint qrcode
Loading

0 comments on commit 6988ff3

Please sign in to comment.