Project: s41467-024-47443-w | Function: get_CPG_RL_actions (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Hierarchical control interface that maps reinforcement learning policy actions to CPG parameters, enabling supraspinal drive modulation of central pattern generator dynamics for adaptive gait control

**KEY TERMS:** {
  "actions": "neural network policy output (supraspinal drive)",
  "_mu": "oscillator amplitude parameters μ",
  "_omega_residuals": "frequency modulation signals ω",
  "_scale_helper": "action scaling from [-1,1] to parameter ranges",
  "x,y,z": "Cartesian foot positions from CPG states",
  "_ground_clearance": "swing phase foot clearance height"
}

**CONTEXT CODE:**         self.X[env_ids,0,:] = torch.rand(len(env_ids),4,device=self._device) * .1
        self.X[env_ids,1,:] = self.PHI[0,:] #*0.0
        self.X_dot[env_ids,:,:] = 0.
 

    def _set_gait(self,gait):
        device = self._device 

        trot = torch.tensor([[ 0, 1, 1, 0 ],
                            [-1, 0, 0,-1 ],
                            [-1, 0, 0,-1 ],
                            [ 0, 1, 1, 0 ]],dtype=torch.float, device=device, requires_grad=False) 
        trot = np.pi * trot
        self.PHI_trot = trot

        if gait == "TROT":
            print('TROT')
            self.PHI = self.PHI_trot
        else:
            raise ValueError( gait + 'not implemented.')


    def update_offset_x(self,offset):
        self._offset_x = offset


    def update(self):
        """ Update oscillator states. """
        device = self._device 

        # update parameters, integrate
        self._integrate_hopf_equations()

        # map CPG variables to Cartesian foot xz positions
        x = self.X[:,0,:] * torch.cos(self.X[:,1,:]) 
        z = torch.where(torch.sin(self.X[:,1,:]) > 0, 
                        -self._robot_height + self._ground_clearance*torch.sin(self.X[:,1,:]),# swing)
                        -self._robot_height + self._ground_penetration*torch.sin(self.X[:,1,:]))

        return -self._des_step_len * x, z

    def _scale_helper(self, action, lower_lim, upper_lim):
        """Helper to linearly scale from [-1,1] to lower/upper limits. 
        This needs to be made general in case action range is not [-1,1]
        """
        new_a = lower_lim + 0.5 * (action + 1) * (upper_lim - lower_lim)
        # verify clip
        new_a = torch.clip(new_a, lower_lim, upper_lim)
        return new_a

    def get_CPG_RL_actions(self,actions,frequency_high,frequency_low,normal_forces):
        """ Map RL actions to CPG signals """
        raise NotImplementedError("This method is masked for testing purposes")

    def integrate_oscillator_equations(self):
        raise NotImplementedError("This method is masked for testing purposes")

    def compute_inverse_kinematics(self,robot,legID, x, y, z):
        l1 = robot.hip_link_length_a1
        l2 = robot.thigh_link_length_a1
        l3 = robot.thigh_link_length_a1
    
        D = (y**2 + (-z)**2 - l1**2 +
        (-x)**2 - l2**2 - l3**2) / (
                 2 * l3 * l2)

        D = torch.clip(D, -1.0, 1.0)

        # check Right vs Left leg for hip angle
        sideSign = 1
        if legID == 0 or legID == 2:
          sideSign = -1

        knee_angle = torch.atan2(-torch.sqrt(1 - D**2), D)
        sqrt_component = y**2 + (-z)**2 - l1**2
        hip_roll_angle = -1*(-torch.atan2(z, y) - torch.atan2(
            torch.sqrt(sqrt_component), sideSign*l1*torch.ones_like(x)))
        hip_thigh_angle = torch.atan2(-x, torch.sqrt(sqrt_component)) -1* torch.atan2(
            l3 * torch.sin(knee_angle),
            l2 + l3 * torch.cos(knee_angle)) 
        output= torch.stack([hip_roll_angle, hip_thigh_angle, knee_angle], dim=-1)  
        return output
                    



Complete the function implementation replacing "raise NotImplementedError".