docs: def classical(group, src_filter, gsims, param, monitor=Monitor()):
      """
      Compute the hazard curves for a set of sources belonging to the same
      tectonic region type for all the GSIMs associated to that TRT.
      The arguments are the same as in :func:`calc_hazard_curves`, except
      for ``gsims``, which is a list of GSIM instances.
  
      :returns:
          a dictionary {grp_id: pmap} with attributes .grp_ids, .calc_times,
          .eff_ruptures
      """
      if not hasattr(src_filter, 'sitecol'):  # a sitecol was passed
          src_filter = SourceFilter(src_filter, {})
  
      # Get the parameters assigned to the group
      src_mutex = getattr(group, 'src_interdep', None) == 'mutex'
      rup_mutex = getattr(group, 'rup_interdep', None) == 'mutex'
      cluster = getattr(group, 'cluster', None)
      # Compute the number of ruptures
      grp_ids = set()
      for src in group:
          if not src.num_ruptures:
              # src.num_ruptures is set when parsing the XML, but not when
              # the source is instantiated manually, so it is set here
              src.num_ruptures = src.count_ruptures()
          # This sets the proper TOM in case of a cluster
          if cluster:
              src.temporal_occurrence_model = FatedTOM(time_span=1)
          # Updating IDs
          grp_ids.update(src.src_group_ids)
      # Now preparing context
      maxdist = src_filter.integration_distance
      imtls = param['imtls']
      trunclevel = param.get('truncation_level')
      cmaker = ContextMaker(
          src.tectonic_region_type, gsims, maxdist, param, monitor)
      # Prepare the accumulator for the probability maps
      pmap = AccumDict({grp_id: ProbabilityMap(len(imtls.array), len(gsims))
                        for grp_id in grp_ids})
      rupdata = {grp_id: [] for grp_id in grp_ids}
      # AccumDict of arrays with 3 elements weight, nsites, calc_time
      calc_times = AccumDict(accum=numpy.zeros(3, numpy.float32))
      eff_ruptures = AccumDict(accum=0)  # grp_id -> num_ruptures
      # Computing hazard
      for src, s_sites in src_filter(group):  # filter now
          t0 = time.time()
          try:
              poemap = cmaker.poe_map(src, s_sites, imtls, trunclevel,
                                      rup_indep=not rup_mutex)
          except Exception as err:
              etype, err, tb = sys.exc_info()
              msg = '%s (source id=%s)' % (str(err), src.source_id)
              raise etype(msg).with_traceback(tb)
          if src_mutex:  # mutex sources, there is a single group
              for sid in poemap:
                  pcurve = pmap[src.src_group_id].setdefault(sid, 0)
                  pcurve += poemap[sid] * src.mutex_weight
          elif poemap:
              for gid in src.src_group_ids:
                  pmap[gid] |= poemap
          if len(cmaker.rupdata):
              for gid in src.src_group_ids:
                  rupdata[gid].append(cmaker.rupdata)
          calc_times[src.id] += numpy.array(
              [src.weight, len(s_sites), time.time() - t0])
          # storing the number of contributing ruptures too
          eff_ruptures += {gid: getattr(poemap, 'eff_ruptures', 0)
                           for gid in src.src_group_ids}
      # Updating the probability map in the case of mutually exclusive
      # sources
      group_probability = getattr(group, 'grp_probability', None)
      if src_mutex and group_probability:
          pmap[src.src_group_id] *= group_probability
      # Processing cluster
      if cluster:
          tom = getattr(group, 'temporal_occurrence_model')
          pmap = _cluster(param, tom, imtls, gsims, grp_ids, pmap)
      # Return results
      for gid, data in rupdata.items():
          if len(data):
              rupdata[gid] = numpy.concatenate(data)
      return dict(pmap=pmap, calc_times=calc_times, eff_ruptures=eff_ruptures,
                  rup_data=rupdata)