LoopStructural 1.6.15__py3-none-any.whl → 1.6.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of LoopStructural might be problematic. Click here for more details.

@@ -19,6 +19,8 @@ ch.setFormatter(formatter)
19
19
  ch.setLevel(logging.WARNING)
20
20
  loggers = {}
21
21
  from .modelling.core.geological_model import GeologicalModel
22
+ from .modelling.core.stratigraphic_column import StratigraphicColumn
23
+ from .modelling.core.fault_topology import FaultTopology
22
24
  from .interpolators._api import LoopInterpolator
23
25
  from .interpolators import InterpolatorBuilder
24
26
  from .datatypes import BoundingBox
@@ -28,26 +30,43 @@ logger = getLogger(__name__)
28
30
  logger.info("Imported LoopStructural")
29
31
 
30
32
 
31
- def setLogging(level="info"):
33
+ def setLogging(level="info", handler=None):
32
34
  """
33
- Set the logging parameters for log file
35
+ Set the logging parameters for log file or custom handler
34
36
 
35
37
  Parameters
36
38
  ----------
37
- filename : string
38
- name of file or path to file
39
- level : str, optional
40
- 'info', 'warning', 'error', 'debug' mapped to logging levels, by default 'info'
39
+ level : str
40
+ 'info', 'warning', 'error', 'debug'
41
+ handler : logging.Handler, optional
42
+ A logging handler to use instead of the default StreamHandler
41
43
  """
42
44
  import LoopStructural
43
45
 
44
- logger = getLogger(__name__)
45
-
46
46
  levels = get_levels()
47
- level = levels.get(level, logging.WARNING)
48
- LoopStructural.ch.setLevel(level)
47
+ level_value = levels.get(level, logging.WARNING)
48
+
49
+ # Create default handler if none provided
50
+ if handler is None:
51
+ handler = logging.StreamHandler()
52
+
53
+ formatter = logging.Formatter(
54
+ "%(levelname)s: %(asctime)s: %(filename)s:%(lineno)d -- %(message)s"
55
+ )
56
+ handler.setFormatter(formatter)
57
+ handler.setLevel(level_value)
49
58
 
59
+ # Replace handlers in all known loggers
50
60
  for name in LoopStructural.loggers:
51
61
  logger = logging.getLogger(name)
52
- logger.setLevel(level)
53
- logger.info(f'Set logging to {level}')
62
+ logger.handlers = []
63
+ logger.addHandler(handler)
64
+ logger.setLevel(level_value)
65
+
66
+ # Also apply to main module logger
67
+ main_logger = logging.getLogger(__name__)
68
+ main_logger.handlers = []
69
+ main_logger.addHandler(handler)
70
+ main_logger.setLevel(level_value)
71
+
72
+ main_logger.info(f"Set logging to {level}")
@@ -154,16 +154,21 @@ class GeologicalInterpolator(metaclass=ABCMeta):
154
154
  ----------
155
155
  points : np.ndarray
156
156
  array containing the value constraints usually 7-8 columns.
157
- X,Y,Z,nx,ny,nz,weight
157
+ X,Y,Z,nx,ny,nz,(weight, default : 1 for each row)
158
158
 
159
159
  Returns
160
160
  -------
161
161
 
