Matlab / CUDA:海浪模拟

时间:2015-02-02 13:56:53

标签: matlab cuda simulation simulator simulate

我已经研究过Jerry Tessendorf撰写的"Simulating Ocean Water"文章,并尝试编制统计波模型,但我没有得到正确的结果而且我不明白为什么。

在我的程序中,我只尝试在时间t = 0创建波高字段,而不会进行任何进一步的更改。执行我的程序后,我没有得到我期望的结果:enter image description here

这是我的源代码:

clear all; close all; clc;
rng(11); % setting seed for random numbers

meshSize = 64; % field size
windDir = [1, 0]; % ||windDir|| = 1
patchSize = 64;
A = 1e+4;
g = 9.81; % gravitational constant
windSpeed = 1e+2;

x1 = linspace(-10, 10, meshSize+1); x = x1(1:meshSize);
y1 = linspace(-10, 10, meshSize+1); y = y1(1:meshSize);
[X,Y] = meshgrid(x, y);

H0 = zeros(size(X)); % height field at time t = 0

for i = 1:meshSize
    for j = 1:meshSize
        kx = 2.0 * pi / patchSize * (-meshSize / 2.0 + x(i)); % = 2*pi*n / Lx
        ky = 2.0 * pi / patchSize * (-meshSize / 2.0 + y(j)); % = 2*pi*m / Ly
        P = phillips(kx, ky, windDir, windSpeed, A, g); % phillips spectrum
        H0(i,j) = 1/sqrt(2) * (randn(1) + 1i * randn(1)) * sqrt(P);
    end
end

H0 = H0 + conj(H0);

surf(X,Y,abs(ifft(H0)));
axis([-10 10 -10 10 -10 10]);

phillips函数:

function P = phillips(kx, ky, windDir, windSpeed, A, g)
    k_sq = kx^2 + ky^2;
    L = windSpeed^2 / g;
    k = [kx, ky] / sqrt(k_sq);
    wk = k(1) * windDir(1) + k(2) * windDir(2);
    P = A / k_sq^2 * exp(-1.0 / (k_sq * L^2)) * wk^2;
end

是否有任何matlab海洋模拟源代码可以帮助我理解我的错误?快速谷歌搜索没有得到任何结果。


在这里"正确"结果我来自" CUDA FFT Ocean Simulation"。我还没有在Matlab中实现这种行为,但是我已经绘制过" surf"在matlab中使用来自" CUDA FFT Ocean Simulation"的数据。这是它的样子: enter image description here enter image description here


我做了一个实验并得到了一个有趣的结果:

我已经从" CUDA FFT海洋模拟"中采集了h0。所以我必须做ifft从频域转换到空间域以绘制图形。我已使用matlab h0并使用来自CUDA库的ifft为同一cufftExecC2C完成了此操作。结果如下:

CUDA ifft: enter image description here

Matlab ifft: enter image description here

要么我不理解cufftExecC2CcufftExecC2C的实现的某些方面,而matlab ifft是不同的算法,会有不同的结果。


顺便说一下,生成这种表面的参数是:

  • meshSize = 32

  • A = 1e-7

  • patchSize = 80

  • windSpeed = 10

2 个答案:

答案 0 :(得分:19)

那绝对是一个有趣的运动。这是一个完全重写的答案,因为您发现了自己提出的问题。

除了删除我的答案外,还有一些优点可以帮助您提供矢量化和/或解释一些代码。

我完全重写了我在之前的答案中给出的GUI,以便合并您的更改并添加几个选项。它开始长出胳膊和腿,所以我不会把这个列表放在这里,但你可以在那里找到完整的文件:

ocean_simulator.m

这是完全自包含的,它包括我矢量化的所有计算函数,并在下面单独列出。


GUI将允许您播放参数,为波浪设置动画,导出GIF文件(以及其他一些选项,例如"预设",但它们还没有被解决)。您可以实现的一些示例:


基本

