200 lines
6.2 KiB
C++
200 lines
6.2 KiB
C++
// Drop-in replacement for OpticsFEM external complex linear solver.
|
|
// Reads CSR matrix + RHS from binary .dat files (same protocol as solver/interface.cpp)
|
|
// and writes x_real_0.dat / x_complex_0.dat for the main program.
|
|
|
|
#include <Eigen/Sparse>
|
|
#include <Eigen/SparseLU>
|
|
#include <Eigen/IterativeLinearSolvers>
|
|
|
|
#include <complex>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using SpMat = Eigen::SparseMatrix<std::complex<double>>;
|
|
using VecX = Eigen::VectorXcd;
|
|
|
|
static std::vector<int> readIntFile(const char* path)
|
|
{
|
|
std::ifstream in(path, std::ios::binary);
|
|
if (!in)
|
|
throw std::runtime_error(std::string("cannot open ") + path);
|
|
in.seekg(0, std::ios::end);
|
|
const std::streamsize bytes = in.tellg();
|
|
in.seekg(0, std::ios::beg);
|
|
std::vector<int> data(static_cast<size_t>(bytes / sizeof(int)));
|
|
if (!data.empty())
|
|
in.read(reinterpret_cast<char*>(data.data()), bytes);
|
|
return data;
|
|
}
|
|
|
|
static std::vector<double> readDoubleFile(const char* path)
|
|
{
|
|
std::ifstream in(path, std::ios::binary);
|
|
if (!in)
|
|
throw std::runtime_error(std::string("cannot open ") + path);
|
|
in.seekg(0, std::ios::end);
|
|
const std::streamsize bytes = in.tellg();
|
|
in.seekg(0, std::ios::beg);
|
|
std::vector<double> data(static_cast<size_t>(bytes / sizeof(double)));
|
|
if (!data.empty())
|
|
in.read(reinterpret_cast<char*>(data.data()), bytes);
|
|
return data;
|
|
}
|
|
|
|
static void writeDoubleFile(const char* path, const double* data, int n)
|
|
{
|
|
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
|
if (!out)
|
|
throw std::runtime_error(std::string("cannot write ") + path);
|
|
out.write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(n) * sizeof(double));
|
|
}
|
|
|
|
static SpMat loadCsrMatrix(const std::vector<int>& roffsets,
|
|
const std::vector<int>& cindices,
|
|
const std::vector<double>& aReal,
|
|
const std::vector<double>& aImag)
|
|
{
|
|
if (roffsets.size() < 2)
|
|
throw std::runtime_error("invalid Roffsets.dat");
|
|
const int n = static_cast<int>(roffsets.size()) - 1;
|
|
const int nnz = roffsets.back();
|
|
if (static_cast<int>(cindices.size()) != nnz
|
|
|| static_cast<int>(aReal.size()) != nnz
|
|
|| static_cast<int>(aImag.size()) != nnz)
|
|
throw std::runtime_error("CSR dimensions mismatch");
|
|
|
|
std::vector<Eigen::Triplet<std::complex<double>>> triplets;
|
|
triplets.reserve(static_cast<size_t>(nnz));
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
for (int k = roffsets[static_cast<size_t>(i)]; k < roffsets[static_cast<size_t>(i + 1)]; ++k)
|
|
{
|
|
triplets.emplace_back(i, cindices[static_cast<size_t>(k)],
|
|
std::complex<double>(aReal[static_cast<size_t>(k)], aImag[static_cast<size_t>(k)]));
|
|
}
|
|
}
|
|
|
|
SpMat A(n, n);
|
|
A.setFromTriplets(triplets.begin(), triplets.end());
|
|
A.makeCompressed();
|
|
return A;
|
|
}
|
|
|
|
static VecX loadRhs(const std::vector<double>& bReal, const std::vector<double>& bImag, int n)
|
|
{
|
|
if (static_cast<int>(bReal.size()) != n || static_cast<int>(bImag.size()) != n)
|
|
throw std::runtime_error("RHS dimensions mismatch");
|
|
VecX b(n);
|
|
for (int i = 0; i < n; ++i)
|
|
b(i) = std::complex<double>(bReal[static_cast<size_t>(i)], bImag[static_cast<size_t>(i)]);
|
|
return b;
|
|
}
|
|
|
|
static VecX solveDirect(const SpMat& A, const VecX& b)
|
|
{
|
|
Eigen::SparseLU<SpMat> lu;
|
|
lu.compute(A);
|
|
if (lu.info() != Eigen::Success)
|
|
throw std::runtime_error("SparseLU factorization failed");
|
|
VecX x = lu.solve(b);
|
|
if (lu.info() != Eigen::Success)
|
|
throw std::runtime_error("SparseLU solve failed");
|
|
return x;
|
|
}
|
|
|
|
static VecX solveIterative(const SpMat& A, const VecX& b, double relTol, double absTol, int& iterations)
|
|
{
|
|
Eigen::BiCGSTAB<SpMat, Eigen::IncompleteLUT<std::complex<double>>> solver;
|
|
solver.preconditioner().setDroptol(1e-4);
|
|
solver.setMaxIterations(2000);
|
|
solver.setTolerance(relTol > 0.0 ? relTol : 1e-8);
|
|
solver.compute(A);
|
|
if (solver.info() != Eigen::Success)
|
|
throw std::runtime_error("BiCGSTAB setup failed");
|
|
|
|
VecX x = solver.solve(b);
|
|
iterations = static_cast<int>(solver.iterations());
|
|
const double bn = b.norm();
|
|
const double rn = (A * x - b).norm();
|
|
if (solver.info() != Eigen::Success && rn > absTol && rn > relTol * bn)
|
|
throw std::runtime_error("BiCGSTAB did not converge");
|
|
return x;
|
|
}
|
|
|
|
int main()
|
|
{
|
|
try
|
|
{
|
|
const std::vector<int> roffsets = readIntFile("Roffsets.dat");
|
|
const std::vector<int> cindices = readIntFile("Cindices.dat");
|
|
const std::vector<double> aReal = readDoubleFile("A_real.dat");
|
|
const std::vector<double> aImag = readDoubleFile("A_imag.dat");
|
|
const std::vector<double> bReal = readDoubleFile("B_real.dat");
|
|
const std::vector<double> bImag = readDoubleFile("B_imag.dat");
|
|
const std::vector<double> opt = readDoubleFile("opt.dat");
|
|
|
|
const int n = static_cast<int>(roffsets.size()) - 1;
|
|
const double relTol = opt.size() > 3 ? opt[3] : 1e-3;
|
|
const double absTol = opt.size() > 4 ? opt[4] : 1e-6;
|
|
|
|
std::cerr << "[complexsolver] n=" << n << " nnz=" << roffsets.back()
|
|
<< " relTol=" << relTol << std::endl;
|
|
|
|
const SpMat A = loadCsrMatrix(roffsets, cindices, aReal, aImag);
|
|
const VecX b = loadRhs(bReal, bImag, n);
|
|
|
|
VecX x;
|
|
std::string method;
|
|
int iterations = 0;
|
|
|
|
const char* mode = std::getenv("OPTICSFEM_COMPLEXSOLVER");
|
|
const bool useIterative = mode && std::strcmp(mode, "iter") == 0;
|
|
|
|
if (useIterative && n >= 5000)
|
|
{
|
|
x = solveIterative(A, b, relTol, absTol, iterations);
|
|
method = "BiCGSTAB+ILUT";
|
|
}
|
|
else
|
|
{
|
|
x = solveDirect(A, b);
|
|
method = "SparseLU";
|
|
}
|
|
|
|
std::vector<double> xr(static_cast<size_t>(n)), xi(static_cast<size_t>(n));
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
xr[static_cast<size_t>(i)] = x(i).real();
|
|
xi[static_cast<size_t>(i)] = x(i).imag();
|
|
}
|
|
|
|
writeDoubleFile("x_real_0.dat", xr.data(), n);
|
|
writeDoubleFile("x_complex_0.dat", xi.data(), n);
|
|
|
|
const double residual = (A * x - b).norm();
|
|
const double bnorm = b.norm();
|
|
writeDoubleFile("res.dat", &residual, 1);
|
|
|
|
std::ofstream log("log.dat", std::ios::trunc);
|
|
log << "method=" << method << "\n";
|
|
log << "n=" << n << " nnz=" << roffsets.back() << "\n";
|
|
log << "|b|=" << bnorm << " |r|=" << residual << "\n";
|
|
if (iterations > 0)
|
|
log << "iterations=" << iterations << "\n";
|
|
|
|
std::cerr << "[complexsolver] done " << method << " |x|=" << x.norm()
|
|
<< " |r|=" << residual << std::endl;
|
|
return 0;
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
std::cerr << "[complexsolver] ERROR: " << ex.what() << std::endl;
|
|
return 1;
|
|
}
|
|
}
|