#include"../common/define.h"
#include"Assemble_Base.h"
#include"Nedelec3D_Util.h"

#include "../Eigen/SparseLU"
#include <cmath>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <string>

#ifdef _WIN32
#include <direct.h>
static void EnsureOutputDir(const std::string& dir)
{
	_mkdir(dir.c_str());
}
#else
#include <sys/stat.h>
static void EnsureOutputDir(const std::string& dir)
{
	mkdir(dir.c_str(), 0755);
}
#endif


/*
2D_EigenMode
*/
void OpticsFEM_2D_EigenMode::Assemble()
{
	//??????????
	_mIsReal = _mMatLib->IsReal();
	if (_mIsReal)
	{
		if (_mPhy->GetNbrPML() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		for (int i = 0; i < _mPhy->GetNbrPBC(); i++)
		{
			if (_mPhy->GetPBCPhi(i).imag()!= 0.0)
			{
				_mIsReal = false;
				break;
			}
		}
	}

	//????????? ????????
	_mDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();

	//????????
	this->Assemble_WaveEquation();

	if (_mIsReal)
	{
		_mA_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
		_mB_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
		_mA_real.setFromTriplets(_mTripleA_real.begin(), _mTripleA_real.end());
		_mB_real.setFromTriplets(_mTripleB_real.begin(), _mTripleB_real.end());
	}
	else
	{
		_mA_complex = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(_mDof, _mDof);
		_mB_complex = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(_mDof, _mDof);
		_mA_complex.setFromTriplets(_mTripleA_complex.begin(), _mTripleA_complex.end());
		_mB_complex.setFromTriplets(_mTripleB_complex.begin(), _mTripleB_complex.end());
	}
	//????????????????????????
	if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBCGroups() > 0))
	{
		this->Assemble_PEC_PBC();

		if (_mIsReal)
		{
			_mA_real = (_mP_real.transpose() * _mA_real) * _mP_real;
			_mB_real = (_mP_real.transpose() * _mB_real) * _mP_real;
		}
		else
		{
			_mA_complex = (_mP_complex.adjoint() * _mA_complex) * _mP_complex;
			_mB_complex = (_mP_complex.adjoint() * _mB_complex) * _mP_complex;
		}
	}
	else
	{
		if (_mIsReal)
		{
			_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
			_mP_real.setIdentity();
		}
		else
		{
			_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(_mDof, _mDof);
			_mP_complex.setIdentity();
		}
	}
	//?????????????
	/*
	double k0 = 2 * Pi / _mSolver->GetLda0();
	double theta2 = k0 * k0 * _mSolver->GetSearchValue() * _mSolver->GetSearchValue();
	if (_mIsReal)
	{ 
		Eigen::SparseMatrix<double, Eigen::RowMajor> tempA = _mA_real;
		_mA_real = _mB_real;
		_mB_real = (1 / theta2) * tempA + _mB_real;
	}
	else
	{
		Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> tempA = _mA_complex;
		_mA_complex = _mB_complex;
		_mB_complex = (1 / theta2) * tempA + _mB_complex;
	}
	*/
}

void OpticsFEM_2D_EigenMode::Run()
{
	double k0 = 2. * Pi / _mSolver->GetLda0();
	double search = -k0 * k0 * _mSolver->GetSearchValue() * _mSolver->GetSearchValue();
	if (_mIsReal)
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_real, &_mB_real, &_mP_real, search);
	}
	else
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex, search);
	}
	_mSolver->Run(&_mX, &_mLambda);
}

void OpticsFEM_2D_EigenMode::Post(string file)
{
	_mPost->GetMesh(_mMesh);
	_mPost->GetSolver(_mSolver);
	_mPost->GetResult(&_mX, &_mLambda);
	_mPost->GetElectric();
	_mPost->OutputData(file);
}

