URI:
       refactor(python): deduplicate run and render implementations - sphere - GPU-based 3D discrete element method algorithm with optional fluid coupling
  HTML git clone git://src.adamsgaard.dk/sphere
   DIR Log
   DIR Files
   DIR Refs
   DIR LICENSE
       ---
   DIR commit 6c9f4c262c98d09947e6c8df549c1fc5ae4d8db2
   DIR parent 83cf64c7d715bcc75ebe0f9632ae6f2de0fbbf4f
  HTML Author: Anders Damsgaard <anders@adamsgaard.dk>
       Date:   Sun,  5 Jul 2026 21:49:23 +0200
       
       refactor(python): deduplicate run and render implementations
       
       The module-level run() and render() are now the single implementations,
       generalized to the superset of their sim-method counterparts; the methods
       delegate. video(), status() and cleanup() methods already delegated.
       Behavior change: module-level run() now warns on nonzero exit status.
       
       Diffstat:
         M python/sphere.py                    |     128 +++++++++++++++----------------
       
       1 file changed, 62 insertions(+), 66 deletions(-)
       ---
   DIR diff --git a/python/sphere.py b/python/sphere.py
       @@ -4460,38 +4460,8 @@ class sim:
                '''
        
                self.writebin(verbose=False)
       -
       -        quiet = ""
       -        stdout = ""
       -        dryarg = ""
       -        fluidarg = ""
       -        devicearg = ""
       -        valgrindbin = ""
       -        cudamemchk = ""
       -        binary = "sphere"
       -        if not verbose:
       -            quiet = "-q "
       -        if hideinputfile:
       -            stdout = " > /dev/null"
       -        if dry:
       -            dryarg = "--dry "
       -        if valgrind:
       -            valgrindbin = "valgrind -q --track-origins=yes "
       -        if cudamemcheck:
       -            cudamemchk = "cuda-memcheck --leak-check full "
       -        if self.fluid:
       -            fluidarg = "--fluid "
       -        if device != -1:
       -            devicearg = "-d " + str(device) + " "
       -
       -        cmd = "cd ..; " + valgrindbin + cudamemchk + "./" + binary + " " \
       -                + quiet + dryarg + fluidarg + devicearg + \
       -                "input/" + self.sid + ".bin " + stdout
       -        #print(cmd)
       -        status = subprocess.call(cmd, shell=True)
       -
       -        if status != 0:
       -            print("Warning: the sphere run returned with status " + str(status))
       +        run('input/' + self.sid + '.bin', verbose, hideinputfile, dry,
       +            valgrind, cudamemcheck, device, self.fluid)
        
            def cleanup(self):
                '''
       @@ -4600,27 +4570,8 @@ class sim:
                '''
        
                print("Rendering {} images with the raytracer".format(self.sid))
       -
       -        quiet = ""
       -        if not verbose:
       -            quiet = "-q"
       -
       -        # Render images using sphere raytracer
       -        if method == "normal":
       -            subprocess.call("cd ..; for F in `ls output/" + self.sid
       -                            + "*.bin`; do ./sphere " + quiet
       -                            + " --render $F; done", shell=True)
       -        else:
       -            subprocess.call("cd ..; for F in `ls output/" + self.sid
       -                            + "*.bin`; do ./sphere " + quiet
       -                            + " --method " + method + " {}".format(max_val)
       -                            + " -l {}".format(lower_cutoff)
       -                            + " --render $F; done", shell=True)
       -
       -        # Convert images to compressed format
       -        if verbose:
       -            print('converting images to ' + graphics_format)
       -        convert(graphics_format=graphics_format)
       +        render('output/' + self.sid + '*.bin', method, max_val, lower_cutoff,
       +               graphics_format, verbose)
        
            def video(self, out_folder="./", video_format="mp4",
                      graphics_folder="../img_out/", graphics_format="png", fps=25,
       @@ -7374,8 +7325,12 @@ def convert(graphics_format='png', folder='../img_out', remove_ppm=False):
        def render(binary, method='pres', max_val=1e3, lower_cutoff=0.0,
                   graphics_format='png', verbose=True):
            '''
       -    Render target binary using the ``sphere`` raytracer.
       +    Render target binary using the ``sphere`` raytracer. The target may be
       +    a shell glob matching several binaries.
        
       +    :param binary: Input file(s) for the raytracer, relative to the
       +        ``sphere`` root folder. May contain shell wildcards.
       +    :type binary: str
            :param method: The color visualization method to use for the particles.
                Possible values are: 'normal': color all particles with the same
                color, 'pres': color by pressure, 'vel': color by translational
       @@ -7400,17 +7355,19 @@ def render(binary, method='pres', max_val=1e3, lower_cutoff=0.0,
        
            # Render images using sphere raytracer
            if method == 'normal':
       -        subprocess.call('cd .. ; ./sphere ' + quiet + \
       -                ' --render ' + binary, shell=True)
       +        subprocess.call('cd ..; for F in `ls ' + binary + '`; do '
       +                        + './sphere ' + quiet
       +                        + ' --render $F; done', shell=True)
            else:
       -        subprocess.call('cd .. ; ./sphere ' + quiet + \
       -                ' --method ' + method + ' {}'.format(max_val) + \
       -                ' -l {}'.format(lower_cutoff) + \
       -                ' --render ' + binary, shell=True)
       +        subprocess.call('cd ..; for F in `ls ' + binary + '`; do '
       +                        + './sphere ' + quiet
       +                        + ' --method ' + method + ' {}'.format(max_val)
       +                        + ' -l {}'.format(lower_cutoff)
       +                        + ' --render $F; done', shell=True)
        
            # Convert images to compressed format
            if verbose:
       -        print('converting to ' + graphics_format)
       +        print('converting images to ' + graphics_format)
            convert(graphics_format)
        
        def video(project, out_folder='./', video_format='mp4',
       @@ -7504,26 +7461,65 @@ def thinsectionVideo(project, out_folder="./", video_format="mp4", fps=25,
                            + out_folder + "/" + project + "-ts-x1x3." + video_format,
                            shell=True)
        
       -def run(binary, verbose=True, hideinputfile=False):
       +def run(binary, verbose=True, hideinputfile=False, dry=False, valgrind=False,
       +        cudamemcheck=False, device=-1, fluid=False):
            '''
            Execute ``sphere`` with target binary file as input.
        
       -    :param binary: Input file for ``sphere``
       +    :param binary: Input file for ``sphere``, relative to the ``sphere``
       +        root folder
            :type binary: str
            :param verbose: Show ``sphere`` output
            :type verbose: bool
            :param hideinputfile: Hide the input file
            :type hideinputfile: bool
       +    :param dry: Perform a dry run. Important parameter values are shown by
       +        the ``sphere`` program, and it exits afterwards.
       +    :type dry: bool
       +    :param valgrind: Run the program with ``valgrind`` in order to check
       +        memory leaks in the host code. This causes a significant increase in
       +        computational time.
       +    :type valgrind: bool
       +    :param cudamemcheck: Run the program with ``cudamemcheck`` in order to
       +        check for device memory leaks and errors. This causes a significant
       +        increase in computational time.
       +    :type cudamemcheck: bool
       +    :param device: Specify the GPU device to execute the program on.
       +        If not specified, sphere will use the device with the most CUDA cores.
       +        To see a list of devices, run ``nvidia-smi`` in the system shell.
       +    :type device: int
       +    :param fluid: Simulate fluid between the particles
       +    :type fluid: bool
            '''
        
            quiet = ''
            stdout = ''
       +    dryarg = ''
       +    fluidarg = ''
       +    devicearg = ''
       +    valgrindbin = ''
       +    cudamemchk = ''
            if not verbose:
       -        quiet = '-q'
       +        quiet = '-q '
            if hideinputfile:
                stdout = ' > /dev/null'
       -    subprocess.call('cd ..; ./sphere ' + quiet + ' ' + binary + ' ' + stdout, \
       -            shell=True)
       +    if dry:
       +        dryarg = '--dry '
       +    if valgrind:
       +        valgrindbin = 'valgrind -q --track-origins=yes '
       +    if cudamemcheck:
       +        cudamemchk = 'cuda-memcheck --leak-check full '
       +    if fluid:
       +        fluidarg = '--fluid '
       +    if device != -1:
       +        devicearg = '-d ' + str(device) + ' '
       +
       +    status = subprocess.call('cd ..; ' + valgrindbin + cudamemchk
       +                             + './sphere ' + quiet + dryarg + fluidarg
       +                             + devicearg + binary + ' ' + stdout, shell=True)
       +
       +    if status != 0:
       +        print('Warning: the sphere run returned with status ' + str(status))
        
        def torqueScriptParallel3(obj1, obj2, obj3, email='adc@geo.au.dk',
                                  email_alerts='ae', walltime='24:00:00',