From 1144280e2bb1bfa75577ffcf4f5a208fd684ea0f Mon Sep 17 00:00:00 2001
From: Quentin Bolsee <quentinbolsee@hotmail.com>
Date: Mon, 22 May 2023 03:56:10 +0200
Subject: [PATCH] ND curve solver

---
 .gitignore                          |   1 +
 curves.py                           | 120 ++++++++++++++++
 curves/p.npy                        | Bin 0 -> 4224 bytes
 curves/p_prime.npy                  | Bin 0 -> 4224 bytes
 curves/p_prime2.npy                 | Bin 0 -> 4224 bytes
 curves/u.npy                        | Bin 0 -> 2176 bytes
 curves/u_dot.npy                    | Bin 0 -> 2176 bytes
 plot_solution_video.py              | 117 +++++++++++++++
 main_animate.py => solve_animate.py |   0
 solve_curves.py                     | 211 ++++++++++++++++++++++++++++
 10 files changed, 449 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 curves.py
 create mode 100644 curves/p.npy
 create mode 100644 curves/p_prime.npy
 create mode 100644 curves/p_prime2.npy
 create mode 100644 curves/u.npy
 create mode 100644 curves/u_dot.npy
 create mode 100644 plot_solution_video.py
 rename main_animate.py => solve_animate.py (100%)
 create mode 100644 solve_curves.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/curves.py b/curves.py
new file mode 100644
index 0000000..4a3aadc
--- /dev/null
+++ b/curves.py
@@ -0,0 +1,120 @@
+import numpy as np
+import math
+
+
+class Curve:
+    def __call__(self, u):
+        raise NotImplementedError()
+
+    def derivative(self, u):
+        raise NotImplementedError()
+
+    def second_derivative(self, u):
+        raise NotImplementedError()
+
+
+class Line1D(Curve):
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
+
+    def __call__(self, u):
+        v = 1 - u
+        return (self.a * v + self.b * u)[:, np.newaxis]
+
+    def derivative(self, u):
+        return u[:, np.newaxis]*0.0 + (self.b - self.a)
+
+    def second_derivative(self, u):
+        return u[:, np.newaxis]*0.0
+
+
+class Line2D(Curve):
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
+
+    def __call__(self, u):
+        v = 1 - u
+        n = len(u)
+        p = np.zeros((n, 2))
+        p[:, 0] = (self.a[0] * v + self.b[0] * u)
+        p[:, 1] = (self.a[1] * v + self.b[1] * u)
+        return p
+
+    def derivative(self, u):
+        n = len(u)
+        p = np.zeros((n, 2))
+        p[:, 0] = (self.b[0] - self.a[0])
+        p[:, 1] = (self.b[1] - self.a[1])
+        return p
+
+    def second_derivative(self, u):
+        n = len(u)
+        p = np.zeros((n, 2))
+        return p
+
+
+class Circle2D(Curve):
+    def __init__(self, r):
+        self.r = r
+
+    def __call__(self, u):
+        n = len(u)
+        p = np.zeros((n, 2))
+        p[:, 0] = np.cos(2*math.pi*u)
+        p[:, 1] = np.sin(2*math.pi*u)
+        return p
+
+    def derivative(self, u):
+        n = len(u)
+        p = np.zeros((n, 2))
+        p[:, 0] = -2*math.pi*np.sin(2*math.pi*u)
+        p[:, 1] = 2*math.pi*np.cos(2*math.pi*u)
+        return p
+
+    def second_derivative(self, u):
+        n = len(u)
+        p = np.zeros((n, 2))
+        p[:, 0] = -4*math.pi*math.pi*np.cos(2*math.pi*u)
+        p[:, 1] = -4*math.pi*math.pi*np.sin(2*math.pi*u)
+        return p
+
+
+class CubicBezier(Curve):
+    def __init__(self, A, B, C, D):
+        self.n_dims = len(A)
+        self.A = A
+        self.B = B
+        self.C = C
+        self.D = D
+
+    def __call__(self, u):
+        v = 1-u
+        n = len(u)
+        p = np.zeros((n, self.n_dims))
+        for i in range(self.n_dims):
+            p[:, i] += (v*v*v)*self.A[i]
+            p[:, i] += 3*v*v*u*self.B[i]
+            p[:, i] += 3*v*u*u*self.C[i]
+            p[:, i] += u*u*u*self.D[i]
+        return p
+
+    def derivative(self, u):
+        v = 1-u
+        n = len(u)
+        p = np.zeros((n, self.n_dims))
+        for i in range(self.n_dims):
+            p[:, i] += 3*v*v*(self.B[i]-self.A[i])
+            p[:, i] += 6*v*u*(self.C[i]-self.B[i])
+            p[:, i] += 3*u*u*(self.D[i]-self.C[i])
+        return p
+
+    def second_derivative(self, u):
+        v = 1 - u
+        n = len(u)
+        p = np.zeros((n, self.n_dims))
+        for i in range(self.n_dims):
+            p[:, i] += 6*v*(self.C[i]-2*self.B[i]+self.A[i])
+            p[:, i] += 6*u*(self.D[i]-2*self.C[i]+self.B[i])
+        return p
diff --git a/curves/p.npy b/curves/p.npy
new file mode 100644
index 0000000000000000000000000000000000000000..8b34498dcf3181666b25fe14d6d3900ba5b1ad8b
GIT binary patch
literal 4224
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
E0IW_&8UO$Q

