From 0d2232aefddc48bdd3567de3f7a6ee4086c02852 Mon Sep 17 00:00:00 2001 From: jonnymaserati Date: Mon, 14 Dec 2020 14:05:53 +0100 Subject: [PATCH 1/3] Added connector module --- examples/connect_two_random_points.py | 151 +++ requirements.txt | 3 +- welleng/__init__.py | 5 +- welleng/__pycache__/__init__.cpython-37.pyc | Bin 422 -> 509 bytes welleng/__pycache__/connector.cpython-37.pyc | Bin 0 -> 21279 bytes welleng/__pycache__/mesh.cpython-37.pyc | Bin 10650 -> 10724 bytes welleng/__pycache__/survey.cpython-37.pyc | Bin 11677 -> 16139 bytes welleng/__pycache__/target.cpython-37.pyc | Bin 0 -> 1546 bytes welleng/__pycache__/utils.cpython-37.pyc | Bin 7251 -> 7517 bytes welleng/__pycache__/visual.cpython-37.pyc | Bin 4595 -> 4671 bytes welleng/connector.py | 1136 +++++++++++++++++ welleng/exchange/__init__.py | 6 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 293 bytes .../exchange/__pycache__/wbp.cpython-37.pyc | Bin 0 -> 3889 bytes welleng/exchange/wbp.py | 165 +++ welleng/mesh.py | 41 +- welleng/survey.py | 224 +++- welleng/target.py | 70 + welleng/utils.py | 24 +- welleng/visual.py | 15 +- 20 files changed, 1811 insertions(+), 29 deletions(-) create mode 100644 examples/connect_two_random_points.py create mode 100644 welleng/__pycache__/connector.cpython-37.pyc create mode 100644 welleng/__pycache__/target.cpython-37.pyc create mode 100644 welleng/connector.py create mode 100644 welleng/exchange/__init__.py create mode 100644 welleng/exchange/__pycache__/__init__.cpython-37.pyc create mode 100644 welleng/exchange/__pycache__/wbp.cpython-37.pyc create mode 100644 welleng/exchange/wbp.py create mode 100644 welleng/target.py diff --git a/examples/connect_two_random_points.py b/examples/connect_two_random_points.py new file mode 100644 index 0000000..76807b8 --- /dev/null +++ b/examples/connect_two_random_points.py @@ -0,0 +1,151 @@ +import welleng as we +import numpy as np +from vedo import Arrows, Lines +import random + +# Generate some random pairs of points + +pos1 = [0,0,0] +md1 = 0 + +pos2 = np.random.random(3) * 1000 + +vec1 = np.random.random(3) +vec1 /= np.linalg.norm(vec1) +inc1, azi1 = np.degrees(we.utils.get_angles(vec1, nev=True)).reshape(2).T + +vec2 = np.random.random(3) +vec2 /= np.linalg.norm(vec2) + +inc2, azi2 = np.degrees(we.utils.get_angles(vec2, nev=True)).reshape(2).T + +md2 = 100 + random.random() * 1000 + +# Define some random input permutations + +number = 7 +rand = random.random() + +# 1: test only md2 (hold) +if rand < 1 * 1 / number: + option = 1 + expected_method = 'hold' + vec2, pos2, inc2, azi2 = None, None, None, None + +# 2: test md2 and an inc2 +elif rand < 1 * 2 / number: + option = 2 + expected_method = 'min_curve' + pos2, vec2, azi2 = None, None, None + +# 3: test md2 and azi2 +elif rand < 1 * 3 / number: + option = 3 + expected_method = 'min_curve' + pos2, vec2, inc2 = None, None, None + +# 4: test md2, inc2 and azi2 +elif rand < 1 * 4 / number: + option = 4 + expected_method = 'min_curve' + pos2, vec2 = None, None + +# 5 test pos2 +elif rand < 1 * 5 / number: + option = 5 + expected_method = 'min_dist_to_target' + vec2, inc2, azi2, md2 = None, None, None, None + +# 6 test pos2 vec2 +elif rand < 1 * 6 / number: + option = 6 + expected_method = 'curve_hold_curve' + md2, inc2, azi2, = None, None, None + +# 7 test pos2, inc2 and azi2 +else: + option = 7 + expected_method = 'curve_hold_curve' + md2, vec2 = None, None + +# Print the input parameters + +print( + f"Option: {option}\tExpected Method: {expected_method}\n" + f"md1: {md1}\tpos1: {pos1}\tvec1: {vec1}\tinc1: {inc1}\tazi1: {azi1}\n" + f"md2: {md2}\tpos2: {pos2}\tvec2: {vec2}\tinc2: {inc2}\tazi2: {azi2}\n" +) + +# Initialize a connector object and connect the inputs +section = we.connector.Connector( + pos1=[0.,0.,0], + vec1=vec1, + md2=md2, + pos2=pos2, + vec2=vec2, + inc2=inc2, + azi2=azi2, + degrees=True +) + +# Print some pertinent calculation data + +print( + f"Method: {section.method}\n", + f"radius_design: {section.radius_design}\t", + f"radius_critical: {section.radius_critical}\n" + f"radius_design2: {section.radius_design2}\t", + f"radius_critical2: {section.radius_critical2}\n" + f"iterations: {section.iterations}" +) + +# Create a survey object of the section with interpolated points and coords +survey = section.survey(radius=5, step=30) + +# As a QAQC step, check that the wellpath hits the defined turn points +start_points = np.array([section.pos1]) +end_points = np.array([section.pos_target]) +if section.pos2 is not None: + start_points = np.concatenate((start_points, [section.pos2])) + end_points = np.concatenate(([section.pos2], [section.pos_target])) +if section.pos3 is not None: + start_points = np.concatenate((start_points, [section.pos3])) + end_points = np.concatenate(([section.pos2], [section.pos3], [section.pos_target])) +lines = Lines( + startPoints=start_points, + endPoints=end_points, + c='green', + lw=5 +) + +# Add some arrows to represent the vectors at the start and end positions +scalar=150 +arrows = Arrows( + startPoints=np.array([ + section.pos1, + section.pos_target + ]), + endPoints=np.array([ + section.pos1 + scalar * section.vec1, + section.pos_target + scalar * section.vec_target + ]), + s=0.5, + res=24 +) + +# generate a mesh of the generated section from the survey +# use the 'circle' method to construct a cylinder with constant radius +mesh = we.mesh.WellMesh( + survey=survey, + method='circle', + n_verts=24, +) + +# plot the results +we.visual.plot( + [mesh.mesh], + lines=lines, + arrows=arrows, +) + +print("Done") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index abad2f5..2206542 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ numpy scipy trimesh -vedo \ No newline at end of file +vedo +transforms3d \ No newline at end of file diff --git a/welleng/__init__.py b/welleng/__init__.py index 5fcc8ef..6c1308a 100644 --- a/welleng/__init__.py +++ b/welleng/__init__.py @@ -6,4 +6,7 @@ import welleng.mesh import welleng.visual import welleng.version -import welleng.errors.iscwsa_mwd \ No newline at end of file +import welleng.errors.iscwsa_mwd +import welleng.exchange.wbp +import welleng.target +import welleng.connector \ No newline at end of file diff --git a/welleng/__pycache__/__init__.cpython-37.pyc b/welleng/__pycache__/__init__.cpython-37.pyc index 6d3b6a5fa3e7507727577d78517d72562f521ff5..6166454ba03e33315ac896709dbc4371d40ff41e 100644 GIT binary patch delta 157 zcmZ3+{Fj-}iI}glpu_soS&DMnp~1! RG+BYMNJ I9E_+C04&1{$N&HU diff --git a/welleng/__pycache__/connector.cpython-37.pyc b/welleng/__pycache__/connector.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3436e9a77617e0953ffc79a5a9676d2a7b6339a1 GIT binary patch literal 21279 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 delta 2207 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 U`C#Ia#QwxyGo8tp6Xw(Z1tVbohX4Qo delta 1335 zcmZvbU1$_n6oBW?CG0|BAS`BGp{VBypFvZHGblv6-Yc~7G zorx%{7FrQO+Y;`Bpa?1W=Hov2pkUjFP#+4Vg%;+q_!1x5hk_4+;5oC}Y(sar-`;!A z&pqedb8lX~x1b-5#Z(D@e;isbY)r1|eQ;@Y&zUiUbu;A}*g8wFC{uA3XGx~97|sl) zv-ma1PB564%+%^HSD+#0;Fy(oV-yZdB)g0CFbx$WXc}WWns&1|GjK;~n~L9H9G(l1 z9~@>-f-C8!&gA7NUvKKo^m4>q_cAQ>6?_S}mI~Qs9NFKDA$K%2Eb&dc1e4-LdQ=Q=e|2O(iBbr^%e_!+IDV+&)c79U zFl9bY!a&2KS%5svdJ%WUx!!5GC2sdle-z~(;-9`r!w#{E%Xi^vcDPY@T``pzvnVQG zt}eFY*$nC(+`o^*-HwnVSy0}5;!fsCCdg$=8dIqEu*I_jX9UvMI^tz>{&SSw&vBnVx_?9rHWGu9Q)w;;C2L0t>q_|YM#Rz^-9rm3hoNO z=v;mwiO0D-oNoP{dn_N^km)c$oG`U^tef{XM6zMRPD0ST!4nu0e~uqL(6J`5jzAwX z_=oN96}jh!U9aBg^eR4?7~C~S+W);NUvn!z>% diff --git a/welleng/__pycache__/target.cpython-37.pyc b/welleng/__pycache__/target.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84e0939642ba6ca5c533a01aea6c1045c6821282 GIT binary patch literal 1546 zcmZuxTW{P%6rPLi^?EOf8VZD~K`PRM=&F^F5JJ!rmQ-9q#a06L#d1AU#~a5sGqY{7 za$ZPY^A8Y$M}7ivBtMnHpC2 ze&fHBbF{#_3)T_SKY)LL{+iY#a%u+X)*R5Q9l#FcA(3BufZe(S7}P#sukHf&>i}?2 z_W*}t2%ojJ{2~Zf?P09qV`-$)o2~aZZL@{JsVpH-oqiNm1-2cT=UJtt?JAj= zxJYwpJ5`yVre$HBs?>KHylXubi)^8rdS0DCsYZO}w|mi@jdj;@CH06aA0iZJf+(!- zaz`J=l?;p0gjo?zAKZU9nJ$OVR9U2(>m7`?tRu3-*x`Mxr7~GrOq42>JwHK(OnrHMpr;tyb{Q8k2a z)1YC;2WA59!}vL7%M94Je8tUO^xu_wNy>!5caJ9 QaeIwVkP3~NecEUL0oh?-wg3PC literal 0 HcmV?d00001 diff --git a/welleng/__pycache__/utils.cpython-37.pyc b/welleng/__pycache__/utils.cpython-37.pyc index fa0d8a036fad28c3c5c2fdde408357e3f7b0ff46..6f0cbfc28278332f77fc61b5b605654dd75b7fa0 100644 GIT binary patch delta 1489 zcmaJ>OK)366rR_8_}Z}@JAS9NMJmXZOlgsTD&^4xky4Zd`XEpPiHyg2;o8;MjVK!9 z1!2jengt6a6oXjslBoOx;15(F1qopZgb)(yk{^J?IoGKPT`*Vk-EYo0Gv|Egj(u?E zm$~S|#Dqs+{E+-<{j1WgXtvjYl_ZijNQIs!l1aWtB!7=pm`q7Wx_h+3!4J>o_`Sho zNP6HAGA+Y00?d*VGP*}9w#>+wjDy9ISve^az+9P>Q{W3pdYSmSyI8b-p-j)}+D%uJuBB<$*1SXZxwiUriU1SZM%H!iv-3l{ z8`#Atz8lg^Y1qZIqg@@^(SH+cxx1t1|Ts(^Gr}dv+zWL zO|b-1=LUXco%tVOn82|3ELZ2@U1PX{;ldoZW@v6llf8W|spNfDAYG!NC9o2G2ArxF zG}#fa^DgW1t^jG4=9*~{$Q2kZEor^aq`kp%L^{cTE&QG(1MP;LHcSVbH{Ro51mwAd zE_-YajvB9mWBN=w&_j2MWFX0irQu5f3u#-!L6S@M0fWooR#zw?W-IoV`A)bMXyRPy z%~q>XuQp4wvc6Hh(df*T7ORbR-SQ~)Y+<+pxd36Ds6)Sw!U4y zS$@Yafnl~;u9tUxvz9tx?#3o~v;3&}D^fJs_}SBV|I|Fdih?SLasYf!rI0^{@HoN( z!Yo43yc<8Bchxhvcmm-`gr^Xm1~8w*{bSz%Y2%$Bv1uCNf<|b9&C=ua3{%U4-{XJK z^lx)BeTvSTFVaiBOK@UPhR@YyN;gfx1!rf3>BLXmbN?>Q&f#RF%1&%Pa?aUPI4SgpnSo8}wi*ssi6g4wQy+ z!0X}Z5eOjEWrC%7R!niVHuy2up*DUyHE&i54^O{=G>Q=1m?E+Z0KQOH&4-1DXu*72 zNX7A=&*z8#c|Kh?)(~o@7s{AZ)7fw_pYo2md2hy>{0H(~ BFzo;U delta 1301 zcmaJ>%TE(Q7@yZ}x1~?$TZ9A?W#gL@B4E@QJrG1e)CwV`BVesSri268rZM8tgPFve z3H9Q^gP8sWUc4DiJeaHzFCNsJH$7F4Q{VzAxY0AJlEpYI|Gv8Y0n6zXNJVJ(K+d3&2 zGAtvonldG$G6tT2Ov|`T0JCJf>;PX-(s5#EzT)dgONcmLJhg&#WdNHf3C}`E5LOfB zS(w#T0Eert6|zBLGKV;Ho;7HLPO+M(8H<)fYQmv4!(rg(j%d&%0VW&+Se6lT@+?Y!9h{ZG*#(HyCm$ zq`1$dF~@R5njL>FfgMYR)bMl!Ov9m#B5fs^7y@PRa74@Mm1^z0G7Tn8Xv9pC47|i= zX;S0B;HVr9mma|O;1aF1Exi@HtyMa$-^3%l@6e)tnCjBM;)4f=K+&i|u_}OoS8H1; z1&eD|ZY>ro^R8H2Qg_s_E+@K7;quj+CH*8}XP!Y!H4H!^Ng8Iow2SsLHPZZ&_)I(e zx>(JtYX>0zQ%8}$)TfgzQM8NN;9FPF=$AUI3e`%gDzOCeT3D z734<{jv`C~{DfI$04A|bHKtEyb2o<3>D4gQD00kmd1+}?T?AGT7e-E3y*fKJv78$k zJvTYCvNQwU53sLB0Z6;W1WWTQ4+E;}(Db|G|Lf7^kiM%&uA4buUsC_b_0=(dTV8=L zREnNFKj&X@A-I$sah4i`G<6jLuSV@hXi0VX+O1{Rs4f(jZ`#|k3m9FoR=S4!_J^Oq^~yWqlx^^ zI{w^VZd_%N=qs&Gd=#Z<9)VN>D1iw9)e#GjMRdWZC{dltA$uZAfdaoK7wxyb0X%SD zB^7G?oc!J{zw|09RX@U<;<;9*UW*qx%ZKVt3YhNPL6-=RMe0{dz=r|gGxDq>LlvIC zfe>c+IouMx)~QzXK?s6=SA3YAOh;JeXHiXLHGgW2n<#Qi>6ztQ?UnZ)ly0mx zc%&}VQRZ<4s(OA+!Hfb`UYceadtV-DMrp6ji|r1()oP3Z`bNhQOc()^y3{WdNT-9F LJ!NJJd9&|-71kkw delta 1298 zcmZ8hO>7%Q6rMM0@Bgmtb;wWCrj%A~w@9c3S^ zKb-peDpzH)!OsfEmo(gve(Wvo}dpVh#?{)Rus`%tK9?`K*MR3M;b;t^tF25>_9optVrn z{{H;-f4_}6xZ|z@Pl1o{72$#W3%;ElQA}_U@alxCObna}MKB@X*9$1CPaRNhUc;IE zBmbnQp&(1zB;vE$!g}%Ucd0E38!$^2zP?HOdfyN_Ggc`7HFb7eVJv&Q&^MpR%$Oie zK*s96k~+)27;1K5iK-%OQDu!9>+qSO-bzct*nqw*=sMF@tdD>{FYKMNphBM@y8uMM zWBxkw6#+xOxDyClS=|2eEs|D+iEf;9L^#V&q2%H^+V_Nq>ln^)W;C^N()aVL_^dcV zgs)OiJSI^R^PbuYPpO#I)I^EdBV>&9%jtwDBX8hvni_Mrw0`J%!~ z*iJPn>Ex&6Gs=Q3cklN?on9C=D%3Li-1!VvGFaQ!KGnH@?df(aN$b7N>P#qa8#ln* zyKj65UsRyp zacJ>N@|b&K_9|{NxHYIkUXO$M@ylq?!I`Mvg$m@;XJC|47z|zGm*qY87kvt~+>{I6 z12`+s`ST}oXKx^zL3}6~K8^GT@^k+MnBM!@KSj;6NZ@_nk&nt3Z@#1sxr1C+d#!Go zWOk#=76&rFg6hK#Bji58ndE;rB^_KC1($Nghgz*x3#`a@g_VCCUvP_wQFWE IsF-8_1Jp7OxBvhE diff --git a/welleng/connector.py b/welleng/connector.py new file mode 100644 index 0000000..cd5c291 --- /dev/null +++ b/welleng/connector.py @@ -0,0 +1,1136 @@ +import numpy as np +import math as m +from .utils import get_vec, get_angles +from .survey import Survey + + +class Connector: + def __init__( + self, + pos1=[0,0,0], + vec1=None, + inc1=None, + azi1=None, + md1=0, + dls_design=3.0, + dls_design2=None, + md2=None, + pos2=None, + vec2=None, + inc2=None, + azi2=None, + degrees=True, + unit='meters', + min_error=1e-5, + delta_radius=10, + min_tangent=10, + max_iterations=1000, + ): + + """ + A class to provide a fast, efficient method for determining well bore + section geometry using the appropriate method based upon the provided + parameters, with the intent of assisting machine learning fitness + fitness functions. Interpolation between the returned control points + can be performed postumously - attempts are made to keep processing to + a minimum in the event that the Connector is being used for machine + learning. + + Only specific combinations of input data are permitted, e.g. if you + input both a start vector AND a start inc and azi you'll get an error + (which one is correct after all?). Think about what you're trying to + achieve and provide the data that will facilitate that outcome. + + Parameters + ---------- + pos1: (3) list or array of floats (default: [0,0,0]) + Start position in NEV coordinates. + vec1: (3) list or array of floats or None (default: None) + Start position unit vector in NEV coordinates. + inc1: float or None (default: None) + Start position inclination. + azi2: float or None (default: None) + Start position azimuth. + md1: float or None (default: None) + Start position measured depth. + dls_design: float (default: 3.0) + The desired Dog Leg Severity (DLS) for the (first) curved + section in degrees per 30 meters or 100 feet. + dls_design2: float or None (default: None) + The desired DLS for the second curve section in degrees per + 30 meters or 100 feet. If set to None then `dls_design` will + be the default value. + md2: float or None (default: None) + The measured depth of the target position. + pos2: (3) list or array of floats or None (default: None) + The position of the target in NEV coordinates. + vec2: (3) list or array of floats or None (default: None) + The target unit vector in NEV coordinates. + inc1: float or None (default: None) + The inclination at the target position. + azi2: float or None (default: None) + The azimuth at the target position. + degrees: boolean (default: True) + Indicates whether the input angles (inc, azi) are in degrees + (True) or radians (False). + unit: string (default: 'meters') + Indicates the distance unit, either 'meters' or 'feet'. + min_error: float (default: 1e-5): + Infers the error tolerance of the results and is used to set + iteration stops when the desired error tolerance is met. Value + must be less than 1. Use with caution as the code may + become unstable if this value is changed. + delta_radius: float (default: 10) + The delta radius (first curve and second curve sections) used + as an iteration stop when balancing radii. If the resulting + delta radius yielded from `dls_design` and `dls_design2` is + larger than `delta_radius`, then `delta_radius` defaults to + the former. + min_tangent: float (default: 10) + The minimum tangent length in the `curve_hold_curve` method + used to mitigate instability during iterations (where the + tangent section approaches or equals 0). + max_iterations: int (default: 1000) + The maximum number of iterations before giving up trying to + fit a `curve_hold_curve`. This number is limited by Python's + depth of recursion, but if you're hitting the default stop + then consider changing `delta_radius` and `min_tangent` as + your expectations may be unrealistic (this is open source + software after all!) + + Results + ------- + connector: welleng.connector.Connector object + """ + + # TODO: remove self.step + + # Set up a lookup dictionary to use with the logic to determine + # what connector method to deploy. Use a binary string to + # represent the inputs in the order: + # (md2, inc2, azi2, pos2, vec2) + # Initially I used boolean logic, but it quickly became non- + # transparent and difficult to debug. + self._get_initial_methods() + + # METHODS = [ + # 'hold', + # 'curve_hold', # + # 'min_dist_to_target', + # 'min_curve_to_target', + # 'curve_hold_curve', + # 'min_curve' + # ] + + # quick check that inputs are workable and if not some steer to + # the user. + assert vec1 is not None or (inc1 is not None and azi1 is not None), "Require either vec1 or (inc1 and azi1)" + if vec1 is not None: + assert inc1 is None and azi1 is None, "Either vec1 or (inc1 and azi1)" + if (inc1 is not None or azi1 is not None): + assert vec1 is None, "Either vec1 or (inc1 and azi1)" + + assert ( + md2 is not None + or pos2 is not None + or vec2 is not None + or inc2 is not None + or azi2 is not None + ), "Missing target parameters" + + if vec2 is not None: + assert not (inc2 or azi2), "Either vec2 or (inc2 and azi2)" + if (inc2 or azi2): + assert not vec2, "Either vec2 or (inc2 and azi2)" + if md2: + assert not pos2, "Either md2 or pos2" + + assert dls_design > 0, "dls_design must be greater than zero" + assert min_error < 1, "min_error must be less than 1.0" + + # figure out what method is required to connect the points + target_input = convert_target_input_to_booleans(md2, inc2, azi2, pos2, vec2) + self.initial_method = self.initial_methods[target_input] + + # do some more initialization stuff + self.min_error = min_error + self.min_tangent = min_tangent + self.iterations = 0 + self.max_iterations = max_iterations + self.errors = [] + self.dogleg_old, self.dogleg2_old = 0, 0 + self.dist_curve2 = 0 + self.pos1 = np.array(pos1) + + self.pos2, self.vec2, self.inc2, self.azi2, self.md2 = ( + None, None, None, None, None + ) + + # fill in the input data gaps + if (inc1 is not None and azi1 is not None): + if degrees: + self.inc1 = np.radians(inc1) + self.azi1 = np.radians(azi1) + else: + self.inc1 = inc1 + self.azi1 = azi1 + self.vec1 = np.array(get_vec(self.inc1, self.azi1, nev=True, deg=False)) + else: + self.vec1 = np.array(vec1) + self.inc1, self.azi1 = get_angles(self.vec1, nev=True).reshape(2) + + self.md1 = md1 + self.pos_target = None if pos2 is None else np.array(pos2) + self.md_target = md2 + + if vec2 is not None: + self.vec_target = np.array(vec2) + self.inc_target, self.azi_target = get_angles( + self.vec_target, + nev=True + ).reshape(2) + elif (inc2 is not None and azi2 is not None): + if degrees: + self.inc_target = np.radians(inc2) + self.azi_target = np.radians(azi2) + else: + self.inc_target = inc2 + self.azi_target = azi2 + self.vec_target = get_vec( + self.inc_target, self.azi_target, nev=True, deg=False + ).reshape(3) + elif inc2 is None and azi2 is None: + self.inc_target, self.azi_target, self.vec_target = ( + self.inc1, self.azi1, self.vec1 + ) + elif inc2 is None: + self.inc_target = self.inc1 + if degrees: + self.azi_target = np.radians(azi2) + else: + self.azi_target = azi2 + self.vec_target = get_vec(self.inc_target, self.azi_target, nev=True, deg=False) + elif azi2 is None: + self.azi_target = self.azi1 + if degrees: + self.inc_target = np.radians(inc2) + else: + self.inc_target = inc2 + self.vec_target = get_vec(self.inc_target, self.azi_target, nev=True, deg=False) + else: + self.vec_target = vec2 + self.inc_target = inc2 + self.azi_target = azi2 + + self.unit = unit + if self.unit == 'meters': + self.denom = 30 + else: + self.denom = 100 + + if degrees: + self.dls_design = np.radians(dls_design) + if dls_design2: + self.dls_design2 = np.radians(dls_design2) + else: + self.dls_design = dls_design + if dls_design2: + self.dls_design2 = dls_design2 + if not dls_design2: + self.dls_design2 = self.dls_design + + self.radius_design = self.denom / self.dls_design + self.radius_design2 = self.denom / self.dls_design2 + + # check that the `delta_radius` actually makes sense + self.delta_radius = max( + delta_radius, + abs(self.radius_design - self.radius_design2) + ) + + # some more initialization stuff + self.tangent_length = None + self.dogleg2 = None + + self.pos3, self.vec3, self.inc3, self.azi3, self.md3 = ( + None, None, None, None, None + ) + self.radius_critical, self.radius_critical2 = np.inf, np.inf + + # Things fall apart if the start and end vectors exactly equal + # one another, so need to check for this and if this is the + # case, modify the end vector slightly. This is a lazy way of + # doing this, but it's fast. Probably a more precise way would + # be to split the dogelg in two, but that's more hassle than + # it's worth. + if ( + self.vec_target is not None + and np.array_equal(self.vec_target, self.vec1 * -1) + ): + ( + self.vec_target, + self.inc_target, + self.azi_target + ) = mod_vec(self.vec_target, self.min_error) + + # properly figure out the method + self._get_method() + + # and finally, actually do something... + self._use_method() + + def _min_dist_to_target(self): + ( + self.tangent_length, + self.dogleg + ) = min_dist_to_target(self.radius_design, self.distances) + self.dogleg = check_dogleg(self.dogleg) + self.dist_curve, self.func_dogleg = get_curve_hold_data( + self.radius_design, self.dogleg + ) + self.vec_target = get_vec_target( + self.pos1, + self.vec1, + self.pos_target, + self.tangent_length, + self.dist_curve, + self.func_dogleg + ) + self._get_angles_target() + self._get_md_target() + self.pos2 = ( + self.pos_target - ( + self.tangent_length * self.vec_target + ) + ) + self.md2 = self.md1 + abs(self.dist_curve) + self.md_target = self.md2 + self.tangent_length + self.vec2 = self.vec_target + + def _min_curve_to_target(self): + ( + self.tangent_length, + self.radius_critical, + self.dogleg + ) = min_curve_to_target(self.distances) + self.dogleg = check_dogleg(self.dogleg) + self.dist_curve, self.func_dogleg = get_curve_hold_data( + min(self.radius_design, self.radius_critical), self.dogleg + ) + self.vec_target = get_vec_target( + self.pos1, + self.vec1, + self.pos_target, + self.tangent_length, + self.dist_curve, + self.func_dogleg + ) + self._get_angles_target() + self._get_md_target() + + def _use_method(self): + if self.method == 'hold': + self._hold() + elif self.method == 'min_curve': + self._min_curve() + elif self.method == 'curve_hold_curve': + self.pos2_list, self.pos3_list = [], [self.pos_target] + self.vec23 = [np.array([0,0,0])] + self._target_pos_and_vec_defined(self.pos_target) + else: + self.distances = get_distances(self.pos1, self.vec1, self.pos_target) + if self.radius_design <= get_radius_critical( + self.radius_design, self.distances, self.min_error + ): + self.method = 'min_dist_to_target' + self._min_dist_to_target() + else: + self.method = 'min_curve_to_target' + self._min_curve_to_target() + + + def _get_method(self): + assert self.initial_method not in [ + 'no_input', + 'vec_and_inc_azi', + 'md_and_pos' + ], f"{self.initial_method}" + if self.initial_method == 'hold': + self.method = 'hold' + elif self.initial_method[-8:] == '_or_hold': + if np.array_equal(self.vec_target, self.vec1): + self.method = 'hold' + else: + self.method = self.initial_method[:-8] + else: + self.method = self.initial_method + + def _get_initial_methods(self): + # TODO: probably better to load this in from a yaml file + self.initial_methods = { + '00000': 'no_input', + '00001': 'min_curve_or_hold', + '00010': 'curve_hold', + '00011': 'curve_hold_curve', + '00100': 'min_curve_or_hold', + '00101': 'vec_and_inc_azi', + '00110': 'curve_hold', + '00111': 'vec_and_inc_azi', + '01000': 'min_curve_or_hold', + '01001': 'vec_and_inc_azi', + '01010': 'curve_hold_or_hold', + '01011': 'vec_and_inc_azi', + '01100': 'min_curve_or_hold', + '01101': 'vec_and_inc_azi', + '01110': 'curve_hold_curve', + '01111': 'vec_and_inc_azi', + '10000': 'hold', + '10001': 'min_curve_or_hold', + '10010': 'md_and_pos', + '10011': 'md_and_pos', + '10100': 'min_curve_or_hold', + '10101': 'vec_and_inc_azi', + '10110': 'md_and_pos', + '10111': 'md_and_pos', + '11000': 'min_curve_or_hold', + '11001': 'vec_and_inc_azi', + '11010': 'md_and_pos', + '11011': 'md_and_pos', + '11100': 'min_curve', + '11101': 'vec_and_inc_azi', + '11110': 'md_and_pos', + '11111': 'md_and_pos' + } + + def _min_curve(self): + self.dogleg = get_dogleg( + self.inc1, self.azi1, self.inc_target, self.azi_target + ) + self.dogleg = check_dogleg(self.dogleg) + if self.md_target is None: + self.md2 = None + self.dist_curve, self.func_dogleg = get_curve_hold_data( + self.radius_design, self.dogleg + ) + self.md_target = self.md1 + abs(self.dist_curve) + self.pos_target = get_pos( + self.pos1, + self.vec1, + self.vec_target, + self.dist_curve, + self.func_dogleg + ).reshape(3) + else: + self.radius_critical = abs((self.md_target - self.md1) / self.dogleg) + if ( + self.radius_critical > self.radius_design + or ( + np.around(self.dogleg, decimals=5) + == np.around(np.pi, decimals=5) + ) + ): + self.md2 = ( + self.md1 + + min(self.radius_design, self.radius_critical) * self.dogleg + ) + ( + self.inc2, self.azi2, self.vec2 + ) = self.inc_target, self.azi_target, self.vec_target + self.dist_curve, self.func_dogleg = get_curve_hold_data( + min(self.radius_design, self.radius_critical), + self.dogleg + ) + self.pos2 = get_pos( + self.pos1, + self.vec1, + self.vec2, + self.dist_curve, + self.func_dogleg + ).reshape(3) + self.pos_target = self.pos2 + ( + self.vec2 * (self.md_target - self.md2) + ) + else: + self.dist_curve, self.func_dogleg = get_curve_hold_data( + self.radius_critical, self.dogleg + ) + self.md2 = None + self.pos_target = get_pos( + self.pos1, + self.vec1, + self.vec_target, + self.dist_curve, + self.func_dogleg + ).reshape(3) + + def _hold(self): + self.pos_target = ( + self.pos1 + self.vec1 * (self.md_target - self.md1) + ) + + def _get_angles_target(self): + self.inc_target, self.azi_target = get_angles(self.vec_target, nev=True).reshape(2) + + def _get_md_target(self): + self.md_target = ( + self.dist_curve + + self.tangent_length + + self.dist_curve2 + + self.md1 + ) + + def _target_pos_and_vec_defined(self, pos3, vec_old=[0,0,0]): + self.distances1 = get_distances(self.pos1, self.vec1, pos3) + self.pos3 = pos3 + + radius_temp1 = get_radius_critical( + self.radius_design, + self.distances1, + self.min_error + ) + if radius_temp1 < self.radius_critical: + self.radius_critical = radius_temp1 + + ( + self.tangent_length, + self.dogleg + ) = min_dist_to_target( + min(self.radius_design, self.radius_critical), + self.distances1 + ) + + self.dogleg = check_dogleg(self.dogleg) + + self.dist_curve, self.func_dogleg = get_curve_hold_data( + min(self.radius_critical, self.radius_design), + self.dogleg + ) + self.vec3 = get_vec_target( + self.pos1, + self.vec1, + self.pos3, + self.tangent_length, + self.dist_curve, + self.func_dogleg + ) + + tangent_temp1 = self._get_tangent_temp(self.tangent_length) + + self.pos2 = ( + self.pos3 - ( + tangent_temp1 * self.vec3 + ) + ) + + self.distances2 = get_distances( + self.pos_target, + self.vec_target * -1, + self.pos2 + ) + + radius_temp2 = get_radius_critical( + self.radius_design2, + self.distances2, + self.min_error + ) + if radius_temp2 < self.radius_critical2: + self.radius_critical2 = radius_temp2 + + ( + self.tangent_length2, + self.dogleg2 + ) = min_dist_to_target( + min(self.radius_design2, self.radius_critical2), + self.distances2 + ) + + self.dogleg2 = check_dogleg(self.dogleg2) + + self.dist_curve2, self.func_dogleg2 = get_curve_hold_data( + min(self.radius_critical2, self.radius_design2), + self.dogleg2 + ) + self.vec2 = get_vec_target( + self.pos_target, + self.vec_target * -1, + self.pos2, + self.tangent_length2, + self.dist_curve2, + self.func_dogleg2 + ) + + tangent_temp2 = self._get_tangent_temp(self.tangent_length2) + + self.pos3 = ( + self.pos2 - ( + tangent_temp2 * self.vec2 + ) + ) + + self.vec23.append((self.pos3 - self.pos2) / np.linalg.norm(self.pos3 - self.pos2)) + + self.error = np.allclose( + self.vec23[-1], + vec_old, + equal_nan=True, + rtol=self.min_error * 10, + atol=self.min_error * 0.1 + ) + + self.errors.append(self.error) + self.pos3_list.append(self.pos3) + self.pos2_list.append(self.pos2) + self.md_target = self.dist_curve + tangent_temp2 + self.dist_curve2 + + # a bit of recursive magic - proceed with caution + if ( + not self.error + or + ( + ( + self.radius_critical2 < self.radius_design2 + or self.radius_critical < self.radius_design + ) + and + abs(self.radius_critical - self.radius_critical2) > self.delta_radius + ) + ): + # A solution will typically be found within a few iterations, + # however it will likely be unbalanced in terms of dls between the + # two curve sections. The code below will re-initiate iterations + # to balance the curvatures until the delta_radius parameter is met. + if self.error: + self.radius_critical = self.md_target / ( + abs(self.dogleg) + abs(self.dogleg2) + ) + self.radius_critical2 = self.radius_critical + self.iterations += 1 + # prevent a loop ad infinitum + assert self.iterations < self.max_iterations, "Out of iteration ammo" + self._target_pos_and_vec_defined(self.pos3, self.vec23[-1]) + else: + # get pos1 to pos2 curve data + self.dist_curve, self.func_dogleg = get_curve_hold_data( + min(self.radius_critical, self.radius_design), + self.dogleg + ) + + self.vec3 = get_vec_target( + self.pos1, + self.vec1, + self.pos3, + self.tangent_length, + self.dist_curve, + self.func_dogleg + ) + + self.vec2 = self.vec3 + + self.pos2 = get_pos( + self.pos1, + self.vec1, + self.vec3, + self.dist_curve, + self.func_dogleg + ).reshape(3) + + self.md2 = self.md1 + abs(self.dist_curve) + + self.dist_curve2, self.func_dogleg2 = get_curve_hold_data( + min(self.radius_critical2, self.radius_design2), + self.dogleg2 + ) + + self.pos3 = get_pos( + self.pos_target, + self.vec_target * -1, + self.vec3 * -1, + self.dist_curve2, + self.func_dogleg2 + ).reshape(3) + + tangent_length = np.linalg.norm( + self.pos3 - self.pos2 + ) + + self.md3 = self.md2 + tangent_length + + self.md_target = self.md3 + abs(self.dist_curve2) + + def interpolate(self, step=30): + return interpolate_well([self], step) + + def survey(self, radius=10, step=30): + interpolation = self.interpolate(step) + + survey = get_survey(interpolation, start_nev=self.pos1, radius=10) + + return survey + + def _get_tangent_temp(self, tangent_length): + if np.isnan(tangent_length): + tangent_temp = self.min_tangent + else: + tangent_temp = max(tangent_length, self.min_tangent) + + return tangent_temp + + +def check_dogleg(dogleg): + # the code assumes angles are positive and clockwise + if dogleg < 0: + dogleg_new = dogleg + 2 * np.pi + return dogleg_new + else: + return dogleg + +def mod_vec(vec, error=1e-5): + # if it's not working then twat it with a hammer + vec_mod = vec * np.array([1, 1, 1 - error]) + vec_mod /= np.linalg.norm(vec_mod) + inc_mod, azi_mod = get_angles(vec_mod, nev=True).T + + return vec_mod, inc_mod, azi_mod + +def get_pos(pos1, vec1, vec2, dist_curve, func_dogleg): + # this function is redundent, but I'd like to reinstate it one day + # TODO: this seems to be a bit dodgy if you send it an array for + # vec2 - investigate. + inc1, azi1 = get_angles(vec1, nev=True).T + inc2, azi2 = get_angles(vec2, nev=True).T + + pos2 = pos1 + ( + ( + dist_curve * func_dogleg / 2 + ) + * + np.array([ + np.sin(inc1) * np.cos(azi1) + np.sin(inc2) * np.cos(azi2), + np.sin(inc1) * np.sin(azi1) + np.sin(inc2) * np.sin(azi2), + np.cos(inc1) + np.cos(inc2) + ]).T + ) + + return pos2 + +def get_vec_target( + pos1, + vec1, + pos_target, + tangent_length, + dist_curve, + func_dogleg +): + vec_target = ( + ( + pos_target - pos1 - ( + dist_curve + * func_dogleg + ) / 2 * vec1 + ) + / + ( + ( + dist_curve * func_dogleg / 2 + ) + tangent_length + ) + ) + + vec_target /= np.linalg.norm(vec_target) + + return vec_target + +def get_curve_hold_data(radius, dogleg): + dist_curve = radius * dogleg + func_dogleg = shape_factor(dogleg) + + return ( + dist_curve, + func_dogleg + ) + +def shape_factor(dogleg): + """ + Function to determine the shape factor of a dogleg. + + Parameters + ---------- + dogleg: float + The dogleg angle in radians of a curve section. + """ + return np.tan(dogleg / 2) / (dogleg / 2) + +def get_distances(pos1, vec1, pos_target): + if np.allclose(pos1, pos_target): + return (0, 0, 0) + + else: + dist_to_target = ( + np.linalg.norm((pos_target - pos1)) + ) + + dist_perp_to_target = ( + np.dot((pos_target - pos1), vec1) + ) + + dist_norm_to_target = ( + ( + dist_to_target ** 2 + - dist_perp_to_target ** 2 + ) ** 0.5 + ) + + return ( + dist_to_target, + dist_perp_to_target, + dist_norm_to_target + ) + +def min_dist_to_target(radius, distances): + """ + Calculates the control points for a curve and hold section from the + start position and vector to the target position. + """ + ( + dist_to_target, + dist_perp_to_target, + dist_norm_to_target + ) = distances + + tangent_length = ( + dist_to_target ** 2 + - 2 * radius * dist_norm_to_target + ) ** 0.5 + + # determine the dogleg angle of the curve section + dogleg = 2 * np.arctan2( + (dist_perp_to_target - tangent_length) + , + ( + 2 * radius - dist_norm_to_target + ) + ) + + return tangent_length, dogleg + +def min_curve_to_target(distances): + """ + Calculates the control points for a curve section from the start + position and vector to the target position which is not achievable with + the provided dls_design. + """ + ( + dist_to_target, + dist_perp_to_target, + dist_norm_to_target + ) = distances + + radius_critical = ( + dist_to_target ** 2 / ( + 2 * dist_norm_to_target + ) + ) + + dogleg = ( + 2 * np.arctan2( + dist_norm_to_target, + dist_perp_to_target + ) + ) + + tangent_length = 0 + + return ( + tangent_length, + radius_critical, + dogleg + ) + +def get_radius_critical(radius, distances, min_error): + ( + dist_to_target, + dist_perp_to_target, + dist_norm_to_target + ) = distances + + radius_critical = ( + dist_to_target ** 2 / ( + 2 * dist_norm_to_target + ) + ) * (1 - min_error) + + return radius_critical + +def angle(vec1, vec2, acute=True): + angle = np.arccos( + np.dot(vec1, vec2) + / + ( + np.linalg.norm(vec1) * np.linalg.norm(vec2) + ) + ) + + if acute: + return angle + + else: + return 2 * np.pi - angle + +def get_dogleg(inc1, azi1, inc2, azi2): + dogleg = ( + 2 * np.arcsin( + ( + np.sin((inc2 - inc1) / 2) ** 2 + + np.sin(inc1) * np.sin(inc2) + * np.sin((azi2 - azi1) / 2) ** 2 + ) ** 0.5 + ) + ) + + return dogleg + +def interpolate_well(sections, step=30): + """ + Constructs a well survey from a list of sections of control points. + + Parameters + ---------- + sections: list of welleng.connector.Connector objects + step: float (default: 30) + The desired delta measured depth between survey points, in + addition to the control points. + + Results + ------- + survey: `welleng.survey.Survey` object + """ + method = { + 'hold': get_interpolate_hold, + 'min_dist_to_target': get_interpolate_min_dist_to_target, + 'min_curve_to_target': get_interpolate_min_curve_to_target, + 'curve_hold_curve': get_interpololate_curve_hold_curve, + 'min_curve': get_min_curve + } + + data = [] + for s in sections: + data.extend(method[s.method](s, step)) + + return data + +def get_survey(section_data, start_nev=[0,0,0], radius=10): + """ + Constructs a well survey from a list of sections of control points. + + Parameters + ---------- + section_data: list of dicts with section data + start_nev: (3) array of floats (default: [0,0,0]) + The starting position in NEV coordinates. + radius: float (default: 10) + The radius is passed to the `welleng.survey.Survey` object + and represents the radius of the wellbore. It is also used + when visualizing the results, so can be used to make the + wellbore *thicker* in the plot. + + Results + ------- + survey: `welleng.survey.Survey` object + """ + + # generate lists for survey + md, inc, azi = np.vstack([np.array(list(zip( + s['md'].tolist(), + s['inc'].tolist(), + s['azi'].tolist() + ))) + for s in section_data + ]).T + + survey = Survey( + md=md, + inc=inc, + azi=azi, + start_nev=start_nev, + deg=False, + radius=radius, + ) + + return survey + +def interpolate_curve(md1, pos1, vec1, vec2, dist_curve, dogleg, func_dogleg, step, endpoint=False): + start_md = step - (md1 % step) + end_md = abs(dist_curve) + md = np.arange(start_md, end_md, step) + md = np.concatenate(([0.], md)) + if endpoint: + md = np.concatenate((md, [end_md])) + dogleg_interp = (dogleg / dist_curve * md).reshape(-1,1) + + vec = ( + ( + np.sin(dogleg - dogleg_interp) / np.sin(dogleg) * vec1 + ) + + + ( + np.sin(dogleg_interp) / np.sin(dogleg) * vec2 + ) + ) + vec = vec / np.linalg.norm(vec, axis=1).reshape(-1,1) + inc, azi = get_angles(vec, nev=True).T + + data = dict( + md = md + md1, + vec = vec, + inc = inc, + azi = azi, + dogleg = np.concatenate(( + np.array([0.]), np.diff(dogleg_interp.reshape(-1)) + )), + ) + + return data + +def interpolate_hold(md1, pos1, vec1, md2, step, endpoint=False): + start_md = step - (md1 % step) + end_md = md2 - md1 + md = np.arange(start_md, end_md, step) + md = np.concatenate(([0.], md)) + if endpoint: + md = np.concatenate((md, [end_md])) + vec = np.full((len(md),3), vec1) + inc, azi = get_angles(vec, nev=True).T + dogleg = np.full_like(md, 0.) + + data = dict( + md = md + md1, + vec = vec, + inc = inc, + azi = azi, + dogleg = dogleg, + ) + + return data + +def get_min_curve(section, step=30, data=None): + if section.md2 is None: + result = ( + get_interpolate_min_curve_to_target( + section, step, data + ) + ) + else: + result = ( + get_interpolate_min_dist_to_target( + section, step, data + ) + ) + return result + +def get_interpolate_hold(section, step=30, data=None): + if data is None: + data = [] + + data.append(interpolate_hold( + md1=section.md1, + pos1=section.pos1, + vec1=section.vec1, + md2=section.md_target, + step=step, + endpoint=True + )) + + return data + +def get_interpolate_min_curve_to_target(section, step=30, data=None): + if data is None: + data = [] + + data.append(interpolate_curve( + md1=section.md1, + pos1=section.pos1, + vec1=section.vec1, + vec2=section.vec_target, + dist_curve=section.dist_curve, + dogleg=section.dogleg, + func_dogleg=section.func_dogleg, + step=step, + endpoint=True + )) + + return data + +def get_interpolate_min_dist_to_target(section, step=30, data=None): + if data is None: + data = [] + + # the first curve section + data.append(interpolate_curve( + md1=section.md1, + pos1=section.pos1, + vec1=section.vec1, + vec2=section.vec2, + dist_curve=section.dist_curve, + dogleg=section.dogleg, + func_dogleg=section.func_dogleg, + step=step + )) + + # the hold section + data.append(interpolate_hold( + md1=section.md2, + pos1=section.pos2, + vec1=section.vec2, + md2=section.md_target, + step=step, + endpoint=True + )) + + return data + +def get_interpololate_curve_hold_curve(section, step=30, data=None): + if data is None: + data = [] + + # the first curve section + data.append(interpolate_curve( + md1=section.md1, + pos1=section.pos1, + vec1=section.vec1, + vec2=section.vec2, + dist_curve=section.dist_curve, + dogleg=section.dogleg, + func_dogleg=section.func_dogleg, + step=step + )) + + # the hold section + data.append(interpolate_hold( + md1=section.md2, + pos1=section.pos2, + vec1=section.vec2, + md2=section.md3, + step=step + )) + + # the second curve section + data.append(interpolate_curve( + md1=section.md3, + pos1=section.pos3, + vec1=section.vec3, + vec2=section.vec_target, + dist_curve=section.dist_curve2, + dogleg=section.dogleg2, + func_dogleg=section.func_dogleg2, + step=step, + endpoint=True + )) + + return data + +def convert_target_input_to_booleans(*inputs): + input = [ + "0" if i is None else "1" for i in inputs + ] + + return ''.join(input) \ No newline at end of file diff --git a/welleng/exchange/__init__.py b/welleng/exchange/__init__.py new file mode 100644 index 0000000..99585fc --- /dev/null +++ b/welleng/exchange/__init__.py @@ -0,0 +1,6 @@ + +""" +welleng/exchange +---------------- +Contains the importers and exporters for various survey formats. +""" \ No newline at end of file diff --git a/welleng/exchange/__pycache__/__init__.cpython-37.pyc b/welleng/exchange/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f87ba3a6fbb0b88621c5d893b711327b1ef9d77 GIT binary patch literal 293 zcmZ8byG{c!5cH*h$Woeo!L})ge*htrfIt*9DI!r|Ip-y3bmu+!5ia}@U&246Ozy3r)#m6;I95Y$*By6=3v(dYiZ`iizw+e^4F@B5k z;veLad@{~QNDYGPdI||c1mi8>P}Ba1Z?IOp0^)II9llT3f2M{t#ir z6^``ttQzW2y^=SH!)pq4PD!m-w{R1v-_W2QP97{z=ABFTd8110vP*rXwev1%J@5C2 Q@nuIgw|I5Nk>5Px7j#uu#{d8T literal 0 HcmV?d00001 diff --git a/welleng/exchange/__pycache__/wbp.cpython-37.pyc b/welleng/exchange/__pycache__/wbp.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..683ae25111c0252aa676520eca426062b0ebe59d GIT binary patch literal 3889 zcma)9&2QVt73YxRhbT(^$fvW}md&Qys0Cs-ML*V9WRp$2`KYTkYA=EcXbDvu&+u zO<}9GraEYoNv|8_K^ArLUYcYbQq4XS%srIsE=WWJYi*q`bCX-IS=-@$zzD^AieG4cQ z^>0zK95!4HmEhWKyB#i&>5x9f$S)&D!A$^HgWltx74c!89q%szO3XrWcUO5s*)SIkuOXZ z$J&8@u*6qZG#U}ReeKWsiuOjsIMNOq|1=5kc9dw)m+1-eb|!rFgZ-p7m2j9q1X#MQQJ#j1X+(qxd=OlZSTe4lvIt= z^f26yLf*Dv_5wTJUh1U%K^z?g(NP{H8L~`PvR)pw7l;PCVHWXVfV^mXWEo~z(a-G+ z#n(}gCh>8*hx2KnAfE&sn3HyP&ON!P#C50CsfO1k;Je`l>Z zx;Y3%l;pwpSd}Xk43l1-`Tb#*`@4}(zWS-~i{u~O;L#x8|DQHf2aZiI5}{KvRe>6} z@9a%&h|Lw*xSwT_pr|*5NQE@utF$ov*U_;wvRp{Bm+#e0I5qFtZGqISAnJGHUmhcFO6 zkrg6WLE0|wiTpUAsM_8a*<)|Di|+yXn#D(^BU@D3yYi=%bo$fd?qrQxG5wkoHg)H^DK*UFSORL4W=QH>gf-D4PmwzEC#A-Q%Xx zS)6cBSJ+{<)WH-qzOaZ8whIFGX$7Qdoa~XTP$o2)_!*hddKYAsxSxPLLCF?DG(_n# zM|aA4w0siXETcL!C4E$1Mp5ByAy{&Kg0ONOKxrrl)d@l;6%GJ@qj5uN=qkXqS!yb$ zG}e1H_Iove{PRZ3s|PN;sL_S?t^Pini3gi{3HRrD)BvzoxP8jU1=ZqE%inOF5TvW9 zt132JiSWiPV{^$$wc~0$w|!Z)*Gl`E8Wk&2UJzbuq;IW@Iu`jI^TiTswZnK;I6Ug~ z`eB@jpQCk3m-$HFT%VHdlv1I2LEJ(G;1~>|gbPApsrR|KPK*(bdeZ169qH~4dod40 zm`Adj4@II_@grD?8$>=Ldz(jH5k;AFfZj1(RN@-7TLt1sb|l%cWTU38k_^{b@!~m? zZ2+c_7uV5O=$;V*YZZ(AyJoS|xi&MJA0>LrP+F^8BTUI@-EmF7WJj0B?4kC@#!DQe ziB3-irafPLR$Q^Hg@Zty6}X8FaA=@&Af4HxOtS2Zo8qT1=%ZwGvNXfhT{gOMaugJp z67CQ<&V8igPFtjW{0o@bc|ffrTa;(u4e@cQnYGZV!82x_OBLuVZ>T@|ftre1I6(k{ z0~Zn5-+&i)h|sEwD@17L#Ro(_Byw^qd_a0^*k_nWkplx z%~a>j;-)&+R;jn=db`vY=YFPl-mm9Cp$Q(&v+=%9&Z`qw`BbAZOZ+)AEA$@5tj@+9 zBikV>OqnAO#Y@jyim$F>^Ge@6>Q`!jVnCnsG(aSZ4wA@Zg&&znX|&%-ABJ(E5o^?w zZe@j53W@}s89^_IQ?4qf)fG;_NF}2Ooa5l#p?>Zsj^|u;9OtSaZPS!yGVBkIi`609 X0vSnp-ni2Fv~ck*g^-b%!#w>z$&;z7 literal 0 HcmV?d00001 diff --git a/welleng/exchange/wbp.py b/welleng/exchange/wbp.py new file mode 100644 index 0000000..303b4a0 --- /dev/null +++ b/welleng/exchange/wbp.py @@ -0,0 +1,165 @@ +import numpy as np +from ..survey import get_sections + +class WellPlan: + def __init__( + self, + survey, + location_type, + plan_method, + plan_name, + parent_name, + depth_unit="meters", + surface_unit="meters", + dirty_flag=3, + sidetrack_id=0, + extension="0.00000", + **targets + ): + + LOCATIONS = [ + "unknown", + "surface", + "ow_sidetrack", + "wp_sidetrack", + "lookahead", + "ow_well", + "complex_extension", + "site", + "site_based_plan", + "compass_well" + ] + + METHODS = [ + "curve_only", + "curve_hold", + "opt_align" + ] + + UNITS = [ + "feet", + "meters" + ] + + assert location_type in LOCATIONS, "location_type not in LOCATIONS" + assert plan_method in METHODS, "plan_method not in METHODS" + assert len(plan_name) < 21, "plan_name too long (max length 20)" + assert len(parent_name) < 21, "parent_name too long (max length 20)" + assert surface_unit in UNITS, "surface_units must be feet or meters" + assert depth_unit in UNITS, "depth_units must be feet or meters" + + self.survey = survey + self.location = str(LOCATIONS.index(location_type)) + self.surface_unit = surface_unit + self.depth_unit = depth_unit + self.method = str(METHODS.index(plan_method)) + self.flag = str(dirty_flag) + self.st_id = str(sidetrack_id).rjust(8) + self.plan_name = str(plan_name).ljust(21) + self.parent_name = str(parent_name).ljust(20) + self.dls = f"{str(np.around(self.survey.dls[1]))[:5]}".rjust(5) + self.kickoff_dls = f"{survey.dls[1]:.2f}".rjust(7) + self.extension = str(extension).rjust(8) + self.targets = targets + + self._get_unit() + + self.doc = [] + + self._make_header() + # self._make_tie_on(target=0) + self._make_wellplan() + + def _get_unit(self): + if self.depth_unit == "meters": + if self.surface_unit == "meters": + self.unit = 2 + else: + self.unit = 3 + elif self.depth_unit == "feet": + if self.surface_unit == "feet": + self.unit = 1 + else: + self.unit = 4 + + def _add_plan( + self, + section + ): + self.doc.append( + f"P:" + f"{str(section.md)[:8].rjust(8)}" + f" {str(section.azi)[:7].rjust(7)}" + f" {str(section.inc)[:7].rjust(7)}" + f" {str(section.build_rate)[:7].rjust(7)}" + f" {str(section.turn_rate)[:7].rjust(7)}" + f" {str(np.around(section.dls, decimals=1))[:7].rjust(7)}" + f" {str(np.degrees(section.toolface))[:7].rjust(7)}" + f" {str(section.method).rjust(4)}" + f" {str(section.target).rjust(10)}" + ) + self.doc.append( + f"L:" + f"{str(section.x)[:13].rjust(13)}" + f"{str(section.y)[:13].rjust(13)}" + f"{str(section.z)[:11].rjust(11)}" + ) + + + def _make_wellplan( + self, + ): + sections = get_sections(self.survey) + + for s in sections: + self._add_plan(s) + + def _make_header(self): + self.doc.append( + f"DEPTH {self.unit}" + ) + + if self.targets: + self.doc.append( + "TARGETS:" + ) + # TODO: add function to retrieve and list target data and implement + # a Target class. + + self.doc.append( + "WELLPLANS:", + ) + self.doc.append( + ( + f"W:{self.location}{self.unit}{self.method}{self.flag} 0 0" + f"{self.st_id} {self.plan_name} {self.parent_name} {self.dls}" + f"{self.extension}{self.kickoff_dls}" + ) + ) + + # I think this is redundent as it's the same line as the first section + def _make_tie_on(self, target): + if self.location == "unknown": + self.doc.append( + f"P:0.000000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0" + ) + else: + self.doc.append(( + f"P:" + f"{str(self.survey.md[0])[:8].rjust(8)}" + f" {str(self.survey.azi_deg[0])[:7].rjust(7)}" + f" {str(self.survey.inc_deg[0])[:7].rjust(7)}" + f" {str(0.00000).rjust(7)}" # build rate + f" {str(0.00000).rjust(7)}" # turn rate + f" {str(0.00000).rjust(7)}" # DLS + f" {str(np.degrees(self.survey.toolface[0]))[:7].rjust(7)}" + f" {self.method.rjust(4)}" + f" {str(target).rjust(10)}" + )) + + self.doc.append( + f"L:" + f"{str(self.survey.x)[:13].rjust(13)}" + f"{str(self.survey.y)[:13].rjust(13)}" + f"{str(self.survey.z)[:11].rjust(11)}" + ) diff --git a/welleng/mesh.py b/welleng/mesh.py index f7fc9df..a5d83b0 100644 --- a/welleng/mesh.py +++ b/welleng/mesh.py @@ -14,7 +14,7 @@ def __init__( sigma=3.0, sigma_pa=0.5, Sm=0, - method="mesh_ellipse", + method="ellipse", ): """ Create a WellMesh object from a welleng Survey object and a @@ -50,11 +50,12 @@ def __init__( self.Sm = Sm self.sigma_pa = sigma_pa - assert method in ["mesh_ellipse", "mesh_pedal_curve"], \ + assert method in ["ellipse", "pedal_curve", "circle"], \ "Invalid method (ellipse or pedal_curve)" self.method = method - self.sigmaH, self.sigmaL, self.sigmaA = get_sigmas(self.s.cov_hla) + if self.method != 'circle': + self.sigmaH, self.sigmaL, self.sigmaA = get_sigmas(self.s.cov_hla) self.nevs = np.array([self.s.n, self.s.e, self.s.tvd]).T self._get_vertices() self._align_verts() @@ -135,22 +136,26 @@ def _get_vertices( Determine the positions of the vertices on the desired uncertainty circumference. ''' + if self.method == "circle": + h = self.s.radius.reshape(-1,1) + l = h - h = ( - np.array(self.sigmaH) * self.sigma - + self.radius + self.Sm - + self.sigma_pa / 2 - ).reshape(-1,1) - - l = ( - np.array(self.sigmaL) * self.sigma - + self.radius - + self.Sm - + self.sigma_pa / 2 - ).reshape(-1,1) - # a = self.s.sigmaA * self.c.k + self.s.radius + self.c.Sm - - if self.method == "mesh_ellipse": + else: + h = ( + np.array(self.sigmaH) * self.sigma + + self.radius + self.Sm + + self.sigma_pa / 2 + ).reshape(-1,1) + + l = ( + np.array(self.sigmaL) * self.sigma + + self.radius + + self.Sm + + self.sigma_pa / 2 + ).reshape(-1,1) + # a = self.s.sigmaA * self.c.k + self.s.radius + self.c.Sm + + if self.method in ["ellipse", "circle"]: lam = np.linspace(0, 2 * pi, self.n_verts, endpoint=False) # theta = np.zeros_like(lam) diff --git a/welleng/survey.py b/welleng/survey.py index eb968aa..562c8a9 100644 --- a/welleng/survey.py +++ b/welleng/survey.py @@ -138,6 +138,7 @@ def __init__( self.vec = vec self._min_curve() + self._get_toolface_and_rates() # initialize errors # TODO: read this from a yaml file in errors @@ -172,6 +173,8 @@ def _min_curve(self): self.rf = mc.rf self.delta_md = mc.delta_md self.dls = mc.dls + self.pos = mc.poss + if self.x is None: # self.x, self.y, self.z = (mc.poss + self.start_xyz).T self.x, self.y, self.z = (mc.poss).T @@ -242,6 +245,71 @@ def _get_errors(self): self.cov_nev += self.start_cov_nev self.cov_hla = NEV_to_HLA(self.survey_rad, self.cov_nev.T).T + def _curvature_to_rate(self, curvature): + with np.errstate(divide='ignore', invalid='ignore'): + radius = 1 / curvature + circumference = 2 * np.pi * radius + if self.unit == 'meters': + x = 30 + else: + x = 100 + rate = np.absolute(np.degrees(2 * np.pi / circumference) * x) + + return rate + + def _get_toolface_and_rates(self): + """ + Reference SPE-84246. + theta is inc, phi is azi + """ + # split the survey + s = SplitSurvey(self) + + if self.unit == 'meters': + x = 30 + else: + x = 100 + + # this is lazy I know, but I'm using this mostly for flags + with np.errstate(divide='ignore', invalid='ignore'): + t1 = np.arctan( + np.sin(s.inc2) * np.sin(s.delta_azi) / + ( + np.sin(s.inc2) * np.cos(s.inc1) * np.cos(s.delta_azi) + - np.sin(s.inc1) * np.cos(s.inc2) + ) + ) + t1 = np.nan_to_num( + np.where(t1 < 0, t1 + 2 * np.pi, t1), + nan=np.nan + ) + t2 = np.arctan( + np.sin(s.inc1) * np.sin(s.delta_azi) / + ( + np.sin(s.inc2) * np.cos(s.inc1) + - np.sin(s.inc1) * np.cos(s.inc2) * np.cos(s.delta_azi) + ) + ) + t2 = np.nan_to_num( + np.where(t2 < 0, t2 + 2 * np.pi, t2), + nan=np.nan + ) + self.curve_radius = (360 / self.dls * x) / (2 * np.pi) + + curvature_dls = 1 / self.curve_radius + + self.toolface = np.concatenate((t1, np.array([t2[-1]]))) + + curvature_turn = curvature_dls * (np.sin(self.toolface) / np.sin(self.inc_rad)) + self.turn_rate = self._curvature_to_rate(curvature_turn) + + curvature_build = curvature_dls * np.cos(self.toolface) + self.build_rate = self._curvature_to_rate(curvature_build) + + # calculate plan normals + n12 = np.cross(s.vec1, s.vec2) + with np.errstate(divide='ignore', invalid='ignore'): + self.normals = n12 / np.linalg.norm(n12, axis=1).reshape(-1,1) def interpolate_survey(survey, x=0, index=0): """ @@ -398,4 +466,158 @@ def make_long_cov(arr): [ac, bc, cc] ]).T - return cov \ No newline at end of file + return cov + +class SplitSurvey: + def __init__( + self, + survey, + ): + self.md1, self.inc1, self.azi1 = survey.survey_rad[:-1].T + self.md2, self.inc2, self.azi2 = survey.survey_rad[1:].T + self.delta_azi = self.azi2 - self.azi1 + self.delta_inc = self.inc2 - self.inc1 + + self.vec1 = survey.vec[:-1] + self.vec2 = survey.vec[1:] + self.dogleg = survey.dogleg[1:] + +def get_circle_radius(survey, **targets): + # TODO: add target data to sections + ss = SplitSurvey(survey) + + x1, y1, z1 = np.cross(ss.vec1, survey.normals).T + x2, y2, z2 = np.cross(ss.vec2, survey.normals).T + + b1 = np.array([y1, x1, z1]).T + b2 = np.array([y2, x2, z2]).T + nev = np.array([survey.n, survey.e, survey.tvd]).T + + cc1 = ( + nev[:-1] - b1 + / np.linalg.norm(b1, axis=1).reshape(-1,1) + * survey.curve_radius[:-1].reshape(-1,1) + ) + cc2 = ( + nev[1:] - b2 + / np.linalg.norm(b2, axis=1).reshape(-1,1) + * survey.curve_radius[1:].reshape(-1,1) + ) + + starts = np.vstack((cc1, cc2)) + ends = np.vstack((nev[:-1], nev[1:])) + + n = 1 + + + return (starts, ends) + +def get_sections(survey, **targets): + # TODO: add target data to sections + ss = SplitSurvey(survey) + + continuous = np.all( + np.isclose( + survey.normals[:-1], + survey.normals[1:], + rtol=1e-01, atol=1e-02, + equal_nan=True + ), axis=-1 + ) + + ends = np.concatenate( + (np.where(continuous == False)[0] + 1, np.array([len(continuous)-1])) + ) + starts = np.concatenate(([0], ends[:-1])) + + actions = [ + "hold" if a else "curve" + for a in np.isnan(survey.toolface[starts]) + ] + + sections = [] + for s, e, a in zip(starts, ends, actions): + md = survey.md[s] + inc = survey.inc_deg[s] + azi = survey.azi_deg[s] + x = survey.e[s] + y = survey.n[s] + z = -survey.tvd[s] + + target = "" + if survey.unit == 'meters': + denominator = 30 + else: + denominator = 100 + + if a == "hold": + dls = 0.0 + toolface = 0.0 + build_rate = 0.0 + turn_rate = 0.0 + method = "" + else: + dls = survey.dls[e] + toolface = survey.toolface[s] + delta_md = survey.md[e] - md + + # TODO: should sum this line by line to avoid issues with long sections + build_rate = abs( + (survey.inc_deg[e] - survey.inc_deg[s]) + / delta_md * denominator + ) + + # TODO: should sum this line by line to avoid issues with long sections + # need to be careful with azimuth straddling north + delta_azi_1 = abs(survey.azi_deg[e] - survey.azi_deg[s]) + delta_azi_2 = abs(360 - delta_azi_1) + delta_azi = min(delta_azi_1, delta_azi_2) + turn_rate = delta_azi / delta_md * denominator + + section = WellSection( + md, + inc, + azi, + build_rate, + turn_rate, + dls, + toolface, + method=0, + target="", + x=x, + y=y, + z=z + ) + + sections.append(section) + + return sections + +class WellSection: + def __init__( + self, + md, + inc, + azi, + build_rate, + turn_rate, + dls, + toolface, + x, + y, + z, + method=0, + target=None + ): + self.md = md + self.inc = inc + self.azi = azi + self.build_rate = build_rate + self.turn_rate = turn_rate + self.dls = dls + self.toolface = toolface + self.method = method + self.target = target + self.x = np.around(x, decimals=2) + self.y = np.around(y, decimals=2) + self.z = np.around(z, decimals=2) \ No newline at end of file diff --git a/welleng/target.py b/welleng/target.py new file mode 100644 index 0000000..bf49df8 --- /dev/null +++ b/welleng/target.py @@ -0,0 +1,70 @@ +import numpy as np +from vedo import Circle + +class Target: + def __init__( + self, + name, + n, + e, + tvd, + shape, + locked=0, + orientation=0, + dip=0, + color='green', + alpha=0.5, + **geometry + ): + """ + Parameters + ---------- + geometry: + """ + + SHAPES = [ + 'circle', + 'ellipse', + 'rectangle', + 'polygon', + ] + + GEOMETRY = dict( + rectangle = ['pos1', 'pos2'], + circle = ['radius'], + ellipse = {'radius_1': 0, 'radius_2': 0, 'res': 120}, + ) + + assert shape in SHAPES, "shape not in SHAPES" + assert set(geometry.keys()) == set(GEOMETRY[shape]), 'wrong geometry' + + self.name = name + self.n = n + self.e = e + self.tvd = tvd + self.shape = shape + self.locked = locked + self.orientation = orientation + self.dip = dip + self.color = color + self.alpha = alpha + self.geometry = geometry + + def plot_data(self): + pos = [self.n, self.e, self.tvd] + if self.shape == "circle": + g = Circle( + pos=pos, + r=self.geometry['radius'], + c=self.color, + alpha=self.alpha, + # res=self.geometry['res'] + ) + g.flag = self.name + g.pos=[self.n, self.e, 0] + g.rotate(self.dip, point=pos, axis=(0,1,0)) + g.rotate(self.orientation, point=pos, axis=(1,0,0)) + + return g + + diff --git a/welleng/utils.py b/welleng/utils.py index 461ee76..f5c9af8 100644 --- a/welleng/utils.py +++ b/welleng/utils.py @@ -121,7 +121,7 @@ def __init__( ) + self.start_xyz ) -def get_vec(inc, azi, r=1, deg=True): +def get_vec(inc, azi, nev=False, r=1, deg=True): """ Convert inc and azi into a vector. @@ -144,8 +144,13 @@ def get_vec(inc, azi, r=1, deg=True): y = r * np.sin(inc_rad) * np.cos(azi_rad) x = r * np.sin(inc_rad) * np.sin(azi_rad) z = r * np.cos(inc_rad) - - return np.array([x,y,z]).T + + if nev: + vec = np.array([y, x, z]).T + else: + vec = np.array([x,y,z]).T + + return vec / np.linalg.norm(vec) def get_nev(poss, start_xyz=[0,0,0], start_nev=[0,0,0]): """ @@ -168,22 +173,29 @@ def get_nev(poss, start_xyz=[0,0,0], start_nev=[0,0,0]): return (np.array([n, e, v]).T + np.array([start_nev])) -def get_angles(vec): +def get_angles(vec, nev=False): ''' Determines the inclination and azimuth from a vector. Params: vec: (n,3) array of floats + nev: boolean (default: False) + Indicates if the vector is in (x,y,z) or (n,e,v) coordinates. Returns: - [inc, azi]: (2,n) array of floats + [inc, azi]: (n,2) array of floats A numpy array of incs and azis in radians ''' # make sure it's a unit vector - vec = vec / np.linalg.norm(vec, axis=-1) + vec = vec / np.linalg.norm(vec, axis=-1).reshape(-1,1) vec = vec.reshape(-1,3) + # if it's nev then need to do the shuffle + if nev: + y, x, z = vec.T + vec = np.array([x, y, z]).T + xy = vec[:,0] ** 2 + vec[:,1] ** 2 inc = np.arctan2(np.sqrt(xy), vec[:,2]) # for elevation angle defined from Z-axis down azi = (np.arctan2(vec[:,0], vec[:,1]) + (2 * np.pi)) % (2 * np.pi) diff --git a/welleng/visual.py b/welleng/visual.py index beefaaa..05fa5dc 100644 --- a/welleng/visual.py +++ b/welleng/visual.py @@ -1,5 +1,5 @@ import trimesh -from vedo import show, Box, Axes, trimesh2vedo, Lines, colorMap +from vedo import show, Box, Axes, trimesh2vedo, Lines, colorMap, Arrows, Text import numpy as np from .version import __version__ as VERSION @@ -23,7 +23,15 @@ def __init__( height ).wireframe() -def plot(data, names=None, colors=None, lines=None): +def plot( + data, + names=None, + colors=None, + lines=None, + targets=None, + arrows=None, + text=None +): """ A vedo wrapper for quicly visualizing well trajectories for QAQC purposes. @@ -54,6 +62,7 @@ def plot(data, names=None, colors=None, lines=None): "Colors must be length of meshes list, 1 else None" meshes_vedo = [] + # TODO: if inly a single mesh is provided then drop the need for a list for i, mesh in enumerate(meshes): if i == 0: vertices = np.array(mesh.vertices) @@ -89,6 +98,8 @@ def plot(data, names=None, colors=None, lines=None): meshes_vedo, w.world, lines, + targets, + arrows, axes, bg='lightgrey', bg2='lavender', From 1d61491e523b46fba8c94d3aa35d4e3e9bbc9394 Mon Sep 17 00:00:00 2001 From: jonnymaserati Date: Mon, 14 Dec 2020 17:43:27 +0100 Subject: [PATCH 2/3] Fixed connector, bumped version and updated requirements.txt --- README.md | 1 + examples/connect_two_random_points.py | 3 +- examples/simple_example.py | 40 ++++++---- requirements.txt | 25 ++++-- welleng/__pycache__/__init__.cpython-38.pyc | Bin 336 -> 513 bytes welleng/__pycache__/clearance.cpython-38.pyc | Bin 12683 -> 13941 bytes welleng/__pycache__/connector.cpython-37.pyc | Bin 21279 -> 21582 bytes welleng/__pycache__/connector.cpython-38.pyc | Bin 0 -> 21919 bytes welleng/__pycache__/error.cpython-38.pyc | Bin 8913 -> 8913 bytes welleng/__pycache__/mesh.cpython-38.pyc | Bin 6901 -> 10831 bytes welleng/__pycache__/survey.cpython-37.pyc | Bin 16139 -> 16139 bytes welleng/__pycache__/survey.cpython-38.pyc | Bin 7030 -> 16275 bytes welleng/__pycache__/target.cpython-38.pyc | Bin 0 -> 1570 bytes welleng/__pycache__/utils.cpython-38.pyc | Bin 7347 -> 7613 bytes welleng/__pycache__/version.cpython-37.pyc | Bin 192 -> 192 bytes welleng/__pycache__/version.cpython-38.pyc | Bin 0 -> 196 bytes welleng/__pycache__/visual.cpython-38.pyc | Bin 0 -> 4706 bytes welleng/connector.py | 74 +++++++++++------- .../__pycache__/__init__.cpython-38.pyc | Bin 217 -> 217 bytes .../__pycache__/iscwsa_mwd.cpython-38.pyc | Bin 12868 -> 13140 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 297 bytes .../exchange/__pycache__/wbp.cpython-38.pyc | Bin 0 -> 3943 bytes welleng/survey.py | 12 +-- welleng/version.py | 2 +- 24 files changed, 98 insertions(+), 59 deletions(-) create mode 100644 welleng/__pycache__/connector.cpython-38.pyc create mode 100644 welleng/__pycache__/target.cpython-38.pyc create mode 100644 welleng/__pycache__/version.cpython-38.pyc create mode 100644 welleng/__pycache__/visual.cpython-38.pyc create mode 100644 welleng/exchange/__pycache__/__init__.cpython-38.pyc create mode 100644 welleng/exchange/__pycache__/wbp.cpython-38.pyc diff --git a/README.md b/README.md index e524c3f..49ddff7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - standard [ISCWSA] method - new mesh based method using the [Flexible Collision Library] ## New Features! + - **Well Path Creation:** the addition of the `connector` module enables drilling well paths to be created simply by providing start and end locations (with some vector data like inclination and azimuth). No need to indicate *how* to connect the points, the module will figure that out itself. - **Fast visualization of well trajectory meshes:** addition of the `visual` module for quick and simple viewing and QAQC of well meshes. - **Mesh Based Collision Detection:** the current method for determining the Separation Factor between wells is constrained by the frequency and location of survey stations or necessitates interpolation of survey stations in order to determine if Anti-Collision Rules have been violated. Meshing the well bore interpolates between survey stations and as such is a more reliable method for identifying potential well bore collisions, especially wth more sparse data sets. - More coming soon! diff --git a/examples/connect_two_random_points.py b/examples/connect_two_random_points.py index 76807b8..12be7b9 100644 --- a/examples/connect_two_random_points.py +++ b/examples/connect_two_random_points.py @@ -3,8 +3,9 @@ from vedo import Arrows, Lines import random -# Generate some random pairs of points +# Some code for testing the connector module. +# Generate some random pairs of points pos1 = [0,0,0] md1 = 0 diff --git a/examples/simple_example.py b/examples/simple_example.py index 0956a90..4748d38 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -4,29 +4,37 @@ # construct simple well paths print("Constructing wells...") -md = np.linspace(0,3000,100) # 30 meter intervals to 3000 mTD -inc = np.concatenate(( - np.zeros(30), # vertical section - np.linspace(0,90,60), # build section to 60 degrees - np.full(10,90) # hold section at 60 degrees -)) -azi1 = np.full(100,60) # constant azimuth at 60 degrees -azi2 = np.full(100,225) # constant azimuth at 225 degrees +connector_reference = we.connector.Connector( + pos1=[0,0,0], + inc1=0, + azi1=0, + pos2=[-100,0,2000.], + inc2=90, + azi2=60, +).survey(step=50) -# make a survey object and calculate the uncertainty covariances +connector_offset = we.connector.Connector( + pos1=[0,0,0], + inc1=0, + azi1=225, + pos2=[-280,-600,2000], + inc2=90, + azi2=270, +).survey(step=50) + +# make a survey objects and calculate the uncertainty covariances print("Making surveys...") survey_reference = we.survey.Survey( - md, - inc, - azi1, + md=connector_reference.md, + inc=connector_reference.inc_deg, + azi=connector_reference.azi_deg, error_model='ISCWSA_MWD' ) -# make another survey with offset surface location and along another azimuth survey_offset = we.survey.Survey( - md, - inc, - azi2, + md=connector_offset.md, + inc=connector_offset.inc_deg, + azi=connector_offset.azi_deg, start_nev=[100,200,0], error_model='ISCWSA_MWD' ) diff --git a/requirements.txt b/requirements.txt index 2206542..c5c7f33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,20 @@ -numpy -scipy -trimesh -vedo -transforms3d \ No newline at end of file +cycler==0.10.0 +Cython==0.29.21 +decorator==4.4.2 +et-xmlfile==1.0.1 +jdcal==1.4.1 +kiwisolver==1.3.1 +matplotlib==3.3.3 +networkx==2.5 +numpy==1.19.4 +openpyxl==3.0.5 +Pillow==8.0.1 +pyparsing==2.4.7 +python-dateutil==2.8.1 +python-fcl==0.0.12 +scipy==1.5.4 +six==1.15.0 +tabulate==0.8.7 +trimesh==3.8.18 +vedo==2020.4.2 +vtk==9.0.1 diff --git a/welleng/__pycache__/__init__.cpython-38.pyc b/welleng/__pycache__/__init__.cpython-38.pyc index 3632c4068a8b09243388132cad7d3f36f5ac6bf3..ec297e43bcbdbc5f3854f58eca2a1ba6e2489a91 100644 GIT binary patch delta 250 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 delta 4963 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 C=PQT+ delta 2061 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 delta 2657 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 C#Y^4* delta 283 zcmeCK>#pN<;^pOH00IxyOYxQ)d94%}RW^GlC@?C8Go)|@G6XS1Fr;wTFl6ydGt{z{ zFf9Hd~wgXJVYO+1mUA6XTr8I#x$z zIzhU+fJ78ma(-S(W?pH2X>rkXkm$6@yw)y^izWwIt1>Q}Tw#5bas6aF8#Bh0lS^%m zvn>QESv1+nHWJ2MXxk{f5-1u1bV)G_kio&rz*w|$vZ-Aw_Y9Eg^&n#9(^b diff --git a/welleng/__pycache__/survey.cpython-38.pyc b/welleng/__pycache__/survey.cpython-38.pyc index 13b1c26c3203157a83b4099e52519d50c58ec8dd..1c218cf7e288f3a877c79c6063993dcaef8e1abd 100644 GIT binary patch literal 16275 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 delta 2883 zcmaJ@O>7%Q6yBL#uh;9f<0N&Qgd`253EhS!ZTN>$XbUX}hy;`hnM!8s*|@ehc9`7+ z8m(nP3qq(Bsz%}fLMjqMsa(-%j3sy{HB+$x4y=j@kn_fOTcR~-c>rUAr+6j#rEOkL+ zsq?g)V?)ej=?kQsXBNv`(8@!sz_QE%N`d8A9w@_%Jfpe8-{W1Ym%rH}8{!msm<)?| z$wX;H36XLd4l01KrG!Vt*W@7iLFDL1Cm5-bPBG3i+RX6laXv0K&G33O!SwUwRRU|R zG8qp6-W2Bmr(y?iI@Yetysr^Z9_}{0cgeN1{vHvZkM1SA#c!jZi7$3NTNnk6q+V}T zrS5Il`HXmF->7(~m_H#+?y>r2$oGK+cM)zy*o!cPP(av)kVnV?lpUY*Hm|I=nID8# zHGV+6Q>>YH085(_>9IMYi~VEQyWbNaY&*x!`qk!aa*u2CVIa!P+GY?`f_l^EN;lIB zqjMX+OsxfNFXGD}!$(js6`u8gt_OaLF95-%{5E7Ni32!Oa1du>0HFGXB&)Of_Wv?8tc=5PM9v< znsV>dAVDv}I>T_^#Rw9y0igt;Wu2!D3wCB&`*5xJpC*#x=c(OU-1$qw5v6Gt|GR6f zatv6PN;BN#TmCtjh4-3Q^(#TU>gxO`{(!Z~mJ3lrPP!R! zbGXCJXMR>8{J4!6aV z^V8&I(VSoC5-^$(Q$|YKgw`bCp-Au5MXizgo6d|T z!hm*5Si2*BFA6D1tc3i2g!>Rwkx}IaOM^7lz38me!+MLSkd2o9|3vPp64TFL|^9nX04qNs^(Sl12w%tdyOo9eF_|DQij{Ng_$Y9X>~Q%MthoKHq2uUgTGjIR6mn zAqI|?p0Ma7oiv2AxM)5NgenyeERK&$n8u`od8wh3#}$?|@#5lqcgOlQbhjCs4RTuV zq&rpz${D)8ze7pbkrnH)5vSsGY{e*(8I%E<{U5aKzi8<$gG*yeVFs}V5eeHJW?XVQ z**FXSPpxwFJ;z3P97^hdoBvrflb)t}u|0S#0wc@pN5)Cbfl8_-QJK+Uw#J?A(khN7-WX* zg%UePDU|sBvx_7TCC0Dj5I-r#mp>y#@zwI;HRHf95>Jvdl2oMuy`rYtq|~9{4QgqvdriLrj6XH8x^fFSEnZw<d6If(U{obN1Mf5Nu2%W;C_P}H zoVq$_w`zF*9*BVuvsp`xgNal({=>T4hPU_Ul`)2!!v7rhN}2nH3Q{)Od);};WPqXu|vDQ53NJ+4YwKihcQ4^e+0eg Q9^0Y$u2!;-SmXBmU#{v?2mk;8 diff --git a/welleng/__pycache__/target.cpython-38.pyc b/welleng/__pycache__/target.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4505827b16d036a6c049cf878bd87030be78c694 GIT binary patch literal 1570 zcmZuxTW{nv6t*4bGMP)OEen)uKmuumXxWvJ5JIr4Qo7<&R?H$`UZQO5O*2iCV0(7k zDt)1S?LR;W9{DZ&13w}0fcgV?L5svW$#gVAIP&Ln^NDlL=X^aJh6Kij{w@C8BjgWM zR)+)19hmw}06_#T$dtm$3U=g79ZL3ya0I^~f}gV~7j#0#?iIw6{V}!PPOj2Irl_wD z7nD0N^(z3f%{XPEC%AAg=+qIO@Gl5t3m{u)y$6ZPq)`-tt(z$+%RfPc@%u+f9aeJ8@Cx8dip&>#o(O&Kn-j9 zKRdsWbCkfl3DP0c--3UL{)RRr<_!aM8wb#9IG_)CNE|dCV6X83!zKXiH$A{X69NvK zKHx}<;M3MlKMAA7xSy!xNE)g1()9LnEKL}k$qEA1$@|gLY`wHCwDo0C#8`(s?zeh(zjZ?Z9SEUe6HJ?UL8ZJ#?Cb8Z1Jt7blW;g>an8&h)}=}qOiWF z8~PxrWmHxs%FAeS@9zEG$?52cs>*D6y#4W-xjIzZdw(P!%d3%AhE%9F~%dywJaW~ z4LsyC2saU)MR*S3d4x@b7Z6@VcnRTUfT`Dg#5CC3{ql?52VZ^nD^ah4D-v@Zzj9Z$EvCyR>#|)E_~6>!nv25d-I3ePoNpH%$BQZX}ndR*dUJc zGBQPR^e>`uUM$7~4zt8DfcO|y2^Q3q;t_R*(?9~oTZ2%fF z!wICFvCKKc)e%5wRU3{}p1Fc;VgwL(<_qVNp8>UJLBoK?i@k7(9MHpt&49qOK|}F6 zUBVw=VHtwOzqSCmgZ+*$1318#TL;Kzsj@Wfs*VQ+V&QUy&`M)w-MY$4pht3?Bk|t2 zc6I*I|F)K45VmoptH=Y(9~DWaUPsYW70^g_%|{wQa{T*rUYfPt?jXYWUR_is7KusT z1yv(w2wkUP+ob^d!=HM70G1o{20L9}^kcbcf9$p=UI7jn6gGV9A_0*3acsjlKB~mL zKzR_y-_Mhx^QhM_{w)L~;tE8lVv{kW#-Cmg5W0B&3g2mc@DjJ29|74|SzG>Cib}P0 a!i_7*+T0P?Z16#MkPopP8gmD9!2SjEePq!9 literal 0 HcmV?d00001 diff --git a/welleng/__pycache__/utils.cpython-38.pyc b/welleng/__pycache__/utils.cpython-38.pyc index e90c3f1cef4aa1a376c72b38782389652698da06..1e2ca199ecd27e174db880289c226607161895e2 100644 GIT binary patch delta 1812 zcmZWqO>7%Q6yDig?`}4ZX*xeDi)b zy;r|IAG>p~g{2JDsd1MqD^$W?tv{~HbcmX;qS7%sOrs!0 z=m?G7m6kP{rK2J6b+L;KE56eE-6YqZdS=faEhlAmsrO0L zkSiFtKNWzoDo6<~z|3*i>?%lA z8Rv(O0b@Ta0Ze>|dxJD&U!sJ{_vAeVMx`qkr4Fo#nc9^>ChSdLYN$7rj@(gpJ1PV@ z;w!%Ft0M3!n$rl4d@56IRnALPPYpE6V@(zk51tylI;OGjqoV~JgezKKD|YJ7#4cT# z>Rv@^7-=aBhX8w!#-L_E8uB$CHlhmN1nS$c7ly^(;CiRBUD+vHpqOk`oXS?& zs{!{3ARn=^3hnhH?B*tW4^paf5e@g1ut62t_QdV?l|Y`F4oE#9-aZMG zh1cH=cN9HfUTD@`ui9SUnEjXkHuk(0UW8ZjIbO+Do)KrigBMstcn?A7(3>p-sD%63 z6LE3LXHgl(oj=T4MPVs?!o%R~6|cSW##?iL@ZYn4pMG+8WCu&f_K)#@az|**o_PZTodxKS`qnTh`a_yc{3 delta 1711 zcmZuxO>7%Q6yBMg{qbTuabi1(leU&3gcb@3K|<H%o zJKtRyT*`0ca*hVS&A+~=FI>2npCjA%UZ`voxyh}&q^5HXpXTtrNx984U}4c|Hafiu&Y zbpJOtEchHYh3H=!KmXWaVilwY!THG#3UD;a`UB9$ki~7X4H@Tz(@*HE)&t#r9cFr; zqRbg*A5m^>(ix4LlOv7wL^GxJuxbp35kt*}drVvbUa)5DM7wh0YPd+b=~He35woaG zL6B4`Ydsw#C}FHa4_inNdeT1pCAmDllavbc`qIT9`80PSsTCGfUPTU$B$ffz1fE-1 z2-tkCoz~cFw*s%-lq~3oU2z>O((*(j@`5GF!Vg3w>0V13kn_fl%cKq!mGr}dQSr~_ z-od(iL9@{I!>_?zm<7;sMUov{n)schv{FXMLosqK6?s(tw9l~Pq;u$tPFQcZc20VM zNi*CRUH=pZX+#41bo8yM7R(_PW!Z$o{Q%y=iNRMzreDW}$9kFpop&PDWyiXtFD**a zk|a7NQeS)ht<N-CO=M`EBY&6Z=XmCza{dn=0;2oUbFigP?S{Q`7+d zh7scDZW2>-P4Om(GUEsPyT0%uKRx+#piC3tCJ&~ov&w~XmNEcB^{7+7@y2UECy%E8 zc=qYANEu9L0@LL2%#iC+Wx0-Fg|aR-;8oMN+MakD87Q|3hV(PgJR|zHqA%XX z#duMO)5L$OT%1#z-$P4E9B3hN;Qy~Sn40~J9AN{fzC0|ibh_16SdF&)>K)Mun$`5X zSM56L5JPylWeh>ZtfF)S0IrSO@RG!nxE-@j65v9qUG=^D&zA{ z))YOy(J)cczPj7t`)z+2sjDl`ruhS> OQYqPGyW-3{h5rEbH)DnX diff --git a/welleng/__pycache__/version.cpython-37.pyc b/welleng/__pycache__/version.cpython-37.pyc index f9477cb0a86f53e1bcee3b5126aeeec25c5c7c8e..3cec3b0e934f8a18a914465d44a7ce2c556a166c 100644 GIT binary patch delta 24 ecmX@Wcz}`HiIg`kf=$WS<3)k=V-N=!FakLaKwQiLBvKfn7*ZI688n%ySPk?H_00V=8E#mBE?C}IMt0~5bO^fU5vQ}wg*^Abxk67!Pti;_!AO7z2nT@_r4@(Ysk yEA+jfath8+N%zbWkJ2Rl^3!4Yfe8pz;=nO>TZlX-=vg$Yq~_m;nIMZ8Z4+ literal 0 HcmV?d00001 diff --git a/welleng/__pycache__/visual.cpython-38.pyc b/welleng/__pycache__/visual.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d491faad01713b0e3be4bd071519b61e0a8223ce GIT binary patch literal 4706 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 Date: Mon, 14 Dec 2020 17:50:07 +0100 Subject: [PATCH 3/3] Updated README.md and updated examples --- README.md | 56 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 49ddff7..3432c3f 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ pip install -e . ``` Make sure you include that `.` in the final line (it's not a typo) as this ensures that any changes to your development version are immediately implemented on save. ## Quick Start -Here's an example using `welleng` to construct a couple of simple well trajectories with `numpy`, creating survey listings for the wells with well bore uncertainty data, using these surveys to create well bore meshes and finally printing the results and plotting the meshes with the closest lines and SF data. +Here's an example using `welleng` to construct a couple of simple well trajectories with the `connector` module, creating survey listings for the wells with well bore uncertainty data, using these surveys to create well bore meshes and finally printing the results and plotting the meshes with the closest lines and SF data. ``` import welleng as we @@ -46,29 +46,37 @@ from tabulate import tabulate # construct simple well paths print("Constructing wells...") -md = np.linspace(0,3000,100) # 30 meter intervals to 3000 mTD -inc = np.concatenate(( - np.zeros(30), # vertical section - np.linspace(0,90,60), # build section to 60 degrees - np.full(10,90) # hold section at 60 degrees -)) -azi1 = np.full(100,60) # constant azimuth at 60 degrees -azi2 = np.full(100,225) # constant azimuth at 225 degrees - -# make a survey object and calculate the uncertainty covariances +connector_reference = we.connector.Connector( + pos1=[0,0,0], + inc1=0, + azi1=0, + pos2=[-100,0,2000.], + inc2=90, + azi2=60, +).survey(step=50) + +connector_offset = we.connector.Connector( + pos1=[0,0,0], + inc1=0, + azi1=225, + pos2=[-280,-600,2000], + inc2=90, + azi2=270, +).survey(step=50) + +# make a survey objects and calculate the uncertainty covariances print("Making surveys...") survey_reference = we.survey.Survey( - md, - inc, - azi1, + md=connector_reference.md, + inc=connector_reference.inc_deg, + azi=connector_reference.azi_deg, error_model='ISCWSA_MWD' ) -# make another survey with offset surface location and along another azimuth survey_offset = we.survey.Survey( - md, - inc, - azi2, + md=connector_offset.md, + inc=connector_offset.inc_deg, + azi=connector_offset.azi_deg, start_nev=[100,200,0], error_model='ISCWSA_MWD' ) @@ -118,21 +126,21 @@ we.visual.plot( print("Done!") ``` -This results in a quick, interactive visualization of the well meshes that's great for QAQC. +This results in a quick, interactive visualization of the well meshes that's great for QAQC. What's interesting about these results is that the ISCWSA method does not explicitly detect a collision in this scenario wheras the mesh method does. -![image](https://user-images.githubusercontent.com/41046859/100718537-c3b12700-33bb-11eb-856e-cf1bd77d3cbf.png) +![image](https://user-images.githubusercontent.com/41046859/102106351-c0dd1a00-3e30-11eb-82f0-a0454dfce1c6.png) For more examples, check out the [examples]. ## Todos - - Add a Target class to see what you're aiming for! - - Export to Landmark's .wbp format so survey listings can be modified in COMPASS + - Add a Target class to see what you're aiming for - **in progress** + - Export to Landmark's .wbp format so survey listings can be modified in COMPASS - **in progress** - Documentation - Generate a scene of offset wells to enable fast screening of collision risks (e.g. hundreds of wells in seconds) - - Well trajectory planning - construct your own trajectories using a range of methods (and of course, including some novel ones) + - Well trajectory planning - construct your own trajectories using a range of methods (and of course, including some novel ones) **- DONE!** - More error models - WebApp for those that just want answers - - Viewer - a 3D viewer to quickly visualize the data and calculated results - **DONE!** + - Viewer - a 3D viewer to quickly visualize the data and calculated results **- DONE!** It's possible to generate data for visualizing well trajectories with [welleng], as can be seen with the rendered scenes below. ![image](https://user-images.githubusercontent.com/41046859/97724026-b78c2e00-1acc-11eb-845d-1220219843a5.png)