/*
2D_EigenFreq
*/
void OpticsFEM_2D_EigenFreq::Assemble()
{
	//??????????
	_mIsReal = _mMatLib->IsReal();
	if (_mIsReal)
	{
		if (_mPhy->GetNbrPML() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		for (int i = 0; i < _mPhy->GetNbrPBC(); i++)
		{
			if (_mPhy->GetPBCPhi(i).imag()!= 0.0)
			{
				_mIsReal = false;
				break;
			}
		}
	}

	//????????
	this->Assemble_WaveEquation();

	//?????????
	if (_mIsReal)
	{
		int tempDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
		_mA_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(tempDof, tempDof);
		_mB_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(tempDof, tempDof);
		_mA_real.setFromTriplets(_mTripleA_real.begin(), _mTripleA_real.end());
		_mB_real.setFromTriplets(_mTripleB_real.begin(), _mTripleB_real.end());
	}
	else
	{
		int tempDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
		_mA_complex = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(tempDof, tempDof);
		_mB_complex = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(tempDof, tempDof);
		_mA_complex.setFromTriplets(_mTripleA_complex.begin(), _mTripleA_complex.end());
		_mB_complex.setFromTriplets(_mTripleB_complex.begin(), _mTripleB_complex.end());
	}

	//????????????????????????
	_mElectricType = _mSolver->GetElectricType();
	if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBCGroups() > 0))
	{
		this->Assemble_PEC_PBC();
	}
	else
	{
		//??T???????? 0-Et 1-Ez 2-E
		if (_mIsReal)
		{
			std::vector<Eigen::Triplet<double>> tempTriple;
			if (_mElectricType == 0)
			{
				_mDof = _mMesh->GetNbrEdge();
				_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					tempTriple.push_back(Eigen::Triplet<double>(_mMesh->GetNbrVertex() + i, i, 1.));
				}
				_mP_real.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else if (_mElectricType == 1)
			{
				_mDof = _mMesh->GetNbrVertex();
				_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					tempTriple.push_back(Eigen::Triplet<double>(i, i, 1.));

				}
				_mP_real.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else
			{
				_mDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
				_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
				_mP_real.setIdentity();
			}

		}
		else
		{
			std::vector<Eigen::Triplet<complex<double>>> tempTriple;
			if (_mElectricType == 0)
			{
				_mDof = _mMesh->GetNbrEdge();
				_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					/*tempTriple.push_back(Eigen::Triplet<complex<double>>(_mMesh->GetNbrVertex() + i, _mMesh->GetNbrVertex() + i, 1.));*/
					tempTriple.push_back(Eigen::Triplet<complex<double>>(_mMesh->GetNbrVertex() + i, i, 1.));
				}
				_mP_complex.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else if (_mElectricType == 1)
			{
				_mDof = _mMesh->GetNbrVertex();
				_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					tempTriple.push_back(Eigen::Triplet<complex<double>>(i, i, 1.));

				}
				_mP_complex.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else
			{
				_mDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
				_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(_mDof, _mDof);
				_mP_complex.setIdentity();
			}
		}

	}

	if (_mIsReal)
	{
		_mA_real = (_mP_real.transpose() * _mA_real) * _mP_real;
		_mB_real = (_mP_real.transpose() * _mB_real) * _mP_real;
	}
	else
	{
		_mA_complex = (_mP_complex.adjoint() * _mA_complex) * _mP_complex;
		_mB_complex = (_mP_complex.adjoint() * _mB_complex) * _mP_complex;
	}
}

void OpticsFEM_2D_EigenFreq::Run()
{
	double k0 = 2. * Pi / (c_const / _mSolver->GetSearchValue());
	double search = k0 * k0;
	if (_mIsReal)
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_real, &_mB_real, &_mP_real, search);
	}
	else
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex, search);
	}
	_mSolver->Run(&_mX, &_mLambda);
}

void OpticsFEM_2D_EigenFreq::Post(string file)
{
	_mPost->GetMesh(_mMesh);
	_mPost->GetSolver(_mSolver);
	_mPost->GetResult(&_mX, &_mLambda);
	_mPost->GetElectric();
	_mPost->OutputData(file);
}