literal 0
HcmV?d00001

diff --git a/curves/p_prime.npy b/curves/p_prime.npy
new file mode 100644
index 0000000000000000000000000000000000000000..a00dc31931dedb2d125dcff6fca6ca5b0728540b
GIT binary patch
literal 4224
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

literal 0
HcmV?d00001

diff --git a/curves/p_prime2.npy b/curves/p_prime2.npy
new file mode 100644
index 0000000000000000000000000000000000000000..2ba851498aa33eaaa1b90fcb4cc53d2fcf424adc
GIT binary patch
literal 4224
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

literal 0
HcmV?d00001

diff --git a/curves/u.npy b/curves/u.npy
new file mode 100644
index 0000000000000000000000000000000000000000..db5f73d34a33111edf97ffafa41855a68631221a
GIT binary patch
literal 2176
zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEPSsIR
zFV09TNL9B|&@eJJ)6vvXs8t{va4|rE02mb5gDHUqdkHY;fbu8Ui-5rtsQ3(f88Daw
zm0w^l1O|(s>Xz7xfx$AU`W5z4V6Y0RZ;ibi7_5Wp-(W8Y1{<N~Y_b;ygUwL$w%Ch;
z!B(ib+w8@`U^~?O9rlu7uoG&}E_-P(*bTLBkG(7y?1kF9&t4u3_CxJIU_Sv24nW;;
z(0<ZH5I6{R&msFsU~mZPuEX|ICWF9XsQZrCPXU7?P<I}+pEeZ)jzZmg%zhde9D}<1
zxc!XjAaES&{uA~yz~BVbA1Cc+%>;pyQ2(5=p9Kb|p#C~-KW8=woQC@EjQt!iI0N<P
zS^If&LEtRZzvt}dfx$Vbzt7t*m=6Nyq5i*M9{>gypy6=A{^A8NfQH9K`#><b2o0Bu
z_LnY#0W^Fr*$08aC1^NZvcG%@44~n4**+KyE<?lZvi+6IU;qujEA}B^a0MEUSM0A|
z0Rw1wUbPPegR9VRy=s5$Di}b+_nLhe7+iyf^ELbH*T4W8-q-EJ!QeVH+^^f;xDE!;
z@V{Xn0R}gq@o>Zb<_$1_#>Y+jNHDkwjhCDDw{C&~G=6T`M}fgDXguArzkLe~pz(Fv
zJ{k;eL*wnX{hixj0FA#p_Ay{^2O5ue?C;(I1898SwT}gZyU=*OYk%)97(nCqo_!n`
z+=IsRJ^TCjzyKQG_wD1s;660o@7q7P4+hZqe_(F_1`nX=;DLPs7(9TchX?i>H-Nwc
zXu5b{|M3F|Jb<Q;hxUeG@DQ3#9@-az!9!?zd1$|BBM3Z%rkjWMpFV=XLumSWWN!op
zkD%%3k$n*uJc6dDNA{aHfxshZx_V^)`4b2{f~K#>_QqiF7@E!=+ZThuV`zGNY`<kQ
z2t0<SyT|rlK7+txX!?6%ZvqBSpy}|5eF+#mfu_eN_FK1rz!PY?d}9Cg3kW=crq8GL
zreN?CnoghEmx94lXnK8WzilfBJcXv)r}p2zg1}Q~`h8|^1_sZd>G+v_85lf+rsrq&
z+qZ$hGibVgX8-*g2t0$P@8|aBVDKE8&Y#<tgTZrXdVg-eV><{uho<}I_CLOZz;kH&
ze_?L{1}~ub;Dvn!7`%YyhZpuccYwePXuf!1|MLe3ynyD9m-d!m@DiF&UfNfJ!Aod<
zd1=3ECkVWR=9`!HzkY(iOKARiWp4!rub}zpm3<W$yn^PZSN6Mifxs(hzItW<`xgkj
zg66N+_SRtV8k)~u+gF3ZYiNFZZNFzX2)u^oyVv%AeuKbkX#RU+ZvzHzp!x8PeGM4A
zf#%0I_Ivk$z#C}3d}IIj4+y-0=FhkGwqWoUnor-_*Mh-YXnuWbzi%%HyoKi5xAy=3
zg1}p7{(Wa}2L|t;`S_iE9T>cW=I3|z`}cvsJ7~UsXaD~n2)u*l@Avi)@E)4a-`m%N
Z!Fy<ae{X+aKM1^s=KJ^dkZS*fJpj_e$HV{t

literal 0
HcmV?d00001

diff --git a/curves/u_dot.npy b/curves/u_dot.npy
new file mode 100644
index 0000000000000000000000000000000000000000..8ddef6c76b92842f1593aa6ed25d31cbb35924e2
GIT binary patch
literal 2176
zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEPSsIR
zFV09TNL9B|&@eJJ)6vvXs8t{va4|qZ#YYo0ue;mqdk=5B`F8gq`&DN>p4?|TVc)dy
zs%w1K8GA#~*$)ELFW4_{{VI|E_M-i~T}^YQPQGma=x*`RD#fezpXwSIZ020Ex4ZPY
zvzh&deZlF2H#pL6+TW-;ee3?tTlQzBrJY;Ja>u?^Ao2PZ_q+CS0`DyZTkhG1y+1Cv
zVAp;7@ZXEx^OZfYf44JVbSlq7`yKE47pg6MXs@a`-SeOEBm1dy_W$)e`pEu@&n9!%
z(8u;F;$_SnPafO1JuAJuv+#-iKF=50mH(dDKbX8+FR|;X{fp;zd|jf?>_fMD<#H}~
zW`AU}lG$DL=k_tp2iYA~J+~L-s=3{&{=$CETmFmR=D)D-a{tJ8QTV03<xDLnujZHb
z$(fE<CcJxTzoL5a^E08Z>{|-osVVM!WuN&czF@V)Yx}s~u9Vi2*Y-|Xr|%v)^V+_I
z>10Ta;v4%0=`hpXMQ`luCMT=)9e87()UDwDpX;stnLi(XCI`K>*PWBd&pZ9Cecagx
zryXy;wO@YQ*waz+oqfj{i#g8W@9YCi8c$VCerNy0<NOTabMNfy;-*=evA?(H=GS`2
zX8+z^!As3$S?PQGjNqNjMyuc3r`$Tub@cvw`?NjU?{{;4u-9^(S7@O8!G2C}xUZMZ
z2m6P~6JHC3ez1?9?D%z3?gx9e;^g#8tsm^4m0VhKW9|q0eQDfnYqox{|ME~KJ>={M
z`y1sfCfA;Pu)n~$w%Gj72YVB_-GQ~jAMJVP`7U3h_0hhN^$OD|$B*{XEn@3$Mtrnq
z+t{)5e*Q;$4&6WB@3(!lH#mBf_ukx(_N7j#1$Vc6w7<o^FZ<!?kM^(bzEOSq=%c;i
zv7?Kae|@y)O=3Q-F8Ik_FU%l1M&pycr9=?l68lf~raPl67{fo=Z)wPrDbM?4@74A;
zi@oiWeZ<p@!&~Nlvfr^hrL1x5C;JD&|BvOL{bc`ZMdh2ur=RTS>{%|n>+dJ~J&CRf
zQevO&8UI?mTcY>bUTXcpOVRG1?aex;$T}u`woi8sKlY&Fv;DM->5EJ!e70Y+|K*f`
zWuNV>Rem};?*455I$2Ch?EGi@z0VpC-Ff)gzFXjJ*TT=A?f1>Nrcl80#lC0f?SNpB
zFZOQ~Y}G=Qzu4d3boNJ`;TQWu+bSz>+I_Jv+1a-@$NP(Yh2V1!hwv}<aq;5f(MezI
zH&*Rfd^qQeefPa5AM4A$*f$^gJ85eD7ki(slWRCTzSviA{Qmc#|BL-=E&Uhr)4$jk
z{maQZHTR4CpGG77CyT$>7gvgR7p(kZ-|frSShVho{T61gqi;5Sv42x^e*eR5U+fog
zZ|@7<^~GNOoHB>Y-Y@pI6F=|Xvj2;H%o44y%MO0A|5>59Tl(-9`~A`dmxPaev5(m|
ze{Vk$4HN$bRR_~|2WpN6)ZC>|d)`Cs-2rvS1gN{pq3&d8g1EZ^>W`05f7L<#83grr
z7c?9uLBnM`G@MeP;Z_F?$4Y3p9*2hW6KJ@9fX2fFXuKSP##0|O-u6S|@hvo7?V$0z
z9vbg6q3IwCnl3D$=>(Q;n4swhmabsw>^C&sJ%XmgUC?wH1x=^0bPG$zuykz!P3OAM
zbk7aV2fEOF@eG<zu0!)pF*G0PL-Q3ZpTY8-R0|{@!ty0QG@qJ5^DQhN!}2vOpTqJ!
ztQ>%q3$StmR&Kz`5m>nbD`#Nk4y+uKgO*Di&~ge^ZasvSV}8(btqoevErphQuyRlj
zS}vZ0mXpcQauZgL!pc=xIqL{5cVXpl7PMT3mD8)B<#qzJ9Jhd$>#%YjR_=>I>w!3E
zy>JX#PeelNjW^JG<UX`s*$b^_VD*kNv>t-hOVgnBlp3_&+6t}5UP0@%5NJIY1g-aC
zp!FcEUOW%2C+|V)%?fBeY7MPdKS1kQSiL(RS`V*+*2`wldU`6f-kt}m$D#Fl7POv+
HR{IP9l;lV2

literal 0
HcmV?d00001

diff --git a/plot_solution_video.py b/plot_solution_video.py
new file mode 100644
index 0000000..cfa785d
--- /dev/null
+++ b/plot_solution_video.py
@@ -0,0 +1,117 @@
+import math
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.animation import FuncAnimation
+import time
+
+ani = None
+interval_ms = 16
+t = 0
+
+
+def update(frame, lines, u, u_dot, p, p_prime, p_prime2):
+    global ani, interval_ms, t
+
+    n = len(u)
+    u_dot_mid = (u_dot[:-1] + u_dot[1:])*0.5
+    t_delta = (u[1:] - u[:-1])/u_dot_mid
+    t_delta_ext = np.zeros((n,))
+    t_delta_ext[1:] = t_delta
+    t_axis = np.cumsum(t_delta_ext)
+    a = np.zeros((n-1, 2))
+    p_prime_mid = 0.5 * (p_prime[:-1, :] + p_prime[1:, :])
+    p_prime2_mid = 0.5 * (p_prime2[:-1, :] + p_prime2[1:, :])
+    u_dot_prime = (u_dot[1:] - u_dot[:-1]) / (u[1:] - u[:-1])
+    for i in range(2):
+        a[:, i] += p_prime2_mid[:, i] * u_dot_mid * u_dot_mid
+        a[:, i] += p_prime_mid[:, i] * u_dot_mid * u_dot_prime
+
+    if t > t_axis[-1]:
+        # cycle
+        t = 0
+        # time.sleep(0.5)
+
+    p_now = np.zeros((2,))
+    a_now = np.zeros((2,))
+    p_now[0] = np.interp(t, t_axis, p[:, 0])
+    p_now[1] = np.interp(t, t_axis, p[:, 1])
+    a_now[0] = np.interp(t, t_axis[:-1], a[:, 0])
+    a_now[1] = np.interp(t, t_axis[:-1], a[:, 1])
+
+    a_scale = 0.03
+
+    lines[0].set_xdata(p[:, 0])
+    lines[0].set_ydata(p[:, 1])
+    lines[1].set_xdata(p_now[0])
+    lines[1].set_ydata(p_now[1])
+    lines[2].set_xdata([p_now[0], p_now[0]+a_scale*a_now[0]])
+    lines[2].set_ydata([p_now[1], p_now[1]+a_scale*a_now[1]])
+
+    lines[4].set_xdata(t_axis)
+    lines[4].set_ydata(u_dot)
+    lines[5].set_xdata([t, t])
+    lines[5].set_ydata([0, np.max(u_dot)*1.5])
+
+    t += interval_ms/1000.0
+
+    return lines
+
+
+def main_func():
+    global ani, interval_ms
+
+    u = np.load("curves/u.npy")
+    u_dot = np.load("curves/u_dot.npy")
+    p = np.load("curves/p.npy")
+    p_prime = np.load("curves/p_prime.npy")
+    p_prime2 = np.load("curves/p_prime2.npy")
+    n = len(u)
+
+    u_mid = (u[:-1] + u[1:]) * 0.5
+    u_delta = u[1:] - u[:-1]
+    u_dot_mid = (u_dot[:-1] + u_dot[1:]) * 0.5
+
+    t_delta = u_delta / u_mid
+    a = (u_dot[1:] - u_dot[:-1]) / t_delta
+
+    t_delta_ext = np.zeros((n,))
+    t_delta_ext[1:] = t_delta
+    t = np.cumsum(t_delta_ext)
+
+    fig, axes = plt.subplots(1, 2)
+    ax = axes[0]
+    ax2 = axes[1]
+    ln0, = ax.plot([], [], 'b')
+    ln1, = ax.plot([], [], 'k.', ms=20)
+    ln2, = ax.plot([], [], 'r-')
+    ln3, = ax.plot([], [], 'g-')
+    ax.set_xlim([np.min(p[:, 0])-0.5, np.max(p[:, 0])+0.5])
+    ax.set_ylim([np.min(p[:, 1])-0.5, np.max(p[:, 1])+0.5])
+    ax.set_aspect("equal")
+    ax.set_xlabel("x")
+    ax.set_ylabel("y")
+    ax.set_title("Motion")
+    # ax.legend(["speed", "min", "max"])
+
+    ln4, = ax2.plot([], [], 'b')
+    ln5, = ax2.plot([], [], 'k')
+    ln6, = ax2.plot([], [], 'r')
+    ax2.set_xlim([0, 2])
+    ax2.set_ylim([0, 1])
+    ax2.set_xlabel("t")
+    ax2.set_ylabel("u_dot")
+    ax2.set_title("Curve reading speed")
+    # ax2.legend(["acc.", "min", "max"])
+
+    lines = [ln0, ln1, ln2, ln3, ln4, ln5, ln6]
+    plt.tight_layout()
+
+    def init():
+        return lines
+
+    ani = FuncAnimation(fig, update, init_func=init, fargs=(lines, u, u_dot, p, p_prime, p_prime2), blit=True, interval=interval_ms)
+    plt.show()
+
+
+if __name__ == "__main__":
+    main_func()
diff --git a/main_animate.py b/solve_animate.py
similarity index 100%
rename from main_animate.py
rename to solve_animate.py
diff --git a/solve_curves.py b/solve_curves.py
new file mode 100644
index 0000000..021fcbd
--- /dev/null
+++ b/solve_curves.py
@@ -0,0 +1,211 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import curves
+
+
+# def a_mp_func(v, v1=0.2, a0=7.0, ap=5.0, am=7.0):
+#     n = len(v)
+#     a_minus = np.zeros((n,))
+#     a_plus = np.zeros((n,))
+#     alpha1 = np.minimum(v[v > 0]/v1, 1)
+#     alpha2 = np.minimum(-v[v <= 0]/v1, 1)
+#     a_minus[v > 0] = -(alpha1 * am + (1-alpha1) * a0)
+#     a_plus[v > 0] = alpha1 * ap + (1-alpha1) * a0
+#     a_minus[v <= 0] = -(alpha2 * ap + (1-alpha2) * a0)
+#     a_plus[v <= 0] = alpha2 * am + (1-alpha2) * a0
+#     return a_minus, a_plus
+
+
+def a_mp_func(v, ap=5.0):
+    # basic, constant acceleration constraint
+    n = len(v)
+    a_minus = np.zeros((n,))
+    a_plus = np.zeros((n,))
+    a_plus[:] = ap
+    a_minus[:] = -ap
+    return a_minus, a_plus
+
+
+def constraints_a(p_prime, p_prime2, u, u_dot, jerk_max=50.0, show=False):
+    n, n_dims = p_prime.shape
+
+    a = np.zeros((n-1, n_dims))
+
+    a_minus = np.zeros((n-1, n_dims))
+    a_plus = np.zeros((n-1, n_dims))
+
+    u_mid = 0.5 * (u[:-1] + u[1:])
+    u_dot_mid = 0.5 * (u_dot[:-1] + u_dot[1:])
+    p_prime_mid = 0.5 * (p_prime[:-1, :] + p_prime[1:, :])
+    p_prime2_mid = 0.5 * (p_prime2[:-1, :] + p_prime2[1:, :])
+    u_dot_prime = (u_dot[1:] - u_dot[:-1]) / (u[1:] - u[:-1])
+    dt = (u[1:] - u[:-1]) / u_dot_mid
+    dt_a = (dt[:-1] + dt[1:]) * 0.5
+
+    u_dot2_min_all = -np.inf * np.ones((n - 1, n_dims))
+    u_dot2_max_all = np.inf * np.ones((n - 1, n_dims))
+
+    v = np.zeros((n-1, n_dims))
+    for i in range(n_dims):
+        v[:, i] = p_prime_mid[:, i] * u_dot_mid
+        a[:, i] += p_prime2_mid[:, i] * u_dot_mid * u_dot_mid
+        a[:, i] += p_prime_mid[:, i] * u_dot_mid * u_dot_prime
+
+        a_plus_next = a[:-1, i] + jerk_max * dt_a
+        a_minus_next = a[:-1, i] - jerk_max * dt_a
+        a_plus_prev = a[1:, i] + jerk_max * dt_a
+        a_minus_prev = a[1:, i] - jerk_max * dt_a
+
+        # boundary condition
+        # a_minus[0, i] = a_minus_prev[0]
+        # a_minus[-1, i] = a_minus_next[-1]
+        # a_plus[0, i] = a_plus_prev[0]
+        # a_plus[-1, i] = a_plus_next[-1]
+
+        # zero acceleration beyond curve
+        a_minus[0] = -jerk_max * dt_a[0]
+        a_plus[0] = jerk_max * dt_a[0]
+        a_minus[-1] = -jerk_max * dt_a[-1]
+        a_plus[-1] = jerk_max * dt_a[-1]
+
+        a_minus[1:-1, i] = np.maximum(a_minus_next[:-1], a_minus_prev[1:])
+        a_plus[1:-1, i] = np.minimum(a_plus_next[:-1], a_plus_prev[1:])
+
+        # min and max prescribed by speed
+        a_neg_bound, a_pos_bound = a_mp_func(v[:, i])
+        a_minus[:, i] = np.minimum(a_minus[:, i], a_pos_bound)
+        a_minus[:, i] = np.maximum(a_minus[:, i], a_neg_bound)
+        a_plus[:, i] = np.minimum(a_plus[:, i], a_pos_bound)
+        a_plus[:, i] = np.maximum(a_plus[:, i], a_neg_bound)
+
+        # acceleration along the curve only
+        a_plus_along = a_plus[:, i] - p_prime2_mid[:, i] * u_dot_mid*u_dot_mid
+        a_minus_along = a_minus[:, i] - p_prime2_mid[:, i] * u_dot_mid*u_dot_mid
+
+        mask = np.abs(p_prime_mid[:, i]) > 1e-7
+
+        u_dot2_1 = a_plus_along[mask]/p_prime_mid[mask, i]
+        u_dot2_2 = a_minus_along[mask]/p_prime_mid[mask, i]
+        u_dot2_min_all[mask, i] = np.minimum(u_dot2_1, u_dot2_2)
+        u_dot2_max_all[mask, i] = np.maximum(u_dot2_1, u_dot2_2)
+
+    u_dot2_min = np.max(u_dot2_min_all, axis=-1)
+    u_dot2_max = np.min(u_dot2_max_all, axis=-1)
+
+    if show:
+        plt.figure()
+        plt.plot(u_mid, u_dot_prime * u_dot_mid, 'k')
+        plt.plot(u_mid, u_dot2_min, 'b')
+        plt.plot(u_mid, u_dot2_max, 'r')
+        plt.title("u_dot2")
+
+        plt.figure()
+        for i in range(n_dims):
+            plt.subplot(n_dims, 1, i+1)
+            plt.plot(u_mid, u_dot2_min_all[:, i], 'b')
+            plt.plot(u_mid, u_dot2_max_all[:, i], 'r')
+            plt.title(f"u_dot2_{i}")
+
+        plt.figure()
+        for i in range(n_dims):
+            plt.subplot(n_dims, 1, i+1)
+            plt.plot(u_mid, a[:, i], 'k')
+            plt.plot(u_mid, a_minus[:, i], 'b')
+            plt.plot(u_mid, a_plus[:, i], 'r')
+            plt.title(f"a_{i}")
+
+    return u_dot2_min, u_dot2_max
+
+
+def constraints_v(p_prime, u, u_dot, u_dot2_min, u_dot2_max, v_abs_max=4.0, show=False):
+    n, n_dims = p_prime.shape
+    u_dot_min = np.zeros((n,))
+    u_dot_max = np.zeros((n,))
+
+    # v = np.zeros((n - 1, n_dims))
+    du = u[1:] - u[:-1]
+    u_dot_max_next = np.sqrt(np.maximum(u_dot[:-1]**2 + 2 * u_dot2_max * du, 0))
+    u_dot_min_next = np.sqrt(np.maximum(u_dot[:-1]**2 + 2 * u_dot2_min * du, 0))
+    u_dot_min_prev = np.sqrt(np.maximum(u_dot[1:]**2 - 2 * u_dot2_max * du, 0))
+    u_dot_max_prev = np.sqrt(np.maximum(u_dot[1:]**2 - 2 * u_dot2_min * du, 0))
+    u_dot_min[0] = u_dot_min_prev[0]
+    u_dot_min[-1] = u_dot_min_next[-1]
+    u_dot_max[0] = u_dot_max_prev[0]
+    u_dot_max[-1] = u_dot_max_next[-1]
+    u_dot_min[1:-1] = np.maximum(u_dot_min_next[:-1], u_dot_min_prev[1:])
+    u_dot_max[1:-1] = np.minimum(u_dot_max_next[:-1], u_dot_max_prev[1:])
+    u_dot_min = np.minimum(u_dot_min, u_dot_max)
+
+    p_prime_norm = np.linalg.norm(p_prime, axis=-1)
+    u_dot_min[:] = np.maximum(u_dot_min, 0)
+    u_dot_max[:] = np.minimum(u_dot_max, v_abs_max / p_prime_norm)
+
+    if show:
+        plt.figure()
+        plt.plot(u, u_dot, 'k')
+        plt.plot(u, u_dot_min, 'b')
+        plt.plot(u, u_dot_max, 'r')
+        plt.title("u_dot")
+
+    return u_dot_min, u_dot_max
+
+
+def main():
+    n = 256
+    # c = curves.Line1D(0, 4)
+    # c = curves.Line2D([0, 0], [4, 0])
+    # c = curves.CubicBezier([0, 0], [1, 0], [1, 2], [0, 0.7])
+    c = curves.CubicBezier([0, 0], [1, 0], [1, 1], [2, 1])
+    # c = curves.Circle2D(1.5)
+    u = np.linspace(0, 1, n)
+    u_dot = np.zeros((n,))
+    p = c(u)
+    p_prime = c.derivative(u)
+    p_prime2 = c.second_derivative(u)
+
+    u_dot[1:-1] = 1e-4
+    # u_dot[:] = 0.1*(2*u - 2*u*u)
+
+    # lambd = 0.1
+    lambd = 0.3
+    optimizing = True
+    max_itr = int(5e4)
+    k = 0
+    while optimizing:
+        show = k == -1
+        u_dot2_min, u_dot2_max = constraints_a(p_prime, p_prime2, u, u_dot, show=show)
+        u_dot_min, u_dot_max = constraints_v(p_prime, u, u_dot, u_dot2_min, u_dot2_max, show=show)
+
+        if show:
+            plt.show()
+            return
+
+        mse_diff = np.mean(np.square(u_dot[1:-1] - u_dot_max[1:-1]))
+        u_dot[1:-1] = lambd * u_dot_max[1:-1] + (1 - lambd) * u_dot[1:-1]
+        k += 1
+        optimizing = (mse_diff > 1e-10) and (k < max_itr)
+
+    np.save("curves/u.npy", u)
+    np.save("curves/u_dot.npy", u_dot)
+    np.save("curves/p.npy", p)
+    np.save("curves/p_prime.npy", p_prime)
+    np.save("curves/p_prime2.npy", p_prime2)
+
+    print(f"{k} steps")
+
+    u_mid = (u[:-1] + u[1:]) * 0.5
+
+    plt.figure()
+    plt.plot(u, u_dot, 'k')
+    plt.plot(u, u_dot_min, 'b')
+    plt.plot(u, u_dot_max, 'r')
+
+    plt.figure()
+    plt.plot(u_mid, u_dot2_min, 'b')
+    plt.plot(u_mid, u_dot2_max, 'r')
+
+    plt.show()
+
+
+if __name__ == "__main__":
+    main()
-- 
GitLab