Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various small fixes #211

Merged
merged 29 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5f7c442
fix: fault segment structural frame was intiialised incorrectly
lachlangrose Jan 5, 2025
9b2880e
fix: adding copy method as a required method for all geological features
lachlangrose Jan 5, 2025
d5bf487
fix: allow feature to be initialised without a builder
lachlangrose Jan 5, 2025
b554522
fix: bug with pyvista glyphs when working in a real world coordinate …
lachlangrose Jan 5, 2025
d4f3e12
fix: rescale/scale no longer default to in place and will copy the po…
lachlangrose Jan 5, 2025
bed530f
fix: interpolator factory with data works
lachlangrose Jan 5, 2025
4d00075
ci: adding icon to docs
lachlangrose Jan 5, 2025
8764278
fix: removing config details for release please
lachlangrose Jan 5, 2025
b95fec0
fix: interface constraints used wrong reference to interpolation matrix
lachlangrose Jan 6, 2025
d0a9c5b
fix: throw error if interpolation support doesn't have 3 steps
lachlangrose Jan 6, 2025
6e378cb
style: style fixes by ruff and autoformatting by black
lachlangrose Jan 6, 2025
ee7f267
typo, steps_vector instead of nsteps
lachlangrose Jan 6, 2025
f427ad0
fix: order of arguments incorrect for fault segment
lachlangrose Jan 6, 2025
08e5957
fix: add copy to intrusion
lachlangrose Jan 6, 2025
4466e00
Merge branch 'fix/cleanup' of github.com:Loop3D/LoopStructural into f…
lachlangrose Jan 6, 2025
a1613b0
style: style fixes by ruff and autoformatting by black
lachlangrose Jan 6, 2025
a4c98ed
fix: bb plotting in correct place when not using global origin
lachlangrose Jan 8, 2025
8fed1f1
fix: inequality pairs being used by FD interpolator
lachlangrose Jan 8, 2025
7bccdf5
Merge branch 'fix/cleanup' of github.com:Loop3D/LoopStructural into f…
lachlangrose Jan 8, 2025
ded85d6
style: style fixes by ruff and autoformatting by black
lachlangrose Jan 8, 2025
04d6f4a
style: remove comments/old code
lachlangrose Jan 8, 2025
307ce66
Merge branch 'fix/cleanup' of github.com:Loop3D/LoopStructural into f…
lachlangrose Jan 8, 2025
3fa75a4
fix: add a feature to project a vector onto a plane
lachlangrose Jan 12, 2025
628e6d8
style: style fixes by ruff and autoformatting by black
lachlangrose Jan 12, 2025
47cb0a5
fix: updating transformer to take angle and translation as input
lachlangrose Jan 12, 2025
7deafab
Merge branch 'fix/cleanup' of github.com:Loop3D/LoopStructural into f…
lachlangrose Jan 12, 2025
3dd2daf
style: style fixes by ruff and autoformatting by black
lachlangrose Jan 12, 2025
b22c0e9
fix: disable axis_function and limb_function for folds
lachlangrose Jan 13, 2025
21bd173
Merge branch 'fix/cleanup' of github.com:Loop3D/LoopStructural into f…
lachlangrose Jan 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build documentation and deploy
name: "📚 Build documentation and deploy "

on:
workflow_dispatch:
Expand Down
43 changes: 43 additions & 0 deletions LoopStructural/datatypes/_bounding_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,46 @@ def structured_grid(
properties=_vertex_data,
name=name,
)

def project(self, xyz):
"""Project a point into the bounding box

Parameters
----------
xyz : np.ndarray
point to project

Returns
-------
np.ndarray
projected point
"""

return (xyz - self.global_origin) / np.max((self.global_maximum-self.global_origin))#np.clip(xyz, self.origin, self.maximum)
Copy link
Preview

Copilot AI Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using np.max for projection might be incorrect for non-uniform bounding boxes. Consider using element-wise division instead: (xyz - self.global_origin) / (self.global_maximum - self.global_origin).

Suggested change
return (xyz - self.global_origin) / np.max((self.global_maximum-self.global_origin))#np.clip(xyz, self.origin, self.maximum)
return (xyz - self.global_origin) / (self.global_maximum - self.global_origin)

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options

def reproject(self, xyz):
"""Reproject a point from the bounding box to the global space

Parameters
----------
xyz : np.ndarray
point to reproject

Returns
-------
np.ndarray
reprojected point
"""

return xyz * np.max((self.global_maximum - self.global_origin)) + self.global_origin
Copy link
Preview

