@@ -137,10 +137,13 @@ def _next_activation(
137137 actuator_actnum : wp .array [int ],
138138 actuator_actlimited : wp .array [bool ],
139139 actuator_dynprm : wp .array2d [vec10f ],
140+ actuator_gainprm : wp .array2d [vec10f ],
141+ actuator_biasprm : wp .array2d [vec10f ],
140142 actuator_actrange : wp .array2d [wp .vec2 ],
141143 # Data in:
142144 act_in : wp .array2d [float ],
143145 act_dot_in : wp .array2d [float ],
146+ actuator_velocity_in : wp .array2d [float ],
144147 # In:
145148 act_dot_scale : float ,
146149 limit : bool ,
@@ -151,20 +154,65 @@ def _next_activation(
151154 opt_timestep_id = worldid % opt_timestep .shape [0 ]
152155 actuator_dynprm_id = worldid % actuator_dynprm .shape [0 ]
153156 actuator_actrange_id = worldid % actuator_actrange .shape [0 ]
157+ actuator_gainprm_id = worldid % actuator_gainprm .shape [0 ]
158+ actuator_biasprm_id = worldid % actuator_biasprm .shape [0 ]
159+
154160 actadr = actuator_actadr [uid ]
155161 actnum = actuator_actnum [uid ]
156- for j in range (actadr , actadr + actnum ):
157- act = next_act (
158- opt_timestep [opt_timestep_id ],
159- actuator_dyntype [uid ],
160- actuator_dynprm [actuator_dynprm_id , uid ],
161- actuator_actrange [actuator_actrange_id , uid ],
162- act_in [worldid , j ],
163- act_dot_in [worldid , j ],
164- act_dot_scale ,
165- limit and actuator_actlimited [uid ],
166- )
167- act_out [worldid , j ] = act
162+ dyntype = actuator_dyntype [uid ]
163+
164+ if dyntype == DynType .DCMOTOR :
165+ dynprm = actuator_dynprm [actuator_dynprm_id , uid ]
166+ gainprm = actuator_gainprm [actuator_gainprm_id , uid ]
167+ biasprm = actuator_biasprm [actuator_biasprm_id , uid ]
168+ slots = util_misc .dcmotor_slots (dynprm , gainprm )
169+
170+ for j in range (actadr , actadr + actnum ):
171+ offset = j - actadr
172+ act = act_in [worldid , j ]
173+ act_dot = act_dot_in [worldid , j ]
174+
175+ if offset == slots [4 ]: # current
176+ R = gainprm [0 ]
177+ te = wp .max (MJ_MINVAL , dynprm [0 ])
178+ act = act + act_dot * te * (1.0 - wp .exp (- opt_timestep [opt_timestep_id ] / te ))
179+ elif offset == slots [3 ]: # bristle
180+ F_C = biasprm [3 ]
181+ F_S = biasprm [4 ]
182+ v_S = biasprm [5 ]
183+ sigma0 = dynprm [5 ]
184+ velocity = actuator_velocity_in [worldid , uid ]
185+ g = util_misc .lugre_stribeck (velocity , F_C , F_S , v_S )
186+
187+ a = - sigma0 * wp .abs (velocity ) / wp .max (MJ_MINVAL , g )
188+ h = opt_timestep [opt_timestep_id ]
189+ exp_ah = wp .exp (a * h )
190+ int_h = h
191+ if wp .abs (a ) > MJ_MINVAL :
192+ int_h = (exp_ah - 1.0 ) / a
193+ act = exp_ah * act + int_h * velocity
194+ elif offset == slots [1 ]: # integral
195+ act = act + act_dot * opt_timestep [opt_timestep_id ]
196+ Imax = dynprm [8 ]
197+ if Imax > 0.0 :
198+ act = wp .clamp (act , - Imax , Imax )
199+ else : # temperature and slew
200+ act = act + act_dot * opt_timestep [opt_timestep_id ]
201+
202+ act_out [worldid , j ] = act
203+ else :
204+ for j in range (actadr , actadr + actnum ):
205+ act = next_act (
206+ opt_timestep [opt_timestep_id ],
207+ dyntype ,
208+ actuator_dynprm [actuator_dynprm_id , uid ],
209+ actuator_actrange [actuator_actrange_id , uid ],
210+ act_in [worldid , j ],
211+ act_dot_in [worldid , j ],
212+ act_dot_scale ,
213+ limit and actuator_actlimited [uid ],
214+ )
215+ act_out [worldid , j ] = act
168216
169217
170218@wp .kernel
@@ -224,9 +272,12 @@ def _advance(m: Model, d: Data, qacc: wp.array, qvel: Optional[wp.array] = None)
224272 m .actuator_actnum ,
225273 m .actuator_actlimited ,
226274 m .actuator_dynprm ,
275+ m .actuator_gainprm ,
276+ m .actuator_biasprm ,
227277 m .actuator_actrange ,
228278 d .act ,
229279 d .act_dot ,
280+ d .actuator_velocity ,
230281 1.0 ,
231282 True ,
232283 ],
@@ -416,9 +467,12 @@ def _rk_perturb_state(
416467 m .actuator_actnum ,
417468 m .actuator_actlimited ,
418469 m .actuator_dynprm ,
470+ m .actuator_gainprm ,
471+ m .actuator_biasprm ,
419472 m .actuator_actrange ,
420473 act_t0 ,
421474 d .act_dot ,
475+ d .actuator_velocity ,
422476 scale ,
423477 False ,
424478 ],
@@ -698,6 +752,96 @@ def _actuator_force(
698752 dynprm = actuator_dynprm [worldid % actuator_dynprm .shape [0 ], uid ]
699753 act = act_in [worldid , act_last ]
700754 act_dot = util_misc .muscle_dynamics (ctrl , act , dynprm )
755+ elif dyntype == DynType .DCMOTOR :
756+ gainprm = actuator_gainprm [worldid % actuator_gainprm .shape [0 ], uid ]
757+ slots = util_misc .dcmotor_slots (dynprm , gainprm )
758+ adr = act_first
759+
760+ act_dot = 0.0
761+
762+ # slew rate
763+ if slots [0 ] >= 0 :
764+ u_prev = act_in [worldid , adr ]
765+ slew_s = dynprm [7 ]
766+ slew = slew_s * opt_timestep [worldid % opt_timestep .shape [0 ]]
767+ u_eff = wp .clamp (ctrl , u_prev - slew , u_prev + slew )
768+ act_dot = (u_eff - u_prev ) / opt_timestep [worldid % opt_timestep .shape [0 ]]
769+ act_dot_out [worldid , adr ] = act_dot
770+ ctrl = u_eff
771+ adr += 1
772+
773+ # integral
774+ if slots [1 ] >= 0 :
775+ x_I = act_in [worldid , adr ]
776+ input_mode = int (gainprm [8 ])
777+ Imax = dynprm [8 ]
778+ act_dot = ctrl
779+ if input_mode == 1 :
780+ act_dot = ctrl - actuator_length_in [worldid , uid ]
781+
782+ if Imax > 0.0 :
783+ if x_I >= Imax :
784+ act_dot = wp .min (act_dot , 0.0 )
785+ elif x_I <= - Imax :
786+ act_dot = wp .max (act_dot , 0.0 )
787+
788+ act_dot_out [worldid , adr ] = act_dot
789+ adr += 1
790+
791+ # voltage
792+ V = util_misc .dcmotor_voltage (
793+ ctrl ,
794+ actuator_length_in [worldid , uid ],
795+ actuator_velocity_in [worldid , uid ],
796+ x_I ,
797+ gainprm ,
798+ )
799+
800+ # temperature
801+ R = gainprm [0 ]
802+ K = gainprm [1 ]
803+ te = wp .max (MJ_MINVAL , dynprm [0 ])
804+
805+ if slots [2 ] >= 0 :
806+ RT = dynprm [2 ]
807+ C = dynprm [3 ]
808+ Ta = dynprm [4 ]
809+ alpha = gainprm [2 ]
810+ T0 = gainprm [3 ]
811+ T = act_in [worldid , adr ]
812+ R_eff = R * (1.0 + alpha * (T + Ta - T0 ))
813+
814+ current = (V - K * actuator_velocity_in [worldid , uid ]) / R_eff
815+ if slots [4 ] >= 0 :
816+ current = act_in [worldid , act_last ]
817+
818+ act_dot = (R_eff * current * current - T / RT ) / C
819+ act_dot_out [worldid , adr ] = act_dot
820+ adr += 1
821+ R = R_eff
822+
823+ # bristle
824+ if slots [3 ] >= 0 :
825+ sigma0 = dynprm [5 ]
826+ biasprm = actuator_biasprm [worldid % actuator_biasprm .shape [0 ], uid ]
827+ F_C = biasprm [3 ]
828+ F_S = biasprm [4 ]
829+ v_S = biasprm [5 ]
830+ z = act_in [worldid , adr ]
831+ g = util_misc .lugre_stribeck (actuator_velocity_in [worldid , uid ], F_C , F_S , v_S )
832+ a = - sigma0 * wp .abs (actuator_velocity_in [worldid , uid ]) / wp .max (MJ_MINVAL , g )
833+ act_dot = a * z + actuator_velocity_in [worldid , uid ]
834+ act_dot_out [worldid , adr ] = act_dot
835+ adr += 1
836+
837+ # current
838+ if slots [4 ] >= 0 :
839+ dimax = dynprm [1 ]
840+ act_dot = (V / R - K / R * actuator_velocity_in [worldid , uid ] - act_in [worldid , act_last ]) / te
841+ if dimax > 0.0 :
842+ act_dot = wp .clamp (act_dot , - dimax , dimax )
843+ act_dot_out [worldid , act_last ] = act_dot
844+
701845 elif dyntype == DynType .USER :
702846 act_dot = 0.0 # set by act_dyn_callback
703847 else : # DynType.NONE
@@ -706,19 +850,54 @@ def _actuator_force(
706850 act_dot_out [worldid , act_last ] = act_dot
707851
708852 if actuator_actearly [uid ]:
709- if dyntype == DynType .INTEGRATOR or dyntype == DynType .NONE :
853+ if dyntype == DynType .INTEGRATOR or dyntype == DynType .NONE or dyntype == DynType . DCMOTOR :
710854 act = act_in [worldid , act_last ]
711855
712- ctrl_act = next_act (
713- opt_timestep [worldid % opt_timestep .shape [0 ]],
714- dyntype ,
715- dynprm ,
716- actuator_actrange [worldid % actuator_actrange .shape [0 ], uid ],
717- act ,
718- act_dot ,
719- 1.0 ,
720- actuator_actlimited [uid ],
721- )
856+ if dyntype == DynType .DCMOTOR :
857+ gainprm = actuator_gainprm [worldid % actuator_gainprm .shape [0 ], uid ]
858+ slots = util_misc .dcmotor_slots (dynprm , gainprm )
859+ offset = actuator_actnum [uid ] - 1
860+
861+ if offset == slots [4 ]: # current
862+ te = wp .max (MJ_MINVAL , dynprm [0 ])
863+ ctrl_act = act + act_dot * te * (1.0 - wp .exp (- opt_timestep [worldid % opt_timestep .shape [0 ]] / te ))
864+ elif offset == slots [3 ]: # bristle
865+ sigma0 = dynprm [5 ]
866+ biasprm = actuator_biasprm [worldid % actuator_biasprm .shape [0 ], uid ]
867+ F_C = biasprm [3 ]
868+ F_S = biasprm [4 ]
869+ v_S = biasprm [5 ]
870+ velocity = actuator_velocity_in [worldid , uid ]
871+ g = util_misc .lugre_stribeck (velocity , F_C , F_S , v_S )
872+ a = - sigma0 * wp .abs (velocity ) / wp .max (MJ_MINVAL , g )
873+ h = opt_timestep [worldid % opt_timestep .shape [0 ]]
874+ exp_ah = wp .exp (a * h )
875+ int_h = h
876+ if wp .abs (a ) > MJ_MINVAL :
877+ int_h = (exp_ah - 1.0 ) / a
878+ ctrl_act = exp_ah * act + int_h * velocity
879+ elif offset == slots [1 ]: # integral
880+ ctrl_act = act + act_dot * opt_timestep [worldid % opt_timestep .shape [0 ]]
881+ Imax = dynprm [8 ]
882+ if Imax > 0.0 :
883+ ctrl_act = wp .clamp (ctrl_act , - Imax , Imax )
884+ else : # temperature or slew or default
885+ ctrl_act = act + act_dot * opt_timestep [worldid % opt_timestep .shape [0 ]]
886+
887+ if actuator_actlimited [uid ]:
888+ actrange = actuator_actrange [worldid % actuator_actrange .shape [0 ], uid ]
889+ ctrl_act = wp .clamp (ctrl_act , actrange [0 ], actrange [1 ])
890+ else :
891+ ctrl_act = next_act (
892+ opt_timestep [worldid % opt_timestep .shape [0 ]],
893+ dyntype ,
894+ dynprm ,
895+ actuator_actrange [worldid % actuator_actrange .shape [0 ], uid ],
896+ act ,
897+ act_dot ,
898+ 1.0 ,
899+ actuator_actlimited [uid ],
900+ )
722901 else :
723902 ctrl_act = act_in [worldid , act_last ]
724903
@@ -738,6 +917,32 @@ def _actuator_force(
738917 acc0 = actuator_acc0 [worldid % actuator_acc0 .shape [0 ], uid ]
739918 lengthrange = actuator_lengthrange [worldid % actuator_lengthrange .shape [0 ], uid ]
740919 gain = util_misc .muscle_gain (length , velocity , lengthrange , acc0 , gainprm )
920+ elif gaintype == GainType .DCMOTOR :
921+ R = gainprm [0 ]
922+ K = gainprm [1 ]
923+ te = dynprm [0 ]
924+
925+ slots = util_misc .dcmotor_slots (dynprm , gainprm )
926+ adr = act_first
927+
928+ if slots [2 ] >= 0 :
929+ T = act_in [worldid , adr + slots [2 ]]
930+ alpha = gainprm [2 ]
931+ T0 = gainprm [3 ]
932+ Ta = dynprm [4 ]
933+ R *= 1.0 + alpha * (T + Ta - T0 )
934+
935+ gain = K if te > 0.0 else K / wp .max (MJ_MINVAL , R )
936+
937+ if te <= 0.0 :
938+ input_mode = int (gainprm [8 ])
939+ if input_mode > 0 :
940+ x_I = 0.0
941+ if slots [1 ] >= 0 :
942+ x_I = act_in [worldid , adr + slots [1 ]]
943+ ctrl_act = util_misc .dcmotor_voltage (ctrl , length , velocity , x_I , gainprm )
944+ else :
945+ ctrl_act = ctrl
741946 # GainType.USER: gain stays 0, modified by act_gain_callback
742947
743948 # bias
@@ -751,13 +956,36 @@ def _actuator_force(
751956 acc0 = actuator_acc0 [worldid % actuator_acc0 .shape [0 ], uid ]
752957 lengthrange = actuator_lengthrange [worldid % actuator_lengthrange .shape [0 ], uid ]
753958 bias = util_misc .muscle_bias (length , lengthrange , acc0 , biasprm )
959+ elif biastype == BiasType .DCMOTOR :
960+ if dynprm [0 ] <= 0.0 :
961+ K = gainprm [1 ]
962+ bias -= gain * K * velocity
754963
755964 force = gain * ctrl_act + bias
756965
757966 if actuator_forcelimited [uid ]:
758967 forcerange = actuator_forcerange [worldid % actuator_forcerange .shape [0 ], uid ]
759968 force = wp .clamp (force , forcerange [0 ], forcerange [1 ])
760969
970+ # add DC motor mechanical forces (not subject to current limits)
971+ if biastype == BiasType .DCMOTOR :
972+ # cogging torque
973+ A = biasprm [0 ]
974+ if A != 0.0 :
975+ Np = biasprm [1 ]
976+ phi = biasprm [2 ]
977+ force += A * wp .sin (Np * length + phi )
978+
979+ # LuGre friction
980+ sigma0 = dynprm [5 ]
981+ if sigma0 > 0.0 :
982+ sigma1 = dynprm [6 ]
983+ slots = util_misc .dcmotor_slots (dynprm , gainprm )
984+ adr = act_first + slots [3 ] # slots[3] is bristle
985+ z = act_in [worldid , adr ]
986+ z_dot = act_dot_out [worldid , adr ]
987+ force -= sigma0 * z + sigma1 * z_dot
988+
761989 actuator_force_out [worldid , uid ] = force
762990
763991
0 commit comments