docs: def save(self, calc, session):
          '''
          Saves tilde_obj into the database
          NB: this is the PUBLIC method
          @returns checksum, error
          '''
          checksum = calc.get_checksum()
  
          try:
              existing_calc = session.query(model.Calculation).filter(model.Calculation.checksum == checksum).one()
          except NoResultFound:
              pass
          else:
              del calc
              return None, "This calculation already exists!"
  
          if not calc.download_size:
              for f in calc.related_files:
                  calc.download_size += os.stat(f).st_size
  
          ormcalc = model.Calculation(checksum = checksum)
  
          if calc._calcset:
              ormcalc.meta_data = model.Metadata(chemical_formula = calc.info['standard'], download_size = calc.download_size)
  
              for child in session.query(model.Calculation).filter(model.Calculation.checksum.in_(calc._calcset)).all():
                  ormcalc.children.append(child)
              ormcalc.siblings_count = len(ormcalc.children)
              ormcalc.nested_depth = calc._nested_depth
  
          else:
              # prepare phonon data for saving
              # this is actually a dict to list conversion TODO re-structure this
              if calc.phonons['modes']:
                  phonons_json = []
  
                  for bzpoint, frqset in calc.phonons['modes'].items():
                      # re-orientate eigenvectors
                      for i in range(0, len(calc.phonons['ph_eigvecs'][bzpoint])):
                          for j in range(0, len(calc.phonons['ph_eigvecs'][bzpoint][i])//3):
                              eigv = array([calc.phonons['ph_eigvecs'][bzpoint][i][j*3], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+1], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+2]])
                              R = dot( eigv, calc.structures[-1].cell ).tolist()
                              calc.phonons['ph_eigvecs'][bzpoint][i][j*3], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+1], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+2] = [round(x, 3) for x in R]
  
                      try: irreps = calc.phonons['irreps'][bzpoint]
                      except KeyError:
                          empty = []
                          for i in range(len(frqset)):
                              empty.append('')
                          irreps = empty
  
                      phonons_json.append({  'bzpoint':bzpoint, 'freqs':frqset, 'irreps':irreps, 'ph_eigvecs':calc.phonons['ph_eigvecs'][bzpoint]  })
                      if bzpoint == '0 0 0':
                          phonons_json[-1]['ir_active'] = calc.phonons['ir_active']
                          phonons_json[-1]['raman_active'] = calc.phonons['raman_active']
                      if calc.phonons['ph_k_degeneracy']:
                          phonons_json[-1]['ph_k_degeneracy'] = calc.phonons['ph_k_degeneracy'][bzpoint]
  
                  ormcalc.phonons = model.Phonons()
                  ormcalc.spectra.append( model.Spectra(kind = model.Spectra.PHONON, eigenvalues = json.dumps(phonons_json)) )
  
              # prepare electron data for saving TODO re-structure this
              for task in ['dos', 'bands']: # projected?
                  if calc.electrons[task]:
                      calc.electrons[task] = calc.electrons[task].todict()
  
              if calc.electrons['dos'] or calc.electrons['bands']:
                  ormcalc.electrons = model.Electrons(gap = calc.info['bandgap'])
                  if 'bandgaptype' in calc.info:
                      ormcalc.electrons.is_direct = 1 if calc.info['bandgaptype'] == 'direct' else -1
                  ormcalc.spectra.append(model.Spectra(
                      kind = model.Spectra.ELECTRON,
                      dos = json.dumps(calc.electrons['dos']),
                      bands = json.dumps(calc.electrons['bands']),
                      projected = json.dumps(calc.electrons['projected']),
                      eigenvalues = json.dumps(calc.electrons['eigvals'])
                  ))
  
              # construct ORM for other props
              calc.related_files = list(map(virtualize_path, calc.related_files))
              ormcalc.meta_data = model.Metadata(location = calc.info['location'], finished = calc.info['finished'], raw_input = calc.info['input'], modeling_time = calc.info['duration'], chemical_formula = html_formula(calc.info['standard']), download_size = calc.download_size, filenames = json.dumps(calc.related_files))
  
              codefamily = model.Codefamily.as_unique(session, content = calc.info['framework'])
              codeversion = model.Codeversion.as_unique(session, content = calc.info['prog'])
  
              codeversion.instances.append( ormcalc.meta_data )
              codefamily.versions.append( codeversion )
  
              pot = model.Pottype.as_unique(session, name = calc.info['H'])
              pot.instances.append(ormcalc)
              ormcalc.recipinteg = model.Recipinteg(kgrid = calc.info['k'], kshift = calc.info['kshift'], smearing = calc.info['smear'], smeartype = calc.info['smeartype'])
              ormcalc.basis = model.Basis(kind = calc.info['ansatz'], content = json.dumps(calc.electrons['basis_set']) if calc.electrons['basis_set'] else None)
              ormcalc.energy = model.Energy(convergence = json.dumps(calc.convergence), total = calc.info['energy'])
  
              ormcalc.spacegroup = model.Spacegroup(n=calc.info['ng'])
              ormcalc.struct_ratios = model.Struct_ratios(chemical_formula=calc.info['standard'], formula_units=calc.info['expanded'], nelem=calc.info['nelem'], dimensions=calc.info['dims'])
              if len(calc.tresholds) > 1:
                  ormcalc.struct_optimisation = model.Struct_optimisation(tresholds=json.dumps(calc.tresholds), ncycles=json.dumps(calc.ncycles))
  
              for n, ase_repr in enumerate(calc.structures):
                  is_final = True if n == len(calc.structures)-1 else False
                  struct = model.Structure(step = n, final = is_final)
  
                  s = cell_to_cellpar(ase_repr.cell)
                  struct.lattice = model.Lattice(a=s[0], b=s[1], c=s[2], alpha=s[3], beta=s[4], gamma=s[5], a11=ase_repr.cell[0][0], a12=ase_repr.cell[0][1], a13=ase_repr.cell[0][2], a21=ase_repr.cell[1][0], a22=ase_repr.cell[1][1], a23=ase_repr.cell[1][2], a31=ase_repr.cell[2][0], a32=ase_repr.cell[2][1], a33=ase_repr.cell[2][2])
  
                  #rmts =      ase_repr.get_array('rmts') if 'rmts' in ase_repr.arrays else [None for j in range(len(ase_repr))]
                  charges =   ase_repr.get_array('charges') if 'charges' in ase_repr.arrays else [None for j in range(len(ase_repr))]
                  magmoms =   ase_repr.get_array('magmoms') if 'magmoms' in ase_repr.arrays else [None for j in range(len(ase_repr))]
                  for n, i in enumerate(ase_repr):
                      struct.atoms.append( model.Atom( number=chemical_symbols.index(i.symbol), x=i.x, y=i.y, z=i.z, charge=charges[n], magmom=magmoms[n] ) )
  
                  ormcalc.structures.append(struct)
              # TODO Forces
  
          ormcalc.uigrid = model.Grid(info=json.dumps(calc.info))
  
          # tags ORM
          uitopics = []
          for entity in self.hierarchy:
  
              if not entity['creates_topic']:
                  continue
  
              if entity['multiple'] or calc._calcset:
                  for item in calc.info.get( entity['source'], [] ):
                      uitopics.append( model.topic(cid=entity['cid'], topic=item) )
              else:
                  topic = calc.info.get(entity['source'])
                  if topic or not entity['optional']:
                      uitopics.append( model.topic(cid=entity['cid'], topic=topic) )
  
          uitopics = [model.Topic.as_unique(session, cid=x.cid, topic="%s" % x.topic) for x in uitopics]
  
          ormcalc.uitopics.extend(uitopics)
  
          if calc._calcset:
              session.add(ormcalc)
          else:
              session.add_all([codefamily, codeversion, pot, ormcalc])
  
          session.commit()
          del calc, ormcalc
          return checksum, None