/*
2D_Scatter
*/
void OpticsFEM_2D_Scatter::Assemble()
{
	_mIsReal = _mMatLib->IsReal();
	if (_mIsReal)
	{
		if (_mPhy->GetNbrPML() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		if (_mPhy->GetNbrSBC() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		/*for (int i = 0; i < _mPhy->GetNbrElE(); i++)
		{
			Eigen::Vector3cd E0;
			_mPhy->GetE0(i, E0);
			for (int j = 0; j < 3; j++)
			{
				if (E0(j).imag() != 0)
				{
					_mIsReal = false;
				}
			}
		}*/
	}
	if (_mIsReal)
	{
		for (int i = 0; i < _mPhy->GetNbrPBC(); i++)
		{
			if (_mPhy->GetPBCPhi(i).imag()!= 0.0)
			{
				_mIsReal = false;
				break;
			}
		}
	}
	if (_mIsReal)
	{
		if (_mPhy->GetBeamState() != 0)
			_mIsReal = false;
	}

	//???????????
	this->Assemble_WaveEquation();

	if (_mIsReal)
	{
		int tempDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
		_mB_real = Eigen::VectorXd::Zero(tempDof);
	}
	else
	{
		int tempDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
		_mB_complex = Eigen::VectorXcd::Zero(tempDof);
	}

	//SBC
	if (_mPhy->GetNbrSBC() > 0)
		this->Assemble_SBC();

	//BELE
	if (_mPhy->GetNbrBELE())
		this->Assemble_BELE();
		
	if (_mIsReal)
	{
		int tempDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
		_mA_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(tempDof, tempDof);
		_mA_real.setFromTriplets(_mTripleA_real.begin(), _mTripleA_real.end());
	}
	else
	{
		int tempDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
		_mA_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(tempDof, tempDof);
		_mA_complex.setFromTriplets(_mTripleA_complex.begin(), _mTripleA_complex.end());
	}

	//MAG
	if (_mPhy->GetNbrMAG())
		this->Assemble_MAG();

	//SCD
	if (_mPhy->GetNbrSCD())
		this->Assemble_SCD();

	//MPD
	if (_mPhy->GetNbrMPD())
		this->Assemble_MPD();

	//EPD
	if (_mPhy->GetNbrEPD())
		this->Assemble_EPD();

	//PEC+ELE
	this->Assemble_PEC_ELE();

	//PBC
	if (_mPhy->GetNbrPBCGroups() > 0)
	{
		this->Assemble_PBC();
	}
	else
	{
		if (_mIsReal)
		{
			std::vector<Eigen::Triplet<double>> tempTriple;
			if (_mSolver->GetElectricType() == 0)
			{
				_mDof = _mMesh->GetNbrEdge();
				_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					tempTriple.push_back(Eigen::Triplet<double>(_mMesh->GetNbrVertex() + i, i, 1.));
				}
				_mP_real.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else if (_mSolver->GetElectricType() == 1)
			{
				_mDof = _mMesh->GetNbrVertex();
				_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					tempTriple.push_back(Eigen::Triplet<double>(i, i, 1.));

				}
				_mP_real.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else
			{
				_mDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
				_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
				_mP_real.setIdentity();
			}

		}
		else
		{
			std::vector<Eigen::Triplet<complex<double>>> tempTriple;
			if (_mSolver->GetElectricType() == 0)
			{
				_mDof = _mMesh->GetNbrEdge();
				_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					tempTriple.push_back(Eigen::Triplet<complex<double>>(_mMesh->GetNbrVertex() + i, i, 1.));
				}
				_mP_complex.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else if (_mSolver->GetElectricType() == 1)
			{
				_mDof = _mMesh->GetNbrVertex();
				_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(_mMesh->GetNbrVertex() + _mMesh->GetNbrEdge(), _mDof);
				for (int i = 0; i < _mDof; i++)
				{
					tempTriple.push_back(Eigen::Triplet<complex<double>>(i, i, 1.));

				}
				_mP_complex.setFromTriplets(tempTriple.begin(), tempTriple.end());
			}
			else
			{
				_mDof = _mMesh->GetNbrVertex() + _mMesh->GetNbrEdge();
				_mP_complex = Eigen::SparseMatrix<complex<double>, Eigen::RowMajor>(_mDof, _mDof);
				_mP_complex.setIdentity();
			}
		}
	}
	if (_mIsReal)
	{
		_mA_real = (_mP_real.transpose() * _mA_real) * _mP_real;
		_mB_real = _mP_real.transpose() * _mB_real;
	}
	else
	{
		_mA_complex = (_mP_complex.adjoint() * _mA_complex) * _mP_complex;
		_mB_complex = _mP_complex.adjoint() * _mB_complex;
	}

}

void OpticsFEM_2D_Scatter::Run()
{
	if (_mIsReal)
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_real, &_mB_real, &_mP_real);
	}
	else
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex);
	}
	_mSolver->Run(&_mX);
}