162
+ Notes
163
+ -------
164
+ If no weights are provided, w = 1 is assigned to each normal constraint.
165
+
162
166
  """
163
167
  if points.shape[1] == self.dimensions * 2:
164
168
  points = np.hstack([points, np.ones((points.shape[0], 1))])
169
+ logger.info("No weight provided for normal constraints, all weights are set to 1")
165
170
  if points.shape[1] < self.dimensions * 2 + 1:
166
- raise ValueError("Nonrmal constraints must at least have X,Y,Z,nx,ny,nz")
171
+ raise ValueError("Normal constraints must at least have X,Y,Z,nx,ny,nz")
167
172
  self.n_n = points.shape[0]
168
173
  self.data["normal"] = points
169
174
  self.up_to_date = False
@@ -0,0 +1,234 @@
1
+ from ..features.fault import FaultSegment
2
+ from ...utils import Observable
3
+ from .stratigraphic_column import StratigraphicColumn
4
+ import enum
5
+ import numpy as np
6
+ class FaultRelationshipType(enum.Enum):
7
+ ABUTTING = "abutting"
8
+ FAULTED = "faulted"
9
+ NONE = "none"
10
+
11
+ class FaultTopology(Observable['FaultTopology']):
12
+ """A graph representation of the relationships between faults and the
13
+ relationship with stratigraphic units.
14
+ """
15
+ def __init__(self, stratigraphic_column: 'StratigraphicColumn'):
16
+ super().__init__()
17
+ self.faults = []
18
+ self.stratigraphic_column = stratigraphic_column
19
+ self.adjacency = {}
20
+ self.stratigraphy_fault_relationships = {}
21
+ def add_fault(self, fault: FaultSegment):
22
+ """
23
+ Adds a fault to the fault topology.
24
+ """
25
+ if not isinstance(fault, str):
26
+ raise TypeError("Expected a fault name.")
27
+
28
+ self.faults.append(fault)
29
+ self.notify('fault_added', fault=fault)
30
+
31
+ def remove_fault(self, fault: str):
32
+ """
33
+ Removes a fault from the fault topology.
34
+ """
35
+ if fault not in self.faults:
36
+ raise ValueError(f"Fault {fault} not found in the topology.")
37
+
38
+ self.faults.remove(fault)
39
+ # Remove any relationships involving this fault
40
+ self.adjacency = {k: v for k, v in self.adjacency.items() if fault not in k}
41
+ self.stratigraphy_fault_relationships = {
42
+ k: v for k, v in self.stratigraphy_fault_relationships.items() if k[1] != fault
43
+ }
44
+ self.notify('fault_removed', fault=fault)
45
+
46
+ def add_abutting_relationship(self, fault_name: str, abutting_fault: str):
47
+ """
48
+ Adds an abutting relationship between two faults.
49
+ """
50
+ if fault_name not in self.faults or abutting_fault not in self.faults:
51
+ raise ValueError("Both faults must be part of the fault topology.")
52
+
53
+ if fault_name not in self.adjacency:
54
+ self.adjacency[fault_name] = []
55
+
56
+ self.adjacency[(fault_name, abutting_fault)] = FaultRelationshipType.ABUTTING
57
+ self.notify('abutting_relationship_added', {'fault': fault_name, 'abutting_fault': abutting_fault})
58
+ def add_stratigraphy_fault_relationship(self, unit_name:str, fault_name: str):
59
+ """
60
+ Adds a relationship between a stratigraphic unit and a fault.
61
+ """
62
+ if fault_name not in self.faults:
63
+ raise ValueError("Fault must be part of the fault topology.")
64
+
65
+ if unit_name is None:
66
+ raise ValueError(f"No stratigraphic group found for unit name: {unit_name}")
67
+ self.stratigraphy_fault_relationships[(unit_name,fault_name)] = True
68
+
69
+ self.notify('stratigraphy_fault_relationship_added', {'unit': unit_name, 'fault': fault_name})
70
+ def add_faulted_relationship(self, fault_name: str, faulted_fault_name: str):
71
+ """
72
+ Adds a faulted relationship between two faults.
73
+ """
74
+ if fault_name not in self.faults or faulted_fault_name not in self.faults:
75
+ raise ValueError("Both faults must be part of the fault topology.")
76
+
77
+ if fault_name not in self.adjacency:
78
+ self.adjacency[fault_name] = []
79
+
80
+ self.adjacency[(fault_name, faulted_fault_name)] = FaultRelationshipType.FAULTED
81
+ self.notify('faulted_relationship_added', {'fault': fault_name, 'faulted_fault': faulted_fault_name})
82
+ def remove_fault_relationship(self, fault_name: str, related_fault_name: str):
83
+ """
84
+ Removes a relationship between two faults.
85
+ """
86
+ if (fault_name, related_fault_name) in self.adjacency:
87
+ del self.adjacency[(fault_name, related_fault_name)]
88
+ elif (related_fault_name, fault_name) in self.adjacency:
89
+ del self.adjacency[(related_fault_name, fault_name)]
90
+ else:
91
+ raise ValueError(f"No relationship found between {fault_name} and {related_fault_name}.")
92
+ self.notify('fault_relationship_removed', {'fault': fault_name, 'related_fault': related_fault_name})
93
+ def update_fault_relationship(self, fault_name: str, related_fault_name: str, new_relationship_type: FaultRelationshipType):
94
+ if new_relationship_type == FaultRelationshipType.NONE:
95
+ self.adjacency.pop((fault_name, related_fault_name), None)
96
+ else:
97
+ self.adjacency[(fault_name, related_fault_name)] = new_relationship_type
98
+ self.notify('fault_relationship_updated', {'fault': fault_name, 'related_fault': related_fault_name, 'new_relationship_type': new_relationship_type})
99
+ def change_relationship_type(self, fault_name: str, related_fault_name: str, new_relationship_type: FaultRelationshipType):
100
+ """
101
+ Changes the relationship type between two faults.
102
+ """
103
+ if (fault_name, related_fault_name) in self.adjacency:
104
+ self.adjacency[(fault_name, related_fault_name)] = new_relationship_type
105
+
106
+ else:
107
+ raise ValueError(f"No relationship found between {fault_name} and {related_fault_name}.")
108
+ self.notify('relationship_type_changed', {'fault': fault_name, 'related_fault': related_fault_name, 'new_relationship_type': new_relationship_type})
109
+ def get_fault_relationships(self, fault_name: str):
110
+ """
111
+ Returns a list of relationships for a given fault.
112
+ """
113
+ relationships = []
114
+ for (f1, f2), relationship_type in self.adjacency.items():
115
+ if f1 == fault_name or f2 == fault_name:
116
+ relationships.append((f1, f2, relationship_type))
117
+ return relationships
118
+ def get_fault_relationship(self, fault_name: str, related_fault_name: str):
119
+ """
120
+ Returns the relationship type between two faults.
121
+ """
122
+ return self.adjacency.get((fault_name, related_fault_name), FaultRelationshipType.NONE)
123
+ def get_faults(self):
124
+ """
125
+ Returns a list of all faults in the topology.
126
+ """
127
+ return self.faults
128
+
129
+ def get_stratigraphy_fault_relationships(self):
130
+ """
131
+ Returns a dictionary of stratigraphic unit to fault relationships.
132
+ """
133
+ return self.stratigraphy_fault_relationships
134
+ def get_fault_stratigraphic_unit_relationships(self):
135
+ units_group_pairs = self.stratigraphic_column.get_group_unit_pairs()
136
+ matrix = np.zeros((len(self.faults), len(units_group_pairs)), dtype=int)
137
+ for i, fault in enumerate(self.faults):
138
+ for j, (unit_name, _group) in enumerate(units_group_pairs):
139
+ if (unit_name, fault) in self.stratigraphy_fault_relationships:
140
+ matrix[i, j] = 1
141
+
142
+ return matrix
143
+ def get_fault_stratigraphic_relationship(self, unit_name: str, fault:str) -> bool:
144
+ """
145
+ Returns a dictionary of fault to stratigraphic unit relationships.
146
+ """
147
+ if unit_name is None:
148
+ raise ValueError(f"No stratigraphic group found for unit name: {unit_name}")
149
+ if (unit_name, fault) not in self.stratigraphy_fault_relationships:
150
+ return False
151
+ return self.stratigraphy_fault_relationships[(unit_name, fault)]
152
+
153
+ def update_fault_stratigraphy_relationship(self, unit_name: str, fault_name: str, flag: bool = True):
154
+ """
155
+ Updates the relationship between a stratigraphic unit and a fault.
156
+ """
157
+ if not flag:
158
+ if (unit_name, fault_name) in self.stratigraphy_fault_relationships:
159
+ del self.stratigraphy_fault_relationships[(unit_name, fault_name)]
160
+ else:
161
+ self.stratigraphy_fault_relationships[(unit_name, fault_name)] = flag
162
+
163
+ self.notify('stratigraphy_fault_relationship_updated', {'unit': unit_name, 'fault': fault_name})
164
+
165
+ def remove_fault_stratigraphy_relationship(self, unit_name: str, fault_name: str):
166
+ """
167
+ Removes a relationship between a stratigraphic unit and a fault.
168
+ """
169
+ if (unit_name, fault_name) not in self.stratigraphy_fault_relationships:
170
+ raise ValueError(f"No relationship found between unit {unit_name} and fault {fault_name}.")
171
+ else:
172
+ self.stratigraphy_fault_relationships.pop((unit_name, fault_name), None)
173
+
174
+ self.notify('stratigraphy_fault_relationship_removed', {'unit': unit_name, 'fault': fault_name})
175
+ def get_matrix(self):
176
+ """
177
+ Returns a matrix representation of the fault relationships.
178
+ """
179
+ matrix = np.zeros((len(self.faults), len(self.faults)), dtype=int)
180
+ for (fault_name, related_fault_name), relationship_type in self.adjacency.items():
181
+ fault_index = self.faults.index(next(f for f in self.faults if f == fault_name))
182
+ related_fault_index = self.faults.index(next(f for f in self.faults if f == related_fault_name))
183
+ if relationship_type == FaultRelationshipType.ABUTTING:
184
+ matrix[fault_index, related_fault_index] = 1
185
+ elif relationship_type == FaultRelationshipType.FAULTED:
186
+ matrix[fault_index, related_fault_index] = 2
187
+ return matrix
188
+
189
+ def to_dict(self):
190
+ """
191
+ Returns a dictionary representation of the fault topology.
192
+ """
193
+ return {
194
+ "faults": self.faults,
195
+ "adjacency": self.adjacency,
196
+ "stratigraphy_fault_relationships": self.stratigraphy_fault_relationships,
197
+ }
198
+
199
+ def update_from_dict(self, data):
200
+ """
201
+ Updates the fault topology from a dictionary representation.
202
+ """
203
+ with self.freeze_notifications():
204
+ self.faults.extend(data.get("faults", []))
205
+ adjacency = data.get("adjacency", {})
206
+ stratigraphy_fault_relationships = data.get("stratigraphy_fault_relationships", {})
207
+ for (fault,abutting_fault) in adjacency.values():
208
+ if fault not in self.faults:
209
+ self.add_fault(fault)
210
+ if abutting_fault not in self.faults:
211
+ self.add_fault(abutting_fault)
212
+ self.add_abutting_relationship(fault, abutting_fault)
213
+ for unit_name, fault_names in stratigraphy_fault_relationships.items():
214
+ for fault_name in fault_names:
215
+ if fault_name not in self.faults:
216
+ self.add_fault(fault_name)
217
+ self.add_stratigraphy_fault_relationship(unit_name, fault_name)
218
+
219
+ @classmethod
220
+ def from_dict(cls, data):
221
+ """
222
+ Creates a FaultTopology instance from a dictionary representation.
223
+ """
224
+ from .stratigraphic_column import StratigraphicColumn
225
+ stratigraphic_column = data.get("stratigraphic_column",None)
226
+ if not isinstance(stratigraphic_column, StratigraphicColumn):
227
+ if isinstance(stratigraphic_column, dict):
228
+ stratigraphic_column = StratigraphicColumn.from_dict(stratigraphic_column)
229
+ elif not isinstance(stratigraphic_column, StratigraphicColumn):
230
+ raise TypeError("Expected 'stratigraphic_column' to be a StratigraphicColumn instance or dict.")
231
+
232
+ topology = cls(stratigraphic_column)
233
+ topology.update_from_dict(data)
234
+ return topology