diff --git a/docs/debugging_tips.md b/docs/debugging_tips.md
new file mode 100644
index 0000000000000000000000000000000000000000..afb7d5533c85fcfc989f69c0e7b2a51e0f9053e1
--- /dev/null
+++ b/docs/debugging_tips.md
@@ -0,0 +1,5 @@
+debuging tips
+-------------
+1. log (thereby also plotting) the interesting quantities, like error norms
+2. import IPython.embed, and just drop in embed() whereever in the code - this drops you into an ipython shell with all the variables there.
+   this is the most pain-free way to quickly look at what's going on
diff --git a/examples/cart_pulling/base_and_ee_path_following.py b/examples/cart_pulling/control_sub_problems/fixed_paths/base_and_ee_path_following.py
similarity index 100%
rename from examples/cart_pulling/base_and_ee_path_following.py
rename to examples/cart_pulling/control_sub_problems/fixed_paths/base_and_ee_path_following.py
diff --git a/examples/cart_pulling/dual_arm_path_following.py b/examples/cart_pulling/control_sub_problems/fixed_paths/dual_arm_path_following.py
similarity index 100%
rename from examples/cart_pulling/dual_arm_path_following.py
rename to examples/cart_pulling/control_sub_problems/fixed_paths/dual_arm_path_following.py
diff --git a/examples/cart_pulling/control_sub_problems/fixed_paths/ee_only_path_following.py b/examples/cart_pulling/control_sub_problems/fixed_paths/ee_only_path_following.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e11a54104211e46764924edde04603c624414d5
--- /dev/null
+++ b/examples/cart_pulling/control_sub_problems/fixed_paths/ee_only_path_following.py
@@ -0,0 +1,53 @@
+from smc import getRobotFromArgs
+from smc import getMinimalArgParser
+from smc.control.optimal_control.util import get_OCP_args
+from smc.control.cartesian_space import getClikArgs
+from smc.control.optimal_control.croco_mpc_path_following import (
+    CrocoEEPathFollowingMPC,
+)
+import numpy as np
+
+
+def path(T_w_e, i):
+    # 2) do T_mobile_base_ee_pos to get
+    # end-effector reference frame(s)
+
+    # generate bullshit just to see it works
+    path = []
+    t = i * robot.dt
+    for i in range(args.n_knots):
+        t += 0.01
+        new = T_w_e.copy()
+        translation = 2 * np.array([np.cos(t / 20), np.sin(t / 20), 0.3])
+        new.translation = translation
+        path.append(new)
+    return path
+
+
+def get_args():
+    parser = getMinimalArgParser()
+    parser = get_OCP_args(parser)
+    parser = getClikArgs(parser)  # literally just for goal error
+    args = parser.parse_args()
+    return args
+
+
+if __name__ == "__main__":
+    args = get_args()
+    robot = getRobotFromArgs(args)
+    x0 = np.concatenate([robot.q, robot.v])
+    robot._step()
+
+    CrocoEEPathFollowingMPC(args, robot, x0, path)
+
+    print("final position:", robot.T_w_e)
+
+    if args.real:
+        robot.stopRobot()
+
+    if args.save_log:
+        robot._log_manager.saveLog()
+        robot._log_manager.plotAllControlLoops()
+
+    if args.visualizer:
+        robot.killManipulatorVisualizer()
diff --git a/examples/cart_pulling/base_and_ee_path_following_from_planner.py b/examples/cart_pulling/control_sub_problems/with_planner/base_and_ee_path_following_from_planner.py
similarity index 100%
rename from examples/cart_pulling/base_and_ee_path_following_from_planner.py
rename to examples/cart_pulling/control_sub_problems/with_planner/base_and_ee_path_following_from_planner.py
diff --git a/examples/cart_pulling/ee_only_path_following_from_plannner.py b/examples/cart_pulling/control_sub_problems/with_planner/ee_only_path_following_from_plannner.py
similarity index 100%
rename from examples/cart_pulling/ee_only_path_following_from_plannner.py
rename to examples/cart_pulling/control_sub_problems/with_planner/ee_only_path_following_from_plannner.py
diff --git a/examples/cart_pulling/disjoint_control/; b/examples/cart_pulling/disjoint_control/;
new file mode 100644
index 0000000000000000000000000000000000000000..b244f1397da7be08cc7d5a7ed068d1e3af2e1a95
--- /dev/null
+++ b/examples/cart_pulling/disjoint_control/;
@@ -0,0 +1,268 @@
+from smc.robots.implementations import *
+
+import numpy as np
+import pinocchio as pin
+import argparse
+
+
+def getMinimalArgParser():
+    """
+    getDefaultEssentialArgs
+    ------------------------
+    returns a parser containing:
+        - essential arguments (run in real or in sim)
+        - parameters for (compliant)moveJ
+        - parameters for (compliant)moveL
+    """
+    parser = argparse.ArgumentParser(
+        description="Run something with \
+            Simple Manipulator Control",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
+    #################################################
+    #  general arguments: connection, plotting etc  #
+    #################################################
+    parser.add_argument(
+        "--robot",
+        type=str,
+        help="which robot you're running or simulating",
+        default="ur5e",
+        choices=["ur5e", "heron", "heronros", "gripperlessur5e", "mirros", "yumi"],
+    )
+    parser.add_argument(
+        "--real",
+        action=argparse.BooleanOptionalAction,
+        help="whether you're running on the real robot or not",
+        default=False,
+    )
+    # if this ends up working, replace --real with --mode, which can be {real, integration (simulation), debugging}
+    parser.add_argument(
+        "--robot-ip",
+        type=str,
+        help="robot's ip address (only needed if running on the real robot)",
+        default="192.168.1.102",
+    )
+    parser.add_argument(
+        "--ctrl-freq",
+        type=int,
+        help="frequency of the control loop. select -1 if you want to go as fast as possible (useful for running tests in sim)",
+        default=500,
+    )
+    parser.add_argument(
+        "--visualizer",
+        action=argparse.BooleanOptionalAction,
+        help="whether you want to visualize the manipulator and workspace with meshcat",
+        default=True,
+    )
+    parser.add_argument(
+        "--viz-update-rate",
+        type=int,
+        help="frequency of visual updates. visualizer and plotter update every viz-update-rate^th iteration of the control loop.",
+        default=20,
+    )
+    parser.add_argument(
+        "--plotter",
+        action=argparse.BooleanOptionalAction,
+        help="whether you want to have some real-time matplotlib graphs (parts of log_dict you select)",
+        default=True,
+    )
+    parser.add_argument(
+        "--gripper",
+        type=str,
+        help="gripper you're using (no gripper is the default)",
+        default="none",
+        choices=["none", "robotiq", "onrobot"],
+    )
+    # TODO: make controlloop manager run in a while True loop and remove this
+    # ==> max-iterations actually needs to be an option. sometimes you want to simulate and exit
+    #     if the convergence does not happen in a reasonable amount of time,
+    #     ex. goal outside of workspace has been passed or something
+    # =======> if it's set to 0 then the loops run infinitely long
+    parser.add_argument(
+        "--max-iterations",
+        type=int,
+        help="maximum allowable iteration number (it runs at 500Hz)",
+        default=100000,
+    )
+    parser.add_argument(
+        "--start-from-current-pose",
+        action=argparse.BooleanOptionalAction,
+        help="if connected to the robot, read the current pose and set it as the initial pose for the robot. \
+                 very useful and convenient when running simulation before running on real",
+        default=False,
+    )
+    parser.add_argument(
+        "--acceleration",
+        type=float,
+        help="robot's joints acceleration. scalar positive constant, max 1.7, and default 0.3. \
+                   BE CAREFUL WITH THIS. the urscript doc says this is 'lead axis acceleration'.\
+                   TODO: check what this means",
+        default=0.3,
+    )
+    parser.add_argument(
+        "--max-v-percentage",
+        type=float,
+        help="select the percentage of the maximum joint velocity the robot can achieve to be the control input maximum (control inputs are clipped to perc * max_v)",
+        default=0.3,
+    )
+    parser.add_argument(
+        "--debug-prints",
+        action=argparse.BooleanOptionalAction,
+        help="print some debug info",
+        default=False,
+    )
+    parser.add_argument(
+        "--save-log",
+        action=argparse.BooleanOptionalAction,
+        help="whether you want to save the log of the run. it saves \
+                        what you pass to ControlLoopManager. check other parameters for saving directory and log name.",
+        default=False,
+    )
+    parser.add_argument(
+        "--save-dir",
+        type=str,
+        help="path to where you store your logs. default is ./data, but if that directory doesn't exist, then /tmp/data is created and used.",
+        default="./data",
+    )
+    parser.add_argument(
+        "--run-name",
+        type=str,
+        help="name the whole run/experiment (name of log file). note that indexing of runs is automatic and under a different argument.",
+        default="latest_run",
+    )
+    parser.add_argument(
+        "--index-runs",
+        action=argparse.BooleanOptionalAction,
+        help="if you want more runs of the same name, this option will automatically assign an index to a new run (useful for data collection).",
+        default=False,
+    )
+    parser.add_argument(
+        "--past-window-size",
+        type=int,
+        help="how many timesteps of past data you want to save",
+        default=5,
+    )
+    # maybe you want to scale the control signal (TODO: NOT HERE)
+    parser.add_argument(
+        "--controller-speed-scaling",
+        type=float,
+        default="1.0",
+        help="not actually_used atm",
+    )
+    ########################################
+    #  environment interaction parameters  #
+    ########################################
+    parser.add_argument(
+        "--contact-detecting-force",
+        type=float,  # default=1.3, help='the force used to detect contact (collision) in the moveUntilContact function')
+        default=2.8,
+        help="the force used to detect contact (collision) in the moveUntilContact function",
+    )
+    parser.add_argument(
+        "--minimum-detectable-force-norm",
+        type=float,
+        help="we need to disregard noise to converge despite filtering. \
+                  a quick fix is to zero all forces of norm below this argument threshold.",
+        default=3.0,
+    )
+    # TODO make this work without parsing (or make it possible to parse two times)
+    # if (args.gripper != "none") and args.simulation:
+    #    raise NotImplementedError('Did not figure out how to put the gripper in \
+    #            the simulation yet, sorry :/ . You can have only 1 these flags right now')
+    parser.add_argument(
+        "--visualize-collision-approximation",
+        action=argparse.BooleanOptionalAction,
+        help="whether you want to visualize the collision approximation used in controllers with obstacle avoidance",
+        default=False,
+    )
+    return parser
+
+
+# TODO: make robot-independent
+def defineGoalPointCLI(robot):
+    """
+    defineGoalPointCLI
+    ------------------
+    get a nice TUI-type prompt to put in a frame goal for p2p motion.
+    --> best way to handle the goal is to tell the user where the gripper is
+        in both UR tcp frame and with pinocchio and have them
+        manually input it when running.
+        this way you force the thinking before the moving,
+        but you also get to view and analyze the information first
+    TODO get the visual thing you did in ivc project with sliders also.
+    it's just text input for now because it's totally usable, just not superb.
+    but also you do want to have both options. obviously you go for the sliders
+    in the case you're visualizing, makes no sense otherwise.
+    """
+    robot._step()
+    q = robot.q
+    # define goal
+    T_w_e = robot.T_w_e
+    print("You can only specify the translation right now.")
+    if robot.args.real:
+        print(
+            "In the following, first 3 numbers are x,y,z position, and second 3 are r,p,y angles"
+        )
+        print(
+            "Here's where the robot is currently. Ensure you know what the base frame is first."
+        )
+        print(
+            "base frame end-effector pose from pinocchio:\n",
+            *robot.data.oMi[6].translation.round(4),
+            *pin.rpy.matrixToRpy(robot.data.oMi[6].rotation).round(4)
+        )
+        print("UR5e TCP:", *np.array(robot.rtde_receive.getActualTCPPose()).round(4))
+    # remain with the current orientation
+    # TODO: add something, probably rpy for orientation because it's the least number
+    # of numbers you need to type in
+    Mgoal = T_w_e.copy()
+    # this is a reasonable way to do it too, maybe implement it later
+    # Mgoal.translation = Mgoal.translation + np.array([0.0, 0.0, -0.1])
+    # do a while loop until this is parsed correctly
+    while True:
+        goal = input(
+            "Please enter the target end-effector position in the x.x,y.y,z.z format: "
+        )
+        try:
+            e = "ok"
+            goal_list = goal.split(",")
+            for i in range(len(goal_list)):
+                goal_list[i] = float(goal_list[i])
+        except:
+            e = exc_info()
+            print("The input is not in the expected format. Try again.")
+            print(e)
+        if e == "ok":
+            Mgoal.translation = np.array(goal_list)
+            break
+    print("this is goal pose you defined:\n", Mgoal)
+
+    # NOTE i'm not deepcopying this on purpose
+    # but that might be the preferred thing, we'll see
+    robot.Mgoal = Mgoal
+    if robot.args.visualizer:
+        # TODO document this somewhere
+        robot.visualizer_manager.sendCommand({"Mgoal": Mgoal})
+    return Mgoal
+
+
+# TODO: finish
+def getRobotFromArgs(args: argparse.Namespace) -> AbstractRobotManager:
+    if args.robot == "ur5e":
+        if args.real:
+            return RealUR5eRobotManager(args)
+        else:
+            return SimulatedUR5eRobotManager(args)
+    if args.robot == "heron":
+        if args.real:
+            pass
+            # TODO: finish it
+            # return RealHeronRobotManager(args)
+        else:
+            return SimulatedHeronRobotManager(args)
+
+
+#    if args.robot == "mir":
+#        return RealUR5eRobotManager(args)
+#    if args.robot == "yumi":
+#        return RealUR5eRobotManager(args)
diff --git a/examples/cart_pulling/disjoint_control/mpc_base_clik_arm_control_loop.py b/examples/cart_pulling/disjoint_control/mpc_base_clik_arm_control_loop.py
index 7ac104519d83e02ea389ea62cde4357b2f47bbc8..86100d8a434f7e0a7161ef64b52d22249538989d 100644
--- a/examples/cart_pulling/disjoint_control/mpc_base_clik_arm_control_loop.py
+++ b/examples/cart_pulling/disjoint_control/mpc_base_clik_arm_control_loop.py
@@ -24,6 +24,7 @@ import types
 from argparse import Namespace
 from pinocchio import SE3, log6
 from collections import deque
+from IPython import embed
 
 
 def BaseMPCEECLIKPathFollowingFromPlannerMPCControlLoop(
@@ -62,10 +63,11 @@ def BaseMPCEECLIKPathFollowingFromPlannerMPCControlLoop(
     v_cmd[3:] = v_arm
 
     if args.visualizer:
-        if t % int(np.ceil(args.ctrl_freq / 25)) == 0:
+        if t % args.viz_update_rate == 0:
             robot.visualizer_manager.sendCommand({"path": path_base})
             robot.visualizer_manager.sendCommand({"frame_path": pathSE3_handlebar})
 
+    embed()
     err_vector_ee = log6(robot.T_w_e.actInv(pathSE3_handlebar[0]))
     err_vector_base = np.linalg.norm(p - path_base[0][:2])  # z axis is irrelevant
     log_item = {}
diff --git a/examples/cart_pulling/starify_nav2_map.py b/examples/cart_pulling/map_tests/starify_nav2_map.py
similarity index 100%
rename from examples/cart_pulling/starify_nav2_map.py
rename to examples/cart_pulling/map_tests/starify_nav2_map.py
diff --git a/examples/cart_pulling/path6d_from_path2d.py b/examples/cart_pulling/path_construction_tests/path6d_from_path2d.py
similarity index 100%
rename from examples/cart_pulling/path6d_from_path2d.py
rename to examples/cart_pulling/path_construction_tests/path6d_from_path2d.py
diff --git a/examples/cart_pulling/pose_initialization.py b/examples/cart_pulling/path_construction_tests/pose_initialization.py
similarity index 100%
rename from examples/cart_pulling/pose_initialization.py
rename to examples/cart_pulling/path_construction_tests/pose_initialization.py
diff --git a/examples/cart_pulling/technical_report/cart_pulling.aux b/examples/cart_pulling/technical_report/cart_pulling.aux
new file mode 100644
index 0000000000000000000000000000000000000000..932fa4c6bc095b427310eb7b1ab8ed3f133d8324
--- /dev/null
+++ b/examples/cart_pulling/technical_report/cart_pulling.aux
@@ -0,0 +1,22 @@
+\relax 
+\@writefile{toc}{\contentsline {section}{\numberline {1}Problem description}{1}{}\protected@file@percent }
+\newlabel{sec-problem-description}{{1}{1}{}{}{}}
+\@writefile{toc}{\contentsline {section}{\numberline {2}Solution approach}{2}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Formal task description}{2}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}System architecture}{2}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {section}{\numberline {3}Control design}{2}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsection}{\numberline {3.1}Optimal control formulation}{2}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.1.1}Turning OCPs into MPCs}{3}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.1.2}Common OCP parts}{3}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.1.3}Decision variables, dynamics contraint}{4}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.1.4}State constraints}{4}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.1.5}Regulation costs (regularization)}{4}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsection}{\numberline {3.2}Task-defining objective functions}{4}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.2.1}Point-to-point tasks}{5}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {paragraph}{End-effector pose task}{5}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {paragraph}{End-effector pose and base position}{5}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.2.2}Path-following tasks}{5}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.2.3}Solver choice}{6}{}\protected@file@percent }
+\@writefile{toc}{\contentsline {subsection}{\numberline {3.3}Reference trajectory construction}{6}{}\protected@file@percent }
+\newlabel{sub-reference-traj-construction}{{3.3}{6}{}{}{}}
+\gdef \@abspage@last{6}
diff --git a/examples/cart_pulling/technical_report/cart_pulling.log b/examples/cart_pulling/technical_report/cart_pulling.log
new file mode 100644
index 0000000000000000000000000000000000000000..c5967794efb1f4ba92483b5a6995f03abad9e627
--- /dev/null
+++ b/examples/cart_pulling/technical_report/cart_pulling.log
@@ -0,0 +1,289 @@
+This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024/Arch Linux) (preloaded format=pdflatex 2025.2.22)  24 FEB 2025 17:23
+entering extended mode
+ \write18 enabled.
+ %&-line parsing enabled.
+**cart_pulling.tex
+(./cart_pulling.tex
+LaTeX2e <2023-11-01> patch level 1
+L3 programming layer <2024-02-20>
+(/usr/share/texmf-dist/tex/latex/base/article.cls
+Document Class: article 2023/05/17 v1.4n Standard LaTeX document class
+(/usr/share/texmf-dist/tex/latex/base/size10.clo
+File: size10.clo 2023/05/17 v1.4n Standard LaTeX file (size option)
+)
+\c@part=\count188
+\c@section=\count189
+\c@subsection=\count190
+\c@subsubsection=\count191
+\c@paragraph=\count192
+\c@subparagraph=\count193
+\c@figure=\count194
+\c@table=\count195
+\abovecaptionskip=\skip48
+\belowcaptionskip=\skip49
+\bibindent=\dimen140
+)
+(/usr/share/texmf-dist/tex/latex/amsmath/amsmath.sty
+Package: amsmath 2023/05/13 v2.17o AMS math features
+\@mathmargin=\skip50
+
+For additional information on amsmath, use the `?' option.
+(/usr/share/texmf-dist/tex/latex/amsmath/amstext.sty
+Package: amstext 2021/08/26 v2.01 AMS text
+
+(/usr/share/texmf-dist/tex/latex/amsmath/amsgen.sty
+File: amsgen.sty 1999/11/30 v2.0 generic functions
+\@emptytoks=\toks17
+\ex@=\dimen141
+))
+(/usr/share/texmf-dist/tex/latex/amsmath/amsbsy.sty
+Package: amsbsy 1999/11/29 v1.2d Bold Symbols
+\pmbraise@=\dimen142
+)
+(/usr/share/texmf-dist/tex/latex/amsmath/amsopn.sty
+Package: amsopn 2022/04/08 v2.04 operator names
+)
+\inf@bad=\count196
+LaTeX Info: Redefining \frac on input line 234.
+\uproot@=\count197
+\leftroot@=\count198
+LaTeX Info: Redefining \overline on input line 399.
+LaTeX Info: Redefining \colon on input line 410.
+\classnum@=\count199
+\DOTSCASE@=\count266
+LaTeX Info: Redefining \ldots on input line 496.
+LaTeX Info: Redefining \dots on input line 499.
+LaTeX Info: Redefining \cdots on input line 620.
+\Mathstrutbox@=\box51
+\strutbox@=\box52
+LaTeX Info: Redefining \big on input line 722.
+LaTeX Info: Redefining \Big on input line 723.
+LaTeX Info: Redefining \bigg on input line 724.
+LaTeX Info: Redefining \Bigg on input line 725.
+\big@size=\dimen143
+LaTeX Font Info:    Redeclaring font encoding OML on input line 743.
+LaTeX Font Info:    Redeclaring font encoding OMS on input line 744.
+\macc@depth=\count267
+LaTeX Info: Redefining \bmod on input line 905.
+LaTeX Info: Redefining \pmod on input line 910.
+LaTeX Info: Redefining \smash on input line 940.
+LaTeX Info: Redefining \relbar on input line 970.
+LaTeX Info: Redefining \Relbar on input line 971.
+\c@MaxMatrixCols=\count268
+\dotsspace@=\muskip16
+\c@parentequation=\count269
+\dspbrk@lvl=\count270
+\tag@help=\toks18
+\row@=\count271
+\column@=\count272
+\maxfields@=\count273
+\andhelp@=\toks19
+\eqnshift@=\dimen144
+\alignsep@=\dimen145
+\tagshift@=\dimen146
+\tagwidth@=\dimen147
+\totwidth@=\dimen148
+\lineht@=\dimen149
+\@envbody=\toks20
+\multlinegap=\skip51
+\multlinetaggap=\skip52
+\mathdisplay@stack=\toks21
+LaTeX Info: Redefining \[ on input line 2953.
+LaTeX Info: Redefining \] on input line 2954.
+)
+(/usr/share/texmf-dist/tex/latex/amsfonts/amssymb.sty
+Package: amssymb 2013/01/14 v3.01 AMS font symbols
+
+(/usr/share/texmf-dist/tex/latex/amsfonts/amsfonts.sty
+Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
+\symAMSa=\mathgroup4
+\symAMSb=\mathgroup5
+LaTeX Font Info:    Redeclaring math symbol \hbar on input line 98.
+LaTeX Font Info:    Overwriting math alphabet `\mathfrak' in version `bold'
+(Font)                  U/euf/m/n --> U/euf/b/n on input line 106.
+))
+(/usr/share/texmf-dist/tex/latex/base/fontenc.sty
+Package: fontenc 2021/04/29 v2.0v Standard LaTeX package
+)
+(/usr/share/texmf-dist/tex/latex/tools/bm.sty
+Package: bm 2023/07/08 v1.2f Bold Symbol Support (DPC/FMi)
+\symboldoperators=\mathgroup6
+\symboldletters=\mathgroup7
+\symboldsymbols=\mathgroup8
+Package bm Info: No bold for \OMX/cmex/m/n, using \pmb.
+Package bm Info: No bold for \U/msa/m/n, using \pmb.
+Package bm Info: No bold for \U/msb/m/n, using \pmb.
+LaTeX Font Info:    Redeclaring math alphabet \mathbf on input line 149.
+)
+(/usr/share/texmf-dist/tex/latex/tools/array.sty
+Package: array 2023/10/16 v2.5g Tabular extension package (FMi)
+\col@sep=\dimen150
+\ar@mcellbox=\box53
+\extrarowheight=\dimen151
+\NC@list=\toks22
+\extratabsurround=\skip53
+\backup@length=\skip54
+\ar@cellbox=\box54
+)
+(/usr/share/texmf-dist/tex/latex/graphics/graphicx.sty
+Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
+
+(/usr/share/texmf-dist/tex/latex/graphics/keyval.sty
+Package: keyval 2022/05/29 v1.15 key=value parser (DPC)
+\KV@toks@=\toks23
+)
+(/usr/share/texmf-dist/tex/latex/graphics/graphics.sty
+Package: graphics 2022/03/10 v1.4e Standard LaTeX Graphics (DPC,SPQR)
+
+(/usr/share/texmf-dist/tex/latex/graphics/trig.sty
+Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
+)
+(/usr/share/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
+File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
+)
+Package graphics Info: Driver file: pdftex.def on input line 107.
+
+(/usr/share/texmf-dist/tex/latex/graphics-def/pdftex.def
+File: pdftex.def 2022/09/22 v1.2b Graphics/color driver for pdftex
+))
+\Gin@req@height=\dimen152
+\Gin@req@width=\dimen153
+)
+(/usr/share/texmf-dist/tex/latex/base/inputenc.sty
+Package: inputenc 2021/02/14 v1.3d Input encoding file
+\inpenc@prehook=\toks24
+\inpenc@posthook=\toks25
+)
+(/usr/share/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
+File: l3backend-pdftex.def 2024-02-20 L3 backend support: PDF output (pdfTeX)
+\l__color_backend_stack_int=\count274
+\l__pdf_internal_box=\box55
+)
+(./cart_pulling.aux)
+\openout1 = `cart_pulling.aux'.
+
+LaTeX Font Info:    Checking defaults for OML/cmm/m/it on input line 17.
+LaTeX Font Info:    ... okay on input line 17.
+LaTeX Font Info:    Checking defaults for OMS/cmsy/m/n on input line 17.
+LaTeX Font Info:    ... okay on input line 17.
+LaTeX Font Info:    Checking defaults for OT1/cmr/m/n on input line 17.
+LaTeX Font Info:    ... okay on input line 17.
+LaTeX Font Info:    Checking defaults for T1/cmr/m/n on input line 17.
+LaTeX Font Info:    ... okay on input line 17.
+LaTeX Font Info:    Checking defaults for TS1/cmr/m/n on input line 17.
+LaTeX Font Info:    ... okay on input line 17.
+LaTeX Font Info:    Checking defaults for OMX/cmex/m/n on input line 17.
+LaTeX Font Info:    ... okay on input line 17.
+LaTeX Font Info:    Checking defaults for U/cmr/m/n on input line 17.
+LaTeX Font Info:    ... okay on input line 17.
+ (/usr/share/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
+[Loading MPS to PDF converter (version 2006.09.02).]
+\scratchcounter=\count275
+\scratchdimen=\dimen154
+\scratchbox=\box56
+\nofMPsegments=\count276
+\nofMParguments=\count277
+\everyMPshowfont=\toks26
+\MPscratchCnt=\count278
+\MPscratchDim=\dimen155
+\MPnumerator=\count279
+\makeMPintoPDFobject=\count280
+\everyMPtoPDFconversion=\toks27
+) (/usr/share/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
+Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
+
+(/usr/share/texmf-dist/tex/generic/infwarerr/infwarerr.sty
+Package: infwarerr 2019/12/03 v1.5 Providing info/warning/error messages (HO)
+)
+(/usr/share/texmf-dist/tex/latex/grfext/grfext.sty
+Package: grfext 2019/12/03 v1.3 Manage graphics extensions (HO)
+
+(/usr/share/texmf-dist/tex/generic/kvdefinekeys/kvdefinekeys.sty
+Package: kvdefinekeys 2019-12-19 v1.6 Define keys (HO)
+))
+(/usr/share/texmf-dist/tex/latex/kvoptions/kvoptions.sty
+Package: kvoptions 2022-06-15 v3.15 Key value format for package options (HO)
+
+(/usr/share/texmf-dist/tex/generic/ltxcmds/ltxcmds.sty
+Package: ltxcmds 2023-12-04 v1.26 LaTeX kernel commands for general use (HO)
+)
+(/usr/share/texmf-dist/tex/latex/kvsetkeys/kvsetkeys.sty
+Package: kvsetkeys 2022-10-05 v1.19 Key value parser (HO)
+))
+(/usr/share/texmf-dist/tex/generic/pdftexcmds/pdftexcmds.sty
+Package: pdftexcmds 2020-06-27 v0.33 Utility functions of pdfTeX for LuaTeX (HO
+)
+
+(/usr/share/texmf-dist/tex/generic/iftex/iftex.sty
+Package: iftex 2022/02/03 v1.0f TeX engine tests
+)
+Package pdftexcmds Info: \pdf@primitive is available.
+Package pdftexcmds Info: \pdf@ifprimitive is available.
+Package pdftexcmds Info: \pdfdraftmode found.
+)
+Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
+85.
+Package grfext Info: Graphics extension search list:
+(grfext)             [.pdf,.png,.jpg,.mps,.jpeg,.jbig2,.jb2,.PDF,.PNG,.JPG,.JPE
+G,.JBIG2,.JB2,.eps]
+(grfext)             \AppendGraphicsExtensions on input line 504.
+
+(/usr/share/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
+File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
+e
+))
+LaTeX Font Info:    Trying to load font information for U+msa on input line 19.
+
+
+(/usr/share/texmf-dist/tex/latex/amsfonts/umsa.fd
+File: umsa.fd 2013/01/14 v3.01 AMS symbols A
+)
+LaTeX Font Info:    Trying to load font information for U+msb on input line 19.
+
+
+(/usr/share/texmf-dist/tex/latex/amsfonts/umsb.fd
+File: umsb.fd 2013/01/14 v3.01 AMS symbols B
+) (./cart_pulling.toc)
+\tf@toc=\write3
+\openout3 = `cart_pulling.toc'.
+
+
+[1
+
+{/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map}{/usr/share/texmf-dist/fonts
+/enc/dvips/cm-super/cm-super-t1.enc}] [2{/usr/share/texmf-dist/fonts/enc/dvips/
+cm-super/cm-super-ts1.enc}] [3] [4] [5] [6] (./cart_pulling.aux)
+ ***********
+LaTeX2e <2023-11-01> patch level 1
+L3 programming layer <2024-02-20>
+ ***********
+ ) 
+Here is how much of TeX's memory you used:
+ 3584 strings out of 476076
+ 51971 string characters out of 5793775
+ 1936187 words of memory out of 5000000
+ 25617 multiletter control sequences out of 15000+600000
+ 578758 words of font info for 92 fonts, out of 8000000 for 9000
+ 14 hyphenation exceptions out of 8191
+ 61i,16n,69p,240b,221s stack positions out of 10000i,1000n,20000p,200000b,200000s
+</usr/share/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb></usr/share/
+texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/share/texmf-dist/fon
+ts/type1/public/amsfonts/cm/cmmi5.pfb></usr/share/texmf-dist/fonts/type1/public
+/amsfonts/cm/cmmi7.pfb></usr/share/texmf-dist/fonts/type1/public/amsfonts/cm/cm
+r10.pfb></usr/share/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/sh
+are/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/share/texmf-dist/f
+onts/type1/public/amsfonts/cm/cmsy10.pfb></usr/share/texmf-dist/fonts/type1/pub
+lic/amsfonts/cm/cmsy7.pfb></usr/share/texmf-dist/fonts/type1/public/cm-super/sf
+bx0700.pfb></usr/share/texmf-dist/fonts/type1/public/cm-super/sfbx1000.pfb></us
+r/share/texmf-dist/fonts/type1/public/cm-super/sfbx1200.pfb></usr/share/texmf-d
+ist/fonts/type1/public/cm-super/sfbx1440.pfb></usr/share/texmf-dist/fonts/type1
+/public/cm-super/sfrm0700.pfb></usr/share/texmf-dist/fonts/type1/public/cm-supe
+r/sfrm1000.pfb></usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm1200.pfb>
+</usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm1728.pfb>
+Output written on cart_pulling.pdf (6 pages, 249938 bytes).
+PDF statistics:
+ 113 PDF objects out of 1000 (max. 8388607)
+ 69 compressed objects within 1 object stream
+ 0 named destinations out of 1000 (max. 500000)
+ 1 words of extra memory for PDF output out of 10000 (max. 10000000)
+
diff --git a/examples/cart_pulling/technical_report/cart_pulling.pdf b/examples/cart_pulling/technical_report/cart_pulling.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..ef8d310f74a933b2857e3eefe9231e4b92e1be83
Binary files /dev/null and b/examples/cart_pulling/technical_report/cart_pulling.pdf differ
diff --git a/examples/cart_pulling/technical_report/cart_pulling.synctex.gz b/examples/cart_pulling/technical_report/cart_pulling.synctex.gz
new file mode 100644
index 0000000000000000000000000000000000000000..cad3b26b6193f91c6e77cc65ad7363a2175f04ba
Binary files /dev/null and b/examples/cart_pulling/technical_report/cart_pulling.synctex.gz differ
diff --git a/examples/cart_pulling/technical_report/cart_pulling.tex b/examples/cart_pulling/technical_report/cart_pulling.tex
new file mode 100644
index 0000000000000000000000000000000000000000..7658d68ff590c4884aa0d022c6561d78a90e544f
--- /dev/null
+++ b/examples/cart_pulling/technical_report/cart_pulling.tex
@@ -0,0 +1,320 @@
+\documentclass{article}
+\usepackage{amsmath}
+\newcommand{\argmin}{\arg\!\min} 
+\newcommand{\argmax}{\arg\!\max} 
+\usepackage{amssymb}
+\usepackage{amsfonts}
+\usepackage[T1]{fontenc}
+\usepackage{bm}
+\usepackage{array}
+\usepackage{graphicx}
+\usepackage[utf8]{inputenc}
+
+\title{Cart pulling research notes}
+\date{\today}
+\author{Marko Guberina}
+
+\begin{document}
+\pagenumbering{gobble}
+\maketitle
+\tableofcontents
+\pagenumbering{arabic}
+
+\section{Problem description}
+\label{sec-problem-description}
+We have a mobile robot with hands at our disposal. 
+We want the robot to take a cart of things, and pull it from point A to B.
+This is taking place in a crowded environment, so the robot needs to be able
+to detect dynamic obstacles (people) and navigate around them.
+The robot has lidar, a camera, positional encoders on everything, an IMU
+and TODO: [other things we use].
+
+The key aspects of this problem are:
+\begin{enumerate}
+		\item mapping and localization, as for anything mobile
+		\item detecting obstacles
+		\item very quick planning - we can't know where the people will go,
+				so we need to plan quickly (can't rely on old plan which
+				assumes obstacle follows a particular trajectory)
+		\item controller capable of simultaneously following the 
+				prescribed path and handling of the cart
+\end{enumerate} 
+We tackle each of these problem as follows:
+\begin{enumerate}
+\item Use off-the-shelf ROS2 Nav2 stack which has an implementation
+	of SLAM. We know the environment we're in, and rely on a map made in 
+	advance.
+\item Some part of the ROS2 stack on the robot TODO: [know which one]
+		takes the lidar point point cloud, filters out the unmapped 
+		detected things as a separate layer on the map. Those can be assumed to be dynamic obstacles.
+\item Use Albin Dahlin's [TODO: citation] dynamic system planning in star-world.
+	Furthermore, use his code implementation. During operation,
+	the planner's own map needs to be updated, as it relies on star-shaped
+	obstacles, and not on a pixel-grid. This should use 
+	as much caching as possible, but at least the static part of the map.
+\item The ideal solution seems to be creating a whole-body MPC. 
+Optimal control strategies can accommodate 
+multiple optimization objectives, and underactuation in the base of the robot.
+An alternative would be to disjointly control the base and the arms.
+As will be explained later, we construct an exact path for the end-effector
+to follow, so as long as it follows it, it doesn't matter whether
+it's full or disjoint body control (as long as we don't have additional criteria
+to on top of the base and the end-effector positions).
+\end{enumerate}
+
+\section{Solution approach}
+
+\subsection{Formal task description}
+\subsection{System architecture}
+TODO: finish
+
+The only communication between the planner and the controller is 
+the planner sending the path. 
+However, in the current implementation, 
+it was easier for the controller to communicate 
+the current position to the planner.
+
+\section{Control design}
+\subsection{Optimal control formulation}
+We propose a direct trajectory optimization approach for our conception
+of the cart pulling problem. Furthermore, we solve the problem
+on the dynamics level. We use the Box-FDDP solver [reference]
+from the Crocoddyl optimal control suite [reference].
+This approach has the following benefits:
+\begin{itemize}
+\item control of underactuated mobile bases like differential drive
+\item flexibility in objective function design 
+makes the approach extensible, for example allowing to penalization of centrifugal 
+forces when handling tall carts, or extending the approach to legged robots
+\end{itemize}
+
+A generic optimal control problem looks like
+%\begin{align*}
+%		\label{eq-original-problem}
+%\min_{x (\cdot), u (\cdot), T} &=
+%\int_{0}^{T} L (x (t), u (t)) dt  + E (x (T))\\
+%\text{subject to } &
+%x (0) = x_{ 0 } \\
+%& \dot{x} (t) = f (x (t), u (t)) \text{ (dynamics)}\\
+%& h (x (t), u (t)) \geq 0 \text{ (path constraints)}\\
+%& r (x (T)) =0 
+%\end{align*}
+
+\begin{align}
+X^{ * }, U^{ * } &= \argmin_{X,U} \sum_{k=1}^{N-1} \int_{t_{ k }}^{t_{ k+1 }} l (x,u) dt +L (x (N)) \\
+\text{such that } & \dot{x} = f (x,u)\\
+& x \in \mathcal{X}, u \in \mathcal{U}\\
+\end{align}
+
+A common choice for $ \Delta t = t_{ k+1 } - t_{ k }  $ is
+$ \Delta t = 0.01  $.
+
+
+%We solve the inverse dynamics problem to solve for accelerations
+%$ \tau = M(q)\ddot{q}+C(q,\dot{q})+G(q)$.
+%TODO: what's the reason i can you either again????
+%TODO: what's the reason i can you either again????
+%TODO: what's the reason i can you either again????
+%TODO: what's the reason i can you either again????
+%TODO: what's the reason i can you either again????
+% TODO: try both ,see if there's a difference.
+% inv dyn should be better because no M(q) inversion, but idk
+
+\subsubsection{Turning OCPs into MPCs}
+Any OCP described here is turned into an MPC by:
+\begin{itemize}
+\item \textbf{Shortening the time horizon $ N  $, capping the maximum number of
+solver iterations $ n_{ \text{solver\_iter} }  $, 
+and reducing control frequency $ f_{ \text{ctrl} }  $}
+until satisfactory performance is reached. 
+From limited qualitative testing, the most common numbers
+are $ N = 30  $, $  n_{ \text{solver\_iter} } = 10   $,
+and $ f_{ \text{ctrl} } = 200  $.
+\item \textbf{Warmstarting} with the previous solution. 
+		The previous solution is offset by the amount of time passed.
+		TODO: make sure it is, make sure the ctrl\_freq amount of time, 
+		not $ \Delta t  $ time. If the second is the case,
+		you probably want to linearly interpolate to offset correctly
+		- also make sure you're time-testing whether this is actually helpful
+		(if it doesn't shave off an iteration off of the solver,
+		it isn't)
+\end{itemize}
+
+\subsubsection{Common OCP parts}
+Depending on the robot used, and the task at hand,
+there are different OCPs being used.
+For example, to initialize the robot's position, we use an objective
+function for point-to-point motions,
+while we use a different one for path tracking.
+
+Due to the complexity of the final controller, and of the total
+control system design (including planning), during the development
+process if was necessary to build smaller controllers
+to increase understanding of the controller, informing 
+and guiding the final design. 
+
+It's easiest to begin by describing the common parts
+of the all OCPs.
+\subsubsection{Decision variables, dynamics contraint}
+In our case, the state is a vector of joint positions and velocities:
+\begin{equation}
+ x = \begin{bmatrix} q & v \end{bmatrix}^{ T }   .
+\end{equation}
+The control inputs can be either acceleration or torques, 
+depending on which we formulate either the inverse or the forward dynamics
+problem respectively, and solve for the other quantity.
+We used inverse dynamics.
+In any case, because the states are also decision variables,
+a state-input pair must follow the system dynamics into the next that.
+Thus to put in the discrete version of $ \dot{x}= f (x,u)    $, we can write
+\begin{gather}
+ \text{dynamics} ( \delta x_{ k+1 }, \delta  x_{ k }, \delta  u_{ k } ) =0 \\
+ \text{dynamics} (\delta \dot{x}, \delta x, \delta u)=
+\delta \dot{x} - (f_{ x } \delta x + f_{ u } \delta u)
+\end{gather}
+TODO: elaborate further . Also explain to self whether efforts are torques.
+also TODO: probably a good idea to actually formulate with the forward 
+dynamics version too, it's about 10 basic lines of code and an argument.
+
+\subsubsection{State constraints}
+Since we use Box-FDDP, we can only have box constraints on torque inputs,
+i.e. $ \tau_{ \min } \leq \tau \leq \tau_{ \max } $.
+In the case of an underactuated joint, we set the torque limit on it to zero.
+
+To put constraints on the state, 
+namely on arms' joint limits and on all joints' velocities, 
+we put them into a quadratic barrier function 
+which we put into the objective function.
+In particular (TODO make sure this is correct):
+\begin{gather}
+		r_{ lb } = x - x_{ \min } \\
+		r_{ ub } = x - x_{ \max } \\
+		B(x) =
+\left\{ 
+\begin{array}{ll}
+		\frac{1}{2}  (\left\lVert r_{ lb } \right \rVert ^{ 2 }
+		+ \left\lVert r_{ ub } \right \rVert ^{ 2 }) & \text{ if } 
+		lb \leq x \leq ub \\
+		\max \text{ float} & \text{ otherwise}
+\end{array}
+\right . 
+\end{gather}
+
+\subsubsection{Regulation costs (regularization)}
+Finally, we add regulation costs on both the state $ c_{ 1 } x^{ T }x  $,
+and control inputs $ c_{ 2 } u^{ T }u  $.
+
+\subsection{Task-defining objective functions}
+Depending on the task, we create a different objective function.
+Before going into the functions themselves, the reader 
+should be briefed about some specifics of Crocoddyl, the library
+used for optimal control (more detailed text can be found in the appendix
+of ``Robotics notes'').
+
+At every timestep of the OCP, also known as a knot,
+there is an ActionModel. An ActionModel store all data and functions
+about the OCP at this point. This means that an OCP can be constructed
+as a combination of different dynamics models, constraints, 
+and objective functions, all defined just at one knot.
+This is used in path-following, where every knot is assigned
+a each own target (in this case a path point )to reach in the objective function.
+Another useful feature is setting different cost coefficients,
+which most useful in setting zero terminal velocities at the final knot.
+Note that the last point is not a good idea in MPC!
+
+\subsubsection{Point-to-point tasks}
+In case there is a fixed end-goal to reach, we use the se3 error
+norm as the task-specific function to put into the objective function.
+\paragraph{End-effector pose task}
+In the particular case of the goal being defined for the end-effector,
+whose pose is denoted as $   T_{ w }^{ e }$ reaching a goal
+denoted as $   T_{ w }^{ \text{goal} }$:
+\begin{equation}
+l_{ e } (x)= \left\lVert 
+\log \left( 
+		\left( 
+T_{ w }^{ e }
+ \right)^{ -1 } 
+\right) T_{ w }^{ \text{goal} }
+\right \rVert _{ 2 }
+\end{equation}
+Here we of course use only joint values $ q  $, not the full state $ x  $,
+because the end-effector pose is only a function of $ q  $.
+
+We put this cost function at every knot.
+
+\paragraph{End-effector pose and base position}
+In case we also want to specify the position of the base,
+we additionally put in the translational cost for the base.
+If we denote the pose of the base is 
+\begin{equation}
+T_{ w }^{ b } = \begin{bmatrix} 
+		R_{ w }^{ b } & p_{ b } \\
+		0 & 1
+\end{bmatrix} 
+\end{equation}
+then the translational cost is given by
+\begin{equation}
+		l_{ b } (x) = \left\lVert   p_{ \text{goal} } - p_{ b } \right \rVert
+\end{equation}
+Here it should be noted that the $ z  $ coordinate is 0 for
+both the goal and the base pose.
+The orientation of the base could be accounted for in the same manner.
+
+To get the joined task, we simply sum the two goal costs:
+\begin{equation}
+l_{ \text{joined} } (x)= l_{e } (x) + l_{ b } (x)
+\end{equation}
+
+\subsubsection{Path-following tasks}
+As discussed in \ref{sec-problem-description}, the controller is tasked with following 
+a path prescribed by a path planner.
+One way, and probably the best way,
+to do this would be to parametrize the path with a parameter $ s \in [0,1] $, 
+and task the controller
+with timing it, i.e. constructing a trajectory on it. In this case the 
+time-scaling factor of the path $ \lambda  $ in the equation
+$ t = \lambda s   $ would be a decision variable in the OCP.
+
+However, we found it easier to manually construct a trajectory, i.e. a timing law
+on the path. This is because it is very easy to construct a trajectory following OCP.
+Namely, it is done by modifying the point-to-point formulation by replacing
+$ T_{ w }^{ goal }  $ with $ _{ i } T_{ w }^{ traj }  $ where 
+$  _{ i } T_{ w }^{ traj }    $ is the point on the desired trajectory corresponding
+to the desired timing law on the path. More on path and trajectory construction can
+be found in \ref{sub-reference-traj-construction}.
+
+TODO write the function
+
+\subsubsection{Solver choice}
+Box-FDDP from Crocoddy seems to be the fastest, 
+and provides control input constraints
+which is crucial - otherwise one would need to overtune control input regulation,
+leading to a steep decrease in performance.
+
+CSQP from mim-solvers was also tested, but it was too slow (NOTE: idk
+if I set it up right, the paper said it should be faster than what I got).
+The benefit it has is that it can accommodate other linear constraints
+(it's based on sequential quadratic programming (SQP), made faster
+for trajectory optimization through it's implementation).
+
+Casadi' IPOPT is a good choice for experimenting 
+with different problem formulations,
+but being a generic optimization solver, and all the derivatives 
+being obtained through finite differencing, it is of course too slow 
+for practical implementation.
+
+
+\subsection{Reference trajectory construction}
+\label{sub-reference-traj-construction}
+The path planner produces a plan for the base of the robot.
+
+
+
+
+
+
+
+
+
+\end{document}
diff --git a/examples/cart_pulling/technical_report/cart_pulling.toc b/examples/cart_pulling/technical_report/cart_pulling.toc
new file mode 100644
index 0000000000000000000000000000000000000000..10fd76c26af60284e20ff24197e847d5eb7b1058
--- /dev/null
+++ b/examples/cart_pulling/technical_report/cart_pulling.toc
@@ -0,0 +1,18 @@
+\contentsline {section}{\numberline {1}Problem description}{1}{}%
+\contentsline {section}{\numberline {2}Solution approach}{2}{}%
+\contentsline {subsection}{\numberline {2.1}Formal task description}{2}{}%
+\contentsline {subsection}{\numberline {2.2}System architecture}{2}{}%
+\contentsline {section}{\numberline {3}Control design}{2}{}%
+\contentsline {subsection}{\numberline {3.1}Optimal control formulation}{2}{}%
+\contentsline {subsubsection}{\numberline {3.1.1}Turning OCPs into MPCs}{3}{}%
+\contentsline {subsubsection}{\numberline {3.1.2}Common OCP parts}{3}{}%
+\contentsline {subsubsection}{\numberline {3.1.3}Decision variables, dynamics contraint}{4}{}%
+\contentsline {subsubsection}{\numberline {3.1.4}State constraints}{4}{}%
+\contentsline {subsubsection}{\numberline {3.1.5}Regulation costs (regularization)}{4}{}%
+\contentsline {subsection}{\numberline {3.2}Task-defining objective functions}{4}{}%
+\contentsline {subsubsection}{\numberline {3.2.1}Point-to-point tasks}{5}{}%
+\contentsline {paragraph}{End-effector pose task}{5}{}%
+\contentsline {paragraph}{End-effector pose and base position}{5}{}%
+\contentsline {subsubsection}{\numberline {3.2.2}Path-following tasks}{5}{}%
+\contentsline {subsubsection}{\numberline {3.2.3}Solver choice}{6}{}%
+\contentsline {subsection}{\numberline {3.3}Reference trajectory construction}{6}{}%
diff --git a/examples/log_analysis/comparing_logs_example.py b/examples/log_analysis/comparing_logs_example.py
index f646df854589f3b3b5bdfb6d3964c840563d28e8..06b15a8317a9f7a3701fc32ac6f938227b3e463f 100644
--- a/examples/log_analysis/comparing_logs_example.py
+++ b/examples/log_analysis/comparing_logs_example.py
@@ -1,4 +1,4 @@
-from smc.visualize.manipulator_comparison_visualizer import (
+from smc.visualization.manipulator_comparison_visualizer import (
     getLogComparisonArgs,
     ManipulatorComparisonManager,
 )
diff --git a/python/smc/control/control_loop_manager.py b/python/smc/control/control_loop_manager.py
index 9ad9ef4557e9442ab7d1d9fcd9009b4124a0dc31..6b92aabf90dfc76c978df72a0f3a1646af65216b 100644
--- a/python/smc/control/control_loop_manager.py
+++ b/python/smc/control/control_loop_manager.py
@@ -2,8 +2,8 @@ from argparse import Namespace
 from smc.robots.robotmanager_abstract import AbstractRobotManager
 from smc.multiprocessing.process_manager import ProcessManager
 from smc.visualization.plotters import realTimePlotter
-from functools import partial
 
+from functools import partial
 import signal
 import time
 import numpy as np
@@ -74,7 +74,10 @@ class ControlLoopManager:
         self.past_data: dict[str, deque[np.ndarray]] = {}
         # NOTE: viz update rate is a magic number that seems to work fine and i don't have
         # any plans to make it smarter
-        self.viz_update_rate: int = int(np.ceil(self.args.ctrl_freq / 25))
+        if args.viz_update_rate < 0:
+            self.viz_update_rate: int = int(np.ceil(self.args.ctrl_freq / 25))
+        else:
+            self.viz_update_rate: int = args.viz_update_rate
         # save_past_dict has to have the key and 1 example of what you're saving
         # so that it's type can be inferred (but we're in python so types don't really work).
         # the good thing is this way you also immediatelly put in the initial values
diff --git a/python/smc/logging/logger.py b/python/smc/logging/logger.py
index 39b875764c38063dde88df1bb584d9e72b9e9344..a8546a96d94486e49c08bb6b53bb3182c1e9c7f3 100644
--- a/python/smc/logging/logger.py
+++ b/python/smc/logging/logger.py
@@ -94,6 +94,11 @@ class Logger:
         (including the data and the args).
         Uses default (un)pickling.
         """
+        if save_file_path is None:
+            print(
+                "you need to provide a path to the log file you want to analyze with --log-file1 [path]. exiting"
+            )
+            exit()
         if os.path.exists(save_file_path):
             log_file = open(save_file_path, "rb")
             tmp_dict = pickle.load(log_file)
diff --git a/python/smc/multiprocessing/process_manager.py b/python/smc/multiprocessing/process_manager.py
index 315587b082242461a417c6722b4c2571aed17309..4615bbe2febd8afbf14baa27b2bfed1e7b05e4c2 100644
--- a/python/smc/multiprocessing/process_manager.py
+++ b/python/smc/multiprocessing/process_manager.py
@@ -86,9 +86,17 @@ class ProcessManager:
             # "sending" the command via shared memory
             # TODO: the name should be random and send over as function argument
             shm_name = "command"
-            self.shm_cmd = shared_memory.SharedMemory(
-                shm_name, create=True, size=init_command.nbytes
-            )
+            # NOTE: if we didn't close properly it will just linger on.
+            # since there is no exist_ok argument for SharedMemory, we catch the error on the fly here
+            try:
+                self.shm_cmd = shared_memory.SharedMemory(
+                    shm_name, create=True, size=init_command.nbytes
+                )
+            except FileExistsError:
+                self.shm_cmd = shared_memory.SharedMemory(
+                    shm_name, create=False, size=init_command.nbytes
+                )
+
             self.shared_command = np.ndarray(
                 init_command.shape, dtype=init_command.dtype, buffer=self.shm_cmd.buf
             )
@@ -98,9 +106,14 @@ class ProcessManager:
             # getting data via different shared memory
             shm_data_name = "data"
             # size is chosen arbitrarily but 10k should be more than enough for anything really
-            self.shm_data = shared_memory.SharedMemory(
-                shm_data_name, create=True, size=10000
-            )
+            try:
+                self.shm_data = shared_memory.SharedMemory(
+                    shm_data_name, create=True, size=10000
+                )
+            except FileExistsError:
+                self.shm_data = shared_memory.SharedMemory(
+                    shm_data_name, create=False, size=10000
+                )
             # initialize empty
             p = pickle.dumps(None)
             self.shm_data.buf[: len(p)] = p
diff --git a/python/smc/path_generation/path_math/path_to_trajectory.py b/python/smc/path_generation/path_math/path_to_trajectory.py
index f07958693afdf1788eb9baa7651bd4d659b38a1a..7c0686e8e3a73a617da03685a79c75eab66d8547 100644
--- a/python/smc/path_generation/path_math/path_to_trajectory.py
+++ b/python/smc/path_generation/path_math/path_to_trajectory.py
@@ -64,6 +64,8 @@ def path2D_timed(args, path2D_untimed, max_base_v):
     # TODO THIS IS N IN PATH PLANNING, MAKE THIS A SHARED ARGUMENT
     max_s = 5
     t_to_s = max_s / total_time
+    TODO TODO trajectory for base and the end-effector are timed differently!!! this kill the MPC, and whatever else !!!
+    NOTE that the either this or the ee timing is set up wrong, but in any event they need to be the same
     # 3) construct the ocp-timed 2D path
     # TODO MAKE REFERENCE_STEP_SIZE A SHARED ARGUMENT
     # TODO: we should know max s a priori
diff --git a/python/smc/robots/utils.py b/python/smc/robots/utils.py
index c3720b626724c3e9d8fe9ff84506284db6195809..6f83f346604f5bd9b32d13f17863195d2e758544 100644
--- a/python/smc/robots/utils.py
+++ b/python/smc/robots/utils.py
@@ -35,6 +35,7 @@ def getMinimalArgParser():
         help="whether you're running on the real robot or not",
         default=False,
     )
+    # if this ends up working, replace --real with --mode, which can be {real, integration (simulation), debugging}
     parser.add_argument(
         "--robot-ip",
         type=str,
@@ -53,6 +54,12 @@ def getMinimalArgParser():
         help="whether you want to visualize the manipulator and workspace with meshcat",
         default=True,
     )
+    parser.add_argument(
+        "--viz-update-rate",
+        type=int,
+        help="frequency of visual updates. visualizer and plotter update every viz-update-rate^th iteration of the control loop. put to -1 to get a reasonable heuristic",
+        default=-1,
+    )
     parser.add_argument(
         "--plotter",
         action=argparse.BooleanOptionalAction,
diff --git a/python/smc/util/debug_entries.py b/python/smc/util/debug_entries.py
new file mode 100644
index 0000000000000000000000000000000000000000..55610b1bcfa3d166d8bab407dd8f2a10774a1f94
--- /dev/null
+++ b/python/smc/util/debug_entries.py
@@ -0,0 +1,48 @@
+import code
+import inspect
+
+
+def get_debug_answer():
+    while True:
+        answer = input(
+            """
+        Do you want to:
+        (1) run an iteration of the control loop?
+        (2) run this control_loop until completion?
+        (3) get dropped in an interactive shell?
+        Answer with [1/2/3]
+        """
+        )
+        try:
+            answer = int(answer)
+        except ValueError:
+            pass
+        if answer in [1, 2, 3]:
+            return answer
+        else:
+            print(
+                "Whatever you typed in is neither 1, 2 or 3. Give it to me straight cheif!"
+            )
+            continue
+
+
+# NOTE: this one works, and you can update
+# internal varibles, but it's a shitty baren python shell.
+# so not the solution i want
+# def start_interactive_shell():
+#    # Get stack frame for caller
+#    stack = inspect.stack()[1]
+#    frame = stack.frame
+#
+#    # Copy locals and globals of caller's stack frame
+#    locals_copy = dict(frame.f_locals)
+#    globals_copy = dict(frame.f_globals)
+#    shell_locals = dict()
+#    shell_locals.update(globals_copy)
+#    shell_locals.update(locals_copy)
+#
+#    # Start interactive shell
+#    code.interact(local=shell_locals)
+#
+#    # Delete frame to avoid cyclic references
+#    del stack
diff --git a/python/smc/visualization/path_visualizer.py b/python/smc/visualization/2Dpath_visualizer.py
similarity index 100%
rename from python/smc/visualization/path_visualizer.py
rename to python/smc/visualization/2Dpath_visualizer.py
diff --git a/python/smc/visualization/manipulator_comparison_visualizer.py b/python/smc/visualization/manipulator_comparison_visualizer.py
index 5c07d23e6d6b17897583c031948ad2b8a8ceb8e9..c982bb1308fc67fb39373aed13849b8303e7ca8c 100644
--- a/python/smc/visualization/manipulator_comparison_visualizer.py
+++ b/python/smc/visualization/manipulator_comparison_visualizer.py
@@ -21,18 +21,28 @@ def getLogComparisonArgs():
         "--log-file1", type=str, help="first log file to load for comparison"
     )
     parser.add_argument(
-        "--log-file2", type=str, help="first log file to load for comparison"
+        "--log-file2", type=str, help="second log file to load for comparison"
     )
     args = parser.parse_args()
     # these are obligatory
-    args.visualize_manipulator = False
-    args.pinocchio_only = True
+    args.visualizer = False
+    args.real = False
     args.simulation = False
     return args
 
 
 # TODO: use ProcessManager for this
 class ManipulatorComparisonManager:
+    """
+    visualizes 2 control loops simulateneously + arbitrary plots.
+    requires that the robots are the same.
+    makes most sense if you are solving the same task with a different controller,
+    or with different parameters on the same controller.
+    the key thing this class brings to the table is the micro-management of what to show
+    when due to the fact that you might have different controlloops in one example
+    and them having different total times.
+    """
+
     def __init__(self, args):
         self.args = args
         # these are obligatory
@@ -40,14 +50,6 @@ class ManipulatorComparisonManager:
         args.pinocchio_only = True
         args.simulation = False
 
-        self.robot1 = getRobotFromArgs(args)
-        self.robot2 = getRobotFromArgs(args)
-
-        # no two loops will have the same amount of timesteps.
-        # we need to store the last available step for both robots.
-        self.lastq1 = np.zeros(self.robot1.model.nq)
-        self.lastq2 = np.zeros(self.robot2.model.nq)
-
         if os.path.exists(args.log_file1):
             self.logm1 = Logger(None)
             self.logm1.loadLog(args.log_file1)
@@ -61,13 +63,22 @@ class ManipulatorComparisonManager:
             print("you did not give me a valid path for log2, exiting")
             exit()
 
+        args.robot = self.logm1.args.robot
+        self.robot1 = getRobotFromArgs(args)
+        self.robot2 = getRobotFromArgs(args)
+
+        # no two loops will have the same amount of timesteps.
+        # we need to store the last available step for both robots.
+        self.lastq1 = np.zeros(self.robot1.model.nq)
+        self.lastq2 = np.zeros(self.robot2.model.nq)
+
         side_function = partial(
             manipulatorComparisonVisualizer,
             self.robot1.model,
             self.robot1.visual_model,
             self.robot1.collision_model,
         )
-        cmd = (np.zeros(self.robot1.model.nq), np.ones(self.robot2.model.nq))
+        cmd = (np.zeros(self.robot1.nq), np.ones(self.robot2.nq))
         self.visualizer_manager = ProcessManager(args, side_function, cmd, 2)
         # wait until it's ready (otherwise you miss half the sim potentially)
         # 5 seconds should be more than enough,
diff --git a/python/smc/visualization/arms/another_debug_dh b/python/smc/visualization/obsolete_to_be_removed/arms/another_debug_dh
similarity index 100%
rename from python/smc/visualization/arms/another_debug_dh
rename to python/smc/visualization/obsolete_to_be_removed/arms/another_debug_dh
diff --git a/python/smc/visualization/arms/j2n6s300_dh_params b/python/smc/visualization/obsolete_to_be_removed/arms/j2n6s300_dh_params
similarity index 100%
rename from python/smc/visualization/arms/j2n6s300_dh_params
rename to python/smc/visualization/obsolete_to_be_removed/arms/j2n6s300_dh_params
diff --git a/python/smc/visualization/arms/j2s7s300_dh_params b/python/smc/visualization/obsolete_to_be_removed/arms/j2s7s300_dh_params
similarity index 100%
rename from python/smc/visualization/arms/j2s7s300_dh_params
rename to python/smc/visualization/obsolete_to_be_removed/arms/j2s7s300_dh_params
diff --git a/python/smc/visualization/arms/kinova_jaco_params b/python/smc/visualization/obsolete_to_be_removed/arms/kinova_jaco_params
similarity index 100%
rename from python/smc/visualization/arms/kinova_jaco_params
rename to python/smc/visualization/obsolete_to_be_removed/arms/kinova_jaco_params
diff --git a/python/smc/visualization/arms/kuka_lbw_iiwa_dh_params b/python/smc/visualization/obsolete_to_be_removed/arms/kuka_lbw_iiwa_dh_params
similarity index 100%
rename from python/smc/visualization/arms/kuka_lbw_iiwa_dh_params
rename to python/smc/visualization/obsolete_to_be_removed/arms/kuka_lbw_iiwa_dh_params
diff --git a/python/smc/visualization/arms/lbr_iiwa_jos_jedan_dh b/python/smc/visualization/obsolete_to_be_removed/arms/lbr_iiwa_jos_jedan_dh
similarity index 100%
rename from python/smc/visualization/arms/lbr_iiwa_jos_jedan_dh
rename to python/smc/visualization/obsolete_to_be_removed/arms/lbr_iiwa_jos_jedan_dh
diff --git a/python/smc/visualization/arms/robot_parameters2 b/python/smc/visualization/obsolete_to_be_removed/arms/robot_parameters2
similarity index 100%
rename from python/smc/visualization/arms/robot_parameters2
rename to python/smc/visualization/obsolete_to_be_removed/arms/robot_parameters2
diff --git a/python/smc/visualization/arms/testing_dh_parameters b/python/smc/visualization/obsolete_to_be_removed/arms/testing_dh_parameters
similarity index 100%
rename from python/smc/visualization/arms/testing_dh_parameters
rename to python/smc/visualization/obsolete_to_be_removed/arms/testing_dh_parameters
diff --git a/python/smc/visualization/arms/ur10e_dh_parameters_from_the_ur_site b/python/smc/visualization/obsolete_to_be_removed/arms/ur10e_dh_parameters_from_the_ur_site
similarity index 100%
rename from python/smc/visualization/arms/ur10e_dh_parameters_from_the_ur_site
rename to python/smc/visualization/obsolete_to_be_removed/arms/ur10e_dh_parameters_from_the_ur_site
diff --git a/python/smc/visualization/arms/ur5e_dh b/python/smc/visualization/obsolete_to_be_removed/arms/ur5e_dh
similarity index 100%
rename from python/smc/visualization/arms/ur5e_dh
rename to python/smc/visualization/obsolete_to_be_removed/arms/ur5e_dh
diff --git a/python/smc/visualization/manipulator_visual_motion_analyzer.py b/python/smc/visualization/obsolete_to_be_removed/manipulator_visual_motion_analyzer.py
similarity index 99%
rename from python/smc/visualization/manipulator_visual_motion_analyzer.py
rename to python/smc/visualization/obsolete_to_be_removed/manipulator_visual_motion_analyzer.py
index 3b9a4254c4fbc6a2514384abd03fd665749c9496..e94a260160205ecb529822e415bce0597933ec09 100644
--- a/python/smc/visualization/manipulator_visual_motion_analyzer.py
+++ b/python/smc/visualization/obsolete_to_be_removed/manipulator_visual_motion_analyzer.py
@@ -17,8 +17,8 @@ from matplotlib.gridspec import GridSpec
 from robot_stuff.InverseKinematics import InverseKinematicsEnv
 from robot_stuff.drawing import *
 from robot_stuff.inv_kinm import *
-from smc.visualize.make_run import makeRun, loadRun
-from smc.managers import RobotManager
+from smc.visualization.obsolete_to_be_removed.make_run import makeRun, loadRun
+from smc import getRobotFromArgs
 from smc.logging.logger import loadRunForAnalysis, cleanUpRun
 
 import numpy as np
@@ -29,7 +29,7 @@ from multiprocessing import Queue
 # for local thread which manages local queue (can be made better,
 # but again, who cares, all of this is ugly as hell anyway ('cos it's front-end))
 import threading
-import time 
+import time
 import pickle
 
 
diff --git a/python/smc/visualization/robot_stuff/InverseKinematics.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/InverseKinematics.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/InverseKinematics.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/InverseKinematics.py
diff --git a/python/smc/visualization/robot_stuff/drawing.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/drawing.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/drawing.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/drawing.py
diff --git a/python/smc/visualization/robot_stuff/drawing_for_anim.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/drawing_for_anim.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/drawing_for_anim.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/drawing_for_anim.py
diff --git a/python/smc/visualization/robot_stuff/follow_curve.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/follow_curve.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/follow_curve.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/follow_curve.py
diff --git a/python/smc/visualization/robot_stuff/forw_kinm.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/forw_kinm.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/forw_kinm.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/forw_kinm.py
diff --git a/python/smc/visualization/robot_stuff/inv_kinm.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/inv_kinm.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/inv_kinm.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/inv_kinm.py
diff --git a/python/smc/visualization/robot_stuff/joint_as_hom_mat.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/joint_as_hom_mat.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/joint_as_hom_mat.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/joint_as_hom_mat.py
diff --git a/python/smc/visualization/robot_stuff/utils.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/utils.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/utils.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/utils.py
diff --git a/python/smc/visualization/robot_stuff/webots_api_helper_funs.py b/python/smc/visualization/obsolete_to_be_removed/robot_stuff/webots_api_helper_funs.py
similarity index 100%
rename from python/smc/visualization/robot_stuff/webots_api_helper_funs.py
rename to python/smc/visualization/obsolete_to_be_removed/robot_stuff/webots_api_helper_funs.py
diff --git a/python/smc/visualization/visualizer.py b/python/smc/visualization/visualizer.py
index 98c07a28d1aa31e26ba92af222c7fae7073ce972..2851764d1b7b6fd55bfa0fb0e5150afae7caf8db 100644
--- a/python/smc/visualization/visualizer.py
+++ b/python/smc/visualization/visualizer.py
@@ -144,6 +144,7 @@ def manipulatorComparisonVisualizer(
                     viz.viewer.window.server_proc.wait()
                     break
             q1, q2 = q
+            print(q)
             viz.display(q1)
             viz2.display(q2)
             # this doesn't really work because meshcat is it's own server