这是您使用快速默认设置和几个渲染选项所获得的。这使用小网格尺寸和快速时间步长,因此它可以在任何机器上快速运行。

water1


我家里很有限(Pentium E2200 32bit),所以我只能在有限的设置下练习。即使设置了最大值,gui也会运行,但它会慢慢变得非常享受。

然而,快速运行ocean_simulator I7 64位,8核,16GB内存,Raid中的2xSSD ),它会让它变得更有趣!以下是一些例子:

虽然在一台更好的机器上完成,但我没有使用任何并行功能,也没有任何GPU计算,所以Matlab只使用了这些规范的一部分,这意味着它可能运行得很好任何具有不错RAM的64位系统


风湖

这是一个像湖一样平坦的水面。即使是大风也不会产生高振幅波(但仍会产生大量的迷你波)。如果你是一个从山顶窗户看风的冲浪者,你的心脏会跳过一个节拍,你的下一步就是打电话给Dave" 男人!装备好。 五点见水!" water2


溶胀

这是你在整个晚上与暴风雨作战后,从船上的桥上看到的。风暴已经消散,长长的大浪是绝对是一个不稳定的夜晚的最后见证(有航海经验的人会知道...... )。 Swell


T-风暴

这就是你前一天晚上的事情...... T-Storm T-Storm3 第二个gif在家完成,因此缺乏细节......抱歉


到底部:

最后,gui会让你在水域周围添加补丁。在gui它是透明的,所以你可以在水下或漂亮的海底添加对象。不幸的是,GIF格式不能包含alpha通道,所以这里没有透明度(但是如果你在视频中导出那么你应该没问题。)

此外,导出到GIF会降低图像质量,如果你在Matlab中运行,那么域边界和水面之间的连接就会完美无缺。在某些情况下,它也会使Matlab降低灯光的渲染效果,因此这绝对不是导出的最佳选择,但它允许在matlab中播放更多内容。 bottom


现在进入代码:

我没有列出完整的GUI,这将是超长的(这篇文章已经足够长了),我将在这里列出你的代码的重写版本,并解释这些变化。