void OpticsFEM_2D_Scatter::Post(string file)
{
	_mPost->GetMesh(_mMesh);
	_mPost->GetSolver(_mSolver);
	_mPost->GetPhy(_mPhy);
	_mPost->GetResult(&_mX);
	_mPost->GetElectric();
	_mPost->OutputData(file);
}

/*
3D_EigenFreq - volume assembly_equ → sparse A (curl-curl) and B (eps mass)
*/
void OpticsFEM_3D_EigenFreq::Assemble()
{
	const int elementOrder = _mPhy->GetElementOrder();
	_mDof = Nedelec3D::globalDofCount(_mMesh, elementOrder);
	_mTripleA_real.clear();
	_mTripleB_real.clear();
	_mTripleA_complex.clear();
	_mTripleB_complex.clear();

	_mIsReal = _mMatLib->IsReal();
	if (_mIsReal)
	{
		for (int i = 0; i < _mPhy->GetNbrPBC(); i++)
		{
			if (_mPhy->GetPBCPhi(i).imag()!= 0.0)
			{
				_mIsReal = false;
				break;
			}
		}
	}

	this->Assemble_WaveEquation();

	if (_mIsReal)
	{
		_mA_real.resize(_mDof, _mDof);
		_mB_real.resize(_mDof, _mDof);
		_mA_real.setFromTriplets(_mTripleA_real.begin(), _mTripleA_real.end());
		_mB_real.setFromTriplets(_mTripleB_real.begin(), _mTripleB_real.end());
	}
	else
	{
		_mA_complex.resize(_mDof, _mDof);
		_mB_complex.resize(_mDof, _mDof);
		_mA_complex.setFromTriplets(_mTripleA_complex.begin(), _mTripleA_complex.end());
		_mB_complex.setFromTriplets(_mTripleB_complex.begin(), _mTripleB_complex.end());
	}

	if ((_mPhy->GetNbrPEC() + _mPhy->GetNbrPBCGroups() > 0))
	{
		this->Assemble_PEC_PBC();
	}
	else
	{
		if (_mIsReal)
		{
			_mP_real.resize(_mDof, _mDof);
			_mP_real.setIdentity();
		}
		else
		{
			_mP_complex.resize(_mDof, _mDof);
			_mP_complex.setIdentity();
		}
	}

	if (_mIsReal)
	{
		_mA_real = (_mP_real.transpose() * _mA_real) * _mP_real;
		_mB_real = (_mP_real.transpose() * _mB_real) * _mP_real;
	}
	else
	{
		_mA_complex = (_mP_complex.adjoint() * _mA_complex) * _mP_complex;
		_mB_complex = (_mP_complex.adjoint() * _mB_complex) * _mP_complex;
	}

	std::cout << "[OpticsFEM_3D_EigenFreq] Assemble done: DOF=" << _mDof
		<< " nnz(A)=" << (_mIsReal ? _mA_real.nonZeros() : _mA_complex.nonZeros())
		<< " nnz(B)=" << (_mIsReal ? _mB_real.nonZeros() : _mB_complex.nonZeros())
		<< std::endl;
}

