Program Listing for File matrixsimulator.cpp

Return to documentation for file (src/matrixsimulator.cpp)

#include "matrixsimulator.h"
#include <iostream>
#include <fstream>
#include <iterator>
//#include "Histogram.h"
#include "H5Cpp.h"
#include <cmath>
#include "helper.h"
#include <hdf5_hl.h>
#include <CCfits/CCfits>
#include <CCfits/FITS.h>
#include <string>
#include <fstream>
#include <chrono>
#include <utility>
#include <omp.h>
#include "random_generator.h"
#define FMT_HEADER_ONLY

#include <fmt/format.h>

#pragma omp declare reduction(vec_int_plus : std::vector<int> : \
                              std::transform(omp_out.begin(), omp_out.end(), omp_in.begin(), omp_out.begin(), std::plus<int>())) \
                    initializer(omp_priv(omp_orig.size(), 0))

typedef struct transformation_hdf {
    float rotation;
    float scale_x;
    float scale_y;
    float shear;
    float translation_x;
    float translation_y;
    float wavelength;
} transformation_hdf;

MatrixSimulator::MatrixSimulator(const std::string path, int fiber_number, bool keep_ccd) {
    this->load_spectrograph_model(path, fiber_number, keep_ccd);
}

void MatrixSimulator::load_spectrograph_model(const std::string path, int fiber_number, bool keep_ccd) {
    if (path.find(".hdf") != std::string::npos) {
        std::cout << "Spectrograph file found: " << path << std::endl;
    } else {
        std::cout << "Spectrograph file not found: " << path << std::endl;
    }
    const H5std_string &filename(path);
    this->efficiencies.clear();
    this->orders.clear();
    this->raw_transformations.clear();
    this->sim_wavelength.clear();
    this->sim_flux.clear();
    this->sim_efficiencies.clear();
    this->sim_1d.clear();

    // open file readonly
    auto *h5file = new H5::H5File(filename, H5F_ACC_RDONLY);

    // read in spectrograph information
    auto *spec = new H5::Group(h5file->openGroup("Spectrograph"));
    auto *attr = new H5::Attribute(spec->openAttribute("blaze"));
    auto *type = new H5::DataType(attr->getDataType());
    double alpha, gpmm = 0;
    attr->read(*type, &alpha);
    spec->openAttribute("gpmm").read(*type, &gpmm);
    this->spec_info.alpha = alpha;
    this->spec_info.gpmm = gpmm;

    // read in field information and transformations and PSFs
    auto *fiber = new H5::Group(h5file->openGroup("fiber_" + std::to_string(fiber_number)));
    this->fiber_number = fiber_number;
    attr = new H5::Attribute(fiber->openAttribute("field_height"));
    type = new H5::DataType(attr->getDataType());
    double field_height, field_width = 0;
    int sampling_input_x;
    int number_of_points;

    attr->read(*type, &field_height);
    fiber->openAttribute("field_with").read(*type, &field_width);
    attr = new H5::Attribute(fiber->openAttribute("sampling_input_x"));
    type = new H5::DataType(attr->getDataType());
    fiber->openAttribute("sampling_input_x").read(*type, &sampling_input_x);
    fiber->openAttribute("MatricesPerOrder").read(*type, &number_of_points);

    std::vector<std::string> group_names;
    herr_t idx = H5Literate(fiber->getId(), H5_INDEX_NAME, H5_ITER_INC, nullptr, file_info, &group_names);
    size_t dst_size = sizeof(transformation_hdf);
    size_t dst_offset[7] = {HOFFSET(transformation_hdf, rotation),
                            HOFFSET(transformation_hdf, scale_x),
                            HOFFSET(transformation_hdf, scale_y),
                            HOFFSET(transformation_hdf, shear),
                            HOFFSET(transformation_hdf, translation_x),
                            HOFFSET(transformation_hdf, translation_y),
                            HOFFSET(transformation_hdf, wavelength)};
    size_t dst_sizes[7] = {sizeof(float), sizeof(float), sizeof(float), sizeof(float), sizeof(float), sizeof(float),
                           sizeof(float)};

    bool file_contains_psfs = false;

    for (const auto &gn: group_names) {
        if (gn.find("psf") == std::string::npos) {
            transformation_hdf data[number_of_points];
            std::string table_name = "fiber_" + std::to_string(fiber_number) + "/" + gn;
            int order = std::stoi(gn.substr(5));
            this->orders.push_back(order);
            herr_t read = H5TBread_table(h5file->getId(), table_name.c_str(), dst_size, dst_offset, dst_sizes, &data);
            for (int i = 0; i < number_of_points; ++i) {
                raw_transformation t;
                t.wavelength = data[i].wavelength;
                if (t.wavelength < this->wavelength_limit_min)
                    wavelength_limit_min = t.wavelength;
                if (t.wavelength > this->wavelength_limit_max)
                    wavelength_limit_max = t.wavelength;
                std::vector<double> result;
                result.push_back(data[i].scale_x);
                result.push_back(data[i].scale_y);
                result.push_back(wrap_rads(data[i].shear));
                result.push_back(data[i].rotation);
                result.push_back(data[i].translation_x);
                result.push_back(data[i].translation_y);
                t.decomposed_matrix = result;
                t.transformation_matrix = compose_matrix(result);
                t.order = order;
                this->raw_transformations[order].push_back(t);
            }
            std::sort(this->raw_transformations[order].begin(), this->raw_transformations[order].end(),
                      [](const raw_transformation &x, const raw_transformation &y) {
                          return (x.wavelength < y.wavelength);
                      });


        } else {
            file_contains_psfs = true;
        }
    }

    std::sort(this->orders.begin(), this->orders.end());
    this->min_order = *std::min_element(std::begin(this->orders), std::end(this->orders));
    this->max_order = *std::max_element(std::begin(this->orders), std::end(this->orders));
    this->n_orders = this->max_order - this->min_order;
    std::cout << "Read in " << this->n_orders << " Orders" << std::endl;
    this->calc_splines();

    if (!keep_ccd) {
        // read in CCD information
        H5::Group *ccd = new H5::Group(h5file->openGroup("CCD"));
        attr = new H5::Attribute(ccd->openAttribute("Nx"));
        type = new H5::DataType(attr->getDataType());
        int Nx = 0;
        int Ny = 0;
        int pixel_size = 0;

        attr->read(*type, &Nx);

        ccd->openAttribute("Ny").read(*type, &Ny);
        ccd->openAttribute("pixelsize").read(*type, &pixel_size);

        this->ccd = new CCD(Nx, Ny, pixel_size);
        delete ccd;
    }


    delete type;
    delete attr;

    delete h5file;

    if (file_contains_psfs) {
        this->psfs = new PSF_ZEMAX(path, fiber_number);
    }
}

