-
Notifications
You must be signed in to change notification settings - Fork 118
Extend WELTRAJ/COMPTRAJ to support multi-segment wells for grid-independent wells #4524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
jenkins build this for me please |
b142936 to
9522075
Compare
|
You seem to be too egocentric 😅 . Skip the "for me" to call jenkins. |
|
jenkins build this please |
|
@bska and @verveerpj, could you guys review this PR? |
bska
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good start, but I would really prefer that Compsegs::Record remain a private implementation detail in Compsegs.cpp. Client code should not need to care about how we structure intermediate information.
| namespace Compsegs { | ||
|
|
||
|
|
||
| struct Record { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There has to be a better way than to make this internal implementation detail public. What information do you really need to accomplish what you want to do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check, maybe we can avoid passing records.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| processCOMPSEGS(const DeckKeyword& compsegs, | ||
| processCOMPSEGS(const std::vector< Record >& compsegs_vector, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you not add a separate overload of processCOMPSEGS(), possibly by extracting the common pieces out to a private helper function, that would allow you to keep the Compsegs::Record structure private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sure there is a way to re-arrange the code, I will need to find some time in the next weeks and I will have a go at it.
| for (size_t is = 0; is < intersections.size(); ++is) { | ||
| const auto startMD = intersections[is].first; | ||
| const auto endMD = intersections[is].second; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the is index, so please use "range for" here,
for (const auto& [startMD, endMD] : intersections) {
// ...
}| const auto startMD = intersections[is].first; | ||
| const auto endMD = intersections[is].second; | ||
| const auto length = (endMD - startMD) / 2.0 + startMD; | ||
| const auto depth = length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this (depth = length) mean that we (currently?) assume vertical wells only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My (admittedly somewhat limited) understanding is that the depth is in fact a measured depth, so that would be valid. The test case does a non-vertical well, and we get the same compared to the keywords generated by ResInsight, so that seems to be working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this (
depth = length) mean that we (currently?) assume vertical wells only?understanding is that the depth is in fact a measured depth, so that would be valid.
we get the same compared to the keywords generated by ResInsight, so that seems to be working.
I believe that at least as far as the rest of the simulator goes, the "length" is the MD and the "depth" is the TVD. Is that accurate, @GitPaean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for bringing me here. You are right, and depth == length should only be possible for strictly vertical wells. For any other wells, it is problematic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was also what I understood to be generally so, but it seemed to different in this part of the code, if my understanding is correct. It may bea confusion with variable naming. In the manual of the COMPSEGS keyword there are also some DEPTH options that are defined as tubing lengths, which presumable are MD.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And in the keyword COMPSEGS , there should be only length/distance, there is no depth. Depth is calculated based on length/distance. The keyword definition should also be changed without using DEPTH. @gdfldm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, it remains a bit confusing to me, this my understanding, which may be wrong, since its not really my area:
- The documentation of
COMPSEGShasDEPTH1andDEPTH2, but the description describes both as a tubing length, which suggest to me it'sMD. So, it seems the names are just confusing. - The documentation of
WELSEGSalso hasDEPTH1andDEPTH2, where the first is again a tubing length, soMD, that seems fine. The second is described as adepth of the tubing, suggesting itsTVD. In that case, these should not be the same if the well is not vertical. However,ResInsightkeeps both the same, when generatingWELSEGSkeywords for a non-vertical well, suggesting it thinks they are alsoMD. But that is strange, why would you need two identical columns?
So, the PR reproduces what ResInsight does, but it is actually unclear it it does what it should. Are we looking at a bug in ResInsight, or is the documtenation just wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I do not work with ResInsight and we do not have control over what ResInsight does. If ResInsight does what you said, they have a bug. I do not use ResInsight regularly, but you can consider to report to them.
As mentioned previously, the OPM-flow document looks indeed confusing regarding the meaning of the items. I will report to @gdfldm for a potential correction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, I will change the PR to generate true depth instead of duplicating length. I will check again what ResInsight exactly does, and report it to them if they do it differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
9522075 to
17be30b
Compare
e6ccaa4 to
77428c9
Compare
14770f1 to
7748652
Compare
|
I have now updated the code to address the length/depth situation so that non-vertical wells are now handled properly. I have tested this against the WELSEGS/COMPSEGS keywords generated by Resinsight, and this gives now virtually identically results, giving me some confidence that is now working as intended. This test is included as a test case. Code-related concerns have also been addressed, so I think this is ready for another round of review. |
|
jenkins build this please |
|
@verveerpj very good. the tests are successful. @bska and @GitPaean, could you check this please? |
|
jenkins build this please |
|
I was hoping to manage to look into this PR but I did not. I will be away for one week. If it is okay, I will review and merge it in the week after. Otherwise, if others have time before that, please feel free to do so. |
|
@GitPaean , @verveerpj is also off this week. |
|
Hei, let us start from the design of the keyword system. I was not involved much in this development, please excuse me if I understand things wrong. In theory, a well can be a segmented well or a standard well (no segments are involved), while can not be both. The keyword Furthermore, the concept of the multisegment wells is pretty abstract and numerical. It means the path of the MSW does not always follow the path of the connections (which is the physical perforations), and also we can decide how many segments we want for each well. It means the same well can have 5 or 10 segments depending what we want. Based on the thoughts above, I suggest that we still use the keyword Please let me now what you think. @verveerpj With more discussion, I might understand the system better and let us work together to get a solid design for the way forward. |
|
Hi @GitPaean, Sure, I am open to changing the design. Let me discuss it with my colleagues at TNO and we will get back to you. |
Great. Feel free to get me involved in the discussion any time. |
|
We just had a short discussion, some of the main points we have:
|
|
IMHO, I think it is worth considering using the WELSEGS keyword to specify that a well is a multi-segment well, perhaps just by specifying the well-name, and defaulting all the other items. The reason being that this is the standard method and it would probably be required in the future in any case for tubing ID, tubing roughness, etc. |
|
Thanks for the feedback. I would like to understand the current design better before I can suggest more. With the keyword WELTRAJ/COMPTRAJ,
|
We are using the approach that ResInsight has for exporting COMPSEG/WELSEGS keywords. Basically we calculate intersections with the cells and add a segment to each cell. Currently that is good enough for us. Next step would be what you mentioned before: be able to specify them (probably by md in WELSEGS?)
We have not implemented multiple branches yet, but the next step is to be able to define multiple branches and generate the correct COMPSET/WELSEG data.
They are currently defaulted. That is also something we also need to work on. We had some further discussions internally and decided to follow you advice and get rid of the MSW flag. We will use WELSEGS, probably still defaulting a lot. But then we are open to extend it, allowing us to address your points about specifying well segments and properties/parameters. |
|
Thanks for the update. I do not have capacity at the moment, while will get back to you in a couple of days. |
No problem, we are not in a hurry! |
98f0b45 to
fe9e060
Compare
|
Is there a test case somewhere that I can test the PR with? You can share with my email. or if you think it is ready, you can also open a PR with opm-tests. Either way works for me. Thanks. |
65dcd8b to
19bdca2
Compare
The PR has a test that uses this case: That should run with the the new code and show the multi-segment structure in its output. |
The description should be updated so that it reflects the current design. I am reviewing the PR now and will comment during the day. |
GitPaean
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are some comments for now, we need to do more refactoring after these. Please let me what you think about the comments.
tests/parser/ConnectionTests.cpp
Outdated
| } | ||
|
|
||
|
|
||
| BOOST_AUTO_TEST_CASE(loadCOMPTRAJTESTSPE1_MSW) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can either to have a separate test file for grid-independent wells or you can have this test in MultisegmentWellTests.cpp .
This file looks like focusing on connections mostly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved it to MultisegmentWellTests.cpp
tests/SPE1CASE1_WELTRAJ_MSW.DATA
Outdated
|
|
||
| -- Note: This is a modified version of SPE1CASE1 | ||
| -- This model specifies the well trajectories independent of grid indexes | ||
| -- This model use keywords WELTRAJ and COMPTRAJ instead of WELSPECS and COMPDAT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think WELSPECS is still necessary.
| ErrorGuard& errors); | ||
|
|
||
| std::pair<WellConnections, WellSegments> | ||
| processCOMPSEGS(std::vector<std::tuple<double, double, std::array<int, 3>>>& segments_md_and_ijk, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we use capitalized COMPSEGS, we are referring to the keyword COMPSEGS, which is not the case here. Even later we use COMPSEGS keyword for the grid-independent multisegment wells, we can still make it clear this function is used for grid-independent wells. There is no need to share the same function name, which might be better for readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have renamed it.
| } | ||
|
|
||
| if (handlerContext.state().wells.get(name).isMultiSegment()) { | ||
| // Retrieve the well path coordinates: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part inside the if (handlerContext.state().wells.get(name).isMultiSegment()) { should probably be made a function (a couple of functions inside for better readability) .
This part as whole is basically generate the pair between the WellConnections and WellSegments.
The definition of the function should not be in this file, which is mostly for Completion handling, we should avoid to introduce Segments related headers in this file also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Splitting it up in a separate function or functions is fine, I will do that. However, I am not clear on how avoid including the segment related headers, since COMPTRAJ is now processing segments which otherwise would be done by WELSEGS and COMPSEGS. We would have to locate the new function(s) in a different file, but I am not sure where.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not thought through/much about it, possibly Well.hpp, while maybe it is time to let grid-independent related keywords to have their own .cpp file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a new file: GridIndependentKeywordHandlers.cpp?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Since we only talk about keywords related to Wells (correct me if I am mistaken), we can have Well in the file name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GridIndependentWellKeywords.cpp? But long, but why not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is fine. and we should have the Handlers in the file name so that it is consistent with other Handlers files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, that would be GridIndependentWellKeywordHandlers.cpp, that seems too long. I will think on a name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have made the new file, and refactored the handleCOMPTRAJ code.
| std::pair<WellConnections, WellSegments> | ||
| processCOMPSEGS(const DeckKeyword& compsegs, | ||
| processCOMPSEGSfromRecords(const std::vector<Record>& compsegs_vector, | ||
| const WellConnections& input_connections, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation of the function parameters. And from is probably should be From.
Technically, this function is not tied to the keyword COMPSEGS anymore, we should not have full capitalized COMPSEGS in the function name anymore, while it is generating a pair of WellConnections and WellSegments. You can suggest better names for this or we keep it in a new round refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
processCOMPSEGSfromRecords is just a local helper function, I renamed it to process_compsegs_records and moved it into an anonymous namespace.
| throw Opm::OpmInputError(msg, handlerContext.keyword.location()); | ||
| } | ||
| // Generate WELSEGS data: | ||
| const auto& diameter = record.getItem("DIAMETER").getSIDouble(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no point to use reference here.
|
|
||
| void Well::addWellSegmentsFromLengthsAndDepths(std::vector<std::pair<double, double>>& lengths_and_depths, double diameter) | ||
| { | ||
| if (this->segments != nullptr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the usage of WELSEGS, should we instead of checking whether this->m_segments->empty()?
And also, with WELSEGS, will the else branch ever be possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly do not know. I followed the same structure as the bool Well::handleWELSEGS(const DeckKeyword& keyword) constructor does. Since segments is a shared pointer, that seemed reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current implementation, if (handlerContext.state().wells.get(name).isMultiSegment()) must be true before we enter this function, which means WELSEGS has already been parsed.
And with WELSEGS, we will generate the top segment already.
Whether we allow WELSEGS input after COMPTRAJ is a design question we need to decide.
handleWELSEGS is different. If it is the first time WELSEGS is parsed for the well, this->segments can be nullptr.
But for this function, with current design and implementation, a top segment is already there. We will not encounter empty this->m_segments.
Please let me know id you see it differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense, I will remove the comparison and the else clause.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you add something like at the beginning of the function.
if (this->segments == nullptr) {
throw OpmInputError{
fmt::format("The WELSEGS keyword must be specified for well {} "
"before creating segments through COMPTRAJ keyword.",
this->name()),
keyword.location()
};
}
But then you need to parse in more argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could be, I will check that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
Will trigger copilot to check the typos. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR extends the WELTRAJ/COMPTRAJ keywords to support multi-segment wells (MSW) for grid-independent wells. When a well is added to WELSEGS with only the first record (top segment definition) and no segment structure, the system now automatically generates the missing WELSEGS records and COMPSEGS information using the trajectory data from COMPTRAJ and WELTRAJ. Each cell in the well trajectory becomes a segment.
Key changes:
- Modified
loadCOMPTRAJto return intersection data needed for MSW generation - Added automatic MSW generation logic in
handleCOMPTRAJwhen a well hasWELSEGSentry - Implemented validation to prevent both
COMPSEGSandCOMPTRAJfrom being used for the same MSW well
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/parser/ConnectionTests.cpp | Added test case for MSW with COMPTRAJ functionality |
| tests/SPE1CASE1_WELTRAJ_MSW.DATA | New test data file with WELSEGS and COMPTRAJ keywords |
| opm/input/eclipse/Schedule/Well/WellConnections.hpp | Changed loadCOMPTRAJ signature to return intersection data |
| opm/input/eclipse/Schedule/Well/WellConnections.cpp | Modified loadCOMPTRAJ to return intersections for MSW processing |
| opm/input/eclipse/Schedule/Well/WellCompletionKeywordHandlers.cpp | Added MSW generation logic in COMPTRAJ handler |
| opm/input/eclipse/Schedule/Well/Well.hpp | Added method for generating segments from lengths and depths |
| opm/input/eclipse/Schedule/Well/Well.cpp | Implemented addWellSegmentsFromLengthsAndDepths method |
| opm/input/eclipse/Schedule/Schedule.hpp | Added comptraj_wells parameter for consistency checking |
| opm/input/eclipse/Schedule/Schedule.cpp | Updated consistency checking for COMPSEGS/COMPTRAJ overlap |
| opm/input/eclipse/Schedule/MSW/WellSegments.hpp | Added constructor and method for MSW from lengths/depths, getLengthDepthType accessor |
| opm/input/eclipse/Schedule/MSW/WellSegments.cpp | Implemented segment generation and stored length_depth_type |
| opm/input/eclipse/Schedule/MSW/WelSegsSet.hpp | Added intersection method declaration |
| opm/input/eclipse/Schedule/MSW/WelSegsSet.cpp | Implemented intersection to check for COMPSEGS/COMPTRAJ overlap |
| opm/input/eclipse/Schedule/MSW/Compsegs.hpp | Added processCOMPSEGS overload for COMPTRAJ data |
| opm/input/eclipse/Schedule/MSW/Compsegs.cpp | Refactored and added processCOMPSEGS for segment MD/IJK data |
| opm/input/eclipse/Schedule/HandlerContext.hpp | Added comptraj_wells tracking support |
| opm/input/eclipse/Schedule/HandlerContext.cpp | Implemented comptraj_handled method |
| CMakeLists_files.cmake | Added new test data file to build |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
48d9e2e to
b539f17
Compare
3f9d7fe to
3f91810
Compare
3f91810 to
37bbd1f
Compare
|
@GitPaean I think I have made adjustments for all your comments, I will wait for your comments on that. |
Thanks. I will look into it today or tomorrow, depending how much I manage today. |
|
I am reviewing the PR, while it does not look like I can finish before the end of the day. Sorry for the delay, while it will be finished soon also. |
This PR adds support for multi-segment wells to the
WELTRAJ/COMPTRAJkeywords. If a well entry is added to theWELSEGSkeyword, without defining the segment structure (i.e. only the first record), information provided by theCOMPTRAJandWELTRAJkeywords is used to generate multi-segment wells for that well (i.e. the missingWELSEGSrecords, and theCOMPSEGSinformation).In this PR the generated segments simply consist of a segment for each cell in the well trajectory generated from
COMPTRAJ. A future extension should allow reading additionalWELSEGSrecords to define the segments as usual, while still generating theCOMPSEGSdata from theCOMPTRAJdata.Currently, all options in the
WELSEGSrecord are used. OnlyROUGHTNESSis defaulted.