/*
3D_Scatter - 3D first-order Nedelec scattering (assembly_equ + assembly_out + assembly_inc)
*/
void OpticsFEM_3D_Scatter::Assemble()
{
	_mFreeDofIndices.clear();
	_mPortExtraDof = 0;
	_mIsReal = _mMatLib->IsReal();
	if (_mIsReal)
	{
		if (_mPhy->GetNbrPML() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		if (_mPhy->GetNbrSBC() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		for (int i = 0; i < _mPhy->GetNbrPBC(); i++)
		{
			if (_mPhy->GetPBCPhi(i).imag() != 0.0)
			{
				_mIsReal = false;
				break;
			}
		}
	}
	if (_mIsReal && _mPhy->GetNbrElE() > 0)
		_mIsReal = false;
	if (_mIsReal && (_mPhy->GetNbrBELE() > 0 || _mPhy->GetNbrMAG() > 0
		|| _mPhy->GetNbrSCD() > 0 || _mPhy->GetNbrMPD() > 0 || _mPhy->GetNbrEPD() > 0))
		_mIsReal = false;
	if (_mIsReal && _mPhy->HasNumericPort())
		_mIsReal = false;

	const int elementOrder = _mPhy->GetElementOrder();
	if (elementOrder == 2)
	{
		if (_mPhy->HasNumericPort())
		{
			std::cerr << "[OpticsFEM] ElementOrder=2: Port not supported yet; skipped."
				<< std::endl;
		}
	}

	_mDof = Nedelec3D::globalDofCount(_mMesh, elementOrder);

	this->Assemble_WaveEquation();

	if (_mIsReal)
		_mB_real = Eigen::VectorXd::Zero(_mDof);
	else
		_mB_complex = Eigen::VectorXcd::Zero(_mDof);

	if (_mPhy->GetNbrSBC() > 0)
		this->Assemble_SBC();

	if (_mPhy->GetNbrBELE() > 0)
		this->Assemble_BELE();

	if (_mIsReal)
	{
		_mA_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
		_mA_real.setFromTriplets(_mTripleA_real.begin(), _mTripleA_real.end());
		_mP_real = Eigen::SparseMatrix<double, Eigen::RowMajor>(_mDof, _mDof);
		_mP_real.setIdentity();
	}
	else
	{
		_mA_complex = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(_mDof, _mDof);
		_mA_complex.setFromTriplets(_mTripleA_complex.begin(), _mTripleA_complex.end());
		_mP_complex = Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>(_mDof, _mDof);
		_mP_complex.setIdentity();
	}

	if (_mExportAsmDir.size() > 0)
		this->Test_OutputMatrix(_mExportAsmDir);

	if (elementOrder != 2 && _mPhy->HasNumericPort())
		this->Assemble_Port();

	if (_mPhy->GetNbrMAG() > 0)
		this->Assemble_MAG();
	if (_mPhy->GetNbrSCD() > 0)
		this->Assemble_SCD();
	if (_mPhy->GetNbrMPD() > 0)
		this->Assemble_MPD();
	if (_mPhy->GetNbrEPD() > 0)
		this->Assemble_EPD();

	if (_mPhy->GetNbrPEC() > 0 || _mPhy->GetNbrElE() > 0)
		this->Assemble_PEC_ELE();

	if (_mPhy->GetNbrPBCGroups() > 0)
		this->Assemble_PBC();

	if (_mIsReal)
	{
		_mA_real = (_mP_real.transpose() * _mA_real) * _mP_real;
		_mB_real = _mP_real.transpose() * _mB_real;
	}
	else
	{
		// After port PEC elimination A/B are already reduced; skip P projection.
		if (_mFreeDofIndices.empty())
		{
			_mA_complex = (_mP_complex.adjoint() * _mA_complex) * _mP_complex;
			_mB_complex = _mP_complex.adjoint() * _mB_complex;
		}
	}
}

void OpticsFEM_3D_Scatter::Run()
{
	if (_mIsReal)
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_real, &_mB_real, &_mP_real);
	}
	else
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex);
	}

	// PEC/ELE: after Dirichlet BC, external complexsolver CSR export fails; use SparseLU here only.
	const bool useBuiltInLu = (_mPhy->GetNbrPEC() > 0 || _mPhy->GetNbrElE() > 0 || _mPhy->HasNumericPort());
	if (!_mIsReal && useBuiltInLu)
	{
		_mA_complex.makeCompressed();
		Eigen::VectorXcd xReduced;
		if (_mPhy->HasNumericPort())
		{
			std::cout << "[OpticsFEM_3D_Scatter] Reduced system"
				<< " size=" << _mA_complex.rows()
				<< " nnz=" << _mA_complex.nonZeros() << std::endl;
			if (_mFreeDofIndices.size() >= 2)
			{
				const size_t n = _mFreeDofIndices.size();
				std::cout << "[OpticsFEM_3D_Scatter] Port free DOFs:"
					<< " e1_idx=" << _mFreeDofIndices[n - 2]
					<< " e2_idx=" << _mFreeDofIndices[n - 1] << std::endl;
			}
		}
		bool solved = false;
		{
			// MATLAB FemMatrixAssembly uses equilibrate(A) before '\'.
			// Ruiz infinity-norm scaling + iterative refinement stabilizes short-cavity ports.
			const int nA = static_cast<int>(_mA_complex.rows());
			Eigen::VectorXd Rsc = Eigen::VectorXd::Ones(nA);
			Eigen::VectorXd Csc = Eigen::VectorXd::Ones(nA);
			Eigen::SparseMatrix<std::complex<double>> Aeq = _mA_complex;
			for (int itEq = 0; itEq < 5; ++itEq)
			{
				Eigen::VectorXd rowInf = Eigen::VectorXd::Zero(nA);
				for (int k = 0; k < Aeq.outerSize(); ++k)
					for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
						rowInf(it.row()) = std::max(rowInf(it.row()), std::abs(it.value()));
				for (int i = 0; i < nA; ++i)
				{
					const double s = (rowInf(i) > 0.0) ? (1.0 / std::sqrt(rowInf(i))) : 1.0;
					Rsc(i) *= s;
				}
				std::vector<Eigen::Triplet<std::complex<double>>> trips;
				trips.reserve(static_cast<size_t>(Aeq.nonZeros()));
				for (int k = 0; k < Aeq.outerSize(); ++k)
					for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
						trips.emplace_back(it.row(), it.col(), it.value() * (rowInf(it.row()) > 0.0 ? (1.0 / std::sqrt(rowInf(it.row()))) : 1.0));
				Aeq.resize(nA, nA);
				Aeq.setFromTriplets(trips.begin(), trips.end());

				Eigen::VectorXd colInf = Eigen::VectorXd::Zero(nA);
				for (int k = 0; k < Aeq.outerSize(); ++k)
					for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
						colInf(it.col()) = std::max(colInf(it.col()), std::abs(it.value()));
				for (int i = 0; i < nA; ++i)
				{
					const double s = (colInf(i) > 0.0) ? (1.0 / std::sqrt(colInf(i))) : 1.0;
					Csc(i) *= s;
				}
				trips.clear();
				for (int k = 0; k < Aeq.outerSize(); ++k)
					for (Eigen::SparseMatrix<std::complex<double>>::InnerIterator it(Aeq, k); it; ++it)
						trips.emplace_back(it.row(), it.col(), it.value() * (colInf(it.col()) > 0.0 ? (1.0 / std::sqrt(colInf(it.col()))) : 1.0));
				Aeq.resize(nA, nA);
				Aeq.setFromTriplets(trips.begin(), trips.end());
			}

			const Eigen::VectorXcd beq = Rsc.asDiagonal() * _mB_complex;
			Eigen::SparseLU<Eigen::SparseMatrix<std::complex<double>>> lu;
			lu.compute(Aeq);
			if (lu.info() == Eigen::Success)
			{
				xReduced = Csc.asDiagonal() * lu.solve(beq);
				// Iterative refinement on the original (unscaled) system.
				for (int ir = 0; ir < 3; ++ir)
				{
					const Eigen::VectorXcd r = _mB_complex - _mA_complex * xReduced;
					Eigen::VectorXcd dr = Csc.asDiagonal() * lu.solve(Rsc.asDiagonal() * r);
					xReduced += dr;
					if (r.norm() < 1e-12 * (_mB_complex.norm() + 1e-30))
						break;
				}
				solved = true;
			}
		}
		if (solved)
		{
			if (_mPhy->HasNumericPort())
			{
				if (!_mFreeDofIndices.empty())
				{
					_mX = Eigen::VectorXcd::Zero(_mDof + _mPortExtraDof);
					for (size_t i = 0; i < _mFreeDofIndices.size(); i++)
						_mX(_mFreeDofIndices[i]) = xReduced(static_cast<int>(i));
				}
				else
					_mX = xReduced;
			}
			else
			{
				_mX = Eigen::VectorXcd::Zero(_mP_complex.rows());
				_mX = _mP_complex * xReduced;
			}
			const Eigen::VectorXcd r = _mA_complex * xReduced - _mB_complex;
			std::cout << "[OpticsFEM_3D_Scatter] SparseLU (PEC/ELE/Port)"
				<< " |x|=" << _mX.norm()
				<< " |r|=" << r.norm()
				<< " |b|=" << _mB_complex.norm() << std::endl;
		}
		else
		{
			std::cout << "[OpticsFEM_3D_Scatter] SparseLU factorization failed" << std::endl;
		}
		return;
	}

	_mSolver->Run(&_mX);

	if (!_mIsReal && _mB_complex.size() > 0)
	{
		const double bNorm = _mB_complex.norm();
		const double xNorm = _mX.norm();
		if (bNorm > 0.0 && (!std::isfinite(_mX(0).real()) || xNorm < 1e-12 * bNorm))
		{
			Eigen::SparseLU<Eigen::SparseMatrix<std::complex<double>>> lu;
			lu.compute(_mA_complex);
			if (lu.info() == Eigen::Success)
				_mX = lu.solve(_mB_complex);
		}
	}
}

