Source code for gempy.core.data.structural_group
import pprint
from abc import ABC
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Annotated, Optional, Union, Generator
from uuid import uuid4
from pydantic import Field
from gempy_engine.core.data.finite_fault import FiniteFault
from gempy_engine.core.data.interpolation_functions import CustomInterpolationFunctions
from gempy_engine.core.data.kernel_classes.faults import FaultsData
from gempy_engine.core.data.raw_arrays_solution import RawArraysSolution
from gempy_engine.core.data.stack_relation_type import StackRelationType
from gempy.core.data.structural_element import StructuralElement
[docs]
class FaultsRelationSpecialCase(Enum):
OFFSET_FORMATIONS = auto()
OFFSET_NONE = auto()
OFFSET_ALL = auto()
class FaultType(str, Enum):
INFINITE = "Infinite"
FINITE = "Finite"
DISABLED = "Disabled"
[docs]
@dataclass
class StructuralGroup(ABC):
"""
An abstract base class that represents a structural group within a geological model.
"""
name: str #: The name of the structural group.
elements: list[StructuralElement] = field(repr=False) #: A list of structural elements within the group.
structural_relation: StackRelationType #: The type of relation between the structural elements in the group.
id: Annotated[Optional[str], Field(exclude=True)] = field(default=None)
#: Relations with other groups in terms of faults.
fault_relations: Optional[Union[list["StructuralGroup"], FaultsRelationSpecialCase]] = field(default=None, repr=False)
faults_input_data: Optional[FaultsData] = field(default=None, repr=False)
finite_fault_draft: Annotated[Optional[FiniteFault], Field(exclude=True)] = field(default=None, repr=False)
custom_interpolation: Annotated[Optional[CustomInterpolationFunctions], Field(exclude=True)] = field(default=None, repr=False)
ignored_grid_types: Annotated[tuple[str, ...], Field(exclude=True)] = field(default_factory=tuple, repr=False)
solution: Optional[RawArraysSolution] = field(init=False, default=None, repr=False) #: Solution related to this group from geological computations.
def __post_init__(self):
if self.id is None:
self.id = str(uuid4())
if not isinstance(self.elements, list):
raise TypeError("elements must be a list of StructuralElement objects.")
for e in self.elements:
if not isinstance(e, StructuralElement):
raise TypeError("elements must be a list of StructuralElement objects.")
def __repr__(self):
elements_repr = ',\n'.join([repr(e) for e in self.elements])
return f"StructuralGroup(\n" \
f"\tname={self.name},\n" \
f"\tstructural_relation={self.structural_relation},\n" \
f"\telements=[\n{elements_repr}\n]\n)"
def _repr_html_(self):
elements_html = '<br>'.join([e._repr_html_() for e in self.elements])
html = f"""
<table style="border-left:1.2px solid black;>
<tr><th colspan="2"><b>StructuralGroup:</b></th></tr>
<tr><td>Name:</td><td>{self.name}</td></tr>
<tr><td>Structural Relation:</td><td>{self.structural_relation}</td></tr>
<tr><td>Elements:</td><td>{elements_html}</td></tr>
</table>
"""
return html
def append_element(self, element: StructuralElement):
self.elements.append(element)
def remove_element(self, element: StructuralElement):
self.elements.remove(element)
@property
def is_fault(self)-> bool:
return self.structural_relation == StackRelationType.FAULT
@property
def fault_type(self) -> FaultType:
finite_fault = self.faults_input_data.finite_fault if self.faults_input_data is not None else None
if finite_fault is not None:
return FaultType.FINITE
if self.finite_fault_draft is not None:
return FaultType.DISABLED
return FaultType.INFINITE
def set_finite_fault(self, finite_fault: FiniteFault) -> None:
self.faults_input_data = FaultsData.from_user_input(thickness=None, finite_fault=finite_fault)
self.finite_fault_draft = None
def disable_finite_fault(self) -> None:
if self.fault_type is not FaultType.FINITE:
return
self.finite_fault_draft = self.faults_input_data.finite_fault
self.faults_input_data.finite_fault = None
def enable_finite_fault(self) -> None:
if self.finite_fault_draft is not None:
self.set_finite_fault(self.finite_fault_draft)
@property
def is_lithology(self)-> bool:
return self.structural_relation == StackRelationType.ERODE or self.structural_relation == StackRelationType.ONLAP
@property
def number_of_points(self) -> int:
return sum([element.number_of_points for element in self.elements])
@property
def number_of_orientations(self) -> int:
return sum([element.number_of_orientations for element in self.elements])
@property
def number_of_elements(self) -> int:
return len(self.elements)
def get_element_by_name(self, element_name: str) -> StructuralElement | None:
matched_elements: Generator = (element for element in self.elements if element.name == element_name)
return next(matched_elements, None)
def _fault_relation_names_for_json(self):
"""Convert list[StructuralGroup] to list[str] names for JSON-safe serialization."""
fr = self.fault_relations
if isinstance(fr, list) and fr:
return [g.name if hasattr(g, 'name') else g for g in fr]
return fr
# ? I think these two subclasses are not necessary
@dataclass
class Stack(StructuralGroup):
def __int__(self, name: str, elements: list[StructuralElement]):
super().__init__(name, elements)
def __repr__(self):
return pprint.pformat(self.__dict__)
@dataclass
class Fault(StructuralGroup):
pass