zCFD Functions¶
Several parameters in the zCFD control dictionary will accept functions as arguments. These functions are then evaluated by the solver when required. This allows you to customise & tune to solver as it runs.
Scale Mesh Function¶
Description 
Transform the location of a supplied point. 
Valid for parameter 

Parameters 
[x, y, z]: Point to transform 
Returns 
{‘coord’: [x, y, z]}: Transformed coordinates 
Example:
# Custom function to scale z coordinate by 100
def my_scale_func(point):
point[2] = point[2] * 100.0
return {'coord' : point}
parameters = {
...
'scale': my_scale_func
...
}
Transform Mesh Function¶
Description 
Transform the location of a supplied point. 
Valid for parameter 

Parameters 
[x, y, z]: Point to transform 
Returns 
{“v1”: x, “v2”: y, “v3”: z}: Transformed coordinates 
Example:
rotation = 10.0
def my_transform(x, y, z):
v = [x, y, z]
v = zutil.rotate_vector(v, rotation, 0.0)
return {"v1": v[0], "v2": v[1], "v3": v[2]}
Ramp CFL Function¶
There may be cases where more detailed control of the CFL number is desired  for example in some aerodynamic simulations where a specific physical event occurs at a given time. The CFL Ramp function lets you do this.
The ramp function returns a single floating point number (the CFL number) and can also be used to update elements of the ‘cfl’ Python object such as cfl.min_cfl, cfl.max_cfl, cfl.current_cfl, cfl.coarse_cfl, cfl.transport_cfl, cfl.cfl_ramp, cfl.cfl_pmg, or cfl.transport_cfl_pmg.
Description 
Provide a dynamic CFL number 
Valid for parameter 

Parameters 
solve_cycle, real_time_cycle, cfl 
Returns 
cfl: float 
Example:
# Custom function to ramp CFL based on cycle, updates the cfl object variables and returns a CFL
def my_cfl_ramp(solve_cycle, real_time_cycle, cfl):
cfl.min_cfl = 1.0
cfl.max_cfl = 10.0
cfl.cfl_ramp = 1.005
cfl_transport = 5.0
if solve_cycle < 500:
cfl_new = min(cfl.min_cfl * cfl.cfl_ramp ** (max(0, solve_cycle  1)), cfl.max_cfl)
cfl.transport_cfl = cfl_transport * cfl_new / cfl.max_cfl
return cfl_new
elif solve_cycle < 1000:
cfl.trasport_cfl = 7.5
cfl.max_cfl = 15.0
return 15.0
else:
cfl.transport_cfl = 10.0
cfl.max_cfl = 30.0
return 30.0
parameters = {
...
'time marching': {
...
'ramp func': my_cfl_ramp,
...
}
...
}
Transform Function¶
A transformation function my be supplied to the force report block to transform the force into a different coordinate system. This is particularly useful for reporting lift and drag, which are often rotated from the solver coordinate system.
Description 
Transform a supplied point  rotate, scale or translate. 
Valid for parameter 

Parameters 
(x, y, z): floats 
Returns 
(x, y, z): floats 
Example:
#zutil is a Zenotech python library providing various utilities, available within a zCFD installation
import zutil
# Angle of attack
alpha = 10.0
# Transform into wind axis
def my_transform(x,y,z):
v = [x,y,z]
v = zutil.rotate_vector(v,alpha,0.0)
return {v[0], v[1], v[2]}
Initialisation Function¶
Description 
Provide the initial flow field variables on a cellbycell basis 
Valid for parameter 

Parameters 
kwargs dictionary containing “pressure”, “temperature”, “velocity”, “wall_distance”, “location” 
Returns 
dictionary containing one or more of “pressure”, “temperature” or “velocity” 
def my_initialisation(**kwargs):
# Dimensional primitive variables
pressure = kwargs['pressure']
temperature = kwargs['temperature']
velocity = kwargs['velocity']
wall_distance = kwargs['wall_distance']
# Cell centre localtion [X, Y, Z]
location = kwargs['location']
if location[0] > 10.0:
velocity[0] *= 2.0
# Return a dictionary with user defined quantity.
return { 'velocity' : velocity }
parameters = {
...
'initial': {
...
'func': my_initialisation,
...
}
...
}
Driven initial condition¶
A ‘driving function’ provides another way of prescribing a farfield initial condition, which on evaluation returns an initial condition dictionary.
The driving function is reevaluated at zCFD’s reporting frequency, meaning that the condition (e.g. inlet velocity) can be programmed to change as the simulation progresses.
At startup, the function is only provided with the key word arguments RealTimeStep=0, Cycle=0. However, the driving function is subsequently provided with key word arguments containing all the data in the ‘_report.csv’ file, evaluated at the previous timestep. For any driven initial condition, every value in the initial condition dictionary is also appended to the _report.csv file.
This means that the initial condition can be programmed to respond to the flow. An example is shown below, where the driven IC will continually adjust inlet angle of attack until a specified lift coefficient is achieved.
If the driven initial condition has been programmed to respond to changes in the flow, it is important to ensure that the flow has had enough time to propagate from the farfield through to the region of interest and settle before readjusting the driven initial condition. For simulations where the farfield is distant from the geometry, local and implicit timestepping will likely give the fastest propagation of adjusted farfield conditions through the domain.
Note
Driven initial conditions cannot be used as reference conditions, to initialise the flow (e.g ‘IC_1’) or within a restarted simulation.
Description 
Provides a farfield initial condition that can respond to changes in the flow. 
Valid for parameter 