void OpticsFEM_3D_Scatter::Post(string file)
{
	_mPost->GetMesh(_mMesh);
	_mPost->GetPhy(_mPhy);
	_mPost->GetResult(&_mX);
	_mPost->GetElectric();
	if (_mPortExtraDof > 0 && _mX.size() >= _mDof + _mPortExtraDof)
	{
		std::cout << "[OpticsFEM_3D_Scatter] Port amplitudes:";
		for (int i = 0; i < _mPortExtraDof; i++)
			std::cout << " e" << (i + 1) << "=" << _mX(_mDof + i);
		std::cout << std::endl;

		std::ofstream sOut(file + "/S_params.txt");
		if (sOut.is_open())
		{
			sOut << std::setprecision(12);
			const double pcoef = std::abs(_mPhy->GetNumericPort(0).powerCoef);
			for (int i = 0; i < _mPortExtraDof; i++)
			{
				const std::complex<double> e = _mX(_mDof + i);
				sOut << "e" << (i + 1) << " " << e.real() << " " << e.imag();
				if (i == 0 && pcoef > 0.0)
					sOut << "  S11=" << (e.real() / pcoef) << " " << (e.imag() / pcoef);
				else if (i == 1 && pcoef > 0.0)
					sOut << "  S21=" << (e.real() / pcoef) << " " << (e.imag() / pcoef);
				sOut << std::endl;
			}
		}
	}
	this->Test_OutputMatrix(file);
	_mPost->OutputData(file);
	std::cout << "[OpticsFEM] post to " << file << std::endl;
}