由于剩余的矢量化,您应该注意到速度执行的大幅增加(数量级),但主要有两个原因:
(i)重复了很多计算。缓存值并重用它们比在循环中重新计算完整矩阵要快得多(在动画部分期间) (ii)注意我如何定义表面图形对象。它只定义一次(空的偶数),然后所有进一步的调用(在循环中)只更新表面对象的基础ZData(而不是在每次迭代时重新创建一个表面对象。

这里是:

%% // clear workspace
clear all; close all; clc;

%% // Default parameters
param.meshsize  = 128 ;     %// main grid size
param.patchsize = 200 ;     
param.windSpeed = 100  ;    %// what unit ? [m/s] ??
param.winddir   = 90   ;    %// Azimuth
param.rng = 13 ;            %// setting seed for random numbers
param.A         = 1e-7 ;    %// Scaling factor
param.g         = 9.81 ;    %// gravitational constant

param.xLim = [-10 10] ;     %// domain limits X
param.yLim = [-10 10] ;     %// domain limits Y
param.zLim = [-1e-4 1e-4]*2 ;

gridSize = param.meshsize * [1 1] ;

%% // Define the grid X-Y domain
x = linspace( param.xLim(1) , param.xLim(2) , param.meshsize ) ;
y = linspace( param.yLim(1) , param.yLim(2) , param.meshsize ) ;
[X,Y] = meshgrid(x, y);

%% // get the grid parameters which remain constants (not time dependent)
[H0, W, Grid_Sign] =  initialize_wave( param ) ;

%% // calculate wave at t0
t0 = 0 ;
Z = calc_wave( H0 , W , t0 , Grid_Sign ) ;

%% // populate the display panel
h.fig  = figure('Color','w') ;
h.ax   = handle(axes) ;                 %// create an empty axes that fills the figure
h.surf = handle( surf( NaN(2) ) ) ;     %// create an empty "surface" object

%% // Display the initial wave surface
set( h.surf , 'XData',X , 'YData',Y , 'ZData',Z )
set( h.ax   , 'XLim',param.xLim , 'YLim',param.yLim , 'ZLim',param.zLim )

%% // Change some rendering options
axis off                                %// make the axis grid and border invisible
shading interp                          %// improve shading (remove "faceted" effect)
blue = linspace(0.4, 1.0, 25).' ; cmap = [blue*0, blue*0, blue]; %'// create blue colormap
colormap(cmap)
%// configure lighting
h.light_handle = lightangle(-45,30) ;   %// add a light source
set(h.surf,'FaceLighting','phong','AmbientStrength',.3,'DiffuseStrength',.8,'SpecularStrength',.9,'SpecularExponent',25,'BackFaceLighting','unlit')

%% // Animate
view(75,55) %// no need to reset the view inside the loop ;)

timeStep = 1./25 ;
nSteps = 2000 ;
for time = (1:nSteps)*timeStep    
    %// update wave surface
    Z = calc_wave( H0,W,time,Grid_Sign ) ;
    h.surf.ZData = Z ;
    pause(0.001);
end


%% // This block of code is only if you want to generate a GIF file
%// be carefull on how many frames you put there, the size of the GIF can
%// quickly grow out of proportion ;)

nFrame = 55 ;
gifFileName = 'MyDancingWaves.gif' ;

view(-70,40)
clear im
f = getframe;
[im,map] = rgb2ind(f.cdata,256,'nodither');
im(1,1,1,20) = 0;
iframe = 0 ;
for time = (1:nFrame)*.5
    %// update wave surface
    Z = calc_wave( H0,W,time,Grid_Sign ) ;
    h.surf.ZData = Z ;
    pause(0.001);

    f = getframe;
    iframe= iframe+1 ;
    im(:,:,1,iframe) = rgb2ind(f.cdata,map,'nodither');
end
imwrite(im,map,gifFileName,'DelayTime',0,'LoopCount',inf)
disp([num2str(nFrame) ' frames written in file: ' gifFileName])

您会注意到我改变了一些内容,但我可以向您保证计算完全相同。这段代码调用了一些子函数,但它们都是矢量化的,所以如果你想要,你可以在这里复制/粘贴它们并运行所有内联。


第一个被调用的函数是initialize_wave.m 此处计算的所有内容将在以后保持不变(当您稍后为波浪设置动画时,它不随时间变化),因此将其置于其自身的块中是有意义的。

function [H0, W, Grid_Sign] =  initialize_wave( param )
% function [H0, W, Grid_Sign] =  initialize_wave( param )
%
% This function return the wave height coefficients H0 and W for the
% parameters given in input. These coefficients are constants for a given
% set of input parameters.
% Third output parameter is optional (easy to recalculate anyway)

rng(param.rng);  %// setting seed for random numbers

gridSize = param.meshsize * [1 1] ;

meshLim = pi * param.meshsize / param.patchsize ;
N = linspace(-meshLim , meshLim , param.meshsize ) ;
M = linspace(-meshLim , meshLim , param.meshsize ) ;
[Kx,Ky] = meshgrid(N,M) ;

K = sqrt(Kx.^2 + Ky.^2);    %// ||K||
W = sqrt(K .* param.g);     %// deep water frequencies (empirical parameter)

[windx , windy] = pol2cart( deg2rad(param.winddir) , 1) ;

P = phillips(Kx, Ky, [windx , windy], param.windSpeed, param.A, param.g) ;
H0 = 1/sqrt(2) .* (randn(gridSize) + 1i .* randn(gridSize)) .* sqrt(P); % height field at time t = 0

if nargout == 3
    Grid_Sign = signGrid( param.meshsize ) ;
end

请注意,初始winDir参数现在用表示"方位角"的单个标量值表示。风的 in degrees (从0到360)。由于函数X,它后来被翻译为Ypol2cart组件。

[windx , windy] = pol2cart( deg2rad(param.winddir) , 1) ;

这确保规范始终为1

该函数分别调用有问题的phillips.m,但如前所述,它甚至可以完全矢量化,因此如果您愿意,可以将其复制回内联。 (不用担心我根据您的版本检查结果=>严格相同)。请注意,此函数不输出复数,因此无需比较虚部。

function P = phillips(Kx, Ky, windDir, windSpeed, A, g)
%// The function now accept scalar, vector or full 2D grid matrix as input
    K_sq = Kx.^2 + Ky.^2;
    L = windSpeed.^2 ./ g;
    k_norm = sqrt(K_sq) ;
    WK = Kx./k_norm * windDir(1) + Ky./k_norm * windDir(2);
    P = A ./ K_sq.^2 .* exp(-1.0 ./ (K_sq * L^2)) .* WK.^2 ;
    P( K_sq==0 | WK<0 ) = 0 ;
end

主程序调用的下一个函数是calc_wave.m。此函数完成给定时间的波场的计算。绝对值得拥有它,因为这是一个mimimun计算集,当你想要为波动画时,必须在每个给定的时间重复这些计算。

function Z = calc_wave( H0,W,time,Grid_Sign )
% Z = calc_wave( H0,W,time,Grid_Sign )
%
% This function calculate the wave height based on the wave coefficients H0
% and W, for a given "time". Default time=0 if not supplied.
% Fourth output parameter is optional (easy to recalculate anyway)

    % recalculate the grid sign if not supplied in input
    if nargin < 4
        Grid_Sign = signGrid( param.meshsize ) ;
    end
    % Assign time=0 if not specified in input
    if nargin < 3 ; time = 0 ; end

    wt = exp(1i .* W .* time ) ;
    Ht = H0 .* wt + conj(rot90(H0,2)) .* conj(wt) ;  
    Z = real( ifft2(Ht) .* Grid_Sign ) ;
end

最后3行计算需要一些解释,因为它们收到了最大的变化(所有这些都是相同的结果,但速度要快得多)。

你原来的一行:

Ht = H0 .* exp(1i .* W .* (t * timeStep)) + conj(flip(flip(H0,1),2)) .* exp(-1i .* W .* (t * timeStep));
为了提高效率,

重新计算同样的事情:

在每个循环中,

(t * timeStep)在行上计算两次,而在循环开始时初始化time时,很容易为每一行获取正确的timefor time = (1:nSteps)*timeStep
另请注意,exp(-1i .* W .* time)conj(exp(1i .* W .* time))相同。不是进行2 * m * n次乘法来计算它们,而是计算一次更快,然后使用更快的conj()运算。 所以你的单行将成为:

wt = exp(1i .* W .* time ) ;
Ht = H0 .* wt + conj(flip(flip(H0,1),2)) .* conj(wt) ;

最后一次轻微触摸,flip(flip(H0,1),2))可以替换为rot90(H0,2)(也稍微快一点)。