double MatrixSimulator::get_maximum_wavelength() {
    return this->wavelength_limit_max;
}

double MatrixSimulator::get_minimum_wavelength() {
    return this->wavelength_limit_min;
}

double MatrixSimulator::get_alpha() {
    return this->spec_info.alpha;
}

double MatrixSimulator::get_gpmm() {
    return this->spec_info.gpmm;
}

void MatrixSimulator::set_telescope(Telescope *telescope) {
    this->telescope = *telescope;
}

void MatrixSimulator::calc_splines() {
    this->tr_p.clear();
    this->tr_q.clear();
    this->tr_phi.clear();
    this->tr_r.clear();
    this->tr_tx.clear();
    this->tr_ty.clear();

    for (int o = 0; o < this->orders.size(); ++o) {
        std::vector<double> wl;
        std::vector<double> p;
        std::vector<double> q;
        std::vector<double> r;
        std::vector<double> phi;
        std::vector<double> tx;
        std::vector<double> ty;

        for (auto rt: this->raw_transformations[o + this->min_order]) {
            wl.push_back(rt.wavelength);
            p.push_back(rt.decomposed_matrix[0]);
            q.push_back(rt.decomposed_matrix[1]);
            r.push_back(rt.decomposed_matrix[2]);
            phi.push_back(rt.decomposed_matrix[3]);
            tx.push_back(rt.decomposed_matrix[4]);
            ty.push_back(rt.decomposed_matrix[5]);
        }
        tk::spline s_p;
        s_p.set_points(wl, p);
        this->tr_p.push_back(s_p);

        tk::spline s_q;
        s_q.set_points(wl, q);
        this->tr_q.push_back(s_q);

        tk::spline s_r;
        s_r.set_points(wl, r);
        this->tr_r.push_back(s_r);

        tk::spline s_phi;
        s_phi.set_points(wl, phi);
        this->tr_phi.push_back(s_phi);

        tk::spline s_tx;
        s_tx.set_points(wl, tx);
        this->tr_tx.push_back(s_tx);

        tk::spline s_ty;
        s_ty.set_points(wl, ty);
        this->tr_ty.push_back(s_ty);

    }

}

