Skip to content

Commit 01a61fe

Browse files
committed
Merge branch 'master' of github.com:Loop3D/LoopStructural
2 parents 38fbc14 + 7ed0355 commit 01a61fe

File tree

14 files changed

+278
-104
lines changed

14 files changed

+278
-104
lines changed

LoopStructural/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
from logging.config import dictConfig
2020
import tempfile
2121
from pathlib import Path
22-
23-
2422
#set up logging
2523
# temp_file = tempfile.mkdtemp()
2624
# if temp_file:

LoopStructural/export/exporters.py

Lines changed: 129 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
Routines to export geological model data to file in a variety of formats
33
"""
44
import logging
5+
import os
56
from pyevtk.hl import unstructuredGridToVTK, pointsToVTK
67
from pyevtk.vtk import VtkTriangle
78
import numpy as np
89

910
from LoopStructural.utils.helper import create_box
1011
from LoopStructural.export.file_formats import FileFormat
1112

12-
13+
1314
from LoopStructural.utils import getLogger
1415
logger = getLogger(__name__)
1516

@@ -29,7 +30,7 @@ def write_cubeface(model, file_name, data_label, nsteps, file_format):
2930
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
3031
3d array dimensions
3132
file_format: export.fileformats.FileFormat object
32-
Desired format of exported file
33+
Desired format of exported file. Supports VTK
3334
3435
Returns
3536
-------
@@ -58,7 +59,7 @@ def write_vol(model, file_name, data_label, nsteps, file_format):
5859
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
5960
3d array dimensions
6061
file_format: export.fileformats.FileFormat object
61-
Desired format of exported file
62+
Desired format of exported file. Supports VTK and GOCAD
6263
6364
Returns
6465
-------
@@ -67,14 +68,16 @@ def write_vol(model, file_name, data_label, nsteps, file_format):
6768
"""
6869
if file_format == FileFormat.VTK:
6970
return _write_vol_evtk(model, file_name, data_label, nsteps)
71+
if file_format == FileFormat.GOCAD:
72+
return _write_vol_gocad(model, file_name, data_label, nsteps)
7073

7174
logger.warning("Cannot export to file - format {} not supported yet".format(str(file_format)))
7275
return False
7376

7477

7578
def _write_cubeface_evtk(model, file_name, data_label, nsteps, real_coords=True):
7679
"""
77-
Writes out the model as a cuboid with six rectangular surfaces
80+
Writes out the model as a cuboid with six rectangular surfaces in VTK unstructured grid format
7881
7982
Parameters
8083
----------
@@ -119,16 +122,17 @@ def _write_cubeface_evtk(model, file_name, data_label, nsteps, real_coords=True)
119122
ctype = np.full(tri.shape[0], VtkTriangle.tid)
120123

121124
try:
122-
unstructuredGridToVTK(file_name, x, y, z, connectivity = conn, offsets = offset, cell_types = ctype, cellData = None, pointData = {data_label: val})
125+
unstructuredGridToVTK(file_name, x, y, z, connectivity=conn, offsets=offset, cell_types=ctype,
126+
cellData=None, pointData={data_label: val})
123127
except Exception as e:
124-
logger.warning("Cannot export cuboid surface to file {}: {}".format(file_name, str(e)))
128+
logger.warning("Cannot export cuboid surface to VTK file {}: {}".format(file_name, str(e)))
125129
return False
126-
return True
130+
return True
127131

128132

129133
def _write_vol_evtk(model, file_name, data_label, nsteps, real_coords=True):
130134
"""
131-
Writes out the model as a 3d volume grid
135+
Writes out the model as a 3d volume grid in VTK points format
132136
133137
Parameters
134138
----------
@@ -153,12 +157,13 @@ def _write_vol_evtk(model, file_name, data_label, nsteps, real_coords=True):
153157

154158
# Generate model values in 3d grid
155159
xx, yy, zz = np.meshgrid(loop_X, loop_Y, loop_Z, indexing='ij')
160+
# xyz is N x 3 vector array
156161
xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
157162
vals = model.evaluate_model(xyz, scale=False)
158163
if real_coords:
159164
model.rescale(xyz)
160165

161-
# Define vertices
166+
# Define vertices - xyz.shape[0] is length of vector array
162167
x = np.zeros(xyz.shape[0])
163168
y = np.zeros(xyz.shape[0])
164169
z = np.zeros(xyz.shape[0])
@@ -167,10 +172,122 @@ def _write_vol_evtk(model, file_name, data_label, nsteps, real_coords=True):
167172

