Skip to content

Commit de3b56c

Browse files
Merge pull request #124 from jonnymaserati/feature/maximum_curvature
2 parents 22c7141 + ebacba4 commit de3b56c

File tree

5 files changed

+313
-71
lines changed

5 files changed

+313
-71
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ welleng is fuelled by copious amounts of coffee, so if you wish to supercharge d
2020
<a href="https://www.buymeacoffee.com/jonnymaserati" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/arial-yellow.png" alt="Buy Me A Coffee" width="217px" ></a>
2121

2222
## New Features!
23+
- **Maximum Curvature Method:** added an alternative `Survey` method for calculating a well trajectory from survey stations that add a more realistic (in terms of tortuosity) versus the traditional *minimum curvature method*. See [this post](https://jonnymaserati.github.io/2022/06/19/modified-tortuosity-index-survey-frequency.html) for more details.
24+
- **Modified Tortuosity Index:** added a `Survey` method for calculating a *modified tortuosity index* as described [here](https://jonnymaserati.github.io/2022/05/26/a-modified-tortuosity-index.html).
25+
- **Panel Plot:** added the `type='panel'` to the `Survey.figure()` method to return plan and section plots.
2326
- **Torque and Drag:** added a simple `torque_drag` module and an `architecture` module for creating scenarios (well bore and simple strings) - see this [post](https://jonnymaserati.github.io/2022/05/22/an-example-of-welleng-torque-drag.html) for instructions.
2427
- **Vertical Section:** this should have been included a long time ago, but finally a vertical section will be calculated if the `vertical_section_azimuth` parameter is included in the `SurveyHeader` when initiating a `Survey` instance. Otherwise, to return the vertical section for a given azimuth (e.g. 45 degrees), or to set the vertical section azimuth and add the vertical section data to the `Survey` then:
2528
```python

welleng/survey.py

Lines changed: 167 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
HLA_to_NEV,
2020
NEV_to_HLA,
2121
get_xyz,
22-
radius_from_dls
22+
radius_from_dls,
23+
get_arc
2324
)
2425
from .error import ErrorModel, ERROR_MODELS
2526
from .node import Node
@@ -256,69 +257,69 @@ def __init__(
256257
257258
Parameters
258259
----------
259-
md: (,n) list or array of floats
260-
List or array of well bore measured depths.
261-
inc: (,n) list or array of floats
262-
List or array of well bore survey inclinations
263-
azi: (,n) list or array of floats
264-
List or array of well bore survey azimuths
265-
n: (,n) list or array of floats (default: None)
266-
List or array of well bore northings
267-
e: (,n) list or array of floats (default: None)
268-
List or array of well bore eastings
269-
tvd: (,n) list or array of floats (default: None)
270-
List or array of local well bore z coordinates, i.e. depth
271-
and usually relative to surface or mean sea level.
272-
x: (,n) list or array of floats (default: None)
273-
List or array of local well bore x coordinates, which is
274-
usually aligned to the east direction.
275-
y: (,n) list or array of floats (default: None)
276-
List or array of local well bore y coordinates, which is
277-
usually aligned to the north direction.
278-
z: (,n) list or array of floats (default: None)
279-
List or array of well bore true vertical depths relative
280-
to the well surface datum (usually the drill floor
281-
elevation DFE, so not always identical to tvd).
282-
vec: (n,3) list or array of (,3) floats (default: None)
283-
List or array of well bore unit vectors that describe the
284-
inclination and azimuth of the well relative to (x,y,z)
285-
coordinates.
286-
header: SurveyHeader object (default: None)
287-
A SurveyHeader object with information about the well location
288-
and survey data. If left default then a SurveyHeader will be
289-
generated with the default properties assigned, but these may
290-
not be relevant and may result in incorrect data.
291-
radius: float or (,n) list or array of floats (default: None)
292-
If a single float is specified, this value will be
293-
assigned to the entire well bore. If a list or array of
294-
floats is provided, these are the radii of the well bore.
295-
If None, a well bore radius of 12" or approximately 0.3 m
296-
is applied.
297-
cov_nev: (n,3,3) list or array of floats (default: None)
298-
List or array of covariance matrices in the (n,e,v)
299-
coordinate system.
300-
cov_hla: (n,3,3) list or array of floats (default: None)
301-
List or array of covariance matrices in the (h,l,a)
302-
well bore coordinate system (high side, lateral, along
303-
hole).
304-
error_model: str (default: None)
305-
If specified, this model is used to calculate the
306-
covariance matrices if they are not present. Currently,
307-
only the "ISCWSA_MWD" model is provided.
308-
start_xyz: (,3) list or array of floats (default: [0,0,0])
309-
The start position of the well bore in (x,y,z) coordinates.
310-
start_nev: (,3) list or array of floats (default: [0,0,0])
311-
The start position of the well bore in (n,e,v) coordinates.
312-
start_cov_nev: (,3,3) list or array of floats (default: None)
313-
The covariance matrix for the start position of the well
314-
bore in (n,e,v) coordinates.
315-
deg: boolean (default: True)
316-
Indicates whether the provided angles are in degrees
317-
(True), else radians (False).
318-
unit: str (default: 'meters')
319-
Indicates whether the provided lengths and distances are
320-
in 'meters' or 'feet', which impacts the calculation of
321-
the dls (dog leg severity).
260+
md: (,n) list or array of floats
261+
List or array of well bore measured depths.
262+
inc: (,n) list or array of floats
263+
List or array of well bore survey inclinations
264+
azi: (,n) list or array of floats
265+
List or array of well bore survey azimuths
266+
n: (,n) list or array of floats (default: None)
267+
List or array of well bore northings
268+
e: (,n) list or array of floats (default: None)
269+
List or array of well bore eastings
270+
tvd: (,n) list or array of floats (default: None)
271+
List or array of local well bore z coordinates, i.e. depth
272+
and usually relative to surface or mean sea level.
273+
x: (,n) list or array of floats (default: None)
274+
List or array of local well bore x coordinates, which is
275+
usually aligned to the east direction.
276+
y: (,n) list or array of floats (default: None)
277+
List or array of local well bore y coordinates, which is
278+
usually aligned to the north direction.
279+
z: (,n) list or array of floats (default: None)
280+
List or array of well bore true vertical depths relative
281+
to the well surface datum (usually the drill floor
282+
elevation DFE, so not always identical to tvd).
283+
vec: (n,3) list or array of (,3) floats (default: None)
284+
List or array of well bore unit vectors that describe the
285+
inclination and azimuth of the well relative to (x,y,z)
286+
coordinates.
287+
header: SurveyHeader object (default: None)
288+
A SurveyHeader object with information about the well location
289+
and survey data. If left default then a SurveyHeader will be
290+
generated with the default properties assigned, but these may
291+
not be relevant and may result in incorrect data.
292+
radius: float or (,n) list or array of floats (default: None)
293+
If a single float is specified, this value will be
294+
assigned to the entire well bore. If a list or array of
295+
floats is provided, these are the radii of the well bore.
296+
If None, a well bore radius of 12" or approximately 0.3 m
297+
is applied.
298+
cov_nev: (n,3,3) list or array of floats (default: None)
299+
List or array of covariance matrices in the (n,e,v)
300+
coordinate system.
301+
cov_hla: (n,3,3) list or array of floats (default: None)
302+
List or array of covariance matrices in the (h,l,a)
303+
well bore coordinate system (high side, lateral, along
304+
hole).
305+
error_model: str (default: None)
306+
If specified, this model is used to calculate the
307+
covariance matrices if they are not present. Currently,
308+
only the "ISCWSA_MWD" model is provided.
309+
start_xyz: (,3) list or array of floats (default: [0,0,0])
310+
The start position of the well bore in (x,y,z) coordinates.
311+
start_nev: (,3) list or array of floats (default: [0,0,0])
312+
The start position of the well bore in (n,e,v) coordinates.
313+
start_cov_nev: (,3,3) list or array of floats (default: None)
314+
The covariance matrix for the start position of the well
315+
bore in (n,e,v) coordinates.
316+
deg: boolean (default: True)
317+
Indicates whether the provided angles are in degrees
318+
(True), else radians (False).
319+
unit: str (default: 'meters')
320+
Indicates whether the provided lengths and distances are
321+
in 'meters' or 'feet', which impacts the calculation of
322+
the dls (dog leg severity).
322323
323324
Returns
324325
-------
@@ -877,10 +878,11 @@ def set_vertical_section(self, vertical_section_azimuth, deg=True):
877878
)
878879

879880
def modified_tortuosity_index(
880-
self, rtol=0.01, dls_tol=None, data=False, **kwargs
881+
self, rtol=0.01, dls_tol=None, step=1.0, dls_noise=1.0, data=False,
882+
**kwargs
881883
):
882884
"""
883-
Convenient method for calculating the Tortuosity Index (TI) using a
885+
Convenience method for calculating the Tortuosity Index (TI) using a
884886
modified version of the method described in the International
885887
Association of Directional Drilling presentation
886888
(https://www.iadd-intl.org/media/files/files/47d68cb4/iadd-luncheon-february-22-2018-v2.pdf)
@@ -895,15 +897,37 @@ def modified_tortuosity_index(
895897
dls_tol: float or None
896898
Indicates whether or not to check for dls continuity within the
897899
defined dls tolerance.
898-
data: float
900+
step: float or None
901+
The step length in meters used for interpolating the survey prior
902+
to calculating trajectory with the maximum curvature method. If
903+
None or dls_noise is None then no interpolation is done.
904+
dls_noise: float or None
905+
The incremental Dog Leg Severity to be added when using the
906+
maximum curvature method. If None then no pre-processing will be
907+
done and minimum curvature is assumed.
908+
data: bool
899909
If true returns a dictionary of properties.
900910
901911
Returns
912+
-------
902913
ti: (n,1) array or dict
903914
Array of tortuosity index or a dict of results where:
915+
916+
References
917+
----------
918+
Further details regarding the maximum curvature method can be read
919+
[here](https://jonnymaserati.github.io/2022/06/19/modified-tortuosity-index-survey-frequency.html)
904920
"""
921+
# Check whether to pre-process the survey to apply maximum curvature.
922+
if bool(dls_noise):
923+
survey = self.interpolate_survey(step=step)
924+
survey = survey.maximum_curvature(dls_noise=dls_noise)
925+
926+
else:
927+
survey = self
928+
905929
return modified_tortuosity_index(
906-
self, rtol=rtol, dls_tol=dls_tol, data=data, **kwargs
930+
survey, rtol=rtol, dls_tol=dls_tol, data=data, **kwargs
907931
)
908932

909933
def tortuosity_index(self, rtol=0.01, dls_tol=None, data=False, **kwargs):
@@ -957,6 +981,80 @@ def directional_difficulty_index(self, **kwargs):
957981

958982
return directional_difficulty_index(self, **kwargs)
959983

984+
def maximum_curvature(self, dls_noise=1.0):
985+
"""
986+
Create a well trajectory using the Maximum Curvature method.
987+
988+
Parameters
989+
----------
990+
survey: welleng.survey.Survey object
991+
dls_noise: float
992+
The additional Dog Leg Severity (DLS) in deg/30m used to calculate
993+
the curvature for the initial section of the survey interval.
994+
995+
Returns
996+
-------
997+
survey_new: welleng.Survey.survey object
998+
A revised survey object calculated using the Minimum Curvature
999+
method with updated survey positions and additional mid-point
1000+
stations.
1001+
"""
1002+
1003+
dls_effective = self.dls + dls_noise
1004+
radius_effective = radius_from_dls(dls_effective)
1005+
1006+
dogleg1 = (
1007+
(self.delta_md / radius_effective) / 2
1008+
)
1009+
1010+
radius_effective = np.where(
1011+
dogleg1 > np.pi,
1012+
self.delta_md * 4 / (2 * np.pi),
1013+
radius_effective
1014+
)
1015+
1016+
arc1 = [
1017+
get_arc(dogleg, _radius_effective, toolface, vec=vec)
1018+
for dogleg, _radius_effective, toolface, vec in zip(
1019+
dogleg1[1:], radius_effective[1:], self.toolface[:-1],
1020+
self.vec_nev[:-1]
1021+
)
1022+
]
1023+
1024+
_survey_new = np.array([
1025+
[row[-1], *np.degrees(get_angles(row[1], nev=True))[0]]
1026+
for row in arc1
1027+
])
1028+
_survey_new[:, 0] += self.md[:-1]
1029+
1030+
survey_new = np.zeros(shape=(len(_survey_new) * 2 + 1, 3))
1031+
survey_new[:-1] = np.stack(
1032+
(self.survey_deg[:-1].T, _survey_new.T),
1033+
axis=1
1034+
).T.reshape(-1, 3)
1035+
survey_new[-1] = self.survey_deg[-1]
1036+
1037+
# Update the new survey header as the new azimuth reference is 'grid'.
1038+
sh = self.header
1039+
sh.azi_reference = 'grid'
1040+
1041+
# Create a new Survey instance
1042+
survey = Survey(
1043+
md=survey_new[:, 0],
1044+
inc=survey_new[:, 1],
1045+
azi=survey_new[:, 2],
1046+
header=sh,
1047+
start_xyz=self.start_xyz,
1048+
start_nev=self.start_nev
1049+
)
1050+
1051+
# Update the interpolated property to keep track of the original survey
1052+
# stations.
1053+
survey.interpolated = np.full_like(survey.md, True)
1054+
survey.interpolated[::2] = self.interpolated
1055+
1056+
return survey
1057+
9601058

9611059
def modified_tortuosity_index(
9621060
survey, rtol=0.01, dls_tol=None, data=False, **kwargs
@@ -1014,7 +1112,7 @@ def modified_tortuosity_index(
10141112
'continuous': continuous, 'starts': starts, 'mds': mds,
10151113
'locs': locs, 'n_sections': n_sections,
10161114
'n_sections_arr': n_sections_arr, 'l_cs': l_cs, 'l_xs': l_xs,
1017-
'mti': mti
1115+
'mti': mti, 'survey': survey
10181116
}
10191117

10201118
return mti

welleng/utils.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,26 @@ def __init__(self, dogleg, radius):
514514
])
515515

516516
def transform(self, toolface, pos=None, vec=None, target=False):
517+
"""
518+
Transforms an Arc to a position and orientation.
519+
520+
Parameters
521+
----------
522+
pos: (,3) array
523+
The desired position to transform the arc.
524+
vec: (,3) array
525+
The orientation unit vector to transform the arc.
526+
target: bool
527+
If true, returned arc vector is reversed.
528+
529+
Returns
530+
-------
531+
tuple (pos_new, vec_new)
532+
pos_new: (,3) array
533+
The position at the end of the arc post transform.
534+
vec_new: (,3) array
535+
The unit vector at the end of the arc post transform.
536+
"""
517537
if vec is None:
518538
vec = np.array([0., 0., 1.])
519539
if target:
@@ -537,6 +557,36 @@ def transform(self, toolface, pos=None, vec=None, target=False):
537557

538558

539559
def get_arc(dogleg, radius, toolface, pos=None, vec=None, target=False):
560+
"""
561+
Creates an Arc instance and transforms it to the desired position and
562+
orientation.
563+
564+
Parameters
565+
----------
566+
dogleg: float
567+
The swept angle of the arc (arc angle) in radians.
568+
radius: float
569+
The radius of the arc (in meters).
570+
toolface: float
571+
The toolface angle in radians (relative to the high side) to rotate the
572+
arc at the desired position and orientation.
573+
pos: (,3) array
574+
The desired position to transform the arc.
575+
vec: (,3) array
576+
The orientation unit vector to transform the arc.
577+
target: bool
578+
If true, returned arc vector is reversed.
579+
580+
Returns
581+
-------
582+
tuple of (pos_new, vec_new, arc.delta_md)
583+
pos_new: (,3) array
584+
The position at the end of the arc post transform.
585+
vec_new: (,3) array
586+
The unit vector at the end of the arc post transform.
587+
arc.delta_md: int
588+
The arc length of the arc.
589+
"""
540590
arc = Arc(dogleg, radius)
541591
pos_new, vec_new = arc.transform(toolface, pos, vec, target)
542592

welleng/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.4.12'
1+
__version__ = '0.4.13'

0 commit comments

Comments
 (0)