void MatrixSimulator::set_wavelength(int N) {
    this->sim_wavelength.clear();
    for (int o = 0; o < this->orders.size(); ++o) {
        this->sim_wavelength.emplace_back(N);
        this->sim_1d.emplace_back(N, 0);
    }

#pragma omp parallel for default(none) shared(N)
    for (int o = 0; o < this->orders.size(); ++o) {
        double min_wl = this->raw_transformations[o + this->min_order].front().wavelength;
        double max_wl = this->raw_transformations[o + this->min_order].back().wavelength;
        double dl = (max_wl - min_wl) / N;
        for (int i = 0; i < N; ++i) {
            this->sim_wavelength[o][i] = min_wl + dl * i;
        }
    }
}

void MatrixSimulator::set_wavelength(std::vector<double> wavelength) {
    this->sim_wavelength.clear();
    for (int o = 0; o < this->orders.size(); ++o) {
        double min_wl = this->raw_transformations[o + this->min_order].front().wavelength;
        double max_wl = this->raw_transformations[o + this->min_order].back().wavelength;
        std::vector<double> wl_in_order;

        for (auto wl: wavelength) {
            if ((wl > min_wl) && (wl < max_wl)) {
                wl_in_order.push_back(wl);
            }
        }
        this->sim_wavelength.push_back(wl_in_order);
    }
}


std::array<double, 6> MatrixSimulator::get_transformation_matrix_lookup(int o, double wavelength) {
    std::vector<double> parameters;
    int idx_matrix = static_cast<int> (floor(
            (wavelength - this->sim_wavelength[o].front()) / this->sim_matrix_dwavelength[o]));

    parameters.push_back(this->sim_p[o][idx_matrix]);
    parameters.push_back(this->sim_q[o][idx_matrix]);
    parameters.push_back(this->sim_r[o][idx_matrix]);
    parameters.push_back(this->sim_phi[o][idx_matrix]);
    parameters.push_back(this->tr_tx[o](wavelength));
    parameters.push_back(this->tr_ty[o](wavelength));
    return compose_matrix(parameters);

}

std::array<double, 6> MatrixSimulator::get_transformation_matrix(int o, double wavelength) {
    std::vector<double> parameters;
    parameters.push_back(this->tr_p[o](wavelength));
    parameters.push_back(this->tr_q[o](wavelength));
    parameters.push_back(this->tr_r[o](wavelength));
    parameters.push_back(this->tr_phi[o](wavelength));
    parameters.push_back(this->tr_tx[o](wavelength));
    parameters.push_back(this->tr_ty[o](wavelength));
    return compose_matrix(parameters);
}