Copilot AI Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using np.max for reprojection might be incorrect for non-uniform bounding boxes. Consider using element-wise multiplication instead: xyz * (self.global_maximum - self.global_origin) + self.global_origin.

Suggested change
return xyz * np.max((self.global_maximum - self.global_origin)) + self.global_origin
return xyz * (self.global_maximum - self.global_origin) + self.global_origin

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options

def __repr__(self):
return f"BoundingBox({self.origin}, {self.maximum}, {self.nsteps})"

def __str__(self):
return f"BoundingBox({self.origin}, {self.maximum}, {self.nsteps})"

def __eq__(self, other):
if not isinstance(other, BoundingBox):
return False
return np.allclose(self.origin, other.origin) and np.allclose(self.maximum, other.maximum) and np.allclose(self.nsteps, other.nsteps)
30 changes: 23 additions & 7 deletions LoopStructural/datatypes/_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ def to_dict(self):
),
}

def vtk(self):
def vtk(self, scalars=None):
import pyvista as pv

points = pv.PolyData(self.locations)
points["values"] = self.values
if scalars is not None and len(scalars) == len(self.locations):
points.point_data['scalars'] = scalars
else:
points["values"] = self.values
return points

def plot(self, pyvista_kwargs={}):
Expand Down Expand Up @@ -123,25 +126,38 @@ def to_dict(self):
def from_dict(self, d):
return VectorPoints(d['locations'], d['vectors'], d['name'], d.get('properties', None))

def vtk(self, geom='arrow', scale=1.0, scale_function=None, normalise=True, tolerance=0.05):
def vtk(self, geom='arrow', scale=.10, scale_function=None, normalise=True, tolerance=0.05, bb=None, scalars=None):
import pyvista as pv

_projected=False
vectors = np.copy(self.vectors)
if normalise:
norm = np.linalg.norm(vectors, axis=1)
vectors[norm > 0, :] /= norm[norm > 0][:, None]
if scale_function is not None:
# vectors /= np.linalg.norm(vectors, axis=1)[:, None]
vectors *= scale_function(self.locations)[:, None]
points = pv.PolyData(self.locations)
locations = self.locations
if bb is not None:
try:
locations = bb.project(locations)
_projected=True
except Exception as e:
logger.error(f'Failed to project points to bounding box: {e}')
logger.error('Using unprojected points, this may cause issues with the glyphing')
points = pv.PolyData(locations)
if scalars is not None and len(scalars) == len(self.locations):
points['scalars'] = scalars
points.point_data.set_vectors(vectors, 'vectors')
if geom == 'arrow':
geom = pv.Arrow(scale=scale)
elif geom == 'disc':
geom = pv.Disc(inner=0, outer=scale)
geom = pv.Disc(inner=0, outer=scale).rotate_y(90)

# Perform the glyph
return points.glyph(orient="vectors", geom=geom, tolerance=tolerance)
glyphed = points.glyph(orient="vectors", geom=geom, tolerance=tolerance,scale=False)
if _projected:
glyphed.points = bb.reproject(glyphed.points)
return glyphed