168173
# Write to grid
169174
try:
170-
pointsToVTK(file_name, x, y, z, data= { data_label: vals})
175+
pointsToVTK(file_name, x, y, z, data={data_label: vals})
171176
except Exception as e:
172-
logger.warning("Cannot export volume to file {}: {}".format(file_name, str(e)))
177+
logger.warning("Cannot export volume to VTK file {}: {}".format(file_name, str(e)))
173178
return False
174-
return True
179+
return True
180+
175181

182+
def _write_vol_gocad(model, file_name, data_label, nsteps, real_coords=True):
183+
"""
184+
Writes out the model as a 3d volume grid in GOCAD VOXET object format
185+
186+
Parameters
187+
----------
188+
model : GeologicalModel object
189+
Geological model to export
190+
file_name : string
191+
Name of file that model is exported to, including path, but without the file extension
192+
data_label : string
193+
A data label to insert into export file
194+
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
195+
3d array dimensions
176196
197+
Returns
198+
-------
199+
True if successful
200+
201+
"""
202+
# Define grid spacing in model scale coords
203+
loop_X = np.linspace(model.bounding_box[0, 0], model.bounding_box[1, 0], nsteps[0])
204+
loop_Y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1], nsteps[1])
205+
loop_Z = np.linspace(model.bounding_box[0, 2], model.bounding_box[1, 2], nsteps[2])
206+
207+
# Generate model values in 3d grid
208+
xx, yy, zz = np.meshgrid(loop_X, loop_Y, loop_Z, indexing='ij')
209+
# xyz is N x 3 vector array
210+
xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
211+
vals = model.evaluate_model(xyz, scale=False)
212+
# Use FORTRAN style indexing for GOCAD VOXET
213+
vol_vals = np.reshape(vals, nsteps, order='F')
214+
bbox = model.bounding_box[:]
215+
216+
# Convert bounding box to real world scale coords
217+
if real_coords:
218+
model.rescale(bbox)
219+
220+
# If integer values
221+
if type(vals[0]) is np.int64:
222+
d_type = np.int8
223+
no_data_val = None
224+
prop_esize = 1
225+
prop_storage_type = "Octet"
226+
227+
# If float values
228+
elif type(vals[0]) is np.float32:
229+
d_type = np.dtype('>f4')
230+
no_data_val = -999999.0
231+
prop_esize = 4
232+
prop_storage_type = "Float"
233+
else:
234+
logger.warning("Cannot export volume to GOCAD VOXET file: Unsupported type {}".format(type(vals[0])))
235+
return False
236+
237+
# Write out VOXET file
238+
vo_filename = file_name + ".vo"
239+
data_filename = file_name + "@@"
240+
try:
241+
with open(vo_filename, "w") as fp:
242+
fp.write("""GOCAD Voxet 1
243+
HEADER {{
244+
name: {name}
245+
}}
246+
GOCAD_ORIGINAL_COORDINATE_SYSTEM
247+
NAME Default
248+
AXIS_NAME "X" "Y" "Z"
249+
AXIS_UNIT "m" "m" "m"
250+
ZPOSITIVE Elevation
251+
END_ORIGINAL_COORDINATE_SYSTEM
252+
AXIS_O 0.000000 0.000000 0.000000
253+
AXIS_U 1.000000 0.000000 0.000000
254+
AXIS_V 0.000000 1.000000 0.000000
255+
AXIS_W 0.000000 0.000000 1.000000
256+
AXIS_MIN {axismin1} {axismin2} {axismin3}
257+
AXIS_MAX {axismax1} {axismax2} {axismax3}
258+
AXIS_N {nsteps1} {nsteps2} {nsteps3}
259+
AXIS_NAME "X" "Y" "Z"
260+
AXIS_UNIT "m" "m" "m"
261+
AXIS_TYPE even even even
262+
PROPERTY 1 {propname}
263+
PROPERTY_CLASS 1 {propname}
264+
PROP_UNIT 1 {propname}
265+
PROPERTY_CLASS_HEADER 1 {propname} {{
266+
}}
267+
PROPERTY_SUBCLASS 1 QUANTITY {prop_storage_type}
268+
""".format(name=os.path.basename(file_name),
269+
nsteps1=nsteps[0], nsteps2=nsteps[1], nsteps3=nsteps[2],
270+
axismin1=bbox[0, 0], axismin2=bbox[0, 1], axismin3=bbox[0, 2],
271+
axismax1=bbox[1, 0], axismax2=bbox[1, 1], axismax3=bbox[1, 2],
272+
propname=data_label, prop_storage_type=prop_storage_type))
273+
if no_data_val is not None:
274+
fp.write("PROP_NO_DATA_VALUE 1 {no_data_val}\n".format(no_data_val=no_data_val))
275+
fp.write("""PROP_ETYPE 1 IEEE
276+
PROP_FORMAT 1 RAW
277+
PROP_ESIZE 1 {prop_esize}
278+
PROP_OFFSET 1 0
279+
PROP_FILE 1 {prop_file}
280+
END\n""".format(prop_file=data_filename, prop_esize=prop_esize))
281+
except IOError as exc:
282+
logger.warning("Cannot export volume to GOCAD VOXET file {}: {}".format(vo_filename, str(exc)))
283+
return False
284+
285+
# Write out accompanying binary data file
286+
export_vals = np.array(vol_vals, dtype=d_type)
287+
try:
288+
with open(data_filename, "wb") as fp:
289+
export_vals.tofile(fp)
290+
except IOError as exc:
291+
logger.warning("Cannot export volume to GOCAD VOXET data file {}: {}".format(data_filename, str(exc)))
292+
return False
293+
return True