请注意,因为函数calc_wave将被广泛重复,所以绝对值得减少计算次数(如上所述),但也可以通过发送Grid_Sign参数(而不是让函数在每次迭代时重新计算它)。这就是原因:

你的神秘功能signCor(ifft2(Ht),meshSize)),只需反转Ht的每个其他元素的符号。有一种更快的方法可以实现这一目标:只需将Ht乘以一个相同大小的矩阵(Grid_Sign),这是一个交替的+1 -1 ...矩阵,依此类推。

因此signCor(ifft2(Ht),meshSize)变为ifft2(Ht) .* Grid_Sign

由于Grid_Sign仅取决于矩阵大小,因此它不会对循环中的每个time进行更改,您只需计算一次(在循环之前),然后按原样使用它其他迭代。它的计算方法如下(矢量化,因此您也可以将其内联到代码中):

function sgn = signGrid(n)
% return a matrix the size of n with alternate sign for every indice
% ex:     sgn = signGrid(3) ;
%         sgn =
%             -1     1    -1
%              1    -1     1
%             -1     1    -1

    [x,y] = meshgrid(1:n,1:n) ;
    sgn = ones( n ) ;
    sgn(mod(x+y,2)==0) = -1 ;
end

最后,您会注意到版本与此版本之间定义网格[Kx,Ky]的方式不同。它们产生的结果略有不同,这只是一个选择问题 要用一个简单的例子来解释,让我们考虑一个小的meshsize=5。你的做事方式会将它分成5个等间隔的值,如下所示:

