cape.cfdx.case: Case Control Module

This module contains templates for interacting with and executing individual cases. Since this is one of the most highly customized modules of the CAPE system, there are few functions here, and the functions that are present are mostly templates.

In general, the case module is used for actually running the CFD solver (and any additional binaries that may be required as part of the run process), and it contains other capabilities for renaming files and determining the settings for a particular case. CAPE saves many settings for the CFD solver and archiving in a file called case.json within each case folder, which allows for the settings of one case to diverge from the other cases in the same run matrix.

Actual functionality is left to individual modules listed below.

class cape.cfdx.case.CaseRunner(fdir=None)

Class to handle running of individual CAPE cases

Call:
>>> runner = CaseRunner(fdir=None)
Inputs:
fdir: {None} | str

Optional case folder (by default os.getcwd())

Outputs:
runner: CaseRunner

Controller to run one case of solver

check_complete()

Check if a case is complete (DONE)

Call:
>>> q = runner.check_complete()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Versions:
  • 2023-06-20 @ddalle: v1.0

  • 2023-07-08 @ddalle: v1.1; support STOP

check_error()

Check for other errors; rewrite for each solver

Call:
>>> ierr = runner.check_error()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
ierr: int

Return code

Versions:
  • 2023-06-20 @ddalle: v1.0

check_running()

Check if a case is already running, raise exception if so

Call:
>>> runner.check_running()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Versions:
  • 2023-06-02 @ddalle: v1.0

  • 2023-06-20 @ddalle: v1.1; instance method

finalize_files(j: int)

Clean up files after running one cycle of phase j

Call:
>>> runner.finalize_files(j)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

Versions:
  • 2023-06-20 @ddalle: cfdx abstract method

get_cpu_time()

Read most appropriate total CPU usage for current case

Call:
>>> corehrs = runner.get_cpu_time()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
corehrs: None | float

Core hours since last start or None if not running

Versions:
  • 2015-12-22 @ddalle: v1.0 (Cntl.GetCPUTimeBoth)

  • 2016-08-30 @ddalle: v1.1; check for RUNNING

  • 2023-07-09 @ddalle: v2.0; rename, CaseRunner

get_cpu_time_start()

Read total core hours since start of current running phase

Call:
>>> corehrs = runner.get_cpu_time_start()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
corehrs: None | float

Core hours since last start or None if not running

Versions:
  • 2015-08-30 @ddalle: v1.0 (Cntl.GetCPUTimeFromStart)

  • 2023-07-09 @ddalle: v2.0

get_cpu_time_user()

Read total core hours from completed phase cycles

Call:
>>> corehrs = runner.get_cpu_time_user()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
corehrs: None | float

Total core hours used or None if no log file found

Versions:
  • 2015-12-22 @ddalle: v1.0 (Cntl.GetCPUTimeFromFile)

  • 2023-07-09 @ddalle: v2.0

get_iter(f=True)

Detect most recent iteration

Call:
>>> n = runner.get_iter(f=True)
Inputs:
runner: CaseRunner

Controller to run one case of solver

f: {True} | False

Force recalculation of phase

Outputs:
n: int

Iteration number

Versions:
  • 2023-06-20 @ddalle: v1.0

get_job_id() str

Get PBS/Slurm job ID, if any

Call:
>>> job_id = runner.get_job_id()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
job_id: str

Text form of job ID; '' if no job found

Versions:
  • 2023-06-16 @ddalle: v1.0

  • 2023-07-05 @ddalle: v1.1; eliminate j arg

get_pbs_script(j=None)

Get file name of PBS script

… or Slurm script or execution script

Call:
>>> fpbs = runner.get_pbs_script(j=None)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: {None} | int

Phase number

Outputs:
fpbs: str

Name of main script to run case

Versions:
  • 2014-12-01 @ddalle: v1.0 (pycart)

  • 2015-10-19 @ddalle: v1.0 (pyfun)

  • 2023-06-18 @ddalle: v1.1; instance method

get_phase(f=True) int

Determine phase number in present case

Call:
>>> j = runner.get_phase(n, f=True)
Inputs:
runner: CaseRunner

Controller to run one case of solver

f: {True} | False

Force recalculation of phase

Outputs:
j: int

Phase number for next restart

Versions:
  • 2014-10-02 @ddalle: v1.0

  • 2015-10-19 @ddalle: v1.1; FUN3D version

  • 2016-04-14 @ddalle: v1.2; CFDX version

  • 2023-06-16 @ddalle: v2.0; CaseRunner method

