Switch to unified view

a b/myosuite/envs/myo/fatigue.py
1
from myosuite.utils import gym
2
import mujoco
3
import numpy as np
4
5
class CumulativeFatigue():
6
    # 3CC-r model, adapted from https://dl.acm.org/doi/pdf/10.1145/3313831.3376701 for muscles 
7
    # based on the implementation from Aleksi Ikkala and Florian Fischer https://github.com/aikkala/user-in-the-box/blob/main/uitb/bm_models/effort_models.py
8
    def __init__(self, mj_model, frame_skip=1, seed=None):
9
        self._r = 10 * 15 # Recovery time multiplier i.e. how many times more than during rest intervals https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6092960/ (factor 10 to compensate for 0.1 below)
10
        self._F = 0.00912  # Fatigue coefficients (default parameter was identified for elbow torque https://pubmed.ncbi.nlm.nih.gov/22579269/)
11
        self._R = 0.1 * 0.00094  # Recivery coefficients (default parameter was identified for elbow torque https://pubmed.ncbi.nlm.nih.gov/22579269/; factor 0.1 to get an approx. 1% R/F ratio)
12
        # self.na = mj_model.na
13
        self._dt = mj_model.opt.timestep * frame_skip # dt might be different from model dt because it might include a frame skip
14
        muscle_act_ind = mj_model.actuator_dyntype == mujoco.mjtDyn.mjDYN_MUSCLE
15
        self.na = sum(muscle_act_ind)  #WARNING: here, self.na denotes the number of muscle actuators only!
16
        self._tauact = np.array([mj_model.actuator_dynprm[i][0] for i in range(len(muscle_act_ind)) if muscle_act_ind[i]])
17
        self._taudeact = np.array([mj_model.actuator_dynprm[i][1] for i in range(len(muscle_act_ind)) if muscle_act_ind[i]])
18
        self._MA = np.zeros((self.na,))  # Muscle Active
19
        self._MR = np.ones((self.na,))   # Muscle Resting
20
        self._MF = np.zeros((self.na,))  # Muscle Fatigue
21
        self.TL  = np.zeros((self.na,))  # Target Load
22
23
        self.seed(seed)  # Create own Random Number Generator (RNG) used when reset is called with fatigue_reset_random=True
24
        ### NOTE: the seed from CumulativeFatigue is not synchronised with the seed used for the rest of MujocoEnv!
25
26
    def set_FatigueCoefficient(self, F):
27
        # Set Fatigue coefficients
28
        self._F = F
29
    
30
    def set_RecoveryCoefficient(self, R):
31
        # Set Recovery coefficients
32
        self._R = R
33
    
34
    def set_RecoveryMultiplier(self, r):
35
        # Set Recovery time multiplier
36
        self._r = r
37
        
38
    def compute_act(self, act):
39
        # Get target load (actual activation, which might be reached only with some "effort", 
40
        # depending on how many muscles can be activated (fast enough) and how many are in fatigue state)
41
        self.TL = act.copy()
42
43
        # Calculate effective time constant tau (see https://mujoco.readthedocs.io/en/stable/modeling.html#muscles)
44
        self._LD = 1/self._tauact*(0.5 + 1.5*self._MA)
45
        self._LR = (0.5 + 1.5*self._MA)/self._taudeact
46
        ## TODO: account for smooth transition phase of length tausmooth (tausmooth = mj_model.actuator_dynprm[i][2])?
47
48
        # Calculate C(t) -- transfer rate between MR and MA
49
        C = np.zeros_like(self._MA)
50
        idxs = (self._MA < self.TL) & (self._MR > (self.TL - self._MA))
51
        C[idxs] = self._LD[idxs] * (self.TL[idxs] - self._MA[idxs])
52
        idxs = (self._MA < self.TL) & (self._MR <= (self.TL - self._MA))
53
        C[idxs] = self._LD[idxs] * self._MR[idxs]
54
        idxs = self._MA >= self.TL
55
        C[idxs] = self._LR[idxs] * (self.TL[idxs] - self._MA[idxs])
56
57
        # Calculate rR
58
        rR = np.zeros_like(self._MA)
59
        idxs = self._MA >= self.TL
60
        rR[idxs] = self._r*self._R
61
        idxs = self._MA < self.TL
62
        rR[idxs] = self._R
63
64
        # Clip C(t) if needed, to ensure that MA, MR, and MF remain between 0 and 1
65
        C = np.clip(C, np.maximum(-self._MA/self._dt + self._F*self._MA, (self._MR - 1)/self._dt + rR*self._MF),
66
                    np.minimum((1 - self._MA)/self._dt + self._F*self._MA, self._MR/self._dt + rR*self._MF))
67
68
        # Update MA, MR, MF
69
        dMA = (C - self._F*self._MA)*self._dt
70
        dMR = (-C + rR*self._MF)*self._dt
71
        dMF = (self._F*self._MA - rR*self._MF)*self._dt
72
        self._MA += dMA
73
        self._MR += dMR
74
        self._MF += dMF
75
76
        return self._MA, self._MR, self._MF
77
78
    def get_effort(self):
79
        # Calculate effort
80
        return np.linalg.norm(self._MA - self.TL)
81
82
    def reset(self, fatigue_reset_vec=None, fatigue_reset_random=False):
83
        if fatigue_reset_random:
84
            assert fatigue_reset_vec is None, "Cannot use 'fatigue_reset_vec' if fatigue_reset_random=False."
85
            non_fatigued_muscles = self.np_random.random(size=(self.na,))
86
            active_percentage = self.np_random.random(size=(self.na,))
87
            self._MA = non_fatigued_muscles * active_percentage         # Muscle Active
88
            self._MR = non_fatigued_muscles * (1 - active_percentage)   # Muscle Resting
89
            self._MF = 1 - non_fatigued_muscles                         # Muscle Fatigue
90
        else:
91
            if fatigue_reset_vec is not None:
92
                assert len(fatigue_reset_vec) == self.na, f"Invalid length of initial/reset fatigue vector (expected {self.na}, but obtained {len(fatigue_reset_vec)})."
93
                self._MF = fatigue_reset_vec     # Muscle Fatigue
94
                self._MR = 1 - fatigue_reset_vec # Muscle Resting
95
                self._MA = np.zeros((self.na,))  # Muscle Active
96
            else:
97
                self._MA = np.zeros((self.na,))  # Muscle Active
98
                self._MR = np.ones((self.na,))   # Muscle Resting
99
                self._MF = np.zeros((self.na,))  # Muscle Fatigue
100
101
    def seed(self, seed=None):
102
        """
103
        Set random number seed
104
        """
105
        self.input_seed = seed
106
        self.np_random, seed = gym.utils.seeding.np_random(seed)
107
        return [seed]
108
109
    @property
110
    def MF(self):
111
        return self._MF
112
    
113
    @property
114
    def MR(self):
115
        return self._MR
116
    
117
    @property
118
    def MA(self):
119
        return self._MA
120
    
121
    @property
122
    def F(self):
123
        return self._F
124
    
125
    @property
126
    def R(self):
127
        return self._R
128
    
129
    @property
130
    def r(self):
131
        return self._r
132
133