code: def E_step_M_step(
      observation_intervals,
      p_old,
      turnbull_interval_lookup,
      weights,
      i,
      optimize,
  ) -> np.ndarray:
      """
      See [1], but also modifications.
  
      References
      -----------
      1. Clifford Anderson-Bergman (2016): An efficient implementation of the
       EMICM algorithm for the interval censored NPMLE, Journal of Computational and Graphical
       Statistics, DOI: 10.1080/10618600.2016.1208616
      """
      N = 0
      m = np.zeros_like(p_old)
      P = cumulative_sum(p_old)
      for observation_interval, w in zip(
          observation_intervals, weights
      ):
          # find all turnbull intervals, t, that are contained in (ol, or). Call this set T
          # the denominator is sum of p_old[T] probabilities
          # the numerator is p_old[t]
          min_, max_ = turnbull_interval_lookup[observation_interval]
          m[min_ : max_ + 1] += w / (P[max_ + 1] - P[min_]).sum()
          N += w
  
      p_new = p_old * (m / N) ** temper(i, optimize)
      p_new /= p_new.sum()
      return p_new
  
imports: from collections import defaultdict, namedtuple
  import warnings
  import numpy
  from numpy.linalg import norm
  import pandas
  from lifelines.exceptions import ConvergenceWarning
  from typing import *
label_desc: Use image node_id 2 for calling a global function with example usage: E_step_M_step(observation_intervals, p_old, turnbull_interval_lookup, weights, i, optimize) and returns: p_new