Multiprocessing Geant4Py in Python 3.X

I’ve gotten Geant4Py working for my medical physics application on a single thread, but I am finding a problem in Python 3.X (3.8.8) running GEANT4.10.7 that I do not get on an older install running Python 2.7.1 with GEANT4.10.2. This problem is distributing the simulation over multiple threads with the python multiprocessing module.

Using multiprocessing.Pool.map I get the error:

multiprocessing.pool.MaybeEncodingError: Error sending result: 
'<multiprocessing.pool.ExceptionWithTraceback object at 0x7ff19e308f40>'. Reason: 
'PicklingError("Can't pickle <class 'Boost.Python.ArgumentError'>: import of module 'Boost.Python' failed")'

And if I try to use ray.util.multiprocessing import Pool I get the error:

RuntimeError: Pickling of "Geant4.G4run.G4RunManager" instances is not enabled (http://www.boost.org/libs/python/doc/v2/pickle.html)          

Is this a problem with how I installed Geant4Py or is there some thing else going on?

did you consider this:

Yes I got it installed by using those commands initially.

There was a typo in my DGEANT4_BUILD_MULTITHREADED flag. I’ve updated it and am reinstalling Geant4. I’m attempting to do it using 8 threads. In the past, G4Py would only work if Geant4 proper was installed on a single thread. I assume that this has been updated since the two are now built together?

I want to comment that I have reinstalled Geant4 with the proper flags set to get Geant4Py working with the multiprocessing flag turned on. However, the base issues has not been resolved. I still get:

multiprocessing.pool.MaybeEncodingError: Error sending result:
 '<multiprocessing.pool.ExceptionWithTraceback object at 0x7f096052df40>'. 
Reason: 'PicklingError("Can't pickle <class 'Boost.Python.ArgumentError'>: 
import of module 'Boost.Python' failed")'  

can you post a minimum working example to reproduce the error? I could test the on my machines…

@weller do you have a gitlab I can grant you access to this project’s repo? I’m having trouble compressing this into a minimum example.

The setup to the multiprocessing is:

# Run sim in multiprocessing mode
if self.multiprocess:

    # Setup pool with max CPU
    ncpus = cpu_count()
    #pool = Pool(processes = ncpus)#, maxtasksperchild=1)


    # If source is not list, distribute single sim over particles
    if not isinstance(self.source, list):
        chunk = 1
        # Set up beam


        # Chunk particles
        particles = [self.source.num_particles / ncpus] * ncpus
        if np.sum(particles) != self.source.num_particles:
            particles.append(self.source.num_particles - np.sum(particles))

        # Build separate source terms
        source_list = []
        sourceTest = []
        for i, j in enumerate(particles):
            # Copy
            s = copy.deepcopy(self.source)
            # Change particles
            s.num_particles = j
            # Change random seed
            s.random_seed = self.source.random_seed + i
            # Change filename
            s.outputfile = self.source.outputfile + "_" + str(i)
            s.sourcedatafile = self.source.sourcedatafile + "_" + str(i)
            # Append to list
            source_list.append(s)
            #sourceTest += [s]

        # Set as source

        print('source list[i] = ', source_list)
        self.source = source_list
    else:
        chunk = int(1.*len(self.source)/ncpus)


    # Beam on!
    start_time = time.time()
    iterableInt = int(1*ncpus)
    iterable = int(len(self.source)*1./(iterableInt))+1
    for i in range(iterable):
        k1 = i*iterableInt
        k2 = (i+1)*iterableInt
        if k2 > int(len(self.source)+1):
            s = self.source[k1:]
        else:
            s = self.source[k1:k2]
        chunk = int(len(s)*1./ncpus)
        pool = Pool(processes = ncpus)
        pool.map(BeamOn, s, chunksize=1)
        pool.close()

where

def BeamOn(source):

    # Set source in primarygeneratoraction
    pga = gRunManager.GetUserPrimaryGeneratorAction()
    pga.set_source(source)

    # Beam on!
    print('Beam On!')
    gRunManager.BeamOn(source.num_particles)

and the source is a class that has the attributes

class Source(object):

    def __init__(self, field='isotropic', particle='neutron', energyDist = None, energy=3, minE=0.25, maxE=15,
                       direction=[0,0], direction_type='angle',
                       num_particles=100, source_rad=20, source_dist=100,
                       energy_unit='MeV', angle_unit='deg', length_unit='cm',
                       random_seed=42, outputfile='output', sourcedatafile='srcoutput')

I am not sure If I understand correctly what you are trying to achieve, and I am certainly not an expert in python, but as far as I understand, multiprocessing is sort of “built into” the RunManager.
In c++, it is more or less

auto runManager = G4RunManagerFactory::CreateRunManager(G4RunManagerType::MT);
runManager->SetNumberOfThreads(nThreads);

and there you go: computations are running parallel… there is no need to manually distribute/chunk particles/whatsoever…
Hence I would be very much surprised if the python interface would be much more complicated!?

Sorry I can’t be of more help here, and I don’t have gitlab access… let’s see what someone with proper knowledge can do for you :slight_smile:

1 Like

Thank you for your help @weller. Unfortunately, g4py does not have a G4RunManagerFactory class. However, I was able to figure it out. It turns out the way I was chunking the particles was making that term into a float object and it wanted an int. So I fixed that and the multiprocessing module is functioning now! :smiley:

1 Like