void OpticsFEM_3D_Scatter::Test_OutputMatrix(const std::string& outDir)
{
	EnsureOutputDir(outDir);
	const std::string prefix = outDir + "/";
	std::ofstream outAi(prefix + "Ai.txt"), outAj(prefix + "Aj.txt"), outAv(prefix + "Av.txt");
	std::ofstream outBv_real(prefix + "Bv_real.txt"), outBv_imag(prefix + "Bv_imag.txt");
	std::ofstream outX_real(prefix + "X_real.txt"), outX_imag(prefix + "X_imag.txt");
	outAv << std::setprecision(12);

	if (_mIsReal)
	{
		for (int col = 0; col < _mA_real.outerSize(); col++)
		{
			for (Eigen::SparseMatrix<double, Eigen::RowMajor>::InnerIterator it(_mA_real, col); it; ++it)
			{
				outAi << it.row() << std::endl;
				outAj << it.col() << std::endl;
				outAv << it.value() << std::endl;
			}
		}
		for (int i = 0; i < _mB_real.size(); i++)
			outBv_real << _mB_real(i) << std::endl;
		for (int i = 0; i < _mX.size(); i++)
			outX_real << _mX(i).real() << std::endl;
	}
	else
	{
		// Export reduced system after PBC/PEC projection (matches MATLAB main_export OutFile).
		for (int col = 0; col < _mA_complex.outerSize(); col++)
		{
			for (Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>::InnerIterator it(_mA_complex, col); it; ++it)
			{
				outAi << it.row() << std::endl;
				outAj << it.col() << std::endl;
				outAv << "(" << it.value().real() << "," << it.value().imag() << ")" << std::endl;
			}
		}
		for (int i = 0; i < _mB_complex.size(); i++)
		{
			outBv_real << _mB_complex(i).real() << std::endl;
			outBv_imag << _mB_complex(i).imag() << std::endl;
		}
		for (int i = 0; i < _mX.size(); i++)
		{
			outX_real << _mX(i).real() << std::endl;
			outX_imag << _mX(i).imag() << std::endl;
		}
	}
}

