Proper orthogonal decomposition (POD)

In this example, we will demonstrate the use of proper orthogonal decomposition (POD) for decomposing a flow field into basis modes.

using ILMPostProcessing
using Plots

Get the flow field data

First, we need some flow field data to analyze. For this purpose, we will use ViscousFlow.jl to get snapshots of the flow for a flat plate at 30 degrees angle of attack at Reynolds number 100.

using ViscousFlow

my_params = Dict()
my_params["Re"] = 100
my_params["freestream speed"] = 1.0 # in x-dir
my_params["freestream angle"] = 0.0 # relative to horizontal

xlim = (-1.0, 5.0)
ylim = (-2.0, 2.0)
my_params["grid Re"] = 4.0

g = setup_grid(xlim, ylim, my_params)

Δs = surface_point_spacing(g, my_params)
body = Plate(1.0, Δs)

cent = [0.5,0.0]
α = -30π/180
X = MotionTransform(cent, α)
joint = Joint(X)

m = RigidBodyMotion(joint, body)
x = init_motion_state(body, m)
update_body!(body, x, m)
sys = viscousflow_system(g, body, phys_params = my_params, motions = m);

u0 = init_sol(sys)
tspan = (0.0, 20.0)
integrator = init(u0, tspan, sys)

# Solve to 10 convective time units
step!(integrator, 10)

Assemble snapshots of the velocity field from the solution data

Here, we make use of the capability of the velocity function to generate an array of velocity fields at a range of times. We will save every 5th time step in this array.

sol = integrator.sol
tpod = sol.t[2:5:end]
X = velocity(sol, sys, tpod);

Perform the POD

The POD is simply performed with the PODModes function. This provides a structure containing the modes (phi), the expansion coefficients (a), and the modal energies (lambda). By default, PODModes retains 99% of the energy. This can be changed with the optional argument tolerance.

modes = pod(X);

The a array is of size $N_t \times r$, where $N_t$ is the number of time values, and $r$ is the number of modes. The modes are ordered from highest energy to lowest energy.

modes.a
101×10 Matrix{Float64}:
  0.14194    0.0152349   -0.0800551  …  -0.0258886    -0.0152696
  0.140543   0.0153246   -0.0790431     -0.0180305    -0.00952855
  0.138571   0.0156234   -0.0783249     -0.00939853   -0.0037204
  0.136239   0.0164329   -0.077899      -0.000959092   0.0014893
  0.133733   0.0179383   -0.0775806      0.00635362    0.00564145
  0.131235   0.0202009   -0.077098   …   0.0119154     0.0085169
  0.128913   0.0231614   -0.076146       0.0154992     0.0100634
  0.126906   0.0266551   -0.0744303      0.0172009     0.0102969
  0.125316   0.0304381   -0.0717035      0.0172914     0.00925677
  0.124198   0.0342168   -0.067791       0.0160731     0.00703318
  ⋮                                  ⋱                
 -0.112304  -0.0344556   -0.0532147     -0.0120673     0.00853449
 -0.114349  -0.0217861   -0.0588577     -0.0090689     0.0097446
 -0.115442  -0.00828272  -0.0625237     -0.00497924    0.00960382
 -0.115547   0.00579294  -0.0640886  …  -0.00014938    0.00812758
 -0.11465    0.0201689   -0.0634731      0.00501764    0.00548013
 -0.112757   0.0345678   -0.0606473      0.0101173     0.00194107
 -0.109901   0.048711    -0.0556342      0.0147904    -0.0021433
 -0.106135   0.0623227   -0.0485125      0.0187581    -0.00641432
 -0.101538   0.0751335   -0.0394171  …   0.0218405    -0.0105507

In this case, 7 modes were retained, at 51 times.

If we wanted to re-assemble the modes and coefficients to recover the flow at some time instant, we could use the mapreduce function, e.g.,

