# 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 scale_mesh 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 mesh transform func 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 ramp func 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 Forces transform 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 initial 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 initial condition 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 Static Pressure Ratio 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 Total Pressure Ratio 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 Total Temperature Ratio 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 vector 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]
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
},
...
}
```