From f40385e51fe995c33a7555b877710596f3559738 Mon Sep 17 00:00:00 2001 From: m-guberina <gubi.guberina@gmail.com> Date: Mon, 21 Oct 2024 11:07:06 +0200 Subject: [PATCH] optimal control looks like it works now that the state bounds have been added. some trajectory-tracking finessing is required to make this operable on the real robot, but that's not a real problem --- .../__pycache__/managers.cpython-312.pyc | Bin 42063 -> 42107 bytes .../basics/__pycache__/basics.cpython-312.pyc | Bin 7093 -> 7809 bytes python/ur_simple_control/basics/basics.py | 21 +++- python/ur_simple_control/managers.py | 17 ++-- .../optimal_control/optimal_control.py | 94 ++++++++++++++++-- 5 files changed, 110 insertions(+), 22 deletions(-) diff --git a/python/ur_simple_control/__pycache__/managers.cpython-312.pyc b/python/ur_simple_control/__pycache__/managers.cpython-312.pyc index cbec686b753e00fb945018a65c3a29abc042fc33..e8f0cf9924bbd4c123fcdc9cf58e2b3c93cdaf56 100644 GIT binary patch delta 1963 zcmX?qg6a1OCf?J$yj%<n3=GeX38(Mc$gAqcxNWns+eBuj6t2lqUP=lz3|TfHc~qP& z#lTR+U&EMXH~C?X@Z@MOZWb^nP)B(3O+P+H2e7cM7Z)Q;fdHe!<Qy+aenpr;DGW6X z@k*2P+~t_ET)@hYdzmqUS(^OW8Vn3Y{w3}(#jBwVkQOMDp@spZPg9uDbMnF}5vD9J zup#B%d>}(Kg&BP(A54~?ywh6*#Lp3y6$Bf~U*Zol0NK#V|Gc9aH75V_wq*>O+^8ir zna`JJGMkSngvV0D5U)Ks!A)zjjjuazB+P&s#w;(8VyPO2cwLxaNi2+oEUga{EJ=j1 zVA29L4Dp6=L58f<$sc2+KsFjrcJNb9hTE1RyqXcB47+tG@>uLckw;j_nB_1zP)C@V zAzop!otqq6v1$!NR?_5$n!=2kkbrfV+^8ix+1+1i@<Tmgfufn1J_LCIZetBYRvbwC z<cI#klP@%Ju+=ivFlFU~V`qUb4>N+z%~PTY@eu<<RsqNrljjC-a)8v=FvM$2KH(-e zxgfxrv3T;wSeeZy0y-EOttJNsofZ`Eb&QC2bar<2aSd_|@$~oGEFEmf$oPM=Z%6^7 zzM@J8%M}jU8~no8`4ukmD=cuiu3~vn#qtur<&Ko=cK#Rb{4WHBUa||lz!7?ZKXmim z&`(T^qLVK~6bLP3U|`T>Dw@u~z)%EA_C<>(2SsW!N^Gu)WM^VrIJqU-hjH5Ei_u<; z(vxLkK8Zrq-(o8-O3h2ofN4>VHDuJ@92&csg>mL&&Lm65Ig_1}+!$w1?n;tnG}yc% ziJM(-CCG|ZAfgRKbb^Rz5COIulr)QaKwPjZmQQ||Dakei#9uTiOPw)fvqsipM($Z4 zfw>@J!sKh&a*T5)f6Z24tl2D<<If^66C^$#L>PdGNs~(pf+cq`FfddJ1?4B@m-r^; zC8nnq>BXm~mIStMepq0_#&!^-^3Y`IaxE!z?I4-MAmYg6_;Mv}Y|ckGdO0W<S&Bf} zw`k4eHRbk<D>uI>?`4qynR<&mJ+;I!xui5PCorW5>bQwD3bG)dYchd#-C`<CDQ03| zU{FvfQUlq#d-Ay&3C0_npVz!(Vgs2{v}N+OdIiSylfTznG5*?Y)DXsK0gesMqI{5s z91u|oA|R?ktT`ZJJp%*7Ew-Y>yp;UhqHd7LqR9sujoCPu85oL#C(E~nZ1!#PXL7=# zt>_X+Iatq05Gxl%fc$!kqbR>5u_QA;?-pZ-CNtOzMLQ?oYms8yGx=wW0;9lWsn%zV za+4X`p7Yc&rZA>3Niozi)-X-}I7ei%p9hyzEn|r=EGyJ7!ZLa-V-4fvgDE1D^NV>I zVJx1>4ei{M&7(yoC&Y74-e1Ma$TPV@L4ESUcAd!+G{h%Mb?`F^Y}V>%V$>BZ0tLe@ zmgJnwf})8a*Mh^Q02~Cx1*xeiUPb*N;TQ%6hROFjrPx4mP_%OLzfJ|lvy<h!j2Pu7 z2X!U;ec)ga5}DvMA#j3Nhtmx~@#%6C<rc797F6wUx+5q$A!)KihtmxnzJAY6&k4yr zen`wag2K}!CQ8gunk>`dbVFRS!==Y#GjsPHM#f2#AM_|QE}G2JYszT5*{Zh-A|%Vf znw*ngoLaPK@~b{`M)%29z5bgE`)6@#f)a1hVvuvOr9O~Pi@*`Sda}t(OSY>Zg)=7? z&(vkT#hjCxH+kty4K`3@6@8g}d1f+W=w|a-Dr}64CdVuYWbB!|Zb1p-!pX`DmBPR_ zECuOa3nDgvh}j?_6hzDg5nz{qlfZ5e_X3Ex3?lY`h&&MS2t+&u5kEl0ugS9)>amqD zvWk8xnS6d>HRH<3-ize9!HJ9oq+r(O(nT4Jj4YGyEl%Q3;9#`>!~i0TG#D5d0M-~C AbpQYW delta 1943 zcmex;g6aGTCf?J$yj%<n3=HS|h0+&p<W+TJoU+;2Z6Y&E4P%zg<id7gZmw)828JSj z7=LoD7dJ$*Kwfn6Nl*F7abDbvV5O65y#yHTCii(s@+*MM1mP5h8ishq$$9Q_Oj%AK z^CrLaGGhd@Z27Y_7#NEDOI$$;L3lNYU|;}ifif9t7_yvTYy_R<K6ygC2ve2^*vOgQ ze4^MCc~5?*B{kV{t|Y`lSwXOc{3X6HgODwptl$&Ps6JW2$Cfc*a-)_MOASN3CMs8J za)O)I<Tzh<-f)-+HH=vvAk|Ve4DmWJ!IEeg3t3tZCRh>=W5J{aY8c`T;DQWU$smV= z1dU*Vc{VkSlO3}~BMnO6cBcrhW`xLNw;V+ti|r`#29r;DDonQX;+@RtFCm%$F&CN4 zN(Tk`<SU*clN}T|Ca3w!GG<Jks3Xij6VtmYH4O1^3u_p%Vvr4ju(Ps3eq^g<s$t5? z1;^q7T^?oxo13RZ6QYuVAuDfkK!7r%`s5RCa+4<oSTh!a^lp9;(8b7TIk`CK^yYwI z14hOVn;Sz47$+NsDQs2<`^3a3GFdpXKxhF21A``0(KH4Ih9Vmfv2gO#NKHoZ&HE$S znHU#LJ|6AEICZi}j2ENS<oK9Rq7e1B*vgAi^O7@QTGC<-8MQXgh~3P>IAgL;k|pEp z$!$q)jI$=6O_F8Q-~2j>o85N>$n=#Uq76iJf(TGbfY`kX#O(nQU{@?-U|_h#7oVP5 z5)QV=Gtbra7IR8UVbOGunuU{{v(y=bH)m%pX5^j;5}5-c`X@`~$T7~DY>}hDSiLzi z$Dc)D21tA!hybOaqKT7t6a-7|WME*Z5(>&s$}jOv%u7s9Ez$#771*{}rO<?p?Epym z!O3yuT2koRK{AIx#No+{%9XUS`3d0@kS8=*irhi!S5JOlZqK-4vwlS{iv-BjTiofX zC637@rHMI#DMe7nU8zx!1x1A>6Ij<RroxnBCI$uu1%)CtkSV(+3)V_7Uf--$`;v(b zWJ=NI$&w8UjO!*_HCQqJ++5la#%KYKBF>_GkcJ!(Q3@g;szI#TAYvUTfZ2)?^HTD2 zi@HG~3nw!-88fnPHg8(N<b*|a(M6Cdu+kGCRxXGDdGQuUQGQ8cNoIcDEyfT{X0XeP zc1%`mm15jI*``&2k$-Y*>oZ1!$u4cr6-oqQ$+m_OmOoRNz$H&q4O5mdIIa78aI(}g zmWWP%S0%=jB|f>ZU6Bz)$}yENPIhdOoqVBPfRh(y&}6j|`N?t}Vv}<xiB9g<keIx{ zi)FHZ2REbO=ID+lMkApjP$=ADNzTbED4GCrH8^Mrz#&jvkeZs}Rn!L(2Ia$B%sHuf zlexO2*g)}6v|_SWmjdIN$=+Q?jB=Cfx|03Aa4-l6PcZ5(pP|@Qb3;^mdfddg1umCG zH779L5tW>wG&y1d(+vTk>6{ZeXDCnMLt@?$6`vk4F=9s2<d_LeH>71JuuS3Dtlxcy zk#XYWkA1T2Ecpegc|{8+8}yno+D!KA_uss=*BT<J!nkm<Sid==+h)K1mz<iQ^jowD z<Ya6K5ain;aGbB2oG{ap?FvZYjLGw7>arHyVqjpHd~v1*8z|z6K2QEXGnp}DbMh<| zHpYdMI~D{oc2B;wpoDS3WdDUqVPG4UfOM|`5$i$3ED#Y2BEUHg>=JNt*aZ?h4<ass zh}|F}4@5iy5l=zHcM$P&^6`awY{iVMqMwQ;|6W+lxMFhoB6)6bf@1+Gn7Mi3q6|hx X=E+=3lKA5}80|kXfXE^Z1_lNIzd-jP diff --git a/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc b/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc index e00e5fe4b51234988fa9300a62849b129253b847..82aa642f4eee74e77dad389ccccdaa2651892031 100644 GIT binary patch delta 1941 zcmdmL-e}8rnwOW0fq{XcN=h{SlF~#z3C0r>)f1fAQ~6U^Qn}MuQaD;zq6AVnTNt7Q z!6Hm4TrDh7LMhxW3{k?VA}KuC8Vn3YbC?)XIah-WVqjp15=|A&(w&^hAte?i&cu+) zohrVX5h52Qkt&g8JeiSChJ%SARVq~?%WQHYhwx-wMxM#VjKWS?mJoHwWC|}#cM4w> zD+5C+bCwmdJcJFCncT-Ht|<pLTn?s>k%5UJl`mC1RU%bxH8Vs#BSWf0mOaQNY?X|a zjGFwLKQOwoS>NI<F3B&5Pb`T~%gjs6NoIwp29xXz3=AOMpLZ}ZFidAiVXS4%WT<7! zWT<7YVa#Hj+{ht2Sx`Wht(LQfxrTA_LJmnruF1POG<~^hm{OP|L7~jezyNa;jIQOb z;V$6?Tfo4;P{WkM3{nY`&5mJUD6&PCpDf5Jl2OZD!%(DE!U@xs#Rp>{Xk<+(tRO>c zxIrY$Xc%3?1e3J^IRM2mlLa{?nRQDzCjS$YVagJoe33)Ao&)B_6gH4?Ra^)^rLbo+ zF)$P{rEt`;!c;*lt>FNvtK~sh2GU!@2^Qr|;jHCL;i~0FljW=70Yz&S$XWa~yb!h! z14FGqjX(-R3bzzPtzZpfmMF+sliv#R)brG^r|^J%!CS+g!V6|`)Uc=Ufmxh2>?!<U z7BrBdPGe6IfQll8QVn~GAXt<&lc9z^jX9V>Q^?P)NPvNX;TBV2@k>yGzQt5l{PO?* z|NqmgMb^1LtYU7l51A~%rO93-4pPQA*?~)jk$ZA1mzwM?)~eK^{Nf@|JQeXXFfbGe zGB7Y`3KeNGFfdH+;xe`<G6d@>xy4=*pP84ET2Z7AQf2@WW%4e##pIoGi?u91IXC4N zTVV=_E*4^7U{IL6S6F;9C$}P(BLf4269WT7@uJBl+?K3g*ckYQCKqr^)<0n9o1#1+ za{<f5{L4aWm)O<r2#LbED+DhJX?3vQ<d(RjuDQHoVZ|2a4W2tvHu@iMzi8rf*}(U* zx?hLqgp^MI8SWQ(R6g)Aa0*N@o=~|!WMchgVf7V?7lpMiacJM+<?oN|jO>r=jGN)O zfOCb)LjDa*7Zr>z${2H8<~7++a*5Y^@<Q%rK@rggkB;C*-y1N-WMv*#6%JU|ngGsP zC7{p+r{oeQFa=5hlQ)WrO4TsdFs){Rh}ANefU+c*lf?l_1k8+CT$8zZ^|{f+#Tm1} zr5so(oXFyXmObn|gU%q16vjDhwJarqlOOWRGYU^;<dbI<nXJes8UspNU@xRF!E!Jo zLnT8cgC?_|CL1ErfYTs2Q56}15|S~9kOw6?o+3~wRs>2f;N({XDt(G9Knbyx&wv|L z1Q)4+R9j6xz^AY3#=yW(07_Sv7#J8D7@i18UXilgVRGHt_oB7$iP8&U@t1`XCUf&^ ztMdu;XLM#vD4kwCv3h#l#JbCTYIm@RO*ZA%<lto&U~BOBBCt7)KZcpLNDpNDaUpvl zZBTk=PAM$b<Sya?Il^SJgm8=nsKkf($O9zk2~x#bm=d3uTvD2tQzQWr^a2U;<R(_c zXXcfprWYmV6xmK*BW%MO%D}+TviYs>5oSe@nj*Lnnk+>i`-*&F+KW6VUlNZN1!;iT z4hoebXOOhZWG4wbMytt<5-O~b3=9lcHm{OkV`NmAyiu}T{1!`6etr%lNI~IM1U4aZ zvZYiLR~`cc11LQgzn^?m%3Ml^QF(*Q2L>HR<pVOGLDZ)hMgc}I#t#~k`K6Zu0D={c AssI20 delta 1370 zcmZp)-D=KvnwOW0fq{Wx)nuXcB!!855{ySCswdd9q;jV*rLecKMDeF^v@k>oq(DSC zTUeq5Q@C0fqJ&a~Q@FD=7#ND?FfpWZt_B&xz`zhCk}8s=Jy}pvcya?1yI7PM6GJL@ zs@Q5qh<ucIs(6+mOqz?4fr%kiGF3dwWU`>7lmjC}Dsz?@L?tqrWsb~)uv2(oW~T5~ zu`+;kT1@`WB<7OJoMiz~f=pI%F)&2Qf^Cz9X$0HOmnxPjo+`VV8KRnzAyquf7Un<> zMutkpN=8k-&0iQ@*_e`9COb-qh;3qIV3^L3!dT0a$xzFh$xzEy!;r-Y)x*KeUc)f? zqL2)0Ek_Mg&EySCqHG}d)pAZg$Dzs0S;IK_7l$-IR}EJQFT_d)h8o7n2RJ3`nNnD5 zS!x(lm?aq)7;3n{Bs<9AwcIs~DQsXqM3e(8%9Fxg%bUVc%ZDb*Tf+^qu?l1gUkwk8 zUCUp?pTdyBDaBALP{WYL53?Oc*E2BG@FT*ehP#F>h06wH2u}@L3OAUA>NvI(9;hg? z<JeMo!J;gg3^i<N%)tzre12|40t^fcx0nixUxHFX$StO_;&h0`AMDd-%>;qT{@jw2 z3%I2jIVSgTtFadeGB7Y`3Qj)HZ7g<+vFsLO$u0Jh_{_YN)QTcq1_p*Ay~$EM(kviV zldX6xxa=4h7@Qaw7>ea5*YH@HJmC|ZqBLE7qWTQQ8JR0sX69d%(!DICcbQMW!Tkdp z0|);U<q4S!SSIFQ7E)Uwcu`2}61&#qDBkADOngF<8TpJR_p)*_-C~?<$0y0pSi=NM zAGORStdkjqW%z0svp66g0FjgD3d>A3VB}ze$g+TBC(q&2XCy(fo<<7e9F|&^5_rti zFxD{FFk~{+u&ibU*}}k3%UZ*lB?wJw>@tiDH4Iro5GiD`h^It!@(uwRBNk{-fx=54 zL>Pbwc~Ho57lD#|5h&P-Kq>JSYgKAde(_{sK?816kSaA0VK&)MP+!J{fq@|z6t)W) z7#JEDo(M@^k+R%jdO-8Ckk8~rg4&b63Tkk0vI?*@czog6tSS`4%=+^G|Ns9dw~5*d zX@O#%Ii;{zle>rqWTxTd>!L9hpnP5=4&u9k2oI1d&cc-V#N?9F#GE1tkf0|>kS8~> zB0e*(BsIM#F{j9Ca<-TaYX}1aL+R!<Vn>)2L28QNMrg7Wf$S^tfoU)Dm^@J`S`?%K zV!H!K#t}p~O=gs~V>FxWAg#h0!N9<9baSRO8zZCq<N}#;K}f)WJYEFW8!`ExOcPfQ p0|Nsn0T;iR+$C!+EYGOC!sauBJfrd_Cq_O-FUAillW)r|0|59q6MFyv diff --git a/python/ur_simple_control/basics/basics.py b/python/ur_simple_control/basics/basics.py index 444ea98..47977c2 100644 --- a/python/ur_simple_control/basics/basics.py +++ b/python/ur_simple_control/basics/basics.py @@ -63,7 +63,7 @@ def moveJP(args, robot, q_desired): print("MoveJP done: convergence achieved, reached destionation!") -def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data): +def jointTrajFollowingPDControlLoop(stop_at_final : bool, robot: RobotManager, reference, i, past_data): breakFlag = False save_past_dict = {} log_item = {} @@ -73,10 +73,21 @@ def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data # which is pretty much true t = i * robot.dt # take the future (next) one + # TODO: turn this into interpolation t_index = int(np.ceil(t / reference['dt'])) + # TODO: move to a different function later + if t_index >= len(reference['qs']): + t_index = len(reference['qs']) - 1 + if stop_at_final: + reference['vs'][t_index] = np.zeros(len(reference['vs'][t_index])) # TODO: check if that's really the last - if t_index == len(reference['qs']) - 1: + #if t_index == len(reference['qs']) - 1: + # breakFlag = True + + # TODO NOTE TODO TODO: remove/change + if (t_index == len(reference['qs']) - 1) and \ + (np.linalg.norm(q - reference['qs'][-1]) < 1e-2): breakFlag = True error_q = reference['qs'][t_index] - q @@ -91,6 +102,8 @@ def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data log_item['error_q'] = error_q log_item['error_v'] = error_v + log_item['q'] = q + log_item['v'] = v log_item['reference_q'] = reference['qs'][t_index] log_item['reference_v'] = reference['vs'][t_index] @@ -98,10 +111,12 @@ def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data def jointTrajFollowingPD(args, robot, reference): # we're not using any past data or logging, hence the empty arguments - controlLoop = partial(jointTrajFollowingPDControlLoop, robot, reference) + controlLoop = partial(jointTrajFollowingPDControlLoop, args.stop_at_final, robot, reference) log_item = { 'error_q' : np.zeros(robot.model.nq), 'error_v' : np.zeros(robot.model.nv), + 'q' : np.zeros(robot.model.nq), + 'v' : np.zeros(robot.model.nv), 'reference_q' : np.zeros(robot.model.nq), 'reference_v' : np.zeros(robot.model.nv) } diff --git a/python/ur_simple_control/managers.py b/python/ur_simple_control/managers.py index 340d36e..3e4cbf9 100644 --- a/python/ur_simple_control/managers.py +++ b/python/ur_simple_control/managers.py @@ -452,7 +452,8 @@ class RobotManager: self.dt = 1 / self.update_rate # you better not give me crazy stuff # and i'm not clipping it, you're fixing it - assert args.acceleration <= 1.7 and args.acceleration > 0.0 + self.MAX_ACCELERATION = 1.7 + assert args.acceleration <= self.MAX_ACCELERATION and args.acceleration > 0.0 # this is the number passed to speedj self.acceleration = args.acceleration # NOTE: this is evil and everything only works if it's set to 1 @@ -816,13 +817,13 @@ class RobotManager: else: # this one takes all 8 elements of qd since we're still in pinocchio # this is ugly, todo: fix - if len(qd) == 6: - qd = qd_cmd.reshape((6,)) - qd = list(qd) - qd.append(0.0) - qd.append(0.0) - qd = np.array(qd) - self.v_q = qd + qd = qd[:6] + qd = qd_cmd.reshape((6,)) + qd = list(qd) + qd.append(0.0) + qd.append(0.0) + qd = np.array(qd) + self.v_q = qd self.q = pin.integrate(self.model, self.q, qd * self.dt) def openGripper(self): diff --git a/python/ur_simple_control/optimal_control/optimal_control.py b/python/ur_simple_control/optimal_control/optimal_control.py index c8daeb8..e3fb969 100644 --- a/python/ur_simple_control/optimal_control/optimal_control.py +++ b/python/ur_simple_control/optimal_control/optimal_control.py @@ -4,6 +4,9 @@ import crocoddyl from ur_simple_control.managers import getMinimalArgParser, ControlLoopManager, RobotManager import argparse from ur_simple_control.basics.basics import jointTrajFollowingPD +from ur_simple_control.visualize.visualize import plotFromDict +import example_robot_data +import time def get_OCP_args(parser : argparse.ArgumentParser): @@ -11,18 +14,31 @@ def get_OCP_args(parser : argparse.ArgumentParser): help="time length between state-control-pair decision variables") parser.add_argument("--n-knots", type=int, default=100, \ help="number of state-control-pair decision variables") + parser.add_argument("--stop-at-final", type=int, default=True, \ + help="the trajectory generated by the OCP will be followed. it might not have finally velocity 0. \ + if this argument is set to true, the final velocity will be set to 0 (there will be overshoot).") return parser # TODO: use fddp and incorporate torque (i.e. velocity constraints) # --> those will correspond to max_qd and acceleration attributes in robotmanager def IKOCP(args, robot : RobotManager, goal : pin.SE3): + # create torque bounds which correspond to percentage + # of maximum allowed acceleration + # NOTE: idk if this makes any sense, but let's go for it + #print(robot.model.effortLimit) + #exit() + #robot.model.effortLimit = robot.model.effortLimit * (args.acceleration / robot.MAX_ACCELERATION) + #robot.model.effortLimit = robot.model.effortLimit * 0.01 + #robot.data = robot.model.createData() # starting state x0 = np.concatenate([robot.getQ(), robot.getQd()]) state = crocoddyl.StateMultibody(robot.model) # command input IS torque actuation = crocoddyl.ActuationModelFull(state) - # we will be summing 3 different costs + + # we will be summing 4 different costs + # first 3 are for tracking, state and control regulation runningCostModel = crocoddyl.CostModelSum(state) terminalCostModel = crocoddyl.CostModelSum(state) uResidual = crocoddyl.ResidualModelControl(state, state.nv) @@ -43,24 +59,56 @@ def IKOCP(args, robot : RobotManager, goal : pin.SE3): # cost 3) x residual (overall amount of movement) xResidual = crocoddyl.ResidualModelState(state, x0, state.nv) # put these costs into the running costs - runningCostModel.addCost("gripperPose", goalTrackingCost, 1) - runningCostModel.addCost("xReg", xRegCost, 1e-1) - runningCostModel.addCost("uReg", uRegCost, 1e-1) + runningCostModel.addCost("gripperPose", goalTrackingCost, 1e2) + runningCostModel.addCost("xReg", xRegCost, 1e-3) + runningCostModel.addCost("uReg", uRegCost, 1e-3) # and add the terminal cost, which is the distance to the goal # NOTE: shouldn't there be a final velocity = 0 here? - terminalCostModel.addCost("gripperPose", goalTrackingCost, 1e3) + terminalCostModel.addCost("gripperPose", goalTrackingCost, 1e2) + terminalCostModel.addCost("uReg", uRegCost, 1e3) + + # the 4th cost is for defining bounds via costs + # NOTE: could have gotten the same info from state.lb and state.ub. + # the first state is unlimited there idk what that means really, + # but the arm's base isn't doing a full rotation anyway, let alone 2 or more + xlb = np.concatenate([ + robot.model.lowerPositionLimit, + -1 * robot.model.velocityLimit]) + xub = np.concatenate([ + robot.model.upperPositionLimit, + robot.model.velocityLimit]) + bounds = crocoddyl.ActivationBounds(xlb, xub, 1.0) + xLimitResidual = crocoddyl.ResidualModelState(state, x0, actuation.nu) + xLimitActivation = crocoddyl.ActivationModelQuadraticBarrier(bounds) + limitCost = crocoddyl.CostModelResidual(state, xLimitActivation, xLimitResidual) + runningCostModel.addCost("limitCost", limitCost, 1e3) + terminalCostModel.addCost("limitCost", limitCost, 1e3) + # NOTE: i have no idea how to incorporate this into anything + # we need to add state constraints for things to make sense + # NOTE!!!: there are no hard inequality constraints on states + # in what's implemented in crocoddyl. + # INSTEAD, you define quadratic barier-type costs for that. + # there are box constraints on control input though, + # but i'm pretty sure i'm not using those correctly. + #x_constraint_lower = x0.copy() + #x_constraint_upper = x0.copy() + #x_constraint_lower[:robot.model.nq] = -2 * np.pi + #x_constraint_upper[:robot.model.nq] = 2 * np.pi + #x_constraint_lower[robot.model.nq:] = -1 * robot.max_qd + #x_constraint_upper[robot.model.nq:] = robot.max_qd + #state_constraint = crocoddyl.ConstraintModelResidual(state, xResidual, x_constraint_lower, x_constraint_upper) # Next, we need to create an action model for running and terminal knots. The # forward dynamics (computed using ABA) are implemented # inside DifferentialActionModelFreeFwdDynamics. runningModel = crocoddyl.IntegratedActionModelEuler( - crocoddyl.DifferentialActionModelFreeFwdDynamics( + crocoddyl.DifferentialActionModelFreeInvDynamics( state, actuation, runningCostModel ), args.ocp_dt, ) terminalModel = crocoddyl.IntegratedActionModelEuler( - crocoddyl.DifferentialActionModelFreeFwdDynamics( + crocoddyl.DifferentialActionModelFreeInvDynamics( state, actuation, terminalCostModel ), 0.0, @@ -69,25 +117,49 @@ def IKOCP(args, robot : RobotManager, goal : pin.SE3): # now we define the problem problem = crocoddyl.ShootingProblem(x0, [runningModel] * args.n_knots, terminalModel) # and the solver - solver = crocoddyl.SolverFDDP(problem) + solver = crocoddyl.SolverBoxFDDP(problem) + #solver = crocoddyl.SolverIpopt(problem) + # TODO: remove / place elsewhere once it works + solver.setCallbacks([crocoddyl.CallbackVerbose(), crocoddyl.CallbackLogger()]) # run solve - solver.solve() + # NOTE: there are some possible parameters here that I'm not using + xs = [x0] * (solver.problem.T + 1) + us = solver.problem.quasiStatic([x0] * solver.problem.T) + solver.solve(xs, us, 5000, False, 1e-9) + #solver.solve() # get reference (state trajectory) # we aren't using controls because we only have velocity inputs xs = np.array(solver.xs) qs = xs[:, :robot.model.nq] vs = xs[:, robot.model.nq:] reference = {'qs' : qs, 'vs' : vs, 'dt' : args.ocp_dt} - return reference + return reference, solver if __name__ == "__main__": parser = getMinimalArgParser() parser = get_OCP_args(parser) args = parser.parse_args() + ex_robot = example_robot_data.load("ur5_gripper") robot = RobotManager(args) + # TODO: remove once things work + robot.max_qd = 3.14 + robot.q = pin.randomConfiguration(robot.model) goal = pin.SE3.Random() goal.translation = np.random.random(3) * 0.4 print("set goal:", goal) - reference = IKOCP(args, robot, goal) + reference, solver = IKOCP(args, robot, goal) + # we only work within -pi - pi (or 0-2pi idk anymore) + #reference['qs'] = reference['qs'] % (2*np.pi) - np.pi + log = solver.getCallbacks()[1] + crocoddyl.plotOCSolution(log.xs, log.us, figIndex=1, show=True) + crocoddyl.plotConvergence(log.costs, log.pregs, log.dregs, log.grads, log.stops, log.steps, figIndex=2) jointTrajFollowingPD(args, robot, reference) + reference.pop('dt') + plotFromDict(reference, args.n_knots + 1, args) print("achieved result", robot.getT_w_e()) + display = crocoddyl.MeshcatDisplay(ex_robot) + display.rate = -1 + display.freq = 1 + while True: + display.displayFromSolver(solver) + time.sleep(1.0) -- GitLab