In our most recent webinar there were some questions about how to maximize the performance of Coreform Cubit scripts. While I included a few tips in a post a couple years ago that are still relevant I thought it would be good to provide an update / more thorough guidance. First, I’ll provide the list then I’ll show them in action:
The Tips List
- Precompute and reuse data to minimize Cubit-Python API calls.
- Minimize redundant calls into the Cubit-Python API
- Minimize repeated calls into nested objects / functions
- Minimize usage of
compress
(e.g. move to outer-most loop) - Turn off messages
- Information:
info off
- Warnings:
warning off
– not as critical asinfo
, but if your script generates a lot of warnings this is a good setting
- Information:
- Turn off undo:
undo off
- Use the Python
time
module to measure performance- e.g.
time.perf_counter()
- e.g.
Additional performance tips that are relevant if you’re using your script within the GUI application:
- Pause graphics:
graphics off
(then dographics on
to unpause)- Equivalently:
graphics pause
→graphics flush
- Equivalently:
- If pausing graphics is unacceptable, then at least ensure:
- A coarse graphics faceting ( e.g.
graphics tolerance angle 15
) - That you’re not using the
transparent
graphics mode
- A coarse graphics faceting ( e.g.
Simple Examples
Precompute and reuse data to minimize Cubit-Python API calls.
import numpy
import time
cubit.cmd( "reset" )
cubit.cmd( "bri x 1" )
cubit.cmd( "vol 1 size 0.025" )
cubit.cmd( "mesh vol 1" )
def do_something( node_coords ):
val = numpy.sqrt( node_coords[0]**2 + node_coords[1]**2 + node_coords[2]**2 )
return val
# DON'T DO THIS
t0 = time.perf_counter()
hex_id_list = cubit.get_entities( "hex" )
for hex_id in hex_id_list:
hex_node_ids = cubit.get_connectivity( "hex", hex_id )
for node_idx in range( 0, len( hex_node_ids ) ):
node_coords = cubit.get_nodal_coordinates( hex_node_ids[node_idx] )
do_something( node_coords )
t1 = time.perf_counter()
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
# DO THIS
t0 = time.perf_counter()
node_id_list = cubit.get_entities( "node" )
node_coords = {}
for node_id in node_id_list:
node_coords[node_id] = cubit.get_nodal_coordinates( node_id )
for node_id in hex_node_ids:
do_something( node_coords[node_id] )
t1 = time.perf_counter()
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
On my machine the first approach takes 1.25 seconds
while the latter takes 0.1 seconds
.
Minimize repeated calls into nested objects / functions
cubit.cmd( "reset" )
cubit.cmd( "create torus major radius 1 minor radius 0.1" )
cubit.cmd( "split periodic vol all" )
cubit.cmd( "Volume all copy move x 3 repeat 10" )
cubit.cmd( "Volume all copy move y 3 repeat 10" )
cubit.cmd( "Volume all copy move z 3 repeat 10" )
curve_id_list = cubit.get_entities( "curve" )
def dosomething( val ):
return 2 * val
# DON'T DO THIS
t0 = time.perf_counter()
for cid in curve_id_list:
val = cubit.curve( cid ).tangent( cubit.curve( cid ).position_from_u( 0.5 ) )
thing = dosomething( val )
t1 = time.perf_counter()
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
# DO THIS
t0 = time.perf_counter()
for cid in curve_id_list:
C = cubit.curve( cid ) # <--- CAPTURE THE CURVE IN A VARIABLE
val = C.tangent( C.position_from_u( 0.5 ) ) # <--- MINIMAL NESTED CALLS
thing = dosomething( val )
t1 = time.perf_counter()
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
On my machine the first approach takes 27 seconds
while the latter takes 12.5 seconds
.
Turn off messages
xyz = numpy.random.random( shape=(1000,3) )
# DON'T DO THIS
cubit.cmd( "reset" )
t0 = time.perf_counter()
for n in range( 0, xyz.shape[0] ):
cubit.cmd( f"create vertex location {xyz[n,0]} {xyz[n,1]} {xyz[n,2]}" )
t1 = time.perf_counter()
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
# DO THIS
cubit.cmd( "reset" )
cubit.cmd( "info off" ) # <--- TURN 'INFORMATION' OFF
t0 = time.perf_counter()
for n in range( 0, xyz.shape[0] ):
cubit.cmd( f"create vertex location {xyz[n,0]} {xyz[n,1]} {xyz[n,2]}" )
t1 = time.perf_counter()
cubit.cmd( "info on" )
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
On my machine the first approach takes 4 seconds
while the latter takes 2 seconds
.
Turn off undo
def do_setup():
cubit.cmd( "reset" )
cubit.cmd( "create torus major radius 1 minor radius 0.1" )
cubit.cmd( "split periodic vol all" )
cubit.cmd( "Volume all copy move x 3 repeat 10" )
cubit.cmd( "Volume all copy move y 3 repeat 10" )
torus_vols = cubit.get_entities( "volume" )
cubit.cmd( "create brick bounding box Volume all tight" )
target_vid = cubit.get_last_id( "volume" )
center = cubit. Volume(target_vid).center_point()
cubit.cmd( f"volume {target_vid} scale x 1.25 y 1.25 z 4 about {center[0]} {center[1]} {center[2]}" )
# DON'T DO THIS
cubit.cmd( "info off" ) # do this though
t0 = time.perf_counter()
for vid in torus_vols:
target_vid = cubit.get_last_id( "volume" )
cubit.cmd( f"subtract volume {vid} from volume {target_vid}" )
t1 = time.perf_counter()
cubit.cmd( "info on" )
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
# DO THIS
cubit.cmd( "info off" )
cubit.cmd( "undo off" ) # <--- TURN 'UNDO' OFF
t0 = time.perf_counter()
for vid in torus_vols:
target_vid = cubit.get_last_id( "volume" )
cubit.cmd( f"subtract volume {vid} from volume {target_vid}" )
t1 = time.perf_counter()
cubit.cmd( "info on" )
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
On my machine the first approach takes 21 seconds
while the latter takes 8 seconds
.
Pause graphics
def do_setup():
cubit.cmd( "reset" )
cubit.cmd( "create torus major radius 1 minor radius 0.1" )
cubit.cmd( "split periodic vol all" )
cubit.cmd( "Volume all copy move x 3 repeat 10" )
cubit.cmd( "Volume all copy move y 3 repeat 10" )
torus_vols = cubit.get_entities( "volume" )
cubit.cmd( "create brick bounding box Volume all tight" )
target_vid = cubit.get_last_id( "volume" )
center = cubit. Volume(target_vid).center_point()
cubit.cmd( f"volume {target_vid} scale x 1.25 y 1.25 z 4 about {center[0]} {center[1]} {center[2]}" )
return torus_vols, target_vid
# DON'T DO THIS
cubit.cmd( "info off" )
cubit.cmd( "undo off" )
torus_vid, target_vid = do_setup()
t0 = time.perf_counter()
for vid in torus_vid:
target_vid = cubit.get_last_id( "volume" )
cubit.cmd( f"subtract volume {vid} from volume {target_vid}" )
t1 = time.perf_counter()
cubit.cmd( "info on" )
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
# DO THIS
cubit.cmd( "info off" )
cubit.cmd( "undo off" )
cubit.cmd( "graphics off" ) # <--- PAUSE GRAPHICS
torus_vid, target_vid = do_setup()
t0 = time.perf_counter()
for vid in torus_vid:
target_vid = cubit.get_last_id( "volume" )
cubit.cmd( f"subtract volume {vid} from volume {target_vid}" )
t1 = time.perf_counter()
cubit.cmd( "info on" )
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
cubit.cmd( "graphics on" ) # <--- UNPAUSE GRAPHICS
On my machine the first approach takes 293 seconds
while the latter takes 8 seconds
.
Of course, with this simple example it would be even better to consider the first few tips… we can combine these many subtract
operations with a single subtract
operation:
def list_to_str( input_list ):
return " ".join( [ str( val ) for val in input_list ] )
cubit.cmd( "info off" )
cubit.cmd( "undo off" )
cubit.cmd( "graphics on" )
torus_vid, target_vid = do_setup()
t0 = time.perf_counter()
cubit.cmd( f"subtract volume {list_to_str( torus_vid )} from {target_vid}" )
t1 = time.perf_counter()
cubit.cmd( "info on" )
print( f"ELAPSED TIME: {t1 - t0} SECONDS" )
Which then only takes approximately 0.005 seconds
.
Use a coarse graphics tolerance
Summary
If this was helpful to you, if you have any other questions, or know of some other performance tips, leave a comment!