get_restart_iter(f=True)

Get number of iteration if case should restart

Call:
>>> nr = runner.get_restart_iter(f=True)
Inputs:
runner: CaseRunner

Controller to run one case of solver

f: {True} | False

Force recalculation of phase

Outputs:
nr: int

Restart iteration number

Versions:
  • 2023-06-20 @ddalle: v1.0; cfdx abstract version

get_stop_iter()

Read iteration at which to stop

Call:
>>> nstop = runner.get_stop_iter()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
nstop: int | None

Iteration at which to stop, if any

Versions:
  • 2023-06-20 @ddalle: v1.0

getx_iter()

Calculate most recent iteration

Call:
>>> n = runner.getx_iter()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
n: int

Iteration number

Versions:
  • 2023-06-20 @ddalle: v1.0

getx_phase(n: int)

Calculate phase based on present files

The x in the title implies this case might be rewritten for each module.

Call:
>>> j = runner.getx_phase(n)
Inputs:
runner: CaseRunner

Controller to run one case of solver

n: int

Iteration number

Outputs:
j: int

Phase number for next restart

Versions:
  • 2023-06-16 @ddalle: v1.0

  • 2023-07-06 @ddalle: v1.1; PhaseSequence repeats ok

getx_restart_iter()

Calculate number of iteration if case should restart

Call:
>>> nr = runner.gets_restart_iter()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
nr: int

Restart iteration number

Versions:
  • 2023-06-20 @ddalle: v1.0; cfdx abstract version

init_post()

Custom initialization hook

Call:
>>> runner.init_post()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Versions:
  • 2023-06-28 @ddalle: v1.0

init_timer()

Mark a case as RUNNING and initialize a timer

Call:
>>> tic = runner.init_timer()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
tic: datetime.datetime

Time at which case was started

Versions:
  • 2021-10-21 @ddalle: v1.0; from run_fun3d()

  • 2023-06-20 @ddalle: v1.1; instance method, no mark()

mark_failure(msg='no details')

Mark the current folder in failure status using FAIL file

Call:
>>> runner.mark_failure(msg="no details")
Inputs:
runner: CaseRunner

Controller to run one case of solver

msg: {"no details"} | str

Error message for output file

Versions:
  • 2023-06-02 @ddalle: v1.0

  • 2023-06-20 @ddalle: v1.1; instance method

mark_running()

Check if cases already running and create RUNNING otherwise

Call:
>>> runner.mark_running()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Versions:
  • 2023-06-02 @ddalle: v1.0

  • 2023-06-20 @ddalle: v1.1; instance method, no check()

mark_stopped()

Delete the RUNNING file if it exists

Call:
>>> mark_stopped()
Versions:
  • 2023-06-02 @ddalle: v1.0

prepare_env(j: int)

Set environment vars, alter resource limits (ulimit)

This function relies on the system module resource

Call:
>>> runner.prepare_env(rc, i=0)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

See also:
Versions:
  • 2015-11-10 @ddalle: v1.0 (PrepareEnvironment())

  • 2023-06-02 @ddalle: v1.1; fix logic for appending
    • E.g. "PATH": "+$HOME/bin"

    • This is designed to append to path

  • 2023-06-20 @ddalle: v1.2; instance mthod

prepare_files(j: int)

Prepare files for phase j

Call:
>>> runner.prepare_files(j)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase index

Versions:
  • 2021-10-21 @ddalle: v1.0 (abstract cfdx method)

read_case_json(f=False)

Read case.json if not already

Call:
>>> rc = runner.read_case_json(f=False)
Inputs:
runner: CaseRunner

Controller to run one case of solver

f: True | {False}

Option to force re-read

Outputs:
rc: RunControlOpts

Options interface from case.json

Versions:
  • 2023-06-15 @ddalle: v1.0

read_condition(key: str, f=False)

Read conditions.json if not already

Call:
>>> v = runner.read_condition(key, f=False)
Inputs:
runner: CaseRunner

Controller to run one case of solver

key: str

Name of run matrix key to query

f: True | {False}

Option to force re-read

Outputs:
v: int | float | str

Value of run matrix key key

Versions:
  • 2023-06-16 @ddalle: v1.0

read_conditions(f=False)

Read conditions.json if not already

