激光谐振腔稳定模式的Fox-Li迭代算法

这学期的激光课设,要求编程实现一个Fox-Li(福克斯-李)迭代算法,用于模拟激光谐振腔中的自再现模式。同时要求有友好的界面,和三维的模场分布图。

大学里面的课设嘛,一般都是用Matlab做,不过这次我想好好弄一弄,想要用C++写(计算速度快),想要有真正的3D图像(用OpenGL),当然,也许我只是想装B。


一、理论基础

激光谐振腔中的模式分部,往往没有解析解,因此只能通过数值计算的方式研究其分部。Fox-Li迭代算法是最常用的方法,其本质是菲涅耳衍射积分:

激光谐振腔稳定模式的Fox-Li迭代算法_第1张图片


对当前源光场u做积分,得到下一个反射镜上的光场,再将该光场作为源光场再积分,得到再下一个反射镜上的光场,以此类推,如此反复,直到光场u的分布趋于稳定。

对于上面的积分,用Matlab的话是轻而易举的,但我既然选择了使用C++,就有必要对上面的积分进行一些变换了,方便代码的编写。

很显然的,我们需要将上式的实部和虚部分开,由于u(x,y)本身是一个复场,因此我们设:


将积分离散化后,变为求和,即下式:


其中u0为下一平面的光场分部。

再将上式的实部与虚部合并在一起,并令左边实部等于右边实部,左边虚部等于右边虚部,可得:


使用以上两式,就可以分别计算光场的实部和虚部,其中在以上各式中:


为简单起见,我只考虑了不同形状的平面腔,因此:

,L为谐振腔的腔长。

在实现上述算法时,一定要注意归一化,即每迭代完一次后,需将所有点的幅值均除以最大值,否则每次迭代都会使幅值减小,最终整个光场就什么也没有了。当然也可以对相位进行归一化,每个相位都减去最小值,由于是否对相位归一化对计算没有影响,所以不是必须的。


二、程序编写

为了获得真正的3D分布图,需要使用OpenGL,这里我选择熟悉的Openframeworks作为开发框架。不管其他的,先把Fox-Li迭代算法写出来才行啊!

void testApp::FoxLiIntegral()
{
	vector src_field;
	src_field.resize(m_field.size());
	memcpy(&src_field[0], &m_field[0], m_field.size()*sizeof(ofVec4f));

	float k = (2*PI/m_wave_length)*1e3;

	parallel_for(0,(int)src_field.size(),1,[&](int i){
		
		if (m_mask[i] == 0)
			return;

		m_field[i].z = m_field[i].w = 0;
		for (int si = 0; si < src_field.size(); ++si)
		{
			if (m_mask[si] == 0)
				continue;

			float dx = m_field[i].x - src_field[si].x;
			float dy = m_field[i].y - src_field[si].y;
			float dist = sqrt(dx*dx + dy*dy + m_resonator_length*m_resonator_length);

			float kr = k*dist;
			float sinkr = sin(kr);
			float coskr = cos(kr);

			float c = (1 + m_resonator_length/dist)/dist;

			m_field[i].z += c*(src_field[si].z*sinkr - src_field[si].w*coskr);
			m_field[i].w += c*(src_field[si].z*coskr + src_field[si].w*sinkr);
		}

	});

	NormalizeField();
	CheckResidual(m_field, src_field);
}
代码中的m_field是一个四维向量的列表,用于保存光场信息,其中,x和y分量为该点的坐标,z和w分量分别对应场的实部和虚部。后面的NormalizField用于对幅值进行归一化,而CheckResidual用于判断当前分布与上一轮的分布是否接近,即判断光场是否已经稳定。为了使计算的速度提高,我使用了parallel_for函数,该函数执行的操作都是并行的,也就是说,这个代码会同时计算好几个点的积分,大大提高了CPU的利用率(主要是多核),加快了迭代速度。


当迭代完成后,为了方便绘制,需要根据z和w分量计算幅值和相位,计算方法为:


代码如下:

void testApp::UpdateResult()
{
	m_amp.resize(m_field.size());
	m_phase.resize(m_field.size());

	for (int i = 0; i < m_field.size(); i++)
	{
		if (m_mask[i] == 0)continue;

		ofVec4f& vec = m_field[i];
		m_amp[i] = sqrt(vec.z*vec.z + vec.w*vec.w);
		m_phase[i] = atan2(vec.w, vec.z)/PI;

	}
}
最后,用m_amp和m_phase的值作为OpenGL坐标中的y值,点的x,y值对应OpenGL坐标中的x和z,就可以将分布画为一个曲面。至于画法的详细说明,请戳 http://blog.csdn.net/aichipmunk/article/details/8721290 。这里直接给出代码。

void testApp::DrawDistribution(vector& strength)
{
	bool mesh_break = true;
	for (int y = 0; y < m_rows - 1; ++y)
	{
		int y_offset = y*m_cols;
		for (int x = 0; x < m_cols; ++x)
		{
			int index = y_offset + x;

			if (m_mask[index] == 0 || m_mask[index + m_cols] == 0)
			{
				if (!mesh_break)
				{
					//If there's no point here, the mesh should break.
					mesh_break = true;
					glEnd();
				}
				continue;
			}

			ofVec4f& space_point1 = m_field[index];
			ofVec4f& space_point2 = m_field[index + m_cols];
			
			if (mesh_break)
			{
				//Start connecting points to form mesh.
				glBegin(GL_TRIANGLE_STRIP);
				mesh_break = false;
			}
			
			float amp_1 = strength[index];
			float amp_2 = strength[index + m_cols];
			
			//Draw the point and set its normal.
			glColor3f(amp_1, 0.2f, 1 - amp_1);
			//glNormal3f(dv21.x, dv21.y, dv21.z);
			glVertex3f(space_point1.x, amp_1, space_point1.y);
			
			//Draw the point below the prior one to form a triangle.
			glColor3f(amp_2, 0.2f, 1 - amp_2);
			glVertex3f(space_point2.x, amp_2, space_point2.y);
		}
		if (!mesh_break) 
		{
			//We break the mesh at the end of the line,.
			glEnd();
			mesh_break = true;
		}
	}
}
其中m_mask用于限定腔的形状,比如矩形腔、圆形腔、椭圆腔等等。m_mask[i]取1的地方代表腔内,取0的地方代表腔外。

基本的东西都有了,下面要编写程序的UI部分,对于Openframeworks来说,也并不难。在Openframeworks的官网上,可以下载很多扩展插件,其中有一个叫做ofxUI的东西,可以方便的构建出有一定交互功能的程序界面,同时提供了大量实例,很容易学习。这里就不细说了,直接给出生成UI的代码:

void testApp::CreateGui()
{
	m_gui = new ofxUICanvas(MENU_WIDTH,480);
	m_gui->setFont("\\GUI\\NewMedia Fett.ttf");
	
	m_gui->addLabel("Wave Length (um):", OFX_UI_FONT_SMALL);
	m_ui_wavelength = m_gui->addTextInput("WaveLength","0.555",80,26);
	m_gui->addLabel("Resonator Length (mm):", OFX_UI_FONT_SMALL);
	m_ui_reslength = m_gui->addTextInput("ResLength","1000",80,26);

	m_gui->addSpacer();
	m_gui->addLabel("Mirror Type:", OFX_UI_FONT_SMALL);
	vector vnames; vnames.push_back("One Dimension"); vnames.push_back("Rectangle"); vnames.push_back("Circle");
	m_ui_mirrortype = m_gui->addRadio("MirrorType", vnames, OFX_UI_ORIENTATION_VERTICAL);
    m_ui_mirrortype->activateToggle("Rectangle");

	m_gui->addLabel("Mirror Width/Diameter (mm):", OFX_UI_FONT_SMALL);
	m_ui_width = m_gui->addTextInput("Width","1",50,26);
	m_gui->addLabel("Mirror Height (mm):", OFX_UI_FONT_SMALL);
	m_ui_height = m_gui->addTextInput("Height","1",50,26);

	m_gui->addSpacer();
	m_gui->addLabel("Iterate Count:", OFX_UI_FONT_SMALL);
	m_ui_itcount = m_gui->addTextInput("ItCount","10",80,26);
	m_gui->addLabel("Minimum Variance:", OFX_UI_FONT_SMALL);
	m_ui_minerror = m_gui->addTextInput("MinError","0.01",80,26);

	m_gui->addSpacer();
	ofxUILabelButton* btn_start = new ofxUILabelButton("Start",false,80,26);
	ofxUILabelButton* btn_stop = new ofxUILabelButton("Stop",false,80,26);
	m_gui->addWidgetPosition(btn_start, OFX_UI_WIDGET_POSITION_DOWN, OFX_UI_ALIGN_RIGHT);
	m_gui->addWidgetPosition(btn_stop, OFX_UI_WIDGET_POSITION_DOWN, OFX_UI_ALIGN_RIGHT);

	m_gui->setColorBack(ofColor(255,100));
	m_gui->setWidgetColor(OFX_UI_WIDGET_COLOR_BACK, ofColor(255,100));
	
	ofAddListener(m_gui->newGUIEvent,this,&testApp::guiEvent);
}