def plot(self, pyvista_kwargs={}):
"""Calls pyvista plot on the vtk object
Expand Down
29 changes: 20 additions & 9 deletions LoopStructural/interpolators/_finite_difference_interpolator.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,28 @@ def add_interface_constraints(self, w=1.0):
# get elements for points
points = self.get_interface_constraints()
if points.shape[0] > 1:
vertices, c, tetras, inside = self.support.get_element_for_location(
# vertices, c, tetras, inside = self.support.get_element_for_location(
# points[:, : self.support.dimension]
# )
# # calculate volume of tetras
# # vecs = vertices[inside, 1:, :] - vertices[inside, 0, None, :]
# # vol = np.abs(np.linalg.det(vecs)) / 6
# A = c[inside, :]
# # A *= vol[:,None]
# idc = tetras[inside, :]
node_idx, inside = self.support.position_to_cell_corners(
points[:, : self.support.dimension]
)
# calculate volume of tetras
# vecs = vertices[inside, 1:, :] - vertices[inside, 0, None, :]
# vol = np.abs(np.linalg.det(vecs)) / 6
A = c[inside, :]
# A *= vol[:,None]
idc = tetras[inside, :]

# print(points[inside,:].shape)
gi = np.zeros(self.support.n_nodes, dtype=int)
gi[:] = -1
gi[self.region] = np.arange(0, self.nx, dtype=int)
idc = np.zeros(node_idx.shape).astype(int)
idc[:] = -1
idc[inside, :] = gi[node_idx[inside, :]]
inside = np.logical_and(~np.any(idc == -1, axis=1), inside)
idc = idc[inside, :]
A = self.support.position_to_dof_coefs(points[inside, : self.support.dimension])
for unique_id in np.unique(
points[
np.logical_and(~np.isnan(points[:, self.support.dimension]), inside),
Expand All @@ -197,7 +209,6 @@ def add_interface_constraints(self, w=1.0):
).T.reshape(-1, 2)
interface_A = np.hstack([A[mask, :][ij[:, 0], :], -A[mask, :][ij[:, 1], :]])
interface_idc = np.hstack([idc[mask, :][ij[:, 0], :], idc[mask, :][ij[:, 1], :]])

# now map the index from global to region create array size of mesh
# initialise as np.nan, then map points inside region to 0->nx
gi = np.zeros(self.support.n_nodes).astype(int)
Expand Down
25 changes: 7 additions & 18 deletions LoopStructural/interpolators/_interpolator_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,14 @@ def create_interpolator_with_data(
gradient_norm_constraints: Optional[np.ndarray] = None,
gradient_constraints: Optional[np.ndarray] = None,
):
if interpolatortype is None:
raise ValueError("No interpolator type specified")
if boundingbox is None:
raise ValueError("No bounding box specified")
if nelements is None:
raise ValueError("No number of elements specified")
if isinstance(interpolatortype, str):
interpolatortype = InterpolatorType._member_map_[interpolatortype].numerator
if support is None:
raise Exception("Support must be specified")
# supporttype = support_interpolator_map[interpolatortype]
# support = SupportFactory.create_support(
# supporttype, boundingbox, nelements, element_volume
# )
interpolator = interpolator_map[interpolatortype](support)
interpolator = InterpolatorFactory.create_interpolator(
interpolatortype, boundingbox, nelements, element_volume, support
)
if value_constraints is not None:
interpolator.add_value_constraints(value_constraints)
interpolator.set_value_constraints(value_constraints)
if gradient_norm_constraints is not None:
interpolator.add_gradient_constraints(gradient_norm_constraints)
interpolator.set_normal_constraints(gradient_norm_constraints)
if gradient_constraints is not None:
interpolator.add_gradient_constraints(gradient_constraints)
interpolator.set_gradient_constraints(gradient_constraints)
interpolator.setup()
return interpolator
2 changes: 2 additions & 0 deletions LoopStructural/interpolators/supports/_3d_base_structured.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def __init__(
raise LoopException("nsteps cannot be zero")
if np.any(nsteps < 0):
raise LoopException("nsteps cannot be negative")
if np.any(step_vector < 3):
raise LoopException("step vector cannot be less than 3. Try increasing the resolution of the interpolator")
self._nsteps = np.array(nsteps, dtype=int) + 1
self._step_vector = np.array(step_vector)
self._origin = np.array(origin)
Expand Down
4 changes: 2 additions & 2 deletions LoopStructural/modelling/core/geological_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,7 @@ def create_and_add_fault(
return fault

# TODO move rescale to bounding box/transformer
def rescale(self, points: np.ndarray, inplace: bool = True) -> np.ndarray:
def rescale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
"""
Convert from model scale to real world scale - in the future this
should also do transformations?
Expand All @@ -1440,7 +1440,7 @@ def rescale(self, points: np.ndarray, inplace: bool = True) -> np.ndarray:
return points

# TODO move scale to bounding box/transformer
def scale(self, points: np.ndarray, inplace: bool = True) -> np.ndarray:
def scale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
"""Take points in UTM coordinates and reproject
into scaled model space

Expand Down
16 changes: 14 additions & 2 deletions LoopStructural/modelling/features/_analytical_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,16 @@ def __init__(
builder=None,
):
BaseFeature.__init__(self, name, model, faults, regions, builder)
self.vector = np.array(vector, dtype=float)
self.origin = np.array(origin, dtype=float)
try:
self.vector = np.array(vector, dtype=float).reshape(3)
except ValueError:
logger.error("AnalyticalGeologicalFeature: vector must be a 3 element array")
raise ValueError("vector must be a 3 element array")
try:
self.origin = np.array(origin, dtype=float).reshape(3)
except ValueError:
logger.error("AnalyticalGeologicalFeature: origin must be a 3 element array")
raise ValueError("origin must be a 3 element array")
self.type = FeatureType.ANALYTICAL

def to_json(self):
Expand Down Expand Up @@ -86,3 +94,7 @@ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False):

def get_data(self, value_map: Optional[dict] = None):
return
def copy(self,name:Optional[str]=None):
if name is None:
name = self.name
return AnalyticalGeologicalFeature(name,self.vector.copy(),self.origin.copy(),list(self.regions),list(self.faults),self.model,self.builder)
13 changes: 12 additions & 1 deletion LoopStructural/modelling/features/_base_geological_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ def surfaces(
self.regions = [
r for r in self.regions if r.name != self.name and r.parent.name != self.name
]
callable = lambda xyz: self.evaluate_value(self.model.scale(xyz))

callable = lambda xyz: self.evaluate_value(self.model.scale(xyz)) if self.model is not None else self.evaluate_value(xyz)
isosurfacer = LoopIsosurfacer(bounding_box, callable=callable)
if name is None and self.name is not None:
name = self.name
Expand Down Expand Up @@ -375,3 +376,13 @@ def get_data(self, value_map: Optional[dict] = None):
dictionary of data
"""
raise NotImplementedError
@abstractmethod
def copy(self,name:Optional[str]=None):
"""Copy the feature

Returns
-------
BaseFeature
copied feature
"""
raise NotImplementedError
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,7 @@ def max(self):