Kx(first line)=[-1.5 -0.5 0.5 1.5 2.5] * 2 * pi / patchSize

虽然我生成网格的方式会产生等间距的值,但也会以域限制为中心,如下所示:

Kx(first line)=[-2.50 -1.25 0.0 1.25 2.50] * 2 * pi / patchSize

在你定义它的行上似乎更尊重你的评论% = 2*pi*n / Lx, -N/2 <= n < N/2

我倾向于选择对称解决方案(加上它也稍微快一点,但它只计算一次所以它不是什么大问题),所以我使用了我的矢量化方式,但它纯粹是一个选择问题,你绝对可以保持你的方式,它只是如此轻微&#34;抵消&#34;整个结果矩阵,但它本身并没有扰乱计算。


第一个答案的最后遗留
侧编程笔记: 我发现你来自C / C ++世界或家庭。在Matlab中,您不需要使用昏迷来定义十进制数字(如2.0,您在大多数数字中使用它)。除非另外特别定义,否则Matlab默认会将任何数字转换为double,这是一个64位浮点类型。因此,编写2 * pi足以获得最大精度( Matlab不会将pi转换为整数; - )),您不需要编写2.0 * pi虽然如果你不想改变你的习惯,它仍会有效。

此外,(Matlab的一大好处),在运算符之前添加.通常意味着&#34;元素方式&#34;操作。您可以通过这种方式添加(.+),减去(.-),乘以(.*),除(./)完整矩阵元素。这就是我摆脱代码中所有循环的方法。这也适用于幂运算符:A.^2将返回与A大小相同的矩阵,每个元素的平方。

答案 1 :(得分:0)

Here是工作计划。

首先 - 源代码:

clear all; close all; clc;
rng(13); % setting seed for random numbers

meshSize = 128; % field size
windDir = [0.1,1];
patchSize = 200;
A = 1e-7;
g = 9.81; % gravitational constant
windSpeed = 100;
timeStep = 1/25;

x1 = linspace(-10, 10, meshSize+1); x = x1(1:meshSize);
y1 = linspace(-10, 10, meshSize+1); y = y1(1:meshSize);
[X,Y] = meshgrid(x,y); % wave field

i = 1:meshSize; j = 1:meshSize; % indecies
[I,J] = meshgrid(i,j); % field of indecies
Kx = 2.0 * pi / patchSize * (-meshSize / 2.0 + I); % = 2*pi*n / Lx, -N/2 <= n < N/2
Ky = 2.0 * pi / patchSize * (-meshSize / 2.0 + J); % = 2*pi*m / Ly, -M/2 <= m < M/2
K = sqrt(Kx.^2 + Ky.^2); % ||K||
W = sqrt(K .* g); % deep water frequencies (empirical parameter)

P = zeros(size(X)); % Cant compute P without loops
for i = 1:meshSize
    for j = 1:meshSize
        P(i,j) = phillips(Kx(i,j), Ky(i,j), windDir, windSpeed, A, g); % phillips spectrum
    end
end

H0 = 1/sqrt(2) .* (randn(size(X)) + 1i .* randn(size(X))) .* sqrt(P); % height field at time t = 0

