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 cell-by-cell 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 re-evaluated 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 time-stepping 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: under-relaxation, >1: over-relaxation
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
},
...
}