void OpticsFEM_3D_EigenFreq::Test_OutputMatrix(const std::string& outDir)
{
	EnsureOutputDir(outDir);
	const std::string prefix = outDir + "/";

	if (_mIsReal)
	{
		std::ofstream outAi(prefix + "Ai.txt"), outAj(prefix + "Aj.txt"), outAv(prefix + "Av.txt");
		std::ofstream outBi(prefix + "Bi.txt"), outBj(prefix + "Bj.txt"), outBv(prefix + "Bv.txt");
		for (const auto& t : _mTripleA_real)
		{
			outAi << t.row() << '\n';
			outAj << t.col() << '\n';
			outAv << t.value() << '\n';
		}
		for (const auto& t : _mTripleB_real)
		{
			outBi << t.row() << '\n';
			outBj << t.col() << '\n';
			outBv << t.value() << '\n';
		}
		std::cout << "[OpticsFEM_3D_EigenFreq] exported A/B COO to " << outDir << std::endl;
	}
	else
	{
		std::ofstream outAi(prefix + "Ai.txt"), outAj(prefix + "Aj.txt");
		std::ofstream outAv_real(prefix + "Av_real.txt"), outAv_imag(prefix + "Av_imag.txt");
		std::ofstream outBi(prefix + "Bi.txt"), outBj(prefix + "Bj.txt");
		std::ofstream outBv_real(prefix + "Bv_real.txt"), outBv_imag(prefix + "Bv_imag.txt");
		for (const auto& t : _mTripleA_complex)
		{
			outAi << t.row() << '\n';
			outAj << t.col() << '\n';
			outAv_real << t.value().real() << '\n';
			outAv_imag << t.value().imag() << '\n';
		}
		for (const auto& t : _mTripleB_complex)
		{
			outBi << t.row() << '\n';
			outBj << t.col() << '\n';
			outBv_real << t.value().real() << '\n';
			outBv_imag << t.value().imag() << '\n';
		}
		std::cout << "[OpticsFEM_3D_EigenFreq] exported A/B COO to " << outDir << std::endl;
	}
}

void OpticsFEM_3D_EigenFreq::Run()
{
	double k0 = 2. * Pi / (c_const / _mSolver->GetSearchValue());
	double search = k0 * k0;
	if (_mIsReal)
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_real, &_mB_real, &_mP_real, search);
	}
	else
	{
		_mSolver->GetRealFlag(_mIsReal);
		_mSolver->SetParam(&_mA_complex, &_mB_complex, &_mP_complex, search);
	}
	_mSolver->Run(&_mX, &_mLambda);
}

void OpticsFEM_3D_EigenFreq::Post(string file)
{
	_mPost->GetMesh(_mMesh);
	_mPost->GetSolver(_mSolver);
	_mPost->GetResult(&_mX, &_mLambda);
	_mPost->GetElectric();
	_mPost->OutputData(file);
	std::cout << "[OpticsFEM_3D_EigenFreq] post to " << file << std::endl;
}

/*
3D_Scatter2 
prism
*/
void OpticsFEM_3D_Scatter2::Assemble()
{
	_mIsReal = _mMatLib->IsReal();
	if (_mIsReal)
	{
		if (_mPhy->GetNbrPML() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		if (_mPhy->GetNbrSBC() > 0)
			_mIsReal = false;
	}
	if (_mIsReal)
	{
		/*for (int i = 0; i < _mPhy->GetNbrElE(); i++)
		{
			Eigen::Vector3cd E0;
			_mPhy->GetE0(i, E0);
			for (int j = 0; j < 3; j++)
			{
				if (E0(j).imag() != 0)
				{
					_mIsReal = false;
				}
			}
		}*/
	}

	this->Assemble_WaveEquation();

	//SBC
	//this->Assemble_SBC();

	//Port (n+m)*(n+m)
	//this->Assemble_PortBC();


	if (_mIsReal)
		_mA_real.setFromTriplets(_mTripleA_real.begin(), _mTripleA_real.end());
	else
		_mA_complex.setFromTriplets(_mTripleA_complex.begin(), _mTripleA_complex.end());

	//PEC+ELE
	//this->Assemble_PEC_ELE();
}