Call:
>>> xi = runner.read_conditions(f=False)
Inputs:
runner: CaseRunner

Controller to run one case of solver

f: True | {False}

Option to force re-read

Outputs:
xi: dict

Run matrix conditions for this case

Versions:
  • 2023-06-16 @ddalle: v1.0

read_start_time()

Read the most recent start time to file

Call:
>>> nProc, tic = runner.read_start_time()
Inputs:
runner: CaseRunner

Controller to run one case of solver

fname: str

Name of file containing CPU usage history

Outputs:
nProc: None | int

Number of cores

tic: None | datetime.datetime

Time at which most recent run was started

Versions:
  • 2016-08-30 @ddalle: v1.0 (stand-alone)

  • 2023-06-17 @ddalle: v2.0; CaseRunner method

resubmit_case(j0: int)

Resubmit a case as a new job if appropriate

Call:
>>> q = runner.resubmit_case(j0)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j0: int

Index of phase most recently run prior (may differ from current phase)

Outputs:
q: True | False

Whether or not a new job was submitted to queue

Versions:
run()

Setup and run appropriate solver commands

Call:
>>> ierr = runner.run()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
ierr: int

Return code; 0 for success

Versions:
  • 2014-10-02 @ddalle: v1.0

  • 2021-10-08 @ddalle: v1.1 (run_overflow)

  • 2023-06-21 @ddalle: v2.0; instance method

run_aflr3(j: int, proj: str, fmt='lb8.ugrid')

Create volume mesh using aflr3

This function looks for several files to determine the most appropriate actions to take:

  • {proj}.i.tri: Triangulation file

  • {proj}.surf: AFLR3 surface file

  • {proj}.aflr3bc: AFLR3 boundary conditions

  • {proj}.xml: Surface component ID mapping file

  • {proj}.{fmt}: Output volume mesh

  • {proj}.FAIL.surf: AFLR3 surface indicating failure

If the volume grid file already exists, this function takes no action. If the surf file does not exist, the function attempts to create it by reading the tri, xml, and aflr3bc files using cape.tri.Tri. The function then calls cape.bin.aflr3() and finally checks for the FAIL file.

Call:
>>> runner.run_aflr3(j, proj, fmt='lb8.ugrid')
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

proj: str

Project root name

fmt: {"lb8.ugrid"} | str

AFLR3 volume mesh format

Versions:
  • 2016-04-05 @ddalle: v1.0 (CaseAFLR3())

  • 2023-06-02 @ddalle: v1.1; use get_aflr3_run()

  • 2023-06-20 @ddalle: v1.1; instance method

run_intersect(j: int, proj='Components')

Run intersect to combine geometries if appropriate

This is a multistep process in order to preserve all the component IDs of the input triangulations. Normally intersect requires each intersecting component to have a single component ID, and each component must be a water-tight surface.

Cape utilizes two input files, Components.c.tri, which is the original triangulation file with intersections and the original component IDs, and Components.tri, which maps each individual original tri file to a single component. The files involved are tabulated below.

  • Components.tri: Intersecting components, each with own compID

  • Components.c.tri: Intersecting triangulation, original compIDs

  • Components.o.tri: Output of intersect, only a few compIDs

  • Components.i.tri: Original compIDs mapped to intersected tris

More specifically, these files are "%s.i.tri" % proj, etc.; the default project name is "Components". This function also calls the Chimera Grid Tools program triged to remove unused nodes from the intersected triangulation and optionally remove small triangles.

Call:
>>> runner.run_intersect(j, proj)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

proj: {'Components'} | str

Project root name

See also:
Versions:
  • 2015-09-07 @ddalle: v1.0 (CaseIntersect)

  • 2016-04-05 @ddalle: v1.1; generalize to cfdx

  • 2023-06-21 @ddalle: v1.2; update name; instance method

run_more_cases()

Submit more cases to the queue

Call:
>>> runner.run_more_cases()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Versions:
  • 2023-12-13 @dvicker: v1.0

run_phase(j: int) int

Run one phase using appropriate commands

Call:
>>> ierr = runner.run_phase(j)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

Outputs:
ierr: int

Return code

Versions:
  • 2023-06-05 @ddalle: v1.0 (pyover)

  • 2023-06-14 @ddalle: v1.0

run_post_shell_cmds(j: int)

Run PostShellCmds after successful run_phase() exit

Call:
>>> runner.run_post_shell_cmds(j)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

