Skip to content

Commit

Permalink
Merge pull request #45 from jonnymaserati/dev
Browse files Browse the repository at this point in the history
Added SurveyHeader class and ISCWSA MWD Rev5 error model
  • Loading branch information
jonnymaserati authored Jan 2, 2021
2 parents 68f0fab + f9db45b commit 2a4af69
Show file tree
Hide file tree
Showing 39 changed files with 2,542 additions and 1,034 deletions.
63 changes: 39 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

## New Features!

- **ISCWSA MWD Rev5 error model:** add the latest ISCWSA error model that includes tortuousity effects on location uncertainty.
- **Import from Landmark .wbp files:** using the `exchange.wbp` module it's now possible to import .wbp files exported from Landmark's COMPASS or DecisionSpace software.
```python
import welleng as we
Expand Down Expand Up @@ -53,11 +54,19 @@ we.exchange.wbp.save_to_file(doc, "demo.wbp") # save the document to file

[welleng] requires [trimesh], [numpy] and [scipy] to run. Other libraries are optional depending on usage and to get [python-fcl] running on which [trimesh] is built may require some additional installations. Other than that, it should be an easy pip install to get up and running with welleng and the minimum dependencies.

Here's how to get the trickier dependencies manually installed on Ubunut (further instructions can be found [here](https://github.com/flexible-collision-library/fcl/blob/master/INSTALL)):

```python
sudo apt-get update
sudo apt-get install libeigen3-dev libccd-dev octomap-tools
```
On a Mac you should be able to install the above with brew and on a Windows maching you'll probably have to build these libraries following the instruction in the link, but it's not too tricky. Once the above are installed, then it should be a simple:

```python
pip install welleng
```

For developers, the repository can be cloned and locally installed in your GitHub directory via your preferred Python env.
For developers, the repository can be cloned and locally installed in your GitHub directory via your preferred Python env (the `dev` branch is usuall a version or two ahead of the `main`).

```python
git clone https://github.com/jonnymaserati/welleng.git
Expand All @@ -73,44 +82,52 @@ Here's an example using `welleng` to construct a couple of simple well trajector

```python
import welleng as we
import numpy as np
from tabulate import tabulate

# construct simple well paths
print("Constructing wells...")
connector_reference = we.connector.Connector(
pos1=[0,0,0],
inc1=0,
azi1=0,
pos2=[-100,0,2000.],
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,
pos1=[0., 0., 0.],
inc1=0.,
azi1=225.,
pos2=[-280., -600., 2000.],
inc2=90.,
azi2=270.,
).survey(step=50)

# make a survey object and calculate the uncertainty covariances
# make survey objects and calculate the uncertainty covariances
print("Making surveys...")
sh_reference = we.survey.SurveyHeader(
name="reference",
azi_reference="grid"
)
survey_reference = we.survey.Survey(
md=connector_reference.md,
inc=connector_reference.inc_deg,
azi=connector_reference.azi_deg,
error_model='ISCWSA_MWD'
header=sh_reference,
error_model='iscwsa_mwd_rev4'
)
sh_offset = we.survey.SurveyHeader(
name="offset",
azi_reference="grid"
)

survey_offset = we.survey.Survey(
md=connector_offset.md,
inc=connector_offset.inc_deg,
azi=connector_offset.azi_deg,
start_nev=[100,200,0],
error_model='ISCWSA_MWD'
start_nev=[100., 200., 0.],
header=sh_offset,
error_model='iscwsa_mwd_rev4'
)

# generate mesh objects of the well paths
Expand Down Expand Up @@ -150,13 +167,14 @@ lines = we.visual.get_lines(clearance_mesh)

# plot the result
we.visual.plot(
[mesh_reference.mesh, mesh_offset.mesh], # list of meshes
names=['reference', 'offset'], # list of names
colors=['red', 'blue'], # list of colors
[mesh_reference.mesh, mesh_offset.mesh], # list of meshes
names=['reference', 'offset'], # list of names
colors=['red', 'blue'], # list of colors
lines=lines
)

print("Done!")

```

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.
Expand All @@ -172,13 +190,10 @@ Well trajectory generated by [build_a_well_from_sections.py]
## Todos

- 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) **- DONE!**
- More error models - **in progress**
- More error models - **added the ISCWSA MWD Rev5 error model**
- WebApp for those that just want answers
- 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)
Expand Down
14 changes: 7 additions & 7 deletions examples/build_a_well_from_sections.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import welleng as we
# import welleng as we
from welleng.connector import Connector, interpolate_well, get_survey
from welleng.mesh import WellMesh
from welleng.visual import plot

# define start position and vector
pos1 = [0,0,0]
vec1 = [0,0,1] # this is the same as inc=0, azi=0
pos1 = [0., 0., 0.]
vec1 = [0., 0., 1.] # this is the same as inc=0, azi=0

# for the first section, we want to hold vertical for the first 500m
md2 = 500
vec2 = [0,0,1]
vec2 = [0., 0., 1.]

# let's connect those points
s1 = Connector(
Expand Down Expand Up @@ -37,7 +37,7 @@
# we want to be near horizontal when we hit this point so that we can
# drill the reservoir horizontal (let's say 88 deg) and the reseroir
# orientation is southeast-northwest and we have good directional control
pos4 = [-800,300,1800]
pos4 = [-800., 300., 1800.]
inc4 = 88
azi4 = 315
dls_design4 = 4
Expand All @@ -53,7 +53,7 @@
)

