|
|
## Custom scripts
|
|
## Custom scripts
|
|
|
Users can customize their use of SDPM by writing their own scripts. Each script follows a similar pattern:
|
|
Users can customize their use of SDPM by writing their own scripts. Each script follows a similar pattern:
|
|
|
1. Creating the mesh
|
|
1. Creating the mesh
|
|
|
2. Defining the problem
|
|
2. Defining the problem
|
|
|
3. Solving the flow
|
|
3. Solving the flow
|
|
|
4. Post-processing the results
|
|
4. Post-processing the results
|
|
|
|
|
|
|
|
_Note_ SDPM can be used in a development or in a production environment. For the former, the code needs to be compiled, and then launched using [run.py](https://gitlab.uliege.be/am-dept/sdpm/blob/master/run.py). For the latter, the code needs to be installed to a location which is in the python path (the default install path is the user's python site-packages).
|
|
_Note_ SDPM can be used in a development or in a production environment. For the former, the code needs to be compiled, and then launched using [run.py](https://gitlab.uliege.be/am-dept/sdpm/blob/master/run.py). For the latter, the code needs to be installed to a location which is in the python path (the default install path is the user's python site-packages).
|
|
|
|
|
|
|
|
### Creating the mesh
|
|
### Creating the mesh
|
|
|
The mesh is created using
|
|
The mesh is created using
|
|
|
```python
|
|
```python
|
|
|
msh = sdpm.gmsh.MeshLoader(model_path).run(pars)
|
|
msh = sdpm.gmsh.MeshLoader(model_path).run(pars)
|
|
|
```
|
|
```
|
|
|
where `model_path` is the path to the file containing the model and `pars` is an optional dictionary of parameters to configure Gmsh. The model can either be a geometry (.geo) or a mesh (.msh). In the latter case, any parameters are ignored.
|
|
where `model_path` is the path to the file containing the model and `pars` is an optional dictionary of parameters to configure Gmsh. The model can either be a geometry (.geo) or a mesh (.msh). In the latter case, any parameters are ignored.
|
|
|
|
|
|
|
|
### Defining the problem
|
|
### Defining the problem
|
|
|
The problem is defined as follows
|
|
The problem is defined as follows
|
|
|
```python
|
|
```python
|
|
|
pbl = sdpm.Problem(msh)
|
|
pbl = sdpm.Problem(msh)
|
|
|
pbl.setGeometry(sref, cref, xref, yref, zref, ysym)
|
|
pbl.setGeometry(sref, cref, xref, yref, zref, ysym)
|
|
|
pbl.setFreestream(aoa, aos, minf)
|
|
pbl.setFreestream(aoa, aos, minf)
|
|
|
pbl.setUnsteady(n_modes, kred)
|
|
pbl.setUnsteady(n_modes, kred)
|
|
|
```
|
|
```
|
|
|
where `sref` is the reference surface area, `cref` is the reference chord length, `xref`, `yref` and `zref` are the coordinates of the reference center around which aerodynamic moments are computed, `ysym` indicates whether only a half model is provided and y-symmetry boundary condition should be applied, `aoa` and `aos` are the angles of attack and of side slip, `minf` is the freestream Mach number, `n_modes` is the number of unsteady motions and `kred` is an array of reduced frequencies. Note that the last line can be ignored for steady problems.
|
|
where `sref` is the reference surface area, `cref` is the reference chord length, `xref`, `yref` and `zref` are the coordinates of the reference center around which aerodynamic moments are computed, `ysym` indicates whether only a half model is provided and y-symmetry boundary condition should be applied, `aoa` and `aos` are the angles of attack and of side slip, `minf` is the freestream Mach number, `n_modes` is the number of unsteady motions and `kred` is an array of reduced frequencies. Note that the last line can be ignored for steady problems.
|
|
|
Lifting bodies are then added using
|
|
Lifting bodies are then added using
|
|
|
```python
|
|
```python
|
|
|
wing = sdpm.Body(msh, body_name, te_name, cref, n_div, n_modes, n_kred)
|
|
wing = sdpm.Body(msh, body_name, te_name, cref, n_div, n_modes, n_kred)
|
|
|
pbl.addBody(wing)
|
|
pbl.addBody(wing)
|
|
|
```
|
|
```
|
|
|
where `body_name` and `te_name` are the names of the lifting body to be added and of its trailing edge in the Gmsh model, `n_div` is the number of chordwise panels, and `n_kred` is the number of reduced frequencies. For steady problems, the last two arguments default to 0 and `n_div` can be set to 1.
|
|
where `body_name` and `te_name` are the names of the lifting body to be added and of its trailing edge in the Gmsh model, `n_div` is the number of chordwise panels, and `n_kred` is the number of reduced frequencies. For steady problems, the last two arguments default to 0 and `n_div` can be set to 1.
|
|
|
Unsteady rigid body motions can be added using
|
|
Unsteady rigid body motions can be added using
|
|
|
```python
|
|
```python
|
|
|
wing.addMotion()
|
|
wing.addMotion()
|
|
|
wing.setRigidMotion(i_mode, r_amp, d_amp, x_ref, z_ref)
|
|
wing.setRigidMotion(i_mode, r_amp, d_amp, x_ref, z_ref)
|
|
|
```
|
|
```
|
|
|
where `i_mode` is the number of the motion being configured, `r_amp` is the amplitude of rotation about y, `d_amp` is the amplitude of displacement along z, and `x_ref` and `z_ref` are the x and z coordinates of the points around which the body rotates.
|
|
where `i_mode` is the number of the motion being configured, `r_amp` is the amplitude of rotation about y, `d_amp` is the amplitude of displacement along z, and `x_ref` and `z_ref` are the x and z coordinates of the points around which the body rotates.
|
|
|
Unsteady flexible motions (mode shapes) can be added using
|
|
Unsteady flexible motions (mode shapes) can be added using
|
|
|
```python
|
|
```python
|
|
|
x_modes, amp_modes = sdpm.utils.interpolate_modes(modes_path, wing.getNodes())
|
|
x_modes, amp_modes = sdpm.utils.interpolate_modes(modes_path, wing.getNodes())
|
|
|
for i in range(n_modes):
|
|
for i in range(n_modes):
|
|
|
wing.addMotion()
|
|
wing.addMotion()
|
|
|
wing.setFlexibleMotion(i, 1 / amp_modes[i], x_modes[:, 3*i:3*i+3])
|
|
wing.setFlexibleMotion(i, 1 / amp_modes[i], x_modes[:, 3*i:3*i+3])
|
|
|
```
|
|
```
|
|
|
where `modes_path` is the path to the file containing the mode shapes. Each row of this file must contain the x, y and z coordinates of each point on the body surface, as well as the components corresponding to the displacement along z and to the rotations around x and y. The data must then be organized as: x, y, z, dz0, rx0, ry0, dz1, rx1, ry1, etc. The utility `interpolate_modes` uses SciPy RBF implementation to interpolate the modes on the aerodynamic grid.
|
|
where `modes_path` is the path to the file containing the mode shapes. Each row of this file must contain the x, y and z coordinates of each point on the body surface, as well as the components corresponding to the displacement along z and to the rotations around x and y. The data must then be organized as: x, y, z, dz0, rx0, ry0, dz1, rx1, ry1, etc. The utility `interpolate_modes` uses SciPy RBF implementation to interpolate the modes on the aerodynamic grid.
|
|
|
A transonic correction can be added using
|
|
A transonic correction can be added using
|
|
|
```python
|
|
```python
|
|
|
dcp = sdpm.utils.interpolate_pressure(pres_path, wing.getNodes())
|
|
dcp = sdpm.utils.interpolate_pressure(pres_path, wing.getNodes())
|
|
|
wing.setTransonicPressureGrad(dcp[:,0])
|
|
wing.setTransonicPressureGrad(dcp[:,0])
|
|
|
```
|
|
```
|
|
|
where `pres_path` is the path to the file containing the reference pressure derivative with respect to the angle of attack. Each row of this file must contain the x, y and z coordinates of each point on the body surface, as well as its associated pressure derivative. The utility `interpolate_pressure` uses SciPy RBF implementation to interpolate the pressure derivative on the aerodynamic grid.
|
|
where `pres_path` is the path to the file containing the reference pressure derivative with respect to the angle of attack. Each row of this file must contain the x, y and z coordinates of each point on the body surface, as well as its associated pressure derivative. The utility `interpolate_pressure` uses SciPy RBF implementation to interpolate the pressure derivative on the aerodynamic grid.
|
|
|
|
|
|
|
|
### Solving the flow
|
|
### Solving the flow
|
|
|
Once the problem has been fully defined, the solver can be instantiated, updated and run using
|
|
Once the problem has been fully defined, the solver can be instantiated, updated and run using
|
|
|
```python
|
|
```python
|
|
|
sol = sdpm.Solver(pbl)
|
|
sol = sdpm.Solver(pbl)
|
|
|
sol.update()
|
|
sol.update()
|
|
|
sol.run()
|
|
sol.run()
|
|
|
```
|
|
```
|
|
|
If a transonic correction is to be applied, `SolverTransonic` can be used instead of `Solver`. The solution can be saved in Gmsh format using
|
|
If a transonic correction is to be applied, `SolverTransonic` can be used instead of `Solver`. The solution can be saved in Gmsh format using
|
|
|
```python
|
|
```python
|
|
|
sol.save(sdpm.GmshExport(msh))
|
|
sol.save(sdpm.GmshExport(msh))
|
|
|
```
|
|
```
|
|
|
If the gradients of the solution are needed, automatic differentiation must be enabled and the solver must be run using
|
|
If the gradients of the solution are needed, automatic differentiation must be enabled and the solver must be run using
|
|
|
```python
|
|
```python
|
|
|
adj = sdpm.Adjoint(sol)
|
|
adj = sdpm.Adjoint(sol)
|
|
|
adj.solve()
|
|
adj.solve()
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
### Post-processing the solution
|
|
### Post-processing the solution
|
|
|
The solution can be accessed in Python, mainly by using
|
|
The solution can be accessed in Python, mainly by using
|
|
|
```python
|
|
```python
|
|
|
# Steady and unsteady pressure
|
|
# Steady and unsteady pressure
|
|
|
sol.getPressure()
|
|
sol.getPressure()
|
|
|
sol.getUnsteadyPressureReal(i_mode, i_freq)
|
|
sol.getUnsteadyPressureReal(i_mode, i_freq)
|
|
|
sol.getUnsteadyPressureImag(i_mode, i_freq)
|
|
sol.getUnsteadyPressureImag(i_mode, i_freq)
|
|
|
# Steady and unsteady aerodynamic load coefficients
|
|
# Steady and unsteady aerodynamic load coefficients
|
|
|
sol.getLiftCoeff()
|
|
sol.getLiftCoeff()
|
|
|
sol.getDragCoeff()
|
|
sol.getDragCoeff()
|
|
|
sol.getSideforceCoeff()
|
|
sol.getSideforceCoeff()
|
|
|
sol.getMomentCoeff()
|
|
sol.getMomentCoeff()
|
|
|
sol.getUnsteadyLiftCoeffReal(i_mode, i_freq)
|
|
sol.getUnsteadyLiftCoeffReal(i_mode, i_freq)
|
|
|
sol.getUnsteadyLiftCoeffImag(i_mode, i_freq)
|
|
sol.getUnsteadyLiftCoeffImag(i_mode, i_freq)
|
|
|
sol.getUnsteadyDragCoeffReal(i_mode, i_freq)
|
|
sol.getUnsteadyDragCoeffReal(i_mode, i_freq)
|
|
|
sol.getUnsteadyDragCoeffImag(i_mode, i_freq)
|
|
sol.getUnsteadyDragCoeffImag(i_mode, i_freq)
|
|
|
sol.getUnsteadySideforceCoeffReal(i_mode, i_freq)
|
|
sol.getUnsteadySideforceCoeffReal(i_mode, i_freq)
|
|
|
sol.getUnsteadySideforceCoeffImag(i_mode, i_freq)
|
|
sol.getUnsteadySideforceCoeffImag(i_mode, i_freq)
|
|
|
sol.getUnsteadyMomentCoeffReal(i_mode, i_freq)
|
|
sol.getUnsteadyMomentCoeffReal(i_mode, i_freq)
|
|
|
sol.getUnsteadyMomentCoeffImag(i_mode, i_freq)
|
|
sol.getUnsteadyMomentCoeffImag(i_mode, i_freq)
|
|
|
# Generalized aerodynamic forces matrix
|
|
# Generalized aerodynamic forces matrix
|
|
|
sol.getGafMatrixRe(i_freq)
|
|
sol.getGafMatrixRe(i_freq)
|
|
|
sol.getGafMatrixIm(i_freq)
|
|
sol.getGafMatrixIm(i_freq)
|
|
|
```
|
|
```
|
|
|
where `i_mode`, `i_freq` and the indices of the requested mode and frequency.
|
|
where `i_mode`, `i_freq` and the indices of the requested mode and frequency.
|
|
|
Additionally, chordwise pressure distributions can be extracted along the wing span by using
|
|
Additionally, chordwise pressure distributions can be extracted along the wing span by using
|
|
|
```python
|
|
```python
|
|
|
sdpm.utils.create_slices(wing, vars, model_name, y_sec)
|
|
sdpm.utils.create_slices(wing, vars, model_name, y_sec)
|
|
|
```
|
|
```
|
|
|
where `vars` is a dictionary containing the name and its corresponding data array (e.g. `{'cp': sol.getPressure()}`), `model_name` is the name of the model (inherited from the model file) and `y_sec` is an array of y coordinates.
|
|
where `vars` is a dictionary containing the name and its corresponding data array (e.g. `{'cp': sol.getPressure()}`), `model_name` is the name of the model (inherited from the model file) and `y_sec` is an array of y coordinates.
|
|
|
The sensitivities of the solution can be computed using
|
|
The derivatives of the Generalized Aerodynamic Force (GAF) matrices can be calculated and accessed using
|
|
|
```python
|
|
```python
|
|
|
sens = adj.compute(of, seeds, wrt)
|
|
grd = sdpm.Gradient(sol)
|
|
|
```
|
|
grd.run()
|
|
|
where `of` is the name of the variable for which the sensitivities are requested, `seeds` is an array of seed values and `wrt` is an array of names of the variables which will be differentiated with respect to. The sensitivities will be stored in the dictionary `sens` and can be accessed using `sens[wrt_var][i]` where `wrt_var` is one of the variable of `wrt` and `i` is the index of interest if the variable `wrt_var` is an array.
|
|
grd.computePartialsGafRe(i_freq, i_mode, i_row, j_col, wrt)
|
|
|
|
|
grd.computePartialsGafIm(i_freq, i_mode, i_row, j_col, wrt)
|
|
|
_Note_ currently, the variables `cl`, `cd`, `cl1_re`, `cl1_im`, `cd1_re` and `cd1_im` can be differentiated with respect to `aoa` and `x_wing`.
|
|
```
|
|
|
_Note_ here, the sensitivity is the (adjoint) transposed matrix-vector product between the gradient and the seed. If `df` and `dx` are the seed and the sensitivity vectors, it can be expressed as
|
|
The last two lines will return the derivative of the real and imaginary parts of the GAF matrix entry at row `i_row` and column `j_col` with respect to the component `wrt` of mode number `i_mode` at frequency `i_freq` for all body nodes. `wrt` can either be `'a'` (rigid pitch), `'h'` (rigid plunge), `'dz'` (modal displacements along z), `'rx'` (modal rotations about x) or `'ry'` (modal rotations about y).
|
|
|
```python
|
|
If automatic differentiation has been enabled, the sensitivities of the solution can be computed using
|
|
|
[dx0] = [df0_dx0 df1_dx0] [df0]
|
|
```python
|
|
|
[dx1] [df0_dx1 df1_dx1] [df1]
|
|
sens = adj.compute(of, seeds, wrt)
|
|
|
|
```
|
|
|
|
where `of` is the name of the variable for which the sensitivities are requested, `seeds` is an array of seed values and `wrt` is an array of names of the variables which will be differentiated with respect to. The sensitivities will be stored in the dictionary `sens` and can be accessed using `sens[wrt_var][i]` where `wrt_var` is one of the variable of `wrt` and `i` is the index of interest if the variable `wrt_var` is an array.
|
|
|
|
|
|
|
|
_Note_ currently, the variables `cl`, `cd`, `cl1_re`, `cl1_im`, `cd1_re` and `cd1_im` can be differentiated with respect to `aoa` and `x_wing`.
|
|
|
|
_Note_ here, the sensitivity is the (adjoint) transposed matrix-vector product between the gradient and the seed. If `df` and `dx` are the seed and the sensitivity vectors, it can be expressed as
|
|
|
|
```python
|
|
|
|
[dx0] = [df0_dx0 df1_dx0] [df0]
|
|
|
|
[dx1] [df0_dx1 df1_dx1] [df1]
|
|
|
``` |
|
``` |
|
|
|
\ No newline at end of file |