Versions:
  • 2023-07-17 @ddalle: v1.0

run_verify(j: int, proj='Components')

Run verify to check triangulation if appropriate

This function checks the validity of triangulation in file "%s.i.tri" % proj. It calls cape.bin.verify().

Call:
>>> runner.run_verify(j, proj='Components', fpre='run')
Inputs:
rc: RunControlOpts

Case options interface from case.json

proj: {'Components'} | str

Project root name

n: int

Iteration number

fpre: {'run'} | str

Standard output file name prefix

Versions:
  • 2015-09-07 @ddalle: v1.0; from run_flowCart()

  • 2016-04-05 @ddalle: v1.1; generalize to cape

  • 2023-06-21 @ddalle: v2.0; instance method

start()

Start or submit initial job

Call:
>>> ierr, job_id = runner.start()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Outputs:
ierr: int

Return code; 0 for success

job_id: None | int

PBS/Slurm job ID number if appropriate

Versions:
  • 2023-06-23 @ddalle: v1.0

stop_case()

Stop a case by deleting PBS job and removing RUNNING file

Call:
>>> runner.stop_case()
Inputs:
runner: CaseRunner

Controller to run one case of solver

Versions:
  • 2014-12-27 @ddalle: v1.0 (StopCase())

  • 2023-06-20 @ddalle: v1.1; instance method

  • 2023-07-05 @ddalle: v1.2; use CaseRunner.get_job_id()

write_start_time(j: int)

Write current start time, runner.tic, to file

Call:
>>> runner.write_start_time(tic, j)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

Versions:
  • 2015-12-09 @ddalle: v1.0 (pycart)

  • 2015-12-22 @ddalle: v1.0; module function

  • 2023-06-16 @ddalle: v2.0; CaseRunner method

write_user_time(j: int)

Write time usage since time tic to file

Call:
>>> runner.write_user_time(tic, j)
Inputs:
runner: CaseRunner

Controller to run one case of solver

j: int

Phase number

Versions:
  • 2015-12-09 @ddalle: v1.0 (pycart)

  • 2015-12-22 @ddalle: v1.0; module function

  • 2023-06-16 @ddalle: v2.0; CaseRunner method

cape.cfdx.case.GetTriqFile(proj='Components')

Get most recent triq file and its associated iterations

This is a template version with specific implementations for each solver. The cape.cfdx version simply returns the most recent triq file in the folder with no iteration information.

Call:
>>> ftriq, n, i0, i1 = GetTriqFile(proj='Components')
Inputs:
proj: {"Components"} | str

File root name

Outputs:
ftriq: str

Name of most recently modified triq file

n: {None}

Number of iterations included

i0: {None}

First iteration in the averaging

i1: {None}

Last iteration in the averaging

Versions:
  • 2016-12-19 @ddalle: v1.0

cape.cfdx.case.StartCase()

Empty template for starting a case

The function is empty but does not raise an error

Call:
>>> cape.case.StartCase()
See also:
  • cape.pycart.case.StartCase()

  • cape.pyfun.case.StartCase()

  • cape.pyover.case.StartCase()

Versions:
  • 2015-09-27 @ddalle: v1.0

  • 2023-06-02 @ddalle: v2.0; empty

cape.cfdx.case.run_rootdir(func)

Decorator to run a function within a specified folder

Call:
>>> func = run_rootdir(func)
Wrapper Signature:
>>> v = runner.func(*a, **kw)
Inputs:
func: func

Name of function

runner: CaseRunner

Controller to run one case of solver

a: tuple

Positional args to cntl.func()

kw: dict

Keyword args to cntl.func()

Versions:
  • 2020-02-25 @ddalle: v1.1 (cape.cntl)

  • 2023-06-16 @ddalle: v1.0

cape.cfdx.case.set_rlimit(r, ulim, u, i=0, unit=1024)

Set resource limit for one variable

Call:
>>> set_rlimit(r, ulim, u, i=0, unit=1024)
Inputs:
r: int

Integer code of particular limit, from resource

ulim: cape.options.ulimit.ulimit

System resource options interface

u: str

Name of limit to set

i: int

Phase number

unit: int

Multiplier, usually for a kbyte

See also:
  • cape.options.ulimit

Versions:
  • 2016-03-13 @ddalle: v1.0

  • 2021-10-21 @ddalle: v1.1; check if Windows

  • 2023-06-20 @ddalle: v1.2; was SetResourceLimit()