Skip to content

Commit 70533bf

Browse files
authored
dc motor (#1293)
1 parent 27f12f9 commit 70533bf

5 files changed

Lines changed: 1550 additions & 23 deletions

File tree

mujoco_warp/_src/derivative.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from mujoco_warp._src import util_misc
1919
from mujoco_warp._src.support import next_act
20+
from mujoco_warp._src.types import MJ_MINVAL
2021
from mujoco_warp._src.types import BiasType
2122
from mujoco_warp._src.types import Data
2223
from mujoco_warp._src.types import DisableBit
@@ -66,6 +67,28 @@ def _qderiv_actuator_passive_vel(
6667

6768
if actuator_biastype[actid] == BiasType.AFFINE:
6869
bias = actuator_biasprm[actuator_biasprm_id, actid][2]
70+
elif actuator_biastype[actid] == BiasType.DCMOTOR:
71+
dynprm = actuator_dynprm[worldid % actuator_dynprm.shape[0], actid]
72+
te = dynprm[0]
73+
if te <= 0.0:
74+
gainprm = actuator_gainprm[actuator_gainprm_id, actid]
75+
R = gainprm[0]
76+
K = gainprm[1]
77+
78+
slots = util_misc.dcmotor_slots(dynprm, gainprm)
79+
slot_Ta = slots[2]
80+
81+
if slot_Ta >= 0:
82+
adr = actuator_actadr[actid] + slot_Ta
83+
T = act_in[worldid, adr]
84+
alpha = gainprm[2]
85+
T0 = gainprm[3]
86+
Ta = dynprm[4]
87+
R *= 1.0 + alpha * (T + Ta - T0)
88+
89+
bias = -K * K / wp.max(MJ_MINVAL, R)
90+
else:
91+
bias = 0.0
6992
else:
7093
bias = 0.0
7194

mujoco_warp/_src/forward.py

Lines changed: 251 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)