有了UI后,新的问题出现了。首先,我发现我画的曲面失去了深度测试,也就是说,可以透视看到本应被挡住的地方。一开始还不知为何原因,最后硬着头皮去读ofxUI的源代码,才发现它在绘制UI时,将深度检测关闭了,因此必须在draw函数中开启,即glEnable(GL_DEPTH_TEST)。

其次,之前的迭代算法都是在主线程上执行的,因此会卡住UI,使程序失去响应。所以必须开一个后台线程,并将大量的计算放到后台线程上去。这一点,对于Openframeworks也很简单,只需写一个继承于ofThread类的子类,并设置线程历程,然后调用startThread即可。

class IterationThread : public ofThread
{
protected:
	void threadedFunction()
	{
		int iterate_count = 0;
		float residual = FLT_MAX;
		while(isThreadRunning() && iterate_count < m_iterate_count && residual > m_min_residual)
		{
			++iterate_count;

			if (Iterate)
				residual = Iterate();
			if (ProgressCallback)
				ProgressCallback(iterate_count, residual);
		}

		if (FinishedCallback)FinishedCallback();
	}

public:
	IterationThread():Iterate(NULL),FinishedCallback(NULL),ProgressCallback(NULL){}

	void SetIterationFunction(float (*iterate)(), int iterate_count, float min_residual)
	{
		Iterate = iterate;
		m_iterate_count = iterate_count;
		m_min_residual = min_residual;
	}
	void SetFinishCallback(void (*finished)())
	{
		FinishedCallback = finished;
	}
	void SetProgressCallback(void (*progress)(int,float))
	{
		ProgressCallback = progress;
	}

private:
	int m_iterate_count;
	float m_min_residual;

	float(*Iterate)();
	void(*FinishedCallback)();
	void(*ProgressCallback)(int,float);
};
为了方便与主线程的通信,我还在子类中实现了迭代进度,迭代结束等回调函数。其中的threadFunction是线程启动时会执行的函数,可知,当迭代达到设定的迭代次数,或分布已经稳定时,迭代就会停止,并调用回调函数通知主线程。


为了方便使用,还写了很多代码处理细节问题,这里就不贴了。最终,将以上这些放在一起,初步达成了我的要求,感觉还是挺爽的啊!!左边是幅值分布,右边是相位分布,使用了Openframeworks中的ofEasyCamera,可以使用鼠标拖拽来改变视角或远近(需按住鼠标右键)。

激光谐振腔稳定模式的Fox-Li迭代算法_第2张图片

最后上代码下载地址:http://pan.baidu.com/share/link?shareid=3159141162&uk=2753596152

使用时将整个文件夹放到“Openframeworks安装目录\apps\myApps"目录下,双击FoxLiIterate.sln打开即可。

你可能感兴趣的:(OpenFrameworks)