LoopStructural/export/file_formats.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"""
44
from enum import Enum
55

6+
67
class FileFormat(Enum):
7-
""" Enumeration of file export formats
8+
""" Enumeration of file export formats
89
"""
9-
OBJ = 1
10+
OBJ = 1 # Not supported yet
1011
VTK = 2
11-
GZ = 3 # Not supported yet
12-
GLTF = 4 # Not supported yet
12+
GOCAD = 3
13+
GLTF = 4 # Not supported yet

LoopStructural/interpolators/discrete_fold_interpolator.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
136136
A *= fold_orientation
137137
B = np.zeros(A.shape[0])
138138
idc = self.support.get_elements()[element_idx[::step],:]
139-
self.add_constraints_to_least_squares(A, B, idc)
139+
self.add_constraints_to_least_squares(A, B, idc, name='fold orientation')
140140

141141
if fold_axis_w is not None:
142142
"""
@@ -151,7 +151,7 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
151151
B = np.zeros(A.shape[0]).tolist()
152152
idc = self.support.get_elements()[element_idx[::step],:]
153153

154-
self.add_constraints_to_least_squares(A, B, idc)
154+
self.add_constraints_to_least_squares(A, B, idc, name='fold axis')
155155

156156
if fold_normalisation is not None:
157157
"""
@@ -171,7 +171,7 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
171171
B *= vol[element_idx[::step]]
172172
idc = self.support.get_elements()[element_idx[::step],:]
173173

174-
self.add_constraints_to_least_squares(A, B, idc)
174+
self.add_constraints_to_least_squares(A, B, idc, name='fold normalisation')
175175

176176
if fold_regularisation is not None:
177177
"""
@@ -185,18 +185,18 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
185185
A *= fold_regularisation[0]
186186
B = np.zeros(A.shape[0])
187187
idc = np.array(idc[:ncons, :])
188-
self.add_constraints_to_least_squares(A, B, idc)
188+
self.add_constraints_to_least_squares(A, B, idc, name='fold regularisation 1')
189189

190190
idc, c, ncons = fold_cg(eg, deformed_orientation, self.support.get_neighbours(), self.support.get_elements(), self.support.nodes)
191191
A = np.array(c[:ncons, :])
192192
A *= fold_regularisation[1]
193193
B = np.zeros(A.shape[0])
194194
idc = np.array(idc[:ncons, :])
195-
self.add_constraints_to_least_squares(A, B, idc)
195+
self.add_constraints_to_least_squares(A, B, idc, name='fold regularisation 2')
196196

197197
idc, c, ncons = fold_cg(eg, fold_axis, self.support.get_neighbours(), self.support.get_elements(), self.support.nodes)
198198
A = np.array(c[:ncons, :])
199199
A *= fold_regularisation[2]
200200
B = np.zeros(A.shape[0])
201201
idc = np.array(idc[:ncons, :])
202-
self.add_constraints_to_least_squares(A, B, idc)
202+
self.add_constraints_to_least_squares(A, B, idc, name='fold regularisation 3')

LoopStructural/interpolators/discrete_interpolator.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,15 @@ def add_constraints_to_least_squares(self, A, B, idc, name='undefined'):
176176
rows += self.c_
177177
constraint_ids = rows.copy()
178178

179-
if name in self.constraints:
180-
self.constraints[name] = np.hstack([self.constraints[name],
181-
constraint_ids])
179+
if name in self.constraints:
180+
181+
self.constraints[name]['A'] = np.vstack([self.constraints[name]['A'],A])
182+
self.constraints[name]['B'] = np.hstack([self.constraints[name]['B'], B])
183+
self.constraints[name]['idc'] = np.vstack([self.constraints[name]['idc'],
184+
idc])
185+
182186
if name not in self.constraints:
183-
self.constraints[name] = constraint_ids
187+
self.constraints[name] = {'node_indexes':constraint_ids,'A':A,'B':B.flatten(),'idc':idc}
184188
rows = np.tile(rows, (A.shape[-1], 1)).T
185189

186190
self.c_ += nr
@@ -195,7 +199,12 @@ def add_constraints_to_least_squares(self, A, B, idc, name='undefined'):
195199
self.row.extend(rows[~mask].tolist())
196200
self.col.extend(idc[~mask].tolist())
197201
self.B.extend(B.tolist())
198-
202+
203+
def calculate_residual_for_constraints(self):
204+
residuals = {}
205+
for constraint_name, constraint in self.constraints:
206+
residuals[constraint_name] = np.einsum('ij,ij->i',constraint['A'],self.c[constraint['idc'].astype(int)]) - constraint['B'].flatten()
207+
return residuals
199208
def remove_constraints_from_least_squares(self, name='undefined',
200209
constraint_ids=None):
201210
"""