Parameters 
kwargs dictionary containing “RealTimeStep”, “Cycle” and all reporting variables 
Returns 
dictionary containing valid keys for an initial condition 
def example_ic_func(**kwargs):
"""
Example driver function to target a lift coefficient by varying
angle of attack
"""
alpha_init = 0 # Initial angle of attack
lift_target= 0.5 # Targeted lift coefficient
update_period = 1000 # How often alpha should be updated
flow_settling_period = 1500
assumed_d_cL_d_alpha = 0.1 # Used to estimate alpha which would give required lift
relaxation_factor = 1.15 # <1: underrelaxation, >1: overrelaxation
if 'lift_target_alpha' in kwargs.keys(): # If timestep > 1
alpha_current = kwargs['lift_target_alpha']
F_xyz = [kwargs['wall_Fx'], kwargs['wall_Fy'], kwargs['wall_Fz']]
F_LDS = zutil.rotate_vector(F_xyz, alpha_current, 0.0)
lift_current = F_LDS[2] # Lift coefficient, having rotated to account for alpha
else:
lift_current = 0 # placeholder value
if kwargs['Cycle'] < flow_settling_period:
alpha_new = alpha_init
else:
if (kwargs['Cycle'] % update_period) == 0:
d_cL_required = lift_target  lift_current
d_alpha = relaxation_factor / assumed_d_cL_d_alpha * d_cL_required
alpha_new = alpha_current + d_alpha
print("Alpha updated from {} to {}".format(alpha_current, alpha_new), flush = True)
else:
alpha_new = alpha_current
dict_out = {
"temperature": 300,
"pressure": 101325.0,
'v': {
'mach' : 0.15,
'vector' : zutil.vector_from_angle(alpha_new, 0.0)
},
"reynolds no": 6.0e6,
"eddy viscosity ratio": 1.0,
"monitor": { # Monitor entries can be floats or lists of floats
"prefix": "lift_target",
"alpha": alpha_new, # Extra keys can be added to a driven IC dictionary,
"lift": lift_current, # allowing monitoring of key parameters flow in the _report.csv
},
}
return dict_out
parameters = {
...
'IC_2' : example_ic_func
...
}
Static Pressure Ratio¶
The python function takes in three values (x, y, z) and returns the static pressure ratio.
Description 
Returns a static pressure ratio for a given point. 
Valid for parameter 

Parameters 
(x, y, z): floats 
Returns 
float 
Total Pressure Ratio¶
The python function takes in three values (x, y, z) and returns the total pressure ratio.
Description 
Returns a total pressure ratio for a given point. 
Valid for parameter 

Parameters 
(x, y, z): floats 
Returns 
float 
Total Temperature Ratio¶
The python function takes in three values (x, y, z) and returns the temperature pressure ratio.
Description 
Returns a total temperature ratio for a given point. 
Valid for parameter 

Parameters 
(x, y, z): floats 
Returns 
float 
Direction Vector¶
The python function takes in three values (x, y, z) and returns a tuple (x, y, z).
Description 
Returns a direction vector for a given point. 
Valid for parameter 

Parameters 
(x, y, z): floats 
Returns 
(x, y, z): floats 
def inflow_direction(x, y, z):
# x, y, and z are the coordinates of the face centre
cen = [1.0, 0.0, 0.0]
y1 = y  cen[1]
z1 = z  cen[2]
rad = math.sqrt(z1*z1 + y1*y1)
phi = math.atan2(y1,z1)
angle = 0.24
x2 = math.cos(angle)
y2 = math.sin(phi) * math.sin(angle)
z2 = math.cos(phi) * math.sin(angle)
return (x2, y2, z2)
parameters = {
...
'IC_2' : {
'reference' : 'IC_1',
'vector' : inflow_direction
},
...
}