# finally, we want a 500m horizontal section in the reservoir
md5=s3.md_target + 500
md5 = s3.md_target + 500
inc5 = 90

s4 = Connector(
Expand All @@ -77,6 +77,6 @@
mesh = WellMesh(survey, method='circle')

# finally, plot it
plot([mesh.mesh])
plot([mesh.mesh], interactive=False)

print("Done!")
105 changes: 35 additions & 70 deletions examples/calibrate_iscwsa_error_example_1.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,63 @@
import numpy as np
import pandas as pd

import welleng as we

from openpyxl import load_workbook
from dataclasses import dataclass

# import the ISCWSA standard Excel data
print("Importing data...")
try:
workbook = load_workbook(
filename="examples/data/error-model-example-mwdrev4-iscwsa-1.xlsx",
data_only=True
data_only=True
)
except:
print("Make sure you have a local copy of ISCWSA's Excel file and have updated the location in the code.")
print(
"Make sure you have a local copy of ISCWSA's Excel file and have"
"updated the location in the code."
)

sheets = workbook.sheetnames
model = workbook['Model']
wellpath = workbook['Wellpath']


@dataclass
class WellHeader:
Latitude: float
G: float
BTotal: float
Dip: float
Declination: float
Convergence: float
AzimuthRef: float
DepthUnits: str
VerticalIncLimit: float
FeetToMeters: float

wh = WellHeader(
Latitude=wellpath['B2'].value,
G = wellpath['B3'].value,
BTotal = wellpath['B4'].value,
Dip = wellpath['B5'].value,
Declination = wellpath['B6'].value,
Convergence = wellpath['B7'].value,
AzimuthRef = wellpath['B8'].value,
DepthUnits = wellpath['B9'].value,
VerticalIncLimit = wellpath['B10'].value,
FeetToMeters = wellpath['B11'].value
sh = we.survey.SurveyHeader(
latitude=wellpath['B2'].value,
G=wellpath['B3'].value,
b_total=wellpath['B4'].value,
dip=wellpath['B5'].value,
declination=wellpath['B6'].value,
convergence=wellpath['B7'].value,
azi_reference=(
'true' if wellpath['B8'].value == 'TRUE'
else 'grid' if wellpath['B8'].value == 'GRID'
else 'magnetic'
),
depth_unit=wellpath['B9'].value,
vertical_inc_limit=wellpath['B10'].value,
)

well_ref_params = dict(
Latitude = wh.Latitude,
G = wh.G,
BTotal = wh.BTotal,
Dip = wh.Dip,
Declination = wh.Declination,
Convergence = wh.Convergence,
)

MD = []
IncDeg = []
AziDeg = []
TVD = []
md = []
inc = []
azi = []

for row in wellpath.iter_rows(
min_row=3,
max_row=(),
min_col=5,
max_col=9
):
MD.append(row[0].value)
IncDeg.append(row[1].value)
AziDeg.append(row[2].value)
TVD.append(row[3].value)

survey_deg = np.stack((MD,IncDeg,AziDeg), axis=-1)

IncRad = np.radians(IncDeg)
AziRad = np.radians(AziDeg)
AziTrue = AziRad + np.full(len(AziRad), np.radians(wh.Convergence))
AziG = AziTrue - np.full(len(AziRad), np.radians(wh.Convergence))
AziMag = AziTrue - np.full(len(AziRad), np.radians(wh.Declination))
md.append(row[0].value)
inc.append(row[1].value)
azi.append(row[2].value)

survey_master = np.stack((MD,IncRad,AziRad), axis=-1)
TVD = np.array([TVD])
AziTrue = np.array([AziTrue])
AziG = np.array([AziG])
AziMag = np.array([AziMag])

# get error for entire survey
print("Calculating ISCWSA MWD Rev4 errors...")
err0 = we.error.ErrorModel(
survey=survey_deg,
well_ref_params=well_ref_params,
AzimuthRef=wh.AzimuthRef,
s = we.survey.Survey(
md=md,
inc=inc,
azi=azi,
header=sh,
error_model="iscwsa_mwd_rev4"
)

err0 = s.err

cov_nev0 = err0.errors.cov_NEVs.T

# print final covariance matrix for each tool
Expand All @@ -105,4 +70,4 @@ class WellHeader:
f'TOTAL:\n{cov_nev0[-1]}'
)

input("Press Enter to end...")
input("Press Enter to end...")
12 changes: 7 additions & 5 deletions examples/connect_two_random_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Some code for testing the connector module.

# Generate some random pairs of points
pos1 = [0,0,0]
pos1 = [0., 0., 0.]
md1 = 0

pos2 = np.random.random(3) * 1000
Expand Down Expand Up @@ -79,7 +79,7 @@

# Initialize a connector object and connect the inputs
section = we.connector.Connector(
pos1=[0.,0.,0],
pos1=[0., 0., 0],
vec1=vec1,
md2=md2,
pos2=pos2,
Expand Down Expand Up @@ -112,7 +112,9 @@
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]))
end_points = np.concatenate(
([section.pos2], [section.pos3], [section.pos_target])
)
lines = Lines(
startPoints=start_points,
endPoints=end_points,
Expand All @@ -121,7 +123,7 @@
)

# Add some arrows to represent the vectors at the start and end positions
scalar=150
scalar = 150
arrows = Arrows(
startPoints=np.array([
section.pos1,
Expand Down Expand Up @@ -150,4 +152,4 @@
arrows=arrows,
)

print("Done")
print("Done")
Loading

0 comments on commit 2a4af69

Please sign in to comment.