LoopStructural/interpolators/finite_difference_interpolator.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ def add_vaue_constraint(self, w=1.):
175175

176176
self.add_constraints_to_least_squares(a.T * w,
177177
points[inside, 3] * w,
178-
idc[inside, :])
178+
idc[inside, :],
179+
name='value')
179180
def add_interface_ctr_pts(self, w=1.0): # for now weight all value points the same
180181
"""
181182
Adds a constraint that defines all points with the same 'id' to be the same value
@@ -262,9 +263,9 @@ def add_gradient_constraint(self, w=1.):
262263
A = np.einsum('ij,ijk->ik', strike_vector.T, T)
263264

264265
B = np.zeros(points[inside, :].shape[0])
265-
self.add_constraints_to_least_squares(A * w, B, idc[inside, :])
266+
self.add_constraints_to_least_squares(A * w, B, idc[inside, :], name='gradient')
266267
A = np.einsum('ij,ijk->ik', dip_vector.T, T)
267-
self.add_constraints_to_least_squares(A * w, B, idc[inside, :])
268+
self.add_constraints_to_least_squares(A * w, B, idc[inside, :], name='gradient')
268269

269270
def add_norm_constraint(self, w=1.):
270271
"""
@@ -302,13 +303,13 @@ def add_norm_constraint(self, w=1.):
302303
w /= 3
303304
self.add_constraints_to_least_squares(T[:, 0, :] * w,
304305
points[inside, 3] * w,
305-
idc[inside, :])
306+
idc[inside, :], name='norm')
306307
self.add_constraints_to_least_squares(T[:, 1, :] * w,
307308
points[inside, 4] * w,
308-
idc[inside, :])
309+
idc[inside, :], name='norm')
309310
self.add_constraints_to_least_squares(T[:, 2, :] * w,
310311
points[inside, 5] * w,
311-
idc[inside, :])
312+
idc[inside, :], name='norm')
312313

313314
def add_gradient_orthogonal_constraint(self, points, vector, w=1.0,
314315
B=0):
@@ -348,7 +349,7 @@ def add_gradient_orthogonal_constraint(self, points, vector, w=1.0,
348349
A = np.einsum('ij,ijk->ik', vector[inside, :3], T)
349350

350351
B = np.zeros(points[inside, :].shape[0])
351-
self.add_constraints_to_least_squares(A * w, B, idc[inside, :])
352+
self.add_constraints_to_least_squares(A * w, B, idc[inside, :], name='gradient orthogonal')
352353

353354
def add_regularisation(self, operator, w=0.1):
354355
"""
@@ -393,6 +394,7 @@ def assemble_inner(self, operator, w):
393394
B = np.zeros(global_indexes.shape[1])
394395
self.add_constraints_to_least_squares(a[inside, :] * w,
395396
B[inside],
396-
idc[inside, :]
397+
idc[inside, :],
398+
name='regularisation'
397399
)
398400
return

0 commit comments

Comments
 (0)