示例#1
0
	void read(const std::string& directory, const std::string& specification)
	{
	    // For this type of rock, the specification is a line with two file names.
	    std::istringstream specstream(specification);
	    std::string rockname[2];
	    specstream >> rockname[0] >> rockname[1];
	    for (int i = 0; i < 2; ++i) {
		std::string rockfilename = directory + rockname[i];
		std::ifstream rock_stream(rockfilename.c_str());
		if (!rock_stream) {
		    OPM_THROW(std::runtime_error, "Could not open file " << rockfilename);
		}
		readAnisoFormat(rock_stream, i);
	    }
	}
示例#2
0
int main(int argc, char** argv)
try
{
    if (argc == 1) {
        std::cout << "Usage: cpchop gridfilename=filename.grdecl [subsamples=10] [ilen=5] [jlen=5] " << std::endl;
        std::cout << "       [zlen=5] [imin=] [imax=] [jmin=] [jmax=] [upscale=true] [bc=fixed]" << std::endl;
        std::cout << "       [resettoorigin=true] [seed=111] [z_tolerance=0.0] [minperm=1e-9] " << std::endl;
        std::cout << "       [dips=false] [azimuthdisplacement=] [satnumvolumes=false] [mincellvolume=1e-9]" << std::endl;
        std::cout << "       [filebase=] [resultfile=] [endpoints=false] [cappres=false]" << std::endl;
        std::cout << "       [rock_list=] [anisotropicrocks=false]" << std::endl;
        exit(1);
    }
    Opm::parameter::ParameterGroup param(argc, argv);
    std::string gridfilename = param.get<std::string>("gridfilename");
    Opm::CornerPointChopper ch(gridfilename);

    // The cells with i coordinate in [imin, imax) are included, similar for j.
    // The z limits may be changed inside the chopper to match actual min/max z.
    const int* dims = ch.dimensions();
    int imin = param.getDefault("imin", 0);
    int imax = param.getDefault("imax", dims[0]);
    int jmin = param.getDefault("jmin", 0);
    int jmax = param.getDefault("jmax", dims[1]);
    double zmin = param.getDefault("zmin", ch.zLimits().first);
    double zmax = param.getDefault("zmax", ch.zLimits().second);
    int subsamples = param.getDefault("subsamples", 1);
    int ilen = param.getDefault("ilen", imax - imin);
    int jlen = param.getDefault("jlen", jmax - jmin);
    double zlen = param.getDefault("zlen", zmax - zmin);
    bool upscale = param.getDefault("upscale", true);
    std::string bc = param.getDefault<std::string>("bc", "fixed");
    bool resettoorigin = param.getDefault("resettoorigin", true);
    boost::mt19937::result_type userseed = param.getDefault("seed", 0);

    int outputprecision = param.getDefault("outputprecision", 8);
    std::string filebase = param.getDefault<std::string>("filebase", "");
    std::string resultfile = param.getDefault<std::string>("resultfile", "");

    double minperm = param.getDefault("minperm", 1e-9);
    double minpermSI = Opm::unit::convert::from(minperm, Opm::prefix::milli*Opm::unit::darcy);

    // Following two options are for dip upscaling (slope of cell top and bottom edges)
    bool dips = param.getDefault("dips", false);  // whether to do dip averaging
    double azimuthdisplacement = param.getDefault("azimuthdisplacement", 0.0);  // posibility to add/subtract a value to/from azimuth for dip plane.
    double mincellvolume = param.getDefault("mincellvolume", 1e-9); // ignore smaller cells for dip calculations

    bool satnumvolumes = param.getDefault("satnumvolumes", false); // whether to count volumes pr. satnum

    // upscaling of endpoints and capillary pressure
    // Conversion factor, multiply mD numbers with this to get m² numbers
    const double milliDarcyToSqMetre = 9.869233e-16;
    // Input for surfaceTension is dynes/cm, SI units are Joules/square metre
    const double surfaceTension = param.getDefault("surfaceTension", 11.0) * 1e-3; // multiply with 10^-3 to obtain SI units

    bool endpoints = param.getDefault("endpoints", false); // whether to upscale saturation endpoints
    bool cappres = param.getDefault("cappres", false); // whether to upscale capillary pressure
    if (cappres) {
        endpoints = true;
    }
    std::string rock_list = param.getDefault<std::string>("rock_list", "no_list");
    bool anisorocks = param.getDefault("anisotropicrocks", false);
    std::vector<std::vector<double> > rocksatendpoints_;
    std::vector<std::vector<double> > jfuncendpoints_; // Used if isotropic rock input
    int nsatpoints = 5; // nuber of saturation points in upscaled capillary pressure function per subsample
    double saturationThreshold = 0.00001;

    // For isotropic input rocks:
    std::vector<Opm::MonotCubicInterpolator> InvJfunctions; // Holds the inverse of the loaded J-functions.
    // For anisotropic input rocks:
    std::vector<Opm::MonotCubicInterpolator> SwPcfunctions; // Holds Sw(Pc) for each rocktype.

    // Read rock data from files specifyed in rock_list
    if (endpoints) {
        if (!rock_list.compare("no_list")) {
            std::cout << "Can't do endponts without rock list (" << rock_list << ")" << std::endl;
            throw std::exception();
        }
        // Code copied from ReservoirPropertyCommon.hpp for file reading
        std::ifstream rl(rock_list.c_str());
        if (!rl) {
            OPM_THROW(std::runtime_error, "Could not open file " << rock_list);
        }
        int num_rocks = -1;
        rl >> num_rocks;
        assert(num_rocks >= 1);
        rocksatendpoints_.resize(num_rocks);
        jfuncendpoints_.resize(num_rocks);
        // Loop through rock files defined in rock_list and store the data we need
        for (int i = 0; i < num_rocks; ++i) {
            std::string spec;
            while (spec.empty()) {
                std::getline(rl, spec);
            }
            // Read the contents of the i'th rock
            std::istringstream specstream(spec);
            std::string rockname;
            specstream >> rockname;
            std::string rockfilename = rockname;

            std::ifstream rock_stream(rockfilename.c_str());
            if (!rock_stream) {
                OPM_THROW(std::runtime_error, "Could not open file " << rockfilename);
            }

            if (! anisorocks) { //Isotropic input rocks (Sw Krw Kro J)
                Opm::MonotCubicInterpolator Jtmp;
                try {
                    Jtmp = Opm::MonotCubicInterpolator(rockname, 1, 4);
                }
                catch (const char * errormessage) {
                    std::cerr << "Error: " << errormessage << std::endl;
                    std::cerr << "Check filename" << std::endl;
                    exit(1);
                }

                // Invert J-function, now we get saturation as a function of pressure:
                if (Jtmp.isStrictlyMonotone()) {
                    InvJfunctions.push_back(Opm::MonotCubicInterpolator(Jtmp.get_fVector(), Jtmp.get_xVector()));
                }
                else {
                    std::cerr << "Error: Jfunction " << i+1 << " in rock file " << rockname << " was not invertible." << std::endl;
                    exit(1);
                }

                jfuncendpoints_[i][0] = Jtmp.getMinimumX().second;
                jfuncendpoints_[i][1] = Jtmp.getMaximumX().second;
                rocksatendpoints_[i][0] = Jtmp.getMinimumX().first;
                rocksatendpoints_[i][1] = Jtmp.getMaximumX().first;
                if (rocksatendpoints_[i][0] < 0 || rocksatendpoints_[i][0] > 1) {
                    OPM_THROW(std::runtime_error, "Minimum rock saturation (" << rocksatendpoints_[i][0] << ") not sane for rock "
                              << rockfilename << "." << std::endl << "Did you forget to specify anisotropicrocks=true ?");
                }
            }
            else { //Anisotropic input rocks (Pc Sw Krxx Kryy Krzz)
                Opm::MonotCubicInterpolator Pctmp;
                try {
                    Pctmp = Opm::MonotCubicInterpolator(rockname, 2, 1);
                }
                catch (const char * errormessage) {
                    std::cerr << "Error: " << errormessage << std::endl;
                    std::cerr << "Check filename and columns 1 and 2 (Pc and Sw)" << std::endl;
                    exit(1);
                }
                if (cappres) {
                    // Invert Pc(Sw) curve into Sw(Pc):
                    if (Pctmp.isStrictlyMonotone()) {
                        SwPcfunctions.push_back(Opm::MonotCubicInterpolator(Pctmp.get_fVector(), Pctmp.get_xVector()));
                    }
                    else {
                        std::cerr << "Error: Pc(Sw) curve " << i+1 << " in rock file " << rockname << " was not invertible." << std::endl;
                        exit(1);
                    }
                }
                rocksatendpoints_[i][0] = Pctmp.getMinimumX().first;
                rocksatendpoints_[i][1] = Pctmp.getMaximumX().first;
            }
        }
    }

    double z_tolerance = param.getDefault("z_tolerance", 0.0);
    double residual_tolerance = param.getDefault("residual_tolerance", 1e-8);
    int linsolver_verbosity = param.getDefault("linsolver_verbosity", 0);
    int linsolver_type = param.getDefault("linsolver_type", 1);

    //  Guarantee initialization
    double Pcmax = -DBL_MAX, Pcmin = DBL_MAX;

    // Check that we do not have any user input
    // that goes outside the coordinates described in
    // the cornerpoint file (runtime-exception will be thrown in case of error)
    ch.verifyInscribedShoebox(imin, ilen, imax,
                              jmin, jlen, jmax,
                              zmin, zlen, zmax);

    // Random number generator from boost.
    boost::mt19937 gen;

    // Seed the random number generators with the current time, unless specified on command line
    // Warning: Current code does not allow 0 for the seed!!
    boost::mt19937::result_type autoseed = time(NULL);
    if (userseed == 0) {
        gen.seed(autoseed);
    }
    else {
        gen.seed(userseed);
    }

    Opm::SinglePhaseUpscaler::BoundaryConditionType bctype = Opm::SinglePhaseUpscaler::Fixed;
    bool isFixed, isPeriodic;
    isFixed = isPeriodic = false;
    if (upscale) {
        if (bc == "fixed") {
            isFixed = true;
            bctype = Opm::SinglePhaseUpscaler::Fixed;
        }
        else if (bc == "periodic") {
            isPeriodic = true;
            bctype = Opm::SinglePhaseUpscaler::Periodic;
        }
        else {
            std::cout << "Boundary condition type (bc=" << bc << ") not allowed." << std::endl;
            std::cout << "Only bc=fixed or bc=periodic implemented." << std::endl;
            throw std::exception();
        }
    }

    // Check for unused parameters (potential typos).
    if (param.anyUnused()) {
        std::cout << "*****     WARNING: Unused parameters:     *****\n";
        param.displayUsage();
    }

    // Note that end is included in interval for uniform_int.
    boost::uniform_int<> disti(imin, imax - ilen);
    boost::uniform_int<> distj(jmin, jmax - jlen);
    boost::uniform_real<> distz(zmin, std::max(zmax - zlen, zmin));
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > ri(gen, disti);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > rj(gen, distj);
    boost::variate_generator<boost::mt19937&, boost::uniform_real<> > rz(gen, distz);

    // Storage for results
    std::vector<double> porosities;
    std::vector<double> permxs;
    std::vector<double> permys;
    std::vector<double> permzs;
    std::vector<double> permyzs;
    std::vector<double> permxzs;
    std::vector<double> permxys;
    std::vector<double> minsws, maxsws;
    std::vector<std::vector<double> > pcvalues;
    std::vector<double> dipangs, azimuths;

    // Initialize a matrix for subsample satnum volumes.
    // Outer index is subsample index, inner index is SATNUM-value
    std::vector<std::vector<double> > rockvolumes;
    int maxSatnum = 0; // This value is determined from the chopped cells.

    int finished_subsamples = 0; // keep explicit count of successful subsamples
    for (int sample = 1; sample <= subsamples; ++sample) {
        int istart = ri();
        int jstart = rj();
        double zstart = rz();
        ch.chop(istart, istart + ilen, jstart, jstart + jlen, zstart, zstart + zlen, resettoorigin);
        std::string subsampledgrdecl = filebase;

        // Output grdecl-data to file if a filebase is supplied.
        if (filebase != "") {
            std::ostringstream oss;
            if ((size_t) subsamples > 1) { // Only add number to filename if more than one sample is asked for
                oss << 'R' << std::setw(4) << std::setfill('0') << sample;
                subsampledgrdecl += oss.str();
            }
            subsampledgrdecl += ".grdecl";
            ch.writeGrdecl(subsampledgrdecl);
        }

        try { /* The upscaling may fail to converge on icky grids, lets just pass by those */
            if (upscale) {
                Opm::EclipseGridParser subparser = ch.subparser();
                subparser.convertToSI();
                Opm::SinglePhaseUpscaler upscaler;

                upscaler.init(subparser, bctype, minpermSI, z_tolerance,
                              residual_tolerance, linsolver_verbosity, linsolver_type, false);

                Opm::SinglePhaseUpscaler::permtensor_t upscaled_K = upscaler.upscaleSinglePhase();
                upscaled_K *= (1.0/(Opm::prefix::milli*Opm::unit::darcy));


                porosities.push_back(upscaler.upscalePorosity());
                permxs.push_back(upscaled_K(0,0));
                permys.push_back(upscaled_K(1,1));
                permzs.push_back(upscaled_K(2,2));
                permyzs.push_back(upscaled_K(1,2));
                permxzs.push_back(upscaled_K(0,2));
                permxys.push_back(upscaled_K(0,1));

            }

            if (endpoints) {
                // Calculate minimum and maximum water volume in each cell
                // Create single-phase upscaling object to get poro and perm values from the grid
                Opm::EclipseGridParser subparser = ch.subparser();
                std::vector<double>  perms = subparser.getFloatingPointValue("PERMX");
                subparser.convertToSI();
                Opm::SinglePhaseUpscaler upscaler;
                upscaler.init(subparser, bctype, minpermSI, z_tolerance,
                              residual_tolerance, linsolver_verbosity, linsolver_type, false);
                std::vector<int>   satnums = subparser.getIntegerValue("SATNUM");
                std::vector<double>  poros = subparser.getFloatingPointValue("PORO");
                std::vector<double> cellVolumes, cellPoreVolumes;
                cellVolumes.resize(satnums.size(), 0.0);
                cellPoreVolumes.resize(satnums.size(), 0.0);
                int tesselatedCells = 0;
                //double maxSinglePhasePerm = 0;
                double Swirvolume = 0;
                double Sworvolume = 0;
                const std::vector<int>& ecl_idx = upscaler.grid().globalCell();
                Dune::CpGrid::Codim<0>::LeafIterator c = upscaler.grid().leafbegin<0>();
                for (; c != upscaler.grid().leafend<0>(); ++c) {
                    unsigned int cell_idx = ecl_idx[c->index()];
                    if (satnums[cell_idx] > 0) { // Satnum zero is "no rock"
                        cellVolumes[cell_idx] = c->geometry().volume();
                        cellPoreVolumes[cell_idx] = cellVolumes[cell_idx] * poros[cell_idx];
                        double Pcmincandidate = 0.0, Pcmaxcandidate = 0.0, minSw, maxSw;
                        if (!anisorocks) {
                            if (cappres) {
                                Pcmincandidate = jfuncendpoints_[int(satnums[cell_idx])-1][1]
                                                 / sqrt(perms[cell_idx] * milliDarcyToSqMetre/poros[cell_idx]) * surfaceTension;
                                Pcmaxcandidate = jfuncendpoints_[int(satnums[cell_idx])-1][0]
                                                 / sqrt(perms[cell_idx] * milliDarcyToSqMetre/poros[cell_idx]) * surfaceTension;
                            }
                            minSw = rocksatendpoints_[int(satnums[cell_idx])-1][0];
                            maxSw = rocksatendpoints_[int(satnums[cell_idx])-1][1];
                        }
                        else { // anisotropic input, we do not to J-function scaling
                            if (cappres) {
                                Pcmincandidate = SwPcfunctions[int(satnums[cell_idx])-1].getMinimumX().first;
                                Pcmaxcandidate = SwPcfunctions[int(satnums[cell_idx])-1].getMaximumX().first;
                            }
                            minSw = rocksatendpoints_[int(satnums[cell_idx])-1][0];
                            maxSw = rocksatendpoints_[int(satnums[cell_idx])-1][1];
                        }
                        if (cappres) {
                            Pcmin = std::min(Pcmincandidate, Pcmin);
                            Pcmax = std::max(Pcmaxcandidate, Pcmax);
                        }
                        Swirvolume += minSw * cellPoreVolumes[cell_idx];
                        Sworvolume += maxSw * cellPoreVolumes[cell_idx];
                    }
                    ++tesselatedCells; // keep count.
                }

                // If upscling=false, we still (may) want to have porosities together with endpoints
                if (!upscale) {
                    porosities.push_back(upscaler.upscalePorosity());
                }

                // Total porevolume and total volume -> upscaled porosity:
                double poreVolume = std::accumulate(cellPoreVolumes.begin(),
                                                    cellPoreVolumes.end(),
                                                    0.0);
                double Swir = Swirvolume/poreVolume;
                double Swor = Sworvolume/poreVolume;
                minsws.push_back(Swir);
                maxsws.push_back(Swor);
                if (cappres) {
                    // Upscale capillary pressure function
                    Opm::MonotCubicInterpolator WaterSaturationVsCapPressure;
                    double largestSaturationInterval = Swor-Swir;
                    double Ptestvalue = Pcmax;
                    while (largestSaturationInterval > (Swor-Swir)/double(nsatpoints)) {
                        if (Pcmax == Pcmin) {
                            // This is a dummy situation, we go through once and then
                            // we are finished (this will be triggered by zero permeability)
                            Ptestvalue = Pcmin;
                            largestSaturationInterval = 0;
                        }
                        else if (WaterSaturationVsCapPressure.getSize() == 0) {
                            /* No data values previously computed */
                            Ptestvalue = Pcmax;
                        }
                        else if (WaterSaturationVsCapPressure.getSize() == 1) {
                            /* If only one point has been computed, it was for Pcmax. So now
                               do Pcmin */
                            Ptestvalue = Pcmin;
                        }
                        else {
                            /* Search for largest saturation interval in which there are no
                               computed saturation points (and estimate the capillary pressure
                               that will fall in the center of this saturation interval)
                            */
                            std::pair<double,double> SatDiff = WaterSaturationVsCapPressure.getMissingX();
                            Ptestvalue = SatDiff.first;
                            largestSaturationInterval = SatDiff.second;
                        }
                        // Check for saneness of Ptestvalue:
                        if (std::isnan(Ptestvalue) || std::isinf(Ptestvalue)) {
                            std::cerr << "ERROR: Ptestvalue was inf or nan" << std::endl;
                            break; // Jump out of while-loop, just print out the results
                            // up to now and exit the program
                        }

                        double waterVolume = 0.0;
                        for (unsigned int i = 0; i < ecl_idx.size(); ++i) {
                            unsigned int cell_idx = ecl_idx[i];
                            double waterSaturationCell = 0.0;
                            if (satnums[cell_idx] > 0) { // handle "no rock" cells with satnum zero
                                double PtestvalueCell;

                                PtestvalueCell = Ptestvalue;

                                if (!anisorocks) {
                                    double Jvalue = sqrt(perms[cell_idx] * milliDarcyToSqMetre /poros[cell_idx]) * PtestvalueCell / surfaceTension;
                                    waterSaturationCell
                                        = InvJfunctions[int(satnums[cell_idx])-1].evaluate(Jvalue);
                                }
                                else { // anisotropic_input, then we do not do J-function-scaling
                                    waterSaturationCell = SwPcfunctions[int(satnums[cell_idx])-1].evaluate(PtestvalueCell);
                                }
                            }
                            waterVolume += waterSaturationCell  * cellPoreVolumes[cell_idx];
                        }
                        WaterSaturationVsCapPressure.addPair(Ptestvalue, waterVolume/poreVolume);
                    }
                    WaterSaturationVsCapPressure.chopFlatEndpoints(saturationThreshold);
                    std::vector<double> wattest = WaterSaturationVsCapPressure.get_fVector();
                    std::vector<double> cprtest = WaterSaturationVsCapPressure.get_xVector();
                    Opm::MonotCubicInterpolator CapPressureVsWaterSaturation(WaterSaturationVsCapPressure.get_fVector(),
                            WaterSaturationVsCapPressure.get_xVector());
                    std::vector<double> pcs;
                    for (int satp=0; satp<nsatpoints; ++satp) {
                        pcs.push_back(CapPressureVsWaterSaturation.evaluate(Swir+(Swor-Swir)/(nsatpoints-1)*satp));
                    }
                    pcvalues.push_back(pcs);
                }

            }


            if (dips) {
                Opm::EclipseGridParser subparser = ch.subparser();
                std::vector<int>  griddims = subparser.getSPECGRID().dimensions;
                std::vector<double> xdips_subsample, ydips_subsample;

                Opm::EclipseGridInspector gridinspector(subparser);
                for (int k=0; k < griddims[2]; ++k) {
                    for (int j=0; j < griddims[1]; ++j) {
                        for (int i=0; i < griddims[0]; ++i) {
                            if (gridinspector.cellVolumeVerticalPillars(i, j, k) > mincellvolume) {
                                std::pair<double,double> xydip = gridinspector.cellDips(i, j, k);
                                xdips_subsample.push_back(xydip.first);
                                ydips_subsample.push_back(xydip.second);
                            }
                        }
                    }
                }


                //  double azimuth = atan(xydip.first/xydip.second);
                //              double dip = acos(1.0/sqrt(pow(xydip.first,2.0)+pow(xydip.second,2.0)+1.0));
                //	dips_subsample.push_back( xydip.first );
                //	azims_subsample.push_back(atan(xydip.first/xydip.second));

                // Average xdips and ydips
                double xdipaverage = accumulate(xdips_subsample.begin(), xdips_subsample.end(), 0.0)/xdips_subsample.size();
                double ydipaverage = accumulate(ydips_subsample.begin(), ydips_subsample.end(), 0.0)/ydips_subsample.size();

                // Convert to dip and azimuth
                double azimuth = atan(xdipaverage/ydipaverage)+azimuthdisplacement;
                double dip = acos(1.0/sqrt(pow(xdipaverage,2.0)+pow(ydipaverage,2.0)+1.0));
                dipangs.push_back(dip);
                azimuths.push_back(azimuth);
            }

            if (satnumvolumes) {
                Opm::EclipseGridParser subparser = ch.subparser();
                Opm::EclipseGridInspector subinspector(subparser);
                std::vector<int>  griddims = subparser.getSPECGRID().dimensions;
                int number_of_subsamplecells = griddims[0] * griddims[1] * griddims[2];

                // If SATNUM is non-existent in input grid, this will fail:
                std::vector<int> satnums = subparser.getIntegerValue("SATNUM");

                std::vector<double> rockvolumessubsample;
                for (int cell_idx=0; cell_idx < number_of_subsamplecells; ++cell_idx) {
                    maxSatnum = std::max(maxSatnum, int(satnums[cell_idx]));
                    rockvolumessubsample.resize(maxSatnum); // Ensure long enough vector
                    rockvolumessubsample[int(satnums[cell_idx])-1] += subinspector.cellVolumeVerticalPillars(cell_idx);
                }

                // Normalize volumes to obtain relative volumes:
                double subsamplevolume = std::accumulate(rockvolumessubsample.begin(),
                                         rockvolumessubsample.end(), 0.0);
                std::vector<double> rockvolumessubsample_normalized;
                for (size_t satnum_idx = 0; satnum_idx < rockvolumessubsample.size(); ++satnum_idx) {
                    rockvolumessubsample_normalized.push_back(rockvolumessubsample[satnum_idx]/subsamplevolume);
                }
                rockvolumes.push_back(rockvolumessubsample_normalized);
            }

            finished_subsamples++;
        }
        catch (...) {
            std::cerr << "Warning: Upscaling chopped subsample nr. " << sample << " failed, proceeding to next subsample\n";
        }

    }


    // Make stream of output data, to be outputted to screen and optionally to file
    std::stringstream outputtmp;

    outputtmp << "################################################################################################" << std::endl;
    outputtmp << "# Results from property analysis on subsamples" << std::endl;
    outputtmp << "#" << std::endl;
    time_t now = time(NULL);
    outputtmp << "# Finished: " << asctime(localtime(&now));

    utsname hostname;
    uname(&hostname);
    outputtmp << "# Hostname: " << hostname.nodename << std::endl;
    outputtmp << "#" << std::endl;
    outputtmp << "# Options used:" << std::endl;
    outputtmp << "#     gridfilename: " << gridfilename << std::endl;
    outputtmp << "#   i; min,len,max: " << imin << " " << ilen << " " << imax << std::endl;
    outputtmp << "#   j; min,len,max: " << jmin << " " << jlen << " " << jmax << std::endl;
    outputtmp << "#   z; min,len,max: " << zmin << " " << zlen << " " << zmax << std::endl;
    outputtmp << "#       subsamples: " << subsamples << std::endl;
    if (userseed == 0) {
        outputtmp << "#      (auto) seed: " << autoseed << std::endl;
    }
    else {
        outputtmp << "#    (manual) seed: " << userseed << std::endl;
    }
    outputtmp << "################################################################################################" << std::endl;
    outputtmp << "# id";
    if (upscale) {
        if (isPeriodic) {
            outputtmp << "          porosity                 permx                   permy                   permz                   permyz                  permxz                  permxy";
        }
        else if (isFixed) {
            outputtmp << "          porosity                 permx                   permy                   permz";
        }
    }
    if (endpoints) {
        if (!upscale) {
            outputtmp << "          porosity";
        }
        outputtmp << "                  Swir                    Swor";
        if (cappres) {
            outputtmp << "                  Pc(Swir)                Pc2                     Pc3                     Pc4                     Pc(Swor)";
        }
    }
    if (dips) {
        outputtmp << "                  dip                     azim(displacement:" << azimuthdisplacement << ")";
    }
    if (satnumvolumes) {
        for (int satnumidx = 0; satnumidx < maxSatnum; ++satnumidx) {
            outputtmp << "               satnum_" << satnumidx+1;
        }
    }
    outputtmp << std::endl;

    const int fieldwidth = outputprecision + 8;
    for (int sample = 1; sample <= finished_subsamples; ++sample) {
        outputtmp << sample << '\t';
        if (upscale) {
            outputtmp <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << porosities[sample-1] << '\t' <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << permxs[sample-1] << '\t' <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << permys[sample-1] << '\t' <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << permzs[sample-1] << '\t';
            if (isPeriodic) {
                outputtmp <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << permyzs[sample-1] << '\t' <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << permxzs[sample-1] << '\t' <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << permxys[sample-1] << '\t';
            }
        }
        if (endpoints) {
            if (!upscale) {
                outputtmp <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << porosities[sample-1] << '\t';
            }
            outputtmp <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << minsws[sample-1] << '\t' <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << maxsws[sample-1];
            if (cappres) {
                outputtmp <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << pcvalues[sample-1][0] << '\t' <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << pcvalues[sample-1][1] << '\t' <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << pcvalues[sample-1][2] << '\t' <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << pcvalues[sample-1][3] << '\t' <<
                          std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << pcvalues[sample-1][4];
            }
        }
        if (dips) {
            outputtmp <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << dipangs[sample-1] << '\t' <<
                      std::showpoint << std::setw(fieldwidth) << std::setprecision(outputprecision) << azimuths[sample-1];
        }
        if (satnumvolumes) {
            rockvolumes[sample-1].resize(maxSatnum, 0.0);
            for (int satnumidx = 0; satnumidx < maxSatnum; ++satnumidx) {
                outputtmp <<
                          std::showpoint << std::setw(fieldwidth) <<
                          std::setprecision(outputprecision) << rockvolumes[sample-1][satnumidx] << '\t';
            }
        }
        outputtmp <<  std::endl;
    }

    if (resultfile != "") {
        std::cout << "Writing results to " << resultfile << std::endl;
        std::ofstream outfile;
        outfile.open(resultfile.c_str(), std::ios::out | std::ios::trunc);
        outfile << outputtmp.str();
        outfile.close();
    }



    std::cout << outputtmp.str();
}
catch (const std::exception &e) {
    std::cerr << "Program threw an exception: " << e.what() << "\n";
    throw;
}