Translating the center of a group of objects to the world origin I would like to translate a group of objects to the world origin, so that the center of mass of this group sits at the origin. 
I wanted to proceed as follow:


Retrieve the center (x,y,z) of the group of objects
Position an empty at that position
Set all the objects as child of the empty
Set the location of the empty to (0,0,0)
Free up the linking and delete the empty


Steps 1 &amp; 2 are fine. On step #3 when parenting to the empty, all child objects are offset by the absolute position of the empty. When moving the empty to (0, 0, 0) the child objects go back to their original position.
The code below illustrate this:

import bpy
import numpy as np

# Create some cubes for the sake  of it
k = 3
bpy.ops.mesh.primitive_cube_add(size=2, location=(k, 2*k, 2*k+1))
bpy.ops.mesh.primitive_cube_add(size=2, location=(2*k, k, 2*k+1))
bpy.ops.mesh.primitive_cube_add(size=2, location=(2*k+1, 0, 2*k))

obj_centers = []
# Get all selected objects
for obj in bpy.context.scene.objects:
    bpy.context.view_layer.objects.active = obj
    if obj.type == 'MESH':
        # retrieve their location
        obj_center =  [obj.location[0],obj.location[1], obj.location[2]]
        obj_centers.append(obj_center)
#Compute the  geometric  center of the collection (average on x/y/z)
center = np.mean(np.asarray(obj_centers), axis=0)
# Create an empty at that location
o = bpy.data.objects.new( "my_temp_empty", None )
bpy.context.scene.collection.objects.link( o )
o.empty_display_size = 1
o.empty_display_type = 'ARROWS'
o.location = (center.tolist()[0], center.tolist()[1], center.tolist()[0])
# Parent all objects to  this empty
for obj in bpy.context.scene.objects:
    bpy.context.view_layer.objects.active = obj
    if obj.type == 'MESH':
        obj.parent =  o
        obj.matrix_parent_inverse = o.matrix_world.inverted()
# Move empty to origin
o.location = (0, 0, 0)


Of course, I can circumvent the problem by defining the position of the empty to the negative of the center of the objects group o.location = (-center.tolist()[0], -center.tolist()[1], -center.tolist()[0]), but I'd prefer to understand what's going on here. It has to do with obj.matrix_parent_inverse = o.matrix_world.inverted() this is supposed to fix the position of the children, but it does not work and I can not get my head around it (sorry, Blender beginner...).
My questions:


is there an alternative to my approach to move the center of a group of objects to the  origin?
Are some my issues due to using an "empty" as parent?
Could someone explain or point to a good explanation of what's going on with the position matrix when parenting. This is at the core of how Blender works, I'd be happy to finally understand it. 
How would I come to release the linking and have my objects stay at their new location (i.e  Clear and Keep Transform)?


Many thanks in advance.
Use the matrix world.


  is there an alternative to my approach to move the center of a group
  of objects to the origin?


An objects matrix world gives us access to global coordinates.  Hence could look at all the global coordinates of objects, get the mean and subtract it from each.

import bpy
from bpy import context
from mathutils import Vector

obs = context.selected_objects

o = sum((o.matrix_world.translation for o in obs), Vector()) / len(obs) 

for ob in obs:
    ob.matrix_world.translation -= o


On parenting. In the UI we are used to seeing child coordinates such that the parent inverse matrix is identity.  To do this in code would set the matrix local of each child to be in the object space of the parent.  To do this we multiply its world matrix by the inverse of the parents.

Taking up script above, from mean calculation

bpy.ops.object.empty_add(location=o)
mt = context.object
mwi = mt.matrix_world.inverted()

for ob in obs:
    ob.parent = mt
    # alter ob.matrix_local
    ob.matrix_local = mwi @ ob.matrix_world
    # instead of
    # ob.matrix_parent_inverse = mwi
    # which retains matrix_local

mt.location = (0, 0, 0)
bpy.data.objects.remove(mt)


Note the difference here is that the local coordinates are being altered directly to what is the result of setting parent inverse (and keeping local coords same).  

The latter is the reason the objects would return to original location when parent  removed,  parent inverse isn't used in calculations.