def get_data(self, value_map: Optional[dict] = None):
return
def copy(self,name:Optional[str]=None):
if name is None:
name = f'{self.name}_copy'
return CrossProductGeologicalFeature(name,self.geological_feature_a,self.geological_feature_b)
4 changes: 3 additions & 1 deletion LoopStructural/modelling/features/_geological_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ def evaluate_value(self, pos: np.ndarray, ignore_regions=False, fillnan=None) ->
# if evaluation_points is not a numpy array try and convert
# otherwise error
evaluation_points = np.asarray(pos)
self.builder.up_to_date()
# if there is a builder lets make sure that the feature is up to date
if self.builder is not None:
self.builder.up_to_date()
# check if the points are within the display region
v = np.zeros(evaluation_points.shape[0])
v[:] = np.nan
Expand Down
5 changes: 5 additions & 0 deletions LoopStructural/modelling/features/_structural_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,8 @@ def get_data(self, value_map: Optional[dict] = None) -> List[Union[ValuePoints,
for f in self.features:
data.extend(f.get_data(value_map))
return data
def copy(self,name:Optional[str]=None):
if name is None:
name = f'{self.name}_copy'
# !TODO check if this needs to be a deep copy
return StructuralFrame(name,self.features,self.fold,self.model)
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,6 @@ def evaluate_on_surface(self, location):

def get_data(self, value_map: Optional[dict] = None):
pass

def copy(self, name: Optional[str] = None):
raise NotImplementedError("Not implemented yet")
3 changes: 2 additions & 1 deletion LoopStructural/modelling/features/fault/_fault_segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(
how many integration steps for faults
kwargs
"""
StructuralFrame.__init__(self, features, name, fold, model)
StructuralFrame.__init__(self, name=name, features=features, fold=fold,model= model)
self.type = FeatureType.FAULT
self.displacement = displacement
self._faultfunction = BaseFault().fault_displacement
Expand Down Expand Up @@ -261,6 +261,7 @@ def evaluate_gradient(self, locations):
logger.error("nan slicing ")
# need to scale with fault displacement
v[mask, :] = self.__getitem__(1).evaluate_gradient(locations[mask, :])
v[mask,:]/=np.linalg.norm(v[mask,:],axis=1)[:,None]
scale = self.displacementfeature.evaluate_value(locations[mask, :])
v[mask, :] *= scale[:, None]
return v
Expand Down
5 changes: 4 additions & 1 deletion docs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ RUN apt-get update -qq && \
libgl1-mesa-glx\
xvfb
RUN conda install -c conda-forge\
-c loop3d\
# python"<=3.8"\
cython\
numpy\
Expand All @@ -32,9 +33,11 @@ RUN conda install -c conda-forge\
meshio\
python=3.10\
pydata-sphinx-theme\
pyvista\
loopstructuralvisualisation\
-y
RUN pip install git+https://github.com/geopandas/[email protected]
RUN pip install loopstructuralvisualisation[all] geoh5py
RUN pip install geoh5py
RUN pip install sphinxcontrib-bibtex
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
Expand Down
8 changes: 1 addition & 7 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
{
"packages": {
"LoopStructural": {
"release-type": "python",
"types": ["feat", "fix"],
"bump-minor-pre-major": true,
"bump-minor-pre-major-pattern": "feat",
"bump-patch-for-minor-pre-major": true,
"bump-patch-for-minor-pre-major-pattern": "fix",
"include-v-in-tag": true
"release-type": "python"
}
}
}
Loading