/* Copyright (C) 2011 Statoil ASA, Norway. The file 'ecl_sum_data.c' is part of ERT - Ensemble based Reservoir Tool. ERT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ERT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "detail/ecl/ecl_sum_file_data.hpp" /* This file implements the sruct ecl_sum_data which manages the actual simulated values from a summary file, including all time-related information. In the case of restarted simulations the different summary cases will be internalized as separate ecl_sum_file_data instances. The ecl_sum_file_data class is an internal implemenation detail which is not exported. More details about the internal storage of summary data can be found in that file. For this file the implementation mainly consists of maintaining an ordered list of ecl_sum_file_data instances and dispatching method calls to the right ecl_sum_file_data instance. */ namespace { /* The class CaseIndex and the struct IndexNode are used to maintain a list of the ecl_sum_file_data instances, and lookup the correct one based one various time related arguments. */ struct IndexNode { IndexNode(int d, int o, int l) { this->data_index = d; this->offset = o; this->length = l; } int end() const { return this->offset + this->length; } int data_index; int offset; int length; int report1; int report2; time_t time1; time_t time2; double days1; double days2; std::vector params_map; }; class CaseIndex { public: IndexNode& add(int length) { int offset = 0; int data_index = this->index.size(); if (!this->index.empty()) offset = this->index.back().end(); this->index.emplace_back(data_index, offset, length); return this->index.back(); } /* The lookup_time() and lookup_report() methods will lookup which file_data instance corresponds to the time/report argument. The methods will return two pointers to file_data instances, if the argument is inside one file_data instance the pointers will be equal - otherwise they will point to the file_data instance before and after the argument: File 1 File 2 |------|-----|------| |----|----------|---| /|\ /|\ | | | | A B For time A the lookup_time function will return whereas for time B the function will return . */ std::pair lookup_time(time_t sim_time) const { auto iter = this->index.begin(); auto next = this->index.begin(); if (sim_time < iter->time1) throw std::invalid_argument("Simulation time out of range"); ++next; while (true) { double t1 = iter->time1; double t2 = iter->time2; if (sim_time>= t1) { if (sim_time <= t2) return std::make_pair(&(*iter), &(*iter)); if (next == this->index.end()) throw std::invalid_argument("Simulation days out of range"); if (sim_time < next->time1) return std::make_pair(&(*iter),&(*next)); } ++next; ++iter; } } std::pair lookup_days(double days) const { auto iter = this->index.begin(); auto next = this->index.begin(); if (days < iter->days1) throw std::invalid_argument("Simulation days out of range"); ++next; while (true) { double d1 = iter->days1; double d2 = iter->days2; if (days >= d1) { if (days <= d2) return std::make_pair(&(*iter), &(*iter)); if (next == this->index.end()) throw std::invalid_argument("Simulation days out of range"); if (days < next->days1) return std::make_pair(&(*iter),&(*next)); } ++next; ++iter; } } const IndexNode& lookup(int internal_index) const { for (const auto& node : this->index) if (internal_index >= node.offset && internal_index < node.end()) return node; throw std::invalid_argument("Internal error when looking up index: " + std::to_string(internal_index)); } const IndexNode& lookup_report(int report) const { for (const auto& node : this->index) if (node.report1 <= report && node.report2 >= report) return node; throw std::invalid_argument("Internal error when looking up report: " + std::to_string(report)); } /* This will check that we have a datafile which report range covers the report argument, in adition there can be 'holes' in the series - that must be checked by actually querying the data_file object. */ bool has_report(int report) const { for (const auto& node : this->index) if (node.report1 <= report && node.report2 >= report) return true; return false; } IndexNode& back() { return this->index.back(); } void clear() { this->index.clear(); } int length() const { return this->index.back().end(); } std::vector::const_iterator begin() const { return this->index.begin(); } std::vector::const_iterator end() const { return this->index.end(); } private: std::vector index; }; } struct ecl_sum_data_struct { const ecl_smspec_type * smspec; std::vector data_files; // List of ecl_sum_file_data instances CaseIndex index; }; static void ecl_sum_data_build_index( ecl_sum_data_type * self ); static double ecl_sum_data_iget_sim_seconds( const ecl_sum_data_type * data , int internal_index ); /*****************************************************************/ void ecl_sum_data_free( ecl_sum_data_type * data ) { if (!data) throw std::invalid_argument(__func__ + std::string(": invalid delete") ); if (data->data_files.size() > 0) delete data->data_files.back(); delete data; } ecl_sum_data_type * ecl_sum_data_alloc(ecl_smspec_type * smspec) { ecl_sum_data_type * data = new ecl_sum_data_type(); data->smspec = smspec; return data; } void ecl_sum_data_reset_self_map( ecl_sum_data_type * data ) { ecl_sum_data_build_index(data); } static void ecl_sum_data_append_file_data( ecl_sum_data_type * sum_data, ecl::ecl_sum_file_data * file_data) { sum_data->data_files.push_back( file_data ); } /** This function will take a report as input , and update the two pointers ministep1 and ministep2 with the range of the report step (in terms of ministeps). Calling this function with report_step == 2 for the example documented at the top of the file will yield: *ministep1 = 3 and *ministep2 = 7. If you are only interested in one of the limits you can pass in NULL for the other limit, i.e. xxx(data , report_step , NULL , &ministep2); to get the last step. If the supplied report_step is invalid the function will set both return values to -1 (the return value from safe_iget). In that case it is the responsability of the calling scope to check the return values, alternatively one can use the query function ecl_sum_data_has_report_step() first. */ static double ecl_sum_data_iget_sim_seconds( const ecl_sum_data_type * data , int internal_index ) { const auto index_node = data->index.lookup(internal_index); const auto data_file = data->data_files[index_node.data_index]; return data_file->iget_sim_seconds( internal_index - index_node.offset ); } double ecl_sum_data_iget_sim_days( const ecl_sum_data_type * data , int internal_index ) { const auto index_node = data->index.lookup(internal_index); const auto data_file = data->data_files[index_node.data_index]; return data_file->iget_sim_days( internal_index - index_node.offset ); } ecl_sum_data_type * ecl_sum_data_alloc_writer( ecl_smspec_type * smspec ) { ecl_sum_data_type * data = ecl_sum_data_alloc( smspec ); ecl::ecl_sum_file_data * file_data = new ecl::ecl_sum_file_data( smspec ); ecl_sum_data_append_file_data( data, file_data ); ecl_sum_data_build_index(data); return data; } static void ecl_sum_data_fwrite_unified( const ecl_sum_data_type * data , const char * ecl_case , bool fmt_case ) { char * filename = ecl_util_alloc_filename( NULL , ecl_case , ECL_UNIFIED_SUMMARY_FILE , fmt_case , 0 ); fortio_type * fortio = fortio_open_writer( filename , fmt_case , ECL_ENDIAN_FLIP ); for (size_t index = 0; index < data->data_files.size(); index++) data->data_files[index]->fwrite_unified( fortio ); fortio_fclose( fortio ); free( filename ); } static void ecl_sum_data_fwrite_multiple( const ecl_sum_data_type * data , const char * ecl_case , bool fmt_case ) { for (size_t index = 0; index < data->data_files.size(); index++) data->data_files[index]->fwrite_multiple(ecl_case, fmt_case); } void ecl_sum_data_fwrite( const ecl_sum_data_type * data , const char * ecl_case , bool fmt_case , bool unified) { if (unified) ecl_sum_data_fwrite_unified( data , ecl_case , fmt_case ); else ecl_sum_data_fwrite_multiple( data , ecl_case , fmt_case ); } bool ecl_sum_data_can_write(const ecl_sum_data_type * data) { bool can_write = true; for (const auto& file_ptr : data->data_files) can_write &= file_ptr->can_write(); return can_write; } time_t ecl_sum_data_get_sim_end(const ecl_sum_data_type * data ) { const auto& file_data = data->data_files.back(); return file_data->get_sim_end(); } time_t ecl_sum_data_get_data_start( const ecl_sum_data_type * data ) { const auto& file_data = data->data_files[0]; return file_data->get_data_start(); } double ecl_sum_data_get_first_day( const ecl_sum_data_type * data) { const auto& file_data = data->data_files[0]; return file_data->get_days_start(); } /** Returns the number of simulations days from the start of the simulation (irrespective of whether the that summary data has actually been loaded) to the last loaded simulation step. */ double ecl_sum_data_get_sim_length( const ecl_sum_data_type * data ) { const auto& file_data = data->data_files.back(); return file_data->get_sim_length(); } /** The check_sim_time() and check_sim_days() routines check if you have summary data for the requested date/days value. In the case of a restarted case, where the original case is missing - this will return false if the input values are in the region after simulation start with no data. */ bool ecl_sum_data_check_sim_time( const ecl_sum_data_type * data , time_t sim_time) { if (sim_time < ecl_sum_data_get_data_start(data)) return false; if (sim_time > ecl_sum_data_get_sim_end(data)) return false; return true; } bool ecl_sum_data_check_sim_days( const ecl_sum_data_type * data , double sim_days) { return sim_days >= ecl_sum_data_get_first_day(data) && sim_days <= ecl_sum_data_get_sim_length(data); } /** This function will return the ministep corresponding to a time_t instance 'sim_time'. The function will fail hard if the time_t is before the simulation start, or after the end of the simulation. Check with ecl_smspec_get_start_time() and ecl_sum_data_get_sim_end() first. See the documentation about report steps, ministeps and rates at the top of this file for how the sim_time relates to to the returned ministep_nr. The indices used in this function are the internal indices, and not ministep numbers. Observe that if there are holes in the time-domain, i.e. if RPTONLY has been used, the function can return a ministep index which does NOT cover the input time: The 'X' should represent report times - the dashed lines represent the temporal extent of two ministeps. Outside the '--' area we do not have any results. The two ministeps we actually have are M15 and M25, i.e. there is a hole. X . +-----X +----X /|\ M15 M25 | | When asking for the ministep number at the location of the arrow, the function will return '15', i.e. the valid ministep following the sim_time. Of course - the ideal situation is if the time sequence has no holes. */ static int ecl_sum_data_get_index_from_sim_time( const ecl_sum_data_type * data , time_t sim_time) { if (!ecl_sum_data_check_sim_time(data, sim_time)) { time_t start_time = ecl_sum_data_get_data_start(data); time_t end_time = ecl_sum_data_get_sim_end(data); fprintf(stderr , "Simulation start: "); util_fprintf_date_utc( ecl_smspec_get_start_time( data->smspec ) , stderr ); fprintf(stderr , "Data start......: "); util_fprintf_date_utc( start_time , stderr ); fprintf(stderr , "Simulation end .: "); util_fprintf_date_utc( end_time , stderr ); fprintf(stderr , "Requested date .: "); util_fprintf_date_utc( sim_time , stderr ); util_abort("%s: invalid time_t instance:%d interval: [%d,%d]\n",__func__, sim_time , start_time, end_time); } /* The moment we have passed the intial test we MUST find a valid ministep index, however care should be taken that there can perfectly well be 'holes' in the time domain, because of e.g. the RPTONLY keyword. */ int low_index = 0; int high_index = ecl_sum_data_get_length(data) - 1; // perform binary search while (low_index+1 < high_index) { int center_index = (low_index + high_index) / 2; const time_t center_time = ecl_sum_data_iget_sim_time(data, center_index); if (sim_time > center_time) low_index = center_index; else high_index = center_index; } return sim_time <= ecl_sum_data_iget_sim_time(data, low_index) ? low_index : high_index; } int ecl_sum_data_get_index_from_sim_days( const ecl_sum_data_type * data , double sim_days) { time_t sim_time = ecl_smspec_get_start_time( data->smspec ); util_inplace_forward_days_utc( &sim_time , sim_days ); return ecl_sum_data_get_index_from_sim_time(data , sim_time ); } /** This function will take a true time 'sim_time' as input. The ministep indices bracketing this sim_time are identified, and the corresponding weights are calculated. The actual value we are interested in can then be computed with the ecl_sum_data_interp_get() function: int param_index; time_t sim_time; { int ministep1 , ministep2; double weight1 , weight2; ecl_sum_data_init_interp_from_sim_time( data , sim_time , &ministep1 , &ministep2 , &weight1 , &weight2); return ecl_sum_data_interp_get( data , ministep1 , ministep2 , weight1 , weight2 , param_index ); } For further explanation (in particular for which keywords the function should be used), consult documentation at the top of this file. */ void ecl_sum_data_init_interp_from_sim_time(const ecl_sum_data_type* data, time_t sim_time, int* index1, int* index2, double* weight1, double* weight2) { int idx = ecl_sum_data_get_index_from_sim_time(data, sim_time); // if sim_time is first date, idx=0 and then we cannot interpolate, so we give // weight 1 to index1=index2=0. if (idx == 0) { *index1 = 0; *index2 = 0; *weight1 = 1; *weight2 = 0; return; } time_t sim_time1 = ecl_sum_data_iget_sim_time(data, idx-1); time_t sim_time2 = ecl_sum_data_iget_sim_time(data, idx); *index1 = idx-1; *index2 = idx; // weights the interpolation each of the ministeps according to distance from sim_time double time_diff = sim_time2 - sim_time1; double time_dist1 = (sim_time - sim_time1); double time_dist2 = -(sim_time - sim_time2); *weight1 = time_dist2 / time_diff; *weight2 = time_dist1 / time_diff; } void ecl_sum_data_init_interp_from_sim_days( const ecl_sum_data_type * data , double sim_days, int *step1, int *step2 , double * weight1 , double *weight2) { time_t sim_time = ecl_smspec_get_start_time( data->smspec ); util_inplace_forward_days_utc( &sim_time , sim_days ); ecl_sum_data_init_interp_from_sim_time( data , sim_time , step1 , step2 , weight1 , weight2); } double_vector_type * ecl_sum_data_alloc_seconds_solution(const ecl_sum_data_type * data, const smspec_node_type * node, double cmp_value, bool rates_clamp_lower) { double_vector_type * solution = double_vector_alloc(0, 0); const int param_index = smspec_node_get_params_index(node); const int size = ecl_sum_data_get_length(data); if (size <= 1) return solution; for (int index = 0; index < size; ++index) { int prev_index = util_int_max(0, index-1); double value = ecl_sum_data_iget(data, index, param_index); double prev_value = ecl_sum_data_iget(data, prev_index, param_index); // cmp_value in interval value (closed) and prev_value (open) bool contained = (value == cmp_value); contained |= (util_double_min(prev_value, value) < cmp_value) && (cmp_value < util_double_max(prev_value, value)); if (!contained) continue; double prev_time = ecl_sum_data_iget_sim_seconds(data, prev_index); double time = ecl_sum_data_iget_sim_seconds(data, index); if (smspec_node_is_rate(node)) { double_vector_append(solution, rates_clamp_lower ? prev_time + 1 : time); } else { double slope = (value - prev_value) / (time - prev_time); double seconds = (cmp_value - prev_value)/slope + prev_time; double_vector_append(solution, seconds); } } return solution; } static void ecl_sum_data_build_index( ecl_sum_data_type * self ) { std::sort(self->data_files.begin(), self->data_files.end(), [](const ecl::ecl_sum_file_data* case1, const ecl::ecl_sum_file_data* case2) { return case1->get_data_start() < case2->get_data_start(); }); self->index.clear(); for (size_t i=0; i < self->data_files.size(); i++) { const auto& data = self->data_files[i]; bool main_case = (i == (self->data_files.size() - 1)); time_t next_start; if (main_case) self->index.add(data->length()); else { const auto& next = self->data_files[i+1]; next_start = next->get_data_start(); self->index.add( data->length_before(next_start)); } auto & node = self->index.back(); if (node.length > 0) { node.report1 = data->first_report(); if (main_case) node.report2 = data->last_report(); else node.report2 = data->report_before( next_start ); node.time1 = data->get_data_start(); node.time2 = data->get_sim_end(); node.days1 = data->get_days_start(); node.days2 = data->get_sim_length(); { int * tmp_map = ecl_smspec_alloc_mapping( self->smspec , data->smspec() ); node.params_map.assign(tmp_map, tmp_map + ecl_smspec_get_params_size(self->smspec)); free( tmp_map ); } } } } /* This function is meant to be called in write mode; and will create a new and empty tstep which is appended to the current data. The tstep will also be returned, so the calling scope can call ecl_sum_tstep_iset() to set elements in the tstep. */ ecl_sum_tstep_type * ecl_sum_data_add_new_tstep( ecl_sum_data_type * data , int report_step , double sim_seconds) { ecl::ecl_sum_file_data * file_data = data->data_files.back(); ecl_sum_tstep_type * tstep = file_data->add_new_tstep( report_step, sim_seconds ); ecl_sum_data_build_index( data ); return tstep; } int * ecl_sum_data_alloc_param_mapping( int * current_param_mapping, int * old_param_mapping, size_t size) { int * new_param_mapping = (int*)util_malloc( size * sizeof * new_param_mapping ); for (size_t i = 0; i < size; i++) { if (current_param_mapping[i] >= 0) new_param_mapping[i] = old_param_mapping[ current_param_mapping[i] ]; else new_param_mapping[i] = -1; } return new_param_mapping; } void ecl_sum_data_add_case(ecl_sum_data_type * self, const ecl_sum_data_type * other) { for (auto other_file : other->data_files) self->data_files.push_back( other_file ); ecl_sum_data_build_index(self); } /* Observe that this can be called several times (but not with the same data - that will die). Warning: The index information of the ecl_sum_data instance has __NOT__ been updated when leaving this function. That is done with a call to ecl_sum_data_build_index(). */ bool ecl_sum_data_fread(ecl_sum_data_type * data , const stringlist_type * filelist, bool lazy_load, int file_options) { ecl::ecl_sum_file_data * file_data = new ecl::ecl_sum_file_data( data->smspec ); if (file_data->fread( filelist, lazy_load, file_options)) { ecl_sum_data_append_file_data( data, file_data ); ecl_sum_data_build_index(data); return true; } return false; } /** If the variable @include_restart is true the function will query the smspec object for restart information, and load summary information from case(s) which this case was restarted from (this only really applies to predictions where the basename has been (manually) changed from the historical part. */ ecl_sum_data_type * ecl_sum_data_fread_alloc( ecl_smspec_type * smspec , const stringlist_type * filelist , bool include_restart, bool lazy_load, int file_options) { ecl_sum_data_type * data = ecl_sum_data_alloc( smspec ); ecl_sum_data_fread( data , filelist, lazy_load, file_options ); /*****************************************************************/ /* OK - now we have loaded all the data. Must sort the internal storage vector, and build up various internal indexing vectors; this is done in a sepearate function. */ ecl_sum_data_build_index( data ); return data; } void ecl_sum_data_summarize(const ecl_sum_data_type * data , FILE * stream) { fprintf(stream , "REPORT INDEX DATE DAYS\n"); fprintf(stream , "---------------------------------------------------------------\n"); { int index; for (index = 0; index < ecl_sum_data_get_length(data); index++) { time_t sim_time = ecl_sum_data_iget_sim_time(data, index); int report_step = ecl_sum_data_iget_report_step(data, index); double days = ecl_sum_data_iget_sim_days(data, index); int day,month,year; ecl_util_set_date_values( sim_time, &day, &month , &year); fprintf(stream , "%04d %6d %02d/%02d/%4d %7.2f \n", report_step , index , day,month,year, days); } } fprintf(stream , "---------------------------------------------------------------\n"); } /*****************************************************************/ bool ecl_sum_data_has_report_step(const ecl_sum_data_type * data , int report_step ) { if (!data->index.has_report(report_step)) return false; const auto& index_node = data->index.lookup_report(report_step); const auto& file_data = data->data_files[index_node.data_index]; return file_data->has_report(report_step); } /** Returns the last index included in report step @report_step. Observe that if the dataset does not include @report_step at all, the function will return INVALID_MINISTEP_NR; this must be checked for in the calling scope. */ int ecl_sum_data_iget_report_end( const ecl_sum_data_type * data , int report_step ) { const auto& index_node = data->index.lookup_report(report_step); const auto& file_data = data->data_files[index_node.data_index]; auto range = file_data->report_range(report_step); return range.second; } int ecl_sum_data_iget_report_step(const ecl_sum_data_type * data , int internal_index) { const auto& index_node = data->index.lookup(internal_index); const auto& file_data = data->data_files[index_node.data_index]; return file_data->iget_report(internal_index - index_node.offset); } /** This will look up a value based on an internal index. The internal index will ALWAYS run in the interval [0,num_ministep), without any holes. */ double ecl_sum_data_iget( const ecl_sum_data_type * data , int time_index , int params_index ) { const auto& index_node = data->index.lookup( time_index ); ecl::ecl_sum_file_data * file_data = data->data_files[index_node.data_index]; const auto& params_map = index_node.params_map; if (params_map[params_index] >= 0) return file_data->iget( time_index - index_node.offset, params_map[params_index] ); else { const smspec_node_type * smspec_node = ecl_smspec_iget_node(data->smspec, params_index); return smspec_node_get_default(smspec_node); } } /** This function will form a weight average of the two ministeps @ministep1 and @ministep2. The weights and the ministep indices should (typically) be determined by the ecl_sum_data_init_interp_from_sim_xxx() functions. The function will typically the last function called when we seek a reservoir state variable at an intermediate time between two ministeps. */ static double ecl_sum_data_interp_get(const ecl_sum_data_type * data , int time_index1 , int time_index2 , double weight1 , double weight2 , int params_index) { return ecl_sum_data_iget(data, time_index1, params_index) * weight1 + ecl_sum_data_iget(data, time_index2, params_index) * weight2; } static double ecl_sum_data_vector_iget(const ecl_sum_data_type * data, time_t sim_time, int params_index, bool is_rate, int time_index1 , int time_index2 , double weight1 , double weight2 ) { double value = 0.0; if (is_rate) { int time_index = ecl_sum_data_get_index_from_sim_time(data, sim_time); // uses step function since it is a rate value = ecl_sum_data_iget(data, time_index, params_index); } else { // uses interpolation between timesteps value = ecl_sum_data_interp_get(data, time_index1, time_index2, weight1, weight2, params_index); } return value; } void ecl_sum_data_fwrite_interp_csv_line(const ecl_sum_data_type * data, time_t sim_time, const ecl_sum_vector_type * keylist, FILE *fp){ int num_keywords = ecl_sum_vector_get_size(keylist); double weight1, weight2; int time_index1, time_index2; ecl_sum_data_init_interp_from_sim_time(data, sim_time, &time_index1, &time_index2, &weight1, &weight2); for (int i = 0; i < num_keywords; i++) { if (ecl_sum_vector_iget_valid(keylist, i)) { int params_index = ecl_sum_vector_iget_param_index(keylist, i); bool is_rate = ecl_sum_vector_iget_is_rate(keylist, i); double value = ecl_sum_data_vector_iget( data, sim_time, params_index , is_rate, time_index1, time_index2, weight1, weight2); if (i == 0) fprintf(fp, "%f", value); else fprintf(fp, ",%f", value); } else { if (i == 0) fputs("", fp); else fputs(",", fp); } } } /* If the keylist contains invalid indices the corresponding element in the results vector will *not* be updated; i.e. it is smart to initialize the results vector with an invalid-value marker before calling this function: double_vector_type * results = double_vector_alloc( ecl_sum_vector_get_size(keys), NAN); ecl_sum_data_get_interp_vector( data, sim_time, keys, results); */ void ecl_sum_data_get_interp_vector( const ecl_sum_data_type * data , time_t sim_time, const ecl_sum_vector_type * keylist, double_vector_type * results){ int num_keywords = ecl_sum_vector_get_size(keylist); double weight1, weight2; int time_index1, time_index2; ecl_sum_data_init_interp_from_sim_time(data, sim_time, &time_index1, &time_index2, &weight1, &weight2); double_vector_reset( results ); for (int i = 0; i < num_keywords; i++) { if (ecl_sum_vector_iget_valid(keylist, i)) { int params_index = ecl_sum_vector_iget_param_index(keylist, i); bool is_rate = ecl_sum_vector_iget_is_rate(keylist, i); double value = ecl_sum_data_vector_iget( data, sim_time, params_index, is_rate, time_index1, time_index2, weight1, weight2); double_vector_iset( results, i , value ); } } } double ecl_sum_data_get_from_sim_time( const ecl_sum_data_type * data , time_t sim_time , const smspec_node_type * smspec_node) { int params_index = smspec_node_get_params_index( smspec_node ); if (smspec_node_is_rate( smspec_node )) { /* In general the mapping from sim_time to index is based on half open intervals, which are closed in the upper end: []<------------]<--------------]<-----------] t0 t1 t2 t3 However - as indicated on the figure above there is a zero measure point right at the start which corresponds to time_index == 0; this is to ensure that there is correspondance with the ECLIPSE results if you ask for a value interpolated to the starting time. */ int time_index = ecl_sum_data_get_index_from_sim_time( data , sim_time ); return ecl_sum_data_iget( data , time_index , params_index); } else { /* Interpolated lookup based on two (hopefully) consecutive ministeps. */ double weight1 , weight2; int time_index1 , time_index2; ecl_sum_data_init_interp_from_sim_time( data , sim_time , &time_index1 , &time_index2 , &weight1 , &weight2); return ecl_sum_data_interp_get( data , time_index1 , time_index2 , weight1 , weight2 , params_index); } } int ecl_sum_data_get_report_step_from_days(const ecl_sum_data_type * data , double sim_days) { if ((sim_days < ecl_sum_data_get_first_day(data)) || (sim_days > ecl_sum_data_get_sim_length(data))) return -1; else { auto files = data->index.lookup_days(sim_days); if (files.first != files.second) return -1; const auto& data_file = data->data_files[files.first->data_index]; return data_file->report_step_from_days(sim_days); } } /** Will go through the data and find the report step which EXACTLY matches the input sim_time. If no report step matches exactly the function will return -1. Observe that by default the report steps consist of half-open time intervals like this: (t1, t2]. However the first report step (i.e. report step 1, is a fully inclusive interval: [t0 , t1] where t0 is the simulation start time. That is not implemented here; meaning that if you supply the start time as @sim_time argument you will get -1 and not 0 as you might expect. It would certainly be possible to detect the start_time input argument and special case the return, but the opposite would be 'impossible' - you would never get anything sensible out when using report_step == 0 as input to one of the functions expecting report_step input. */ int ecl_sum_data_get_report_step_from_time(const ecl_sum_data_type * data , time_t sim_time) { if (!ecl_sum_data_check_sim_time(data , sim_time)) return -1; else { auto files = data->index.lookup_time(sim_time); if (files.first != files.second) return -1; const auto& data_file = data->data_files[files.first->data_index]; return data_file->report_step_from_time(sim_time); } } double ecl_sum_data_time2days( const ecl_sum_data_type * data , time_t sim_time) { time_t start_time = ecl_smspec_get_start_time( data->smspec ); return util_difftime_days( start_time , sim_time ); } double ecl_sum_data_get_from_sim_days( const ecl_sum_data_type * data , double sim_days , const smspec_node_type * smspec_node) { time_t sim_time = ecl_smspec_get_start_time( data->smspec ); util_inplace_forward_days_utc( &sim_time , sim_days ); return ecl_sum_data_get_from_sim_time( data , sim_time , smspec_node ); } time_t ecl_sum_data_iget_sim_time(const ecl_sum_data_type * data, int ministep_index) { const auto& index_node = data->index.lookup( ministep_index ); const auto data_file = data->data_files[index_node.data_index]; return data_file->iget_sim_time(ministep_index - index_node.offset); } time_t ecl_sum_data_get_report_time( const ecl_sum_data_type * data , int report_step) { if (report_step == 0) return ecl_smspec_get_start_time( data->smspec ); else { int internal_index = ecl_sum_data_iget_report_end( data , report_step ); return ecl_sum_data_iget_sim_time(data, internal_index); } } int ecl_sum_data_get_first_report_step( const ecl_sum_data_type * data ) { const auto& data_file = data->data_files[0]; return data_file->first_report(); } int ecl_sum_data_get_last_report_step( const ecl_sum_data_type * data ) { const auto& data_file = data->data_files.back(); return data_file->last_report(); } /*****************************************************************/ /* High level vector routines */ static void ecl_sum_data_init_time_vector__(const ecl_sum_data_type * data, time_t * output_data, bool report_only) { int offset = 0; for (const auto& index_node : data->index) { const auto& data_file = data->data_files[index_node.data_index]; if (report_only) offset += data_file->get_time_report(index_node.length, &output_data[offset]); else { data_file->get_time(index_node.length, &output_data[offset]); offset += index_node.length; } } } void ecl_sum_data_init_time_vector(const ecl_sum_data_type * data, time_t * output_data) { ecl_sum_data_init_time_vector__(data, output_data, false); } time_t_vector_type * ecl_sum_data_alloc_time_vector( const ecl_sum_data_type * data , bool report_only) { std::vector output_data; if (report_only) output_data.resize( 1 + ecl_sum_data_get_last_report_step(data) - ecl_sum_data_get_first_report_step(data)); else output_data.resize( ecl_sum_data_get_length(data) ); ecl_sum_data_init_time_vector__(data, output_data.data(), report_only); time_t_vector_type * time_vector = time_t_vector_alloc(output_data.size(),0); { time_t * tmp_data = time_t_vector_get_ptr( time_vector ); memcpy(tmp_data, output_data.data(), output_data.size() * sizeof(time_t)); } return time_vector; } static void ecl_sum_data_init_double_vector__(const ecl_sum_data_type * data, int main_params_index, double * output_data, bool report_only) { int offset = 0; for (const auto& index_node : data->index) { const auto& data_file = data->data_files[index_node.data_index]; const auto& params_map = index_node.params_map; int params_index = params_map[main_params_index]; if (report_only) { const smspec_node_type * smspec_node = ecl_smspec_iget_node(data->smspec, main_params_index); double default_value = smspec_node_get_default(smspec_node); offset += data_file->get_data_report(params_index, index_node.length, &output_data[offset], default_value); } else { if (params_index >= 0) data_file->get_data(params_index, index_node.length, &output_data[offset]); else { const smspec_node_type * smspec_node = ecl_smspec_iget_node(data->smspec, main_params_index); for (int i=0; i < index_node.length; i++) output_data[offset + i] = smspec_node_get_default(smspec_node); } offset += index_node.length; } } } void ecl_sum_data_init_datetime64_vector(const ecl_sum_data_type * data, int64_t * output_data, int multiplier) { for (int i = 0; i < ecl_sum_data_get_length(data); i++) output_data[i] = ecl_sum_data_iget_sim_time(data, i) * multiplier; } void ecl_sum_data_init_double_vector(const ecl_sum_data_type * data, int params_index, double * output_data) { ecl_sum_data_init_double_vector__(data, params_index, output_data, false); } double_vector_type * ecl_sum_data_alloc_data_vector( const ecl_sum_data_type * data , int params_index , bool report_only) { std::vector output_data; if (report_only) output_data.resize( 1 + ecl_sum_data_get_last_report_step(data) - ecl_sum_data_get_first_report_step(data)); else output_data.resize( ecl_sum_data_get_length(data) ); ecl_sum_data_init_double_vector__(data, params_index, output_data.data(), report_only); double_vector_type * data_vector = double_vector_alloc(output_data.size(), 0); { double * tmp_data = double_vector_get_ptr( data_vector ); memcpy(tmp_data, output_data.data(), output_data.size() * sizeof(double)); } return data_vector; } void ecl_sum_data_init_double_vector_interp(const ecl_sum_data_type * data, const smspec_node_type * smspec_node, const time_t_vector_type * time_points, double * output_data) { bool is_rate = smspec_node_is_rate(smspec_node); int params_index = smspec_node_get_params_index(smspec_node); time_t start_time = ecl_sum_data_get_data_start(data); time_t end_time = ecl_sum_data_get_sim_end(data); double start_value = 0; double end_value = 0; if (!is_rate) { start_value = ecl_sum_data_iget_first_value(data, params_index); end_value = ecl_sum_data_iget_last_value(data, params_index); } for (int time_index=0; time_index < time_t_vector_size(time_points); time_index++) { time_t sim_time = time_t_vector_iget( time_points, time_index); double value; if (sim_time < start_time) value = start_value; else if (sim_time > end_time) value = end_value; else { int time_index1, time_index2; double weight1, weight2; ecl_sum_data_init_interp_from_sim_time(data, sim_time, &time_index1, &time_index2, &weight1, &weight2); value = ecl_sum_data_vector_iget( data, sim_time, params_index, is_rate, time_index1, time_index2, weight1, weight2); } output_data[time_index] = value; } } void ecl_sum_data_init_double_frame(const ecl_sum_data_type * data, const ecl_sum_vector_type * keywords, double *output_data) { int time_stride = ecl_sum_vector_get_size(keywords); int key_stride = 1; for (int time_index=0; time_index < ecl_sum_data_get_length(data); time_index++) { for (int key_index = 0; key_index < ecl_sum_vector_get_size(keywords); key_index++) { int param_index = ecl_sum_vector_iget_param_index(keywords, key_index); int data_index = key_index*key_stride + time_index * time_stride; output_data[data_index] = ecl_sum_data_iget(data, time_index, param_index); } } } void ecl_sum_data_init_double_frame_interp(const ecl_sum_data_type * data, const ecl_sum_vector_type * keywords, const time_t_vector_type * time_points, double * output_data) { int num_keywords = ecl_sum_vector_get_size(keywords); int time_stride = num_keywords; int key_stride = 1; time_t start_time = ecl_sum_data_get_data_start(data); time_t end_time = ecl_sum_data_get_sim_end(data); for (int time_index=0; time_index < time_t_vector_size(time_points); time_index++) { time_t sim_time = time_t_vector_iget( time_points, time_index); if (sim_time < start_time) { for (int key_index = 0; key_index < num_keywords; key_index++) { int param_index = ecl_sum_vector_iget_param_index(keywords, key_index); int data_index = key_index*key_stride + time_index*time_stride; bool is_rate = ecl_sum_vector_iget_is_rate(keywords, key_index); if (is_rate) output_data[data_index] = 0; else output_data[data_index] = ecl_sum_data_iget_first_value(data, param_index); } } else if (sim_time > end_time) { for (int key_index = 0; key_index < num_keywords; key_index++) { int param_index = ecl_sum_vector_iget_param_index(keywords, key_index); int data_index = key_index*key_stride + time_index*time_stride; bool is_rate = ecl_sum_vector_iget_is_rate(keywords, key_index); if (is_rate) output_data[data_index] = 0; else output_data[data_index] = ecl_sum_data_iget_last_value(data, param_index); } } else { double weight1, weight2; int time_index1, time_index2; ecl_sum_data_init_interp_from_sim_time(data, sim_time, &time_index1, &time_index2, &weight1, &weight2); for (int key_index = 0; key_index < num_keywords; key_index++) { int param_index = ecl_sum_vector_iget_param_index(keywords, key_index); int data_index = key_index*key_stride + time_index*time_stride; bool is_rate = ecl_sum_vector_iget_is_rate(keywords, key_index); double value = ecl_sum_data_vector_iget( data, sim_time, param_index , is_rate, time_index1, time_index2, weight1, weight2); output_data[data_index] = value; } } } } /** This function will return the total number of ministeps in the current ecl_sum_data instance; but observe that actual series of ministeps can have non-zero offset and also "holes" in the series. */ int ecl_sum_data_get_length( const ecl_sum_data_type * data ) { return data->index.length(); } static bool ecl_sum_data_report_step_equal__( const ecl_sum_data_type * data1 , const ecl_sum_data_type * data2, bool strict) { if (data1->data_files.size() != data2->data_files.size()) return false; for (size_t i=0; i < data1->data_files.size(); i++) { const auto& data_file1 = data1->data_files[i]; const auto& data_file2 = data2->data_files[i]; if (!data_file1->report_step_equal(*data_file2, strict)) return false; } return true; } bool ecl_sum_data_report_step_compatible( const ecl_sum_data_type * data1 , const ecl_sum_data_type * data2) { return ecl_sum_data_report_step_equal__(data1, data2, false); } bool ecl_sum_data_report_step_equal( const ecl_sum_data_type * data1 , const ecl_sum_data_type * data2) { return ecl_sum_data_report_step_equal__(data1, data2, true); } double ecl_sum_data_iget_last_value(const ecl_sum_data_type * data, int param_index) { return ecl_sum_data_iget(data, ecl_sum_data_get_length(data)-1, param_index); } double ecl_sum_data_get_last_value(const ecl_sum_data_type * data, int param_index) { return ecl_sum_data_iget_last_value(data, param_index); } double ecl_sum_data_iget_first_value(const ecl_sum_data_type * data, int param_index) { return ecl_sum_data_iget(data, 0, param_index); }