Files
ResInsight/ApplicationLibCode/FileInterface/RifReaderSumoGridProperty.h
T
Jørgen Herje c49f298a7d [ri-cloud-api]: Add grid data from sumo using api (#14653)
Add grid data from Sumo using ri-cloud-api

Load Eclipse grid data (roff) from Sumo as RimReservoirGridEnsemble, one case per realization, with async fetch of grid properties per timestep.

- New: RiaSumoGrid (transport), RifReaderSumoGridProperty (lazy property reader), RimReservoirGridEnsembleSumo, RimRoffCaseSumo, and a "Create Reservoir Grid Ensemble" command.
- Shared grid: IJK dimensions compared before download; one RigMainGrid is reused across the ensemble when they match, each realization still parses its own ACTNUM before releasing geometry.
- Rename folders: Summary/Sumo -> Summary/Cloud, RimSumoDataSource -> Cloud/ (PDM keywords unchanged, old project files still load).
- UI: cloud data sources also shown in the 3D window's Data Sources dock.
- Fix: realizations no longer lost on project reload; realizations missing data are now reported instead of silently skipped.
2026-09-10 10:33:58 +02:00

190 lines
11 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2024 Equinor ASA
//
// ResInsight 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.
//
// ResInsight 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RifReaderInterface.h"
#include <QByteArray>
#include <QPointer>
#include <QString>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
class RiaSumoConnector;
//==================================================================================================
//
// Lazily fetches grid cell properties for a single Sumo grid realization. ResInsight's result
// machinery calls staticResult()/dynamicResult() the first time a property is displayed; this reader
// then downloads the corresponding roff blob from Sumo and decodes the cell values.
//
//==================================================================================================
class RifReaderSumoGridProperty : public RifReaderInterface
{
public:
// Called whenever what is pending changes, with a user-facing message describing it (empty when nothing
// is pending). No GUI code lives here: the owner decides where and how to show it.
using PendingChangedCallback = std::function<void( const QString& )>;
// Called once a requested time step is done, successfully or not, so the owner can redraw whatever
// shows this data.
using TimeStepArrivedCallback = std::function<void( const QString&, size_t, bool )>;
// Called whenever how many of a batch's time steps are done changes: completed, total, and a description
// of what is currently being transferred (matches pendingDataDescription()). total is 0 once nothing is
// pending, meaning the batch is over. No GUI code lives here: the owner decides whether and how to show a
// progress bar from these numbers.
using BatchProgressChangedCallback = std::function<void( size_t /*completed*/, size_t /*total*/, const QString& /*description*/ )>;
RifReaderSumoGridProperty( RiaSumoConnector* connector,
const QString& caseId,
const QString& ensembleName,
const QString& gridName,
int realization );
// Aborts any transfers still in flight for this reader, see RiaSumoConnector::cancelGroup, so they stop
// consuming connections and logging results for a reader that is no longer there.
~RifReaderSumoGridProperty() override;
void setPendingChangedCallback( PendingChangedCallback callback );
void setTimeStepArrivedCallback( TimeStepArrivedCallback callback );
void setBatchProgressChangedCallback( BatchProgressChangedCallback callback );
void setStaticProperties( const std::vector<QString>& propertyNames );
void setDynamicProperties( const std::map<QString, std::vector<QString>>& propertyNameToTimestamps );
bool open( const QString& fileName, RigEclipseCaseData* eclipseCase ) override;
// Take one time step fetched before this reader existed, see RimRoffCaseSumo::startPropertyFetch.
void acceptFetchedTimeStep( const QString& propertyName, size_t stepIndex, const QString& isoDateOrInterval, const QByteArray& contents );
// Record a transfer started before this reader existed as already on its way. Without this the reader
// reports nothing in flight, so the user is told nothing while waiting and a second, duplicate transfer
// is issued the first time the property is read.
void markTimeStepPending( const QString& propertyName, size_t stepIndex );
// What is being transferred right now, for display to the user. Empty when nothing is.
QString pendingDataDescription() const;
bool staticResult( const QString& result, RiaDefines::PorosityModelType matrixOrFracture, std::vector<double>* values ) override;
bool dynamicResult( const QString& result, RiaDefines::PorosityModelType matrixOrFracture, size_t stepIndex, std::vector<double>* values ) override;
// Fetches and stores exactly the given time steps of a dynamic property - no other time step of it is
// pulled in. Meant for a caller with no redraw loop of its own that needs a specific set of time steps,
// e.g. RimStatisticsContourMap: having them already present keeps
// RigCaseCellResultsData::ensureKnownResultLoaded() from falling back to loading the whole time series.
// The given steps are requested together as one async batch (so the transfers run in parallel, the same
// as dynamicResult()'s own look-ahead batch), and this call blocks until every one of them has either
// arrived or timed out before returning - so by the time it returns, nothing requested by this call is
// left in flight. Returns false if any requested step never got a value (missing, failed, or timed out).
bool prefetchDynamicResult( const QString& propertyName, const std::vector<size_t>& stepIndices );
private:
bool fetchAndDecode( const QString& propertyName, const QString& isoDateOrInterval, std::vector<double>* values );
bool decodeInto( const QByteArray& contents, const QString& propertyName, std::vector<double>* values );
// One line per transferred time step, naming the realization and whether it came from a batch or from
// a single request. This reader is per realization, so without the realization a run of single
// requests from several readers is indistinguishable from one reader fetching the same steps twice.
void logTransfer( const QString& propertyName, const QString& isoDateOrInterval, size_t byteCount, bool fromBatch ) const;
// The time steps to download together with stepIndex: stepIndex itself, plus every other unloaded step
// of this property not already pending or loaded, so the whole property can be requested in one batch.
// Which of these actually run at once is left to QNetworkAccessManager - this reader does not throttle
// the batch itself.
std::vector<size_t> timeStepsToFetch( const QString& propertyName, const std::vector<QString>& timestamps, size_t stepIndex );
// Where the case keeps the values of one time step of a dynamic property, or null when the property is
// not registered as a result. Writing here is what makes a prefetched time step available without
// holding on to the downloaded bytes.
std::vector<double>* resultValueSlot( const QString& propertyName, size_t stepIndex );
// One requested time step of one dynamic property.
using PendingKey = std::pair<QString, size_t>;
// Request the given time steps without waiting, marking them pending. Already pending steps are skipped,
// so a redraw mid-transfer does not issue a second request. Not capped: every given step not already
// pending is requested in the same batch, relying on QNetworkAccessManager to bound how many actually
// run at once and queue the rest.
void requestTimeStepsAsync( const QString& propertyName, const std::vector<QString>& timestamps, const std::vector<size_t>& steps );
// Blocks the calling thread until the given time step is no longer pending or the transfer times out
// (RiaSumoDefines::gridPropertyTransferTimeoutMillis()). Lets dynamicResult() hand back the real values
// instead of a placeholder, so callers never see partially-loaded data - the same blocking contract a
// disk-based reader already has. No GUI code here: whatever progress bar the owner shows via
// m_onBatchProgressChanged runs independently, driven by requestTimeStepsAsync/onTimeStepArrived. A no-op
// if the step is not currently pending.
void waitForTimeStepToArrive( const QString& propertyName, size_t stepIndex );
// Called on the connector thread, which is the GUI thread, once per requested time step.
void onTimeStepArrived( const QString& propertyName, size_t stepIndex, const QString& isoDateOrInterval, const QByteArray& contents );
// Fill values with the undefined-cell value, sized to the grid, and report success, so a time step still
// on its way draws blank instead of logging a load failure on every redraw.
bool fillWithUndefinedValues( std::vector<double>* values ) const;
// Reports the time step is done via m_onTimeStepArrived, successfully or not, so the owner can redraw.
void notifyTimeStepArrived( const QString& propertyName, size_t stepIndex, bool ok ) const;
// Reports what is pending via m_onPendingChanged, built from pendingDataDescription().
void notifyPendingChanged() const;
// Reports how many of a batch's time steps are done via m_onBatchProgressChanged: opens/grows the count
// the first time something becomes pending (called from requestTimeStepsAsync/markTimeStepPending/
// acceptFetchedTimeStep) and ticks it as each step arrives (called from onTimeStepArrived), reporting
// total 0 once m_pending is empty again so the owner knows the batch is over. No GUI code here: the
// owner decides whether and how to show a progress bar from these numbers.
void updateBatchProgress();
private:
QPointer<RiaSumoConnector> m_connector;
QString m_caseId;
QString m_ensembleName;
QString m_gridName;
int m_realization;
RigEclipseCaseData* m_caseData; // set in open(); used for cell count and active cell masking
std::vector<QString> m_staticProperties;
std::map<QString, std::vector<QString>> m_dynamicTimestamps; // property name -> sorted iso timestamps
// Requested and not yet arrived. Claimed before the request is issued, so it also answers "already on
// its way?".
std::set<PendingKey> m_pending;
// Dropped when this reader dies. Callbacks hold a weak_ptr and return early once it expires, so a reply
// outliving the reader cannot write into freed memory.
std::shared_ptr<bool> m_lifetimeToken;
PendingChangedCallback m_onPendingChanged;
TimeStepArrivedCallback m_onTimeStepArrived;
BatchProgressChangedCallback m_onBatchProgressChanged;
// Bookkeeping only, no GUI: how many time steps have been added to the current batch and how many of
// those have arrived (successfully or not), reported to the owner via m_onBatchProgressChanged. Both
// reset to 0 once m_pending empties again.
size_t m_batchProgressTotal = 0;
size_t m_batchProgressCompleted = 0;
};