docstr: Computes roots (qmin and qmax) of the score function for given q
  
  :param observed_sum: sum of observed binary outcomes for all i
  :param expectations: predicted outcomes for each data element i
  :param penalty: penalty coefficient
code: def compute_qs(
      self, observed_sum: float, expectations: np.array, penalty: float
  ):
      """
      Computes roots (qmin and qmax) of the score function for given q
  
      :param observed_sum: sum of observed binary outcomes for all i
      :param expectations: predicted outcomes for each data element i
      :param penalty: penalty coefficient
      """
  
      direction = self.direction
      q_mle = self.qmle(observed_sum, expectations)
  
      if self.score(observed_sum, expectations, penalty, q_mle) > 0:
          exist = 1
          q_min = optim.bisection_q_min(
              self, observed_sum, expectations, penalty, q_mle
          )
          q_max = optim.bisection_q_max(
              self, observed_sum, expectations, penalty, q_mle
          )
      else:
          # there are no roots
          exist = 0
          q_min = 0
          q_max = 0
  
      # only consider the desired direction, positive or negative
      if exist:
          exist, q_min, q_max = optim.direction_assertions(
              direction, q_min, q_max
          )
  
      ans = [exist, q_mle, q_min, q_max]
      return ans
  
file_path: AIF360/aif360/detectors/mdss/ScoringFunctions/Poisson.py
imports: from aif360.detectors.mdss.ScoringFunctions.ScoringFunction import ScoringFunction
  from aif360.detectors.mdss.ScoringFunctions import optim
  import numpy
label_desc: Use image node_id 4 for calling the Poisson obj's underlying member method code with example usage: obj.compute_qs(observed_sum, expectations, penalty) and returns: ans