vel_assemble = mapreduce((aj, phi_j) -> aj .* phi_j, +, modes.a[end,:], modes.phi) + modes.Xmean
CartesianGrids.Edges{CartesianGrids.Primal, 154, 104, Float64, Vector{Float64}} data
u (in grid orientation)
103×154 Matrix{Float64}:
 1.032     1.03397   1.03586   1.03773   …  0.994236  0.992556  0.991314
 1.03115   1.03309   1.03501   1.03691      0.993158  0.991552  0.990161
 1.03018   1.0321    1.03403   1.03596      0.991975  0.990395  0.988936
 1.02911   1.03104   1.03297   1.03491      0.990679  0.989092  0.987592
 1.02797   1.02989   1.03182   1.03377      0.989258  0.987656  0.986126
 1.02673   1.02866   1.0306    1.03255   …  0.987707  0.986089  0.984537
 1.02541   1.02734   1.02928   1.03124      0.986024  0.984393  0.982825
 1.024     1.02592   1.02787   1.02983      0.984205  0.982566  0.98099
 1.02249   1.02441   1.02635   1.02832      0.98225   0.980608  0.979029
 1.02088   1.0228    1.02474   1.0267       0.980157  0.978516  0.976941
 ⋮                                       ⋱                      
 0.950972  0.951146  0.951332  0.951533     1.03813   1.03596   1.03368
 0.952807  0.953047  0.953298  0.953563  …  1.03729   1.03525   1.0331
 0.954589  0.954895  0.955211  0.955538     1.03645   1.03453   1.0325
 0.956318  0.956691  0.957072  0.95746      1.0356    1.0338    1.03187
 0.957994  0.958436  0.958882  0.95933      1.03475   1.03305   1.03123
 0.959616  0.960131  0.960643  0.961148     1.0339    1.0323    1.03058
 0.961176  0.961772  0.96235   0.962904  …  1.03303   1.03154   1.02991
 0.962655  0.963345  0.963978  0.964566     1.03215   1.0308    1.02924
 0.963999  0.964798  0.965449  0.966044     1.03125   1.03007   1.02854
v (in grid orientation)
104×153 Matrix{Float64}:
  0.079378    0.0796791   0.0799135  …  -0.086456   -0.0852579  -0.0836666
  0.0813472   0.0815723   0.0817851     -0.0879538  -0.0869376  -0.0849086
  0.0832878   0.0834915   0.083691      -0.0894933  -0.0885432  -0.0862995
  0.0852161   0.0854194   0.0856175     -0.091066   -0.0901241  -0.0877583
  0.0871396   0.0873514   0.0875567     -0.0926647  -0.0917105  -0.0892583
  0.089062    0.0892871   0.0895049  …  -0.0942849  -0.0933124  -0.0907882
  0.0909849   0.0912261   0.0914604     -0.0959226  -0.09493    -0.0923406
  0.0929083   0.0931678   0.0934215     -0.0975739  -0.0965605  -0.0939084
  0.0948313   0.0951108   0.0953864     -0.0992345  -0.0981994  -0.0954849
  0.0967519   0.097053    0.0973528     -0.1009     -0.0998417  -0.0970637
  ⋮                                  ⋱   ⋮                      
 -0.0351887  -0.0356327  -0.0361486  …   0.0347907   0.0362171   0.0387756
 -0.0349488  -0.035381   -0.0358838      0.0328253   0.0341775   0.0366264
 -0.0346428  -0.0350646  -0.0355569      0.0309782   0.0322575   0.0345954
 -0.03427    -0.0346837  -0.035169       0.0292412   0.0304494   0.0326741
 -0.0338283  -0.0342378  -0.0347216      0.0276078   0.0287478   0.0308556
 -0.0333131  -0.0337257  -0.0342172  …   0.0260736   0.0271513   0.0291339
 -0.0327164  -0.0331483  -0.0336627      0.0246344   0.0256662   0.027502
 -0.0320263  -0.0325156  -0.033075       0.0232839   0.0243153   0.0259442
 -0.0312274  -0.0318645  -0.0324798      0.0220126   0.0231401   0.0244124

In this last line, modes.a[end,:] obtains the expansion coefficients at the last time available.

Let's print the first mode, and the corresponding history of the modal coefficient in the decomposition

plot(layout=[2;1],plot(modes.phi[1].u,sys,title="u"),
   plot(modes.phi[1].v,sys,title="v"),
   plot(tpod,modes.a[:,1],xlim=(0,Inf),xlabel="\$t\$",ylabel="\$a_1(t)\$"))
Example block output

The energy associated with this mode is

modes.lambda[1]
0.7533616864230657

Now let's print the $r$th mode, and the history of the coefficient in the decomposition

plot(layout=[2;1],plot(modes.phi[end].u,sys,title="u"),
   plot(modes.phi[end].v,sys,title="v"),
   plot(tpod,modes.a[:,end],xlim=(0,Inf),xlabel="\$t\$",ylabel="\$a_r(t)\$"))
Example block output

The energy associated with this mode is

modes.lambda[end]
0.006931914393839419

This page was generated using Literate.jl.