int main(int argc, char ** argv) {

try {

	// Parameters
	Parameters param;

	// Output filename
	std::string strOutputFile;

	// Horizontal minimum wave number
	int nKmin;

	// Horizontal maximum wave number
	int nKmax;

	// Parse the command line
	BeginCommandLine()
		CommandLineInt(param.nPhiElements, "n", 40);
		CommandLineInt(nKmin, "kmin", 1);
		CommandLineInt(nKmax, "kmax", 20);
		CommandLineDouble(param.dXscale, "X", 1.0);
		CommandLineDouble(param.dT0, "T0", 300.0);
		CommandLineDouble(param.dU0, "U0", 20.0);
		CommandLineDouble(param.dG, "G", 9.80616);
		CommandLineDouble(param.dOmega, "omega", 7.29212e-5);
		CommandLineDouble(param.dGamma, "gamma", 1.4);
		CommandLineString(strOutputFile, "out", "wave.nc");

		ParseCommandLine(argc, argv);
	EndCommandLine(argv)

	AnnounceBanner();

	// Generate latitude values
	param.GenerateLatituteArray(param.nPhiElements);

	// Open NetCDF file
	NcFile ncdf_out(strOutputFile.c_str(), NcFile::Replace);

	NcDim *dimK = ncdf_out.add_dim("k", nKmax - nKmin + 1);
	NcDim *dimLat = ncdf_out.add_dim("lat", param.nPhiElements);
	NcDim *dimEig = ncdf_out.add_dim("eig", param.nPhiElements);

	// Write parameters and latitudes to file
	param.WriteToNcFile(ncdf_out, dimLat, dimLatS);

	// Wave numbers 
	NcVar *varK = ncdf_out.add_var("k", ncInt, dimK);

	DataVector<int> vecK;
	vecK.Initialize(nKmax - nKmin + 1);
	for (int nK = nKmin; nK <= nKmax; nK++) { 
		vecK[nK - nKmin] = nK;
	}

	varK->set_cur((long)0);
	varK->put(vecK, nKmax - nKmin + 1);

	// Eigenvalues
	NcVar *varMR = ncdf_out.add_var("mR", ncDouble, dimK, dimEig);
	NcVar *varMI = ncdf_out.add_var("mI", ncDouble, dimK, dimEig);

	NcVar *varUR = ncdf_out.add_var("uR", ncDouble, dimK, dimEig, dimLat);
	NcVar *varUI = ncdf_out.add_var("uI", ncDouble, dimK, dimEig, dimLat);

	NcVar *varVR = ncdf_out.add_var("vR", ncDouble, dimK, dimEig, dimLatS);
	NcVar *varVI = ncdf_out.add_var("vI", ncDouble, dimK, dimEig, dimLatS);

	NcVar *varPR = ncdf_out.add_var("pR", ncDouble, dimK, dimEig, dimLat);
	NcVar *varPI = ncdf_out.add_var("pI", ncDouble, dimK, dimEig, dimLat);

	NcVar *varWR = ncdf_out.add_var("wR", ncDouble, dimK, dimEig, dimLat);
	NcVar *varWI = ncdf_out.add_var("wI", ncDouble, dimK, dimEig, dimLat);

	NcVar *varRhoR = ncdf_out.add_var("rhoR", ncDouble, dimK, dimEig, dimLat);
	NcVar *varRhoI = ncdf_out.add_var("rhoI", ncDouble, dimK, dimEig, dimLat);

	// Allocate temporary arrays
	DataVector<double> dUR;
	dUR.Initialize(param.nPhiElements);

	DataVector<double> dUI;
	dUI.Initialize(param.nPhiElements);

	DataVector<double> dVR;
	dVR.Initialize(param.nPhiElements-1);

	DataVector<double> dVI;
	dVI.Initialize(param.nPhiElements-1);

	DataVector<double> dPR;
	dPR.Initialize(param.nPhiElements);

	DataVector<double> dPI;
	dPI.Initialize(param.nPhiElements);

	DataVector<double> dWR;
	dWR.Initialize(param.nPhiElements);

	DataVector<double> dWI;
	dWI.Initialize(param.nPhiElements);

	DataVector<double> dRhoR;
	dRhoR.Initialize(param.nPhiElements);

	DataVector<double> dRhoI;
	dRhoI.Initialize(param.nPhiElements);

	// Loop over all horizontal wave numbers
	for (int nK = nKmin; nK <= nKmax; nK++) {

		// Build matrices
		char szMessage[100];
		sprintf(szMessage, "Building evolution matrices (k = %i)", nK);
		AnnounceStartBlock(szMessage);

		DataMatrix<double> matM;
		DataMatrix<double> matB;

		GenerateEvolutionMatrix(nK, param, matM, matB);

		AnnounceEndBlock("Done");

		// Solve the matrices
		AnnounceStartBlock("Solving evolution matrices");

		DataVector<double> vecAlphaR;
		DataVector<double> vecAlphaI;
		DataVector<double> vecBeta;
		DataMatrix<double> matVR;

		vecAlphaR.Initialize(matM.GetRows());
		vecAlphaI.Initialize(matM.GetRows());
		vecBeta  .Initialize(matM.GetRows());
		matVR    .Initialize(matM.GetRows(), matM.GetColumns());

		SolveEvolutionMatrix(
			matM,
			matB,
			vecAlphaR,
			vecAlphaI,
			vecBeta,
			matVR);

		// Sort eigenvalues
		std::multimap<double, int> mapSortedRows;
		for (int i = 0; i < vecBeta.GetRows(); i++) {
			if (vecBeta[i] != 0.0) {

				double dLambdaR = vecAlphaR[i] / vecBeta[i];
				double dLambdaI = vecAlphaI[i] / vecBeta[i];

				double dMR = dLambdaI;
				double dMI = - dLambdaR - 1.0;

				double dEvalMagnitude = fabs(dMR);

				//printf("\n%1.5e %1.5e ", dMR, dMI);

				// Purely imaginary eigenvalue
				if (dMR == 0.0) {
					// Wave must decay with height
					if (dMI < 0.0) {
						continue;
					}

				// Complex-conjugate pair of eigenvalues
				} else {
					// Phase propagation must be downward, and energy
					// propagation upwards
					if (dMR < 0.0) {
						continue;
					}
				}

				//printf("(OK)");

				mapSortedRows.insert(
					std::pair<double, int>(dEvalMagnitude, i));

				// Only store one entry for complex-conjugate pairs
				if (vecAlphaI[i] != 0.0) {
					i++;
				}
			}
		}

		Announce("%i eigenmodes found to satisfy entropy condition",
			mapSortedRows.size());
/*
		if (mapSortedRows.size() != param.nPhiElements) {
			_EXCEPTIONT("Mismatch between eigenmode count and latitude count");
		}
*/
		AnnounceEndBlock("Done");

		// Write the matrices to a file
		AnnounceStartBlock("Writing results");

		int iKix = nK - nKmin;
		int iWave = 0;
		std::map<double, int>::const_iterator it;
		for (it = mapSortedRows.begin(); it != mapSortedRows.end(); it++) {
			int i = it->second;

			double dLambdaR = vecAlphaR[i] / vecBeta[i];
			double dLambdaI = vecAlphaI[i] / vecBeta[i];

			double dMR = dLambdaI;
			double dMI = - dLambdaR - 1.0;

			// Dump eigenvalue to NetCDF file
			varMR->set_cur(iKix, iWave);
			varMR->put(&dMR, 1, 1);

			varMI->set_cur(iKix, iWave);
			varMI->put(&dMI, 1, 1);

			// Store real part of eigenfunction
			for (int j = 0; j < param.nPhiElements; j++) {
				dUR  [j] = matVR[i][4*j  ];
				dPR  [j] = matVR[i][4*j+1];
				dWR  [j] = matVR[i][4*j+2];
				dRhoR[j] = matVR[i][4*j+3];
			}
			for (int j = 0; j < param.nPhiElements-1; j++) {
				dVR[j] = matVR[i][4 * param.nPhiElements + j];
			}

			// Complex eigenvalue / eigenfunction pair
			if (dLambdaI != 0.0) {

				// Eigenvalue Lambda is complex conjugate
				dMR = -dMR;

				// Dump eigenvalue to NetCDF file
				varMR->set_cur(iKix, iWave+1);
				varMR->put(&dMR, 1, 1);

				varMI->set_cur(iKix, iWave+1);
				varMI->put(&dMI, 1, 1);

				// Store imaginary component of vector
				for (int j = 0; j < param.nPhiElements; j++) {
					dUI  [j] = matVR[i+1][4*j  ];
					dPI  [j] = matVR[i+1][4*j+1];
					dWI  [j] = matVR[i+1][4*j+2];
					dRhoI[j] = matVR[i+1][4*j+3];
				}
				for (int j = 0; j < param.nPhiElements-1; j++) {
					dVI[j] = matVR[i+1][4 * param.nPhiElements + j];
				}

			// Real eigenvalue / eigenvector pair
			} else {
				dUI.Zero();
				dPI.Zero();
				dWI.Zero();
				dRhoI.Zero();
				dVI.Zero();
			}

			// Dump the first eigenfunction to the file
			varUR->set_cur(iKix, iWave, 0);
			varUR->put(dUR, 1, 1, param.nPhiElements);

			varVR->set_cur(iKix, iWave, 0);
			varVR->put(dVR, 1, 1, param.nPhiElements-1);

			varPR->set_cur(iKix, iWave, 0);
			varPR->put(dPR, 1, 1, param.nPhiElements);

			varWR->set_cur(iKix, iWave, 0);
			varWR->put(dWR, 1, 1, param.nPhiElements);

			varRhoR->set_cur(iKix, iWave, 0);
			varRhoR->put(dRhoR, 1, 1, param.nPhiElements);

			varUI->set_cur(iKix, iWave, 0);
			varUI->put(dUI, 1, 1, param.nPhiElements);

			varVI->set_cur(iKix, iWave, 0);
			varVI->put(dVI, 1, 1, param.nPhiElements-1);

			varPI->set_cur(iKix, iWave, 0);
			varPI->put(dPI, 1, 1, param.nPhiElements);

			varWI->set_cur(iKix, iWave, 0);
			varWI->put(dWI, 1, 1, param.nPhiElements);

			varRhoI->set_cur(iKix, iWave, 0);
			varRhoI->put(dRhoI, 1, 1, param.nPhiElements);

			// Complex eigenvalue / eigenvector pair
			if (dLambdaI != 0.0) {
				for (int j = 0; j < param.nPhiElements; j++) {
					dUI  [j] = - dUI  [j];
					dPI  [j] = - dPI  [j];
					dWI  [j] = - dWI  [j];
					dRhoI[j] = - dRhoI[j];
				}
				for (int j = 0; j < param.nPhiElements-1; j++) {
					dVI[j] = - dVI[j];
				}

				varUR->set_cur(iKix, iWave+1, 0);
				varUR->put(dUR, 1, 1, param.nPhiElements);

				varVR->set_cur(iKix, iWave+1, 0);
				varVR->put(dVR, 1, 1, param.nPhiElements-1);

				varPR->set_cur(iKix, iWave+1, 0);
				varPR->put(dPR, 1, 1, param.nPhiElements);

				varWR->set_cur(iKix, iWave+1, 0);
				varWR->put(dWR, 1, 1, param.nPhiElements);

				varRhoR->set_cur(iKix, iWave+1, 0);
				varRhoR->put(dRhoR, 1, 1, param.nPhiElements);

				varUI->set_cur(iKix, iWave+1, 0);
				varUI->put(dUI, 1, 1, param.nPhiElements);

				varVI->set_cur(iKix, iWave+1, 0);
				varVI->put(dVI, 1, 1, param.nPhiElements-1);

				varPI->set_cur(iKix, iWave+1, 0);
				varPI->put(dPI, 1, 1, param.nPhiElements);

				varWI->set_cur(iKix, iWave+1, 0);
				varWI->put(dWI, 1, 1, param.nPhiElements);

				varRhoI->set_cur(iKix, iWave+1, 0);
				varRhoI->put(dRhoI, 1, 1, param.nPhiElements);
			}

			// Increment wave index
			iWave++;
			if (dLambdaI != 0.0) {
				iWave++;
			}
		}

		AnnounceEndBlock("Done");

		AnnounceBanner();
	}

} catch(Exception & e) {
	Announce(e.ToString().c_str());
}
}