rotate3d on;
for t = 1:10000 % 10000 * timeStep (sec)
    Ht = H0 .* exp(1i .* W .* (t * timeStep)) + ...
         conj(flip(flip(H0,1),2)) .* exp(-1i .* W .* (t * timeStep));
    [az,el] = view;
    surf(X,Y,real(signCor(ifft2(Ht),meshSize)));
    axis([-10 10 -10 10 -1e-4 1e-4]); view(az,el);
    blue = linspace(0.4, 1.0, 25)'; map = [blue*0, blue*0, blue];
    %shading interp; % improve shading (remove "faceted" effect)
    colormap(map);
    pause(1/60);
end

phillips.m :(我试图对Phillips频谱的计算进行矢量化,但我遇到了一个难度,我将进一步展示)

function P = phillips(kx, ky, windDir, windSpeed, A, g)
    k_sq = kx^2 + ky^2;
    if k_sq == 0
        P = 0;
    else
        L = windSpeed^2 / g;
        k = [kx, ky] / sqrt(k_sq);
        wk = k(1) * windDir(1) + k(2) * windDir(2);
        P = A / k_sq^2 * exp(-1.0 / (k_sq * L^2)) * wk^2;
        if wk < 0
            P = 0;
        end
    end
end

signCor.m :(这个功能对我来说绝对神秘......我已经从“CUDA FFT Ocean Simulation”实现中复制了它。没有它,仿真效果会更糟。再次我不知道如何向量化这个功能。)

function H = signCor(H1, meshSize)
    H = H1;
    for i = 1:meshSize
        for j = 1:meshSize
            if mod(i+j,2) == 0
                sign = -1; % works fine if we change signs vice versa
            else
                sign = 1;
            end
            H(i,j) = H1(i,j) * sign;
        end
    end
end

我所犯的最大错误是我使用了ifft而不是ifft2,这就是为什么CUDA ifft和Matlab ifft不匹配的原因。

我的第二个错误是在这行代码中:

kx = 2.0 * pi / patchSize * (-meshSize / 2.0 + x(i)); % = 2*pi*n / Lx
ky = 2.0 * pi / patchSize * (-meshSize / 2.0 + y(j)); % = 2*pi*m / Ly

我应该写:

kx = 2.0 * pi / patchSize * (-meshSize / 2.0 + i); % = 2*pi*n / Lx
ky = 2.0 * pi / patchSize * (-meshSize / 2.0 + j); % = 2*pi*m / Ly

我玩了一些参数A,meshSize,patchSize,我得出结论:

波幅的合理参数是A *(patchSize / meshSize),其中A只是比例因子。

  • 对于'冷静'patchSize / meshSize <= 0.5

  • 对于'海啸'patchSize / meshSize >= 3.0


Phillips光谱矢量化的难度:

我有两个功能:

% non-vectorized spectrum
function P = phillips1(kx, ky, windDir, windSpeed, A, g)
    k_sq = kx^2 + ky^2;
    if k_sq == 0
        P = 0;
    else
        L = windSpeed^2 / g;
        k = [kx, ky] / sqrt(k_sq);
        wk = k(1) * windDir(1) + k(2) * windDir(2);
        P = A / k_sq^2 * exp(-1.0 / (k_sq * L^2)) * wk^2;
        if wk < 0
            P = 0;
        end
    end
end

% vectorized spectrum
function P = phillips2(Kx, Ky, windDir, windSpeed, A, g)
    K_sq = Kx .^ 2 + Ky .^ 2;
    L = -g^2 / windSpeed^4;
    WK = (Kx ./ K_sq) .* windDir(1) + (Ky ./ K_sq) .* windDir(2);
    P = (A ./ (K_sq .^ 2)) .* ( exp(L ./ K_sq) .* (WK .^ 2) );
    P(K_sq == 0) = 0;
    P(WK < 0) = 0;
    P(isinf(P)) = 0;
end

在使用P1phillips1使用P2计算phillips2之后,我绘制了他们的差异:

subplot(2,1,1); surf(X,Y,real(P2-P1)); title('Difference in real part');
subplot(2,1,2); surf(X,Y,imag(P2-P1)); title('Difference in imaginary part');

enter image description here

它完美地说明了实际上这2个光谱之间存在巨大差异。