m-lundberg / simple-pid

A simple and easy to use PID controller in Python
MIT License
799 stars 217 forks source link

PID output is the same at every call? #90

Open aaprasad opened 1 month ago

aaprasad commented 1 month ago

Hi, I'm not sure if this is expected behavior but when I try running the PID controller with different inputs at each call it outputs the same value every time regardless of what gains I choose

for instance if I run

pid = PID(1, 0.5, 0.01, setpoint=1)
for _ in range(10):
    print(pid(np.random.normal()))

It will print the same value each time but my understanding is this should change no?

m-lundberg commented 1 month ago

Hi! The pid has a sample time and will only produce new values after the sample time has passed. Since your loop is very fast, it probably loops over all iterations before the sample time has passed. If you truly want a new value every time, you can set the sample time to None: pid.sample_time = None.

You can read more on this topic here.

aaprasad commented 1 month ago

I see, that makes a lot of sense! I'm trying to use your controller for what basically boils down to RL-based position control in gym so I'm not really sure what the right sample_time would be? Right now my control loop looks something like this:

def step(action):
      """Compute torques and take one environment step
           Args:
                   action: an n_joint x 1 vector of desired joint angles
           Returns:
                    obs, reward, terminated, truncated
      """
      torques = self.simulate(action)
      self.env.step(torques)

def simulate(actions, controller, substeps=10):
       """Forward simulate and tune torques using PD control

             Args:
                     actions: an n_joints x 1 vector of desired joint angles
                     controllers = a list of n_joints `PID` controllers for each joint
                     substeps: number of substeps to tune torque output
       """
       set_desired_joint_angles(actions, controllers) # sets the set point of each controller to corresponding joint angle
       og_state = self.env.get_state()
       predicted_joint_angles = self.env.get_joint_angles()

       for i in range(substeps):
            self.env.reset_state(og_state)
            torques = []
            for controller, joint_angle in zip(controllers, current_joint_angles):
                  torque = controller(joint_angle)
                  torques.append(torque)
            self.env.step(torques)
            predicted_joint_angles = self.env.get_joint_angles()

      return torques

In this case should I set the sample_time to 1/substeps and just override the time_function to return i/substep? where i is the ith substep?