docs: def classify(self, calc, symprec=None):
          '''
          Reasons on normalization, invokes hierarchy API and prepares calc for saving
          NB: this is the PUBLIC method
          @returns tilde_obj, error
          '''
          error = None
          symbols = calc.structures[-1].get_chemical_symbols()
          calc.info['formula'] = self.formula(symbols)
          calc.info['cellpar'] = cell_to_cellpar(calc.structures[-1].cell).tolist()
          if calc.info['input']:
              try:
                  calc.info['input'] = str(calc.info['input'], errors='ignore')
              except:
                  pass
  
          # applying filter: todo
          if (calc.info['finished'] == 0x1 and self.settings['skip_unfinished']) or \
             (not calc.info['energy'] and self.settings['skip_notenergy']):
              return None, 'data do not satisfy the active filter'
  
          # naive elements extraction
          fragments = re.findall(r'([A-Z][a-z]?)(\d*[?:.\d+]*)?', calc.info['formula'])
          for fragment in fragments:
              if fragment[0] == 'X':
                  continue
              calc.info['elements'].append(fragment[0])
              calc.info['contents'].append(int(fragment[1])) if fragment[1] else calc.info['contents'].append(1)
  
          # extend hierarchy with modules
          for C_obj in self.Classifiers:
              try:
                  calc = C_obj['classify'](calc)
              except:
                  exc_type, exc_value, exc_tb = sys.exc_info()
                  error = "Fatal error during classification:\n %s" % "".join(traceback.format_exception( exc_type, exc_value, exc_tb ))
                  return None, error
  
          # chemical ratios
          if not len(calc.info['standard']):
              if len(calc.info['elements']) == 1: calc.info['expanded'] = 1
              if not calc.info['expanded']:
                  calc.info['expanded'] = reduce(gcd, calc.info['contents'])
              for n, i in enumerate([x//calc.info['expanded'] for x in calc.info['contents']]):
                  if i == 1:
                      calc.info['standard'] += calc.info['elements'][n]
                  else:
                      calc.info['standard'] += calc.info['elements'][n] + str(i)
          if not calc.info['expanded']:
              del calc.info['expanded']
  
          calc.info['nelem'] = len(calc.info['elements'])
          if calc.info['nelem'] > 13:
              calc.info['nelem'] = 13
          calc.info['natom'] = len(symbols)
  
          # periodicity
          if calc.info['periodicity'] == 0:
              calc.info['periodicity'] = 0x4
          elif calc.info['periodicity'] == -1:
              calc.info['periodicity'] = 0x5
  
          # general calculation type reasoning
          if (calc.structures[-1].get_initial_charges() != 0).sum():
              calc.info['calctypes'].append(0x4) # numpy count_nonzero implementation
          if (calc.structures[-1].get_initial_magnetic_moments() != 0).sum():
              calc.info['calctypes'].append(0x5)
          if calc.phonons['modes']:
              calc.info['calctypes'].append(0x6)
          if calc.phonons['ph_k_degeneracy']:
              calc.info['calctypes'].append(0x7)
          if calc.phonons['dielectric_tensor']:
              calc.info['calctypes'].append(0x8) # CRYSTAL-only!
          if len(calc.tresholds) > 1:
              calc.info['calctypes'].append(0x3)
              calc.info['optgeom'] = True
          if calc.electrons['dos'] or calc.electrons['bands']:
              calc.info['calctypes'].append(0x2)
          if calc.info['energy']:
              calc.info['calctypes'].append(0x1)
          calc.info['spin'] = 0x2 if calc.info['spin'] else 0x1
  
          # TODO: standardize
          if 'vac' in calc.info:
              if 'X' in symbols:
                  calc.info['techs'].append('vacancy defect: ghost')
              else:
                  calc.info['techs'].append('vacancy defect: void space')
  
          calc.info['lata'] = round(calc.info['cellpar'][0], 3)
          calc.info['latb'] = round(calc.info['cellpar'][1], 3)
          calc.info['latc'] = round(calc.info['cellpar'][2], 3)
          calc.info['latalpha'] = round(calc.info['cellpar'][3], 2)
          calc.info['latbeta'] = round(calc.info['cellpar'][4], 2)
          calc.info['latgamma'] = round(calc.info['cellpar'][5], 2)
  
          # invoke symmetry finder
          found = SymmetryHandler(calc, symprec)
          if found.error:
              return None, found.error
  
          calc.info['sg'] = found.i
          calc.info['ng'] = found.n
          calc.info['symmetry'] = found.symmetry
          calc.info['spg'] = "%s &mdash; %s" % (found.n, found.i)
          calc.info['pg'] = found.pg
          calc.info['dg'] = found.dg
  
          # phonons
          if calc.phonons['dfp_magnitude']: calc.info['dfp_magnitude'] = round(calc.phonons['dfp_magnitude'], 3)
          if calc.phonons['dfp_disps']: calc.info['dfp_disps'] = len(calc.phonons['dfp_disps'])
          if calc.phonons['modes']:
              calc.info['n_ph_k'] = len(calc.phonons['ph_k_degeneracy']) if calc.phonons['ph_k_degeneracy'] else 1
  
          #calc.info['rgkmax'] = calc.electrons['rgkmax'] # LAPW
  
          # electronic properties reasoning by bands
          if calc.electrons['bands']:
              if calc.electrons['bands'].is_conductor():
                  calc.info['etype'] = 0x2
                  calc.info['bandgap'] = 0.0
                  calc.info['bandgaptype'] = 0x1
              else:
                  try:
                      gap, is_direct = calc.electrons['bands'].get_bandgap()
                  except ElectronStructureError as e:
                      calc.electrons['bands'] = None
                      calc.warning(e.value)
                  else:
                      calc.info['etype'] = 0x1
                      calc.info['bandgap'] = round(gap, 2)
                      calc.info['bandgaptype'] = 0x2 if is_direct else 0x3
  
          # electronic properties reasoning by DOS
          if calc.electrons['dos']:
              try: gap = round(calc.electrons['dos'].get_bandgap(), 2)
              except ElectronStructureError as e:
                  calc.electrons['dos'] = None
                  calc.warning(e.value)
              else:
                  if calc.electrons['bands']: # check coincidence
                      if abs(calc.info['bandgap'] - gap) > 0.2:
                          calc.warning('Bans gaps in DOS and bands data differ considerably! The latter will be considered.')
                  else:
                      calc.info['bandgap'] = gap
                      if gap:
                          calc.info['etype'] = 0x1
                      else:
                          calc.info['etype'] = 0x2
                          calc.info['bandgaptype'] = 0x1
  
          # TODO: beware to add something new to an existing item!
          # TODO2: unknown or absent?
          for entity in self.hierarchy:
              if entity['creates_topic'] and not entity['optional'] and not calc.info.get(entity['source']):
                  if entity['enumerated']:
                      calc.info[ entity['source'] ] = [0x0] if entity['multiple'] else 0x0
                  else:
                      calc.info[ entity['source'] ] = ['none'] if entity['multiple'] else 'none'
  
          calc.benchmark() # this call must be at the very end of parsing
  
          return calc, error