void MatrixSimulator::simulate(double t, unsigned long seed) {
    this->set_efficiencies(this->efficiencies);

    this->prepare_source(this->source);

    this->prepare_psfs(1000);

    this->prepare_matrix_lookup(1000);

    std::vector<RandomGenerator<double> *> wl_s(orders.size());
    std::vector<int> N_photons(orders.size());

    double psf_scaling = (*this->ccd->get_pixelsize() / this->psfs->pixelsampling);

    std::cout << std::endl << fmt::format("{:*^50}", " Number of photons per order ") << std::endl;

    long int N_tot = 0;
    for (int o = 0; o < this->orders.size(); ++o) {
        if (!sim_wavelength[o].empty()) {
            double total_photons =
                    std::accumulate(flux_times_efficiency[o].begin(), flux_times_efficiency[o].end(), 0.) * t;
            if (total_photons > UINT_MAX)
                throw (std::invalid_argument(
                        "Number of photons is too big. Please check integration time and units in given source spectrum."));
            N_photons[o] = (unsigned int) nearbyint(total_photons);

        } else {
            N_photons[o] = 0;
        }
        N_tot += N_photons[o];
        std::cout << fmt::format("Order {:3d}: {:10n}", this->min_order + o, N_photons[o]) << std::endl;
    }
    std::cout << fmt::format("Total number of photons: {:12n}", N_tot) << std::endl;
    std::cout << std::endl << fmt::format("{:*^50}", " Start tracing ") << std::endl;
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();

    std::vector<int> local_data = this->ccd->data;

    for (int o = 0; o < this->orders.size(); ++o) {
        std::cout << fmt::format("Simulating Order {:3d}/{:3d}", o + this->min_order, this->max_order) << std::endl;
#pragma omp parallel default(none) shared(seed, local_data,wl_s, o, N_photons, psf_scaling)
        {
            double min_wl = this->raw_transformations[o + this->min_order].front().wavelength;
            double max_wl = this->raw_transformations[o + this->min_order].back().wavelength;
            double dl = (max_wl - min_wl) / this->sim_wavelength[o].size();

            std::uniform_real_distribution<float> rgx((float) 0., (float) 1.);
            std::uniform_real_distribution<float> rgy((float) 0., (float) 1.);
            std::vector<double> a(sim_wavelength[o].begin(), sim_wavelength[o].end()); //units are um
            std::vector<double> b(flux_times_efficiency[o].begin(),
                                  flux_times_efficiency[o].end()); //units are photons per s

            std::default_random_engine gen;
            if (seed == 0) {
                std::random_device rd;
                gen.seed(rd());
            } else {
#if defined(_OPENMP)
                //TODO: better handling of seeds for parallel execution.. (i.e. use random seed sequence)
                gen.seed(seed + omp_get_thread_num());
#else
                gen.seed(seed);
#endif
            }

            if (this->source->is_list_like())
                wl_s[o] = new discrete_RNG<double, std::default_random_engine>(a, b);
            else
                wl_s[o] = new piecewise_linear_RNG<double, std::default_random_engine>(a, b, gen);

#pragma omp for reduction(vec_int_plus : local_data)
            for (int i = 0; i < N_photons[o]; ++i) {
                double wl = wl_s[o]->draw();

                // index for transformation matrix in lookup table
                int idx_matrix = static_cast<int> (floor(
                        (wl - this->sim_wavelength[o].front()) / this->sim_matrix_dwavelength[o]));

                if (!this->source->is_list_like()) {
                    // index for wavelength bin in sim_1d vector
                    int idx_1d = static_cast<int> (floor(
                            (wl - this->sim_wavelength[o].front()) / dl));

#pragma omp atomic
                    this->sim_1d[o][idx_1d]++;
                }

                float x = rgx(gen);
                float y = rgy(gen);

                float newx = (sim_m00[o][idx_matrix] * x + sim_m01[o][idx_matrix] * y + this->tr_tx[o](wl));
                float newy = (sim_m10[o][idx_matrix] * x + sim_m11[o][idx_matrix] * y + this->tr_ty[o](wl));

                //            this is not accurate enough.  probably fixable by linear interpolation of tx and ty
                //            float newx = (sim_m00[o][idx_matrix] * x + sim_m01[o][idx_matrix] * y + sim_tx[o][idx_matrix]);
                //            float newy = (sim_m10[o][idx_matrix]  * x + sim_m11[o][idx_matrix]  * y + sim_ty[o][idx_matrix]);

                //  index for PSF in lookup table
                auto idx_psf = static_cast<int> (floor(
                        (wl - this->sim_wavelength[o].front()) / this->sim_psfs_dwavelength[o]));

                std::uniform_real_distribution<float> abr_x(0, this->sim_psfs[o][idx_psf].cols - 1);
                std::uniform_real_distribution<float> abr_y(0, this->sim_psfs[o][idx_psf].rows - 1);
                std::uniform_real_distribution<float> abr_z(0, 1);
                float abx = abr_x(gen);
                float aby = abr_y(gen);
                float abz = abr_z(gen);
                while (abz > this->sim_psfs[o][idx_psf].data[floor(aby)][floor(abx)]) {
                    abx = abr_x(gen);
                    aby = abr_y(gen);
                    abz = abr_z(gen);
                }

                newx += (abx - this->sim_psfs[o][idx_psf].cols / 2.) / psf_scaling;
                newy += (aby - this->sim_psfs[o][idx_psf].rows / 2.) / psf_scaling;

                if (newx > 0 && newx < this->ccd->Nx && newy > 0 && newy < this->ccd->Ny)
                    local_data[(int) floor(newx) + this->ccd->Nx * (int) floor(newy)] += 1;
            }
        };
        this->ccd->data = local_data;
    };
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() / 1000000.;
    std::cout << "Duration Tracing: \t" << duration << " s" << std::endl;
}

void MatrixSimulator::set_efficiencies(std::vector<Efficiency *> &efficiencies) {
    std::cout << std::endl << fmt::format("{:*^50}", " Set Efficiencies ") << std::endl;
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();

    for (int o = 0; o < this->orders.size(); ++o) {
        this->sim_efficiencies.push_back(std::vector<double>(this->sim_wavelength[o].size(), 1.));
        this->sim_total_efficiency_per_order.push_back(0.);
    }

#pragma omp parallel for default(none) shared(efficiencies)
    for (int o = 0; o < this->orders.size(); ++o) {
        for (auto &e : efficiencies) {
            std::vector<double> sim_eff = e->get_efficiency(o + this->min_order, this->sim_wavelength[o]);
            for (int i = 0; i < this->sim_efficiencies[o].size(); ++i) {
                this->sim_efficiencies[o][i] *= sim_eff[i];
                this->sim_total_efficiency_per_order[o] += sim_eff[i];
            }
        }
    }
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() / 1000000.;
    std::cout << "Duration: \t" << duration << " s" << std::endl;
}

void MatrixSimulator::prepare_psfs(int N) {

    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
    std::cout << std::endl << fmt::format("{:*^50}", " Prepare PSFs ") << std::endl;

    for (int o = 0; o < this->orders.size(); ++o) {
        this->sim_psfs.emplace_back(std::vector<Matrix>(N));
        this->sim_psfs_wavelength.emplace_back(std::vector<double>(N));
        this->sim_psfs_dwavelength.push_back(0.);
    }

#pragma omp parallel for default(none) shared(N)
    for (int o = 0; o < this->orders.size(); ++o) {
        double min_wl = this->raw_transformations[o + this->min_order].front().wavelength;
        double max_wl = this->raw_transformations[o + this->min_order].back().wavelength;
        double dl = (max_wl - min_wl) / N;
        this->sim_psfs_dwavelength[o] = dl;
        for (int i = 0; i < this->sim_psfs_wavelength[o].size(); ++i) {
            this->sim_psfs_wavelength[o][i] = min_wl + i * dl;
            this->sim_psfs[o][i] = Matrix(this->psfs->get_PSF(o + this->min_order, min_wl + i * dl));
        }
    }
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() / 1000000.;
    std::cout << "Duration: \t" << duration << " s" << std::endl;
}


void MatrixSimulator::prepare_matrix_lookup(int N) {

    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
    std::cout << std::endl << fmt::format("{:*^50}", " Prepare matrix elements ") << std::endl;

    for (int o = 0; o < this->orders.size(); ++o) {
        this->sim_matrix_wavelength.push_back(std::vector<double>(N));
        this->sim_matrix_dwavelength.push_back(0.);
    }

    for (int o = 0; o < this->orders.size(); ++o) {
        double min_wl = this->raw_transformations[o + this->min_order].front().wavelength;
        double max_wl = this->raw_transformations[o + this->min_order].back().wavelength;
        double dl = (max_wl - min_wl) / N;
        this->sim_matrix_dwavelength[o] = dl;
        std::vector<double> p;
        std::vector<double> q;
        std::vector<double> r;
        std::vector<double> phi;
        std::vector<double> tx;
        std::vector<double> ty;

        std::vector<double> m00;
        std::vector<double> m10;
        std::vector<double> m01;
        std::vector<double> m11;


        for (int i = 0; i < this->sim_matrix_wavelength[o].size(); ++i) {
            this->sim_matrix_wavelength[o][i] = min_wl + i * dl;

            p.push_back(this->tr_p[o](this->sim_matrix_wavelength[o][i]));
            q.push_back(this->tr_q[o](this->sim_matrix_wavelength[o][i]));
            r.push_back(this->tr_r[o](this->sim_matrix_wavelength[o][i]));
            phi.push_back(this->tr_phi[o](this->sim_matrix_wavelength[o][i]));
            tx.push_back(this->tr_tx[o](this->sim_matrix_wavelength[o][i]));
            ty.push_back(this->tr_ty[o](this->sim_matrix_wavelength[o][i]));

            std::vector<double> params;
            params.push_back(p[i]);
            params.push_back(q[i]);
            params.push_back(r[i]);
            params.push_back(phi[i]);
            params.push_back(tx[i]);
            params.push_back(ty[i]);
            std::array<double, 6> tm = compose_matrix(params);

            m00.push_back(tm[0]);
            m01.push_back(tm[1]);
            m10.push_back(tm[3]);
            m11.push_back(tm[4]);
        }
        this->sim_p.push_back(p);
        this->sim_q.push_back(q);
        this->sim_r.push_back(r);
        this->sim_phi.push_back(phi);
        this->sim_tx.push_back(tx);
        this->sim_ty.push_back(ty);
        this->sim_m00.push_back(m00);
        this->sim_m01.push_back(m01);
        this->sim_m10.push_back(m10);
        this->sim_m11.push_back(m11);


    }
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() / 1000000.;
    std::cout << "Duration: \t" << duration << " s" << std::endl;
}

void MatrixSimulator::prepare_source(Source *source) {
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
    std::cout << std::endl << fmt::format("{:*^50}", " Prepare sources ") << std::endl;
    this->sim_flux.clear();
    // allocate memory so we can use omp parallel for filling
    for (int o = 0; o < this->orders.size(); ++o) {
        this->sim_flux.emplace_back(this->sim_wavelength[o].size());
        this->flux_times_efficiency.emplace_back(this->sim_wavelength[o].size());
    }

#pragma omp parallel for default(none), shared(source)
    for (int o = 0; o < this->orders.size(); ++o) {
        std::vector<double> flux = source->get_photon_flux(this->sim_wavelength[o]);
        this->sim_flux[o] = flux;
        double total_flux = 0.;
        for (int i = 0; i < flux.size(); ++i) {
            this->flux_times_efficiency[o][i] = this->sim_flux[o][i] * this->sim_efficiencies[o][i];
            total_flux += this->sim_flux[o][i] * this->sim_efficiencies[o][i];
        }
    }
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() / 1000000.;
    std::cout << "Duration: \t" << duration << " s" << std::endl;
}

void MatrixSimulator::add_efficiency(Efficiency *eff) {
    this->efficiencies.push_back(eff);
}


//void MatrixSimulator::set_ccd(CCD *ccd) {
//    this->ccd = ccd;
//}

//void MatrixSimulator::set_slit(Slit *slit) {
//    this->slit = slit;
//}

//void MatrixSimulator::set_psfs(PSF *psfs) {
//    this->psfs = psfs;
//}

void MatrixSimulator::set_source(Source *src) {
    this->source = src;
}

void MatrixSimulator::save_to_hdf(const std::string filename, bool bleed, bool overwrite) {
    this->ccd->save_to_hdf(filename, bleed, overwrite);
}

void MatrixSimulator::save_to_fits(const std::string filename, bool bleed, bool overwrite) {
    this->ccd->save_to_fits(filename, overwrite);
}

void MatrixSimulator::save_1d_to_fits(const std::string filename) {
    CCfits::FITS infile(filename.c_str(), CCfits::Write);
    string hduName("Fiber_" + std::to_string(this->fiber_number));
    for (unsigned int o = 0; o < this->orders.size(); ++o) {
        CCfits::Table *newTable = infile.addTable(hduName + "order_" + std::to_string(this->orders[o]),
                                                  this->sim_wavelength[o].size());
        newTable->addColumn(CCfits::Tfloat, "wavelength", 1, "nm");
        newTable->addColumn(CCfits::Tfloat, "model", 1, "nm");
        newTable->addColumn(CCfits::Tfloat, "efficiency", 1, "nm");
        newTable->addColumn(CCfits::Tfloat, "flux", 1, "nm");

        newTable->column("wavelength").write(this->sim_wavelength[o], 1);
        newTable->column("model").write(this->sim_flux[o], 1);
        newTable->column("efficiency").write(this->sim_efficiencies[o], 1);
        if (!this->source->is_list_like())
            newTable->column("flux").write(this->sim_1d[o], 1);
    }
    infile.flush();
}

void MatrixSimulator::add_background(double bias, double noise, unsigned long seed) {
    std::mt19937 gen;

    if (seed == 0) {
        std::random_device rd;
        gen.seed(rd());
    } else {
        gen.seed(seed);
    }
    std::normal_distribution<double> dis(bias, noise);

    for (int i = 0; i < this->ccd->Nx * this->ccd->Ny; ++i) {
        this->ccd->data[i] += dis(gen);
    }
}