apsg 1.3.0__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.
@@ -0,0 +1,425 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import os
4
+ import re
5
+ from copy import deepcopy
6
+ from datetime import datetime
7
+
8
+ import numpy as np
9
+
10
+ from apsg.helpers._helper import eformat
11
+ from apsg.math._vector import Vector3
12
+ from apsg.feature._geodata import Lineation, Foliation, Pair
13
+ from apsg.feature._container import Vector3Set
14
+ from apsg.feature._tensor3 import DeformationGradient3
15
+
16
+ __all__ = ("Core",)
17
+
18
+
19
+ class Core(object):
20
+ """
21
+ ``Core`` class to store palemomagnetic analysis data
22
+
23
+ Keyword Args:
24
+ info:
25
+ specimen:
26
+ filename:
27
+ alpha:
28
+ beta:
29
+ strike:
30
+ dip:
31
+ volume:
32
+ date:
33
+ steps:
34
+ a95:
35
+ comments:
36
+ vectors:
37
+
38
+ Returns:
39
+ ``Core`` object instance
40
+
41
+ """
42
+
43
+ def __init__(self, **kwargs):
44
+ self.site = kwargs.get("site", "Default")
45
+ self.specimen = kwargs.get("specimen", "Default")
46
+ self.filename = kwargs.get("filename", None)
47
+ self.latitude = kwargs.get("latitude", None)
48
+ self.longitude = kwargs.get("longitude", None)
49
+ self.height = kwargs.get("height", None)
50
+ self.rock = kwargs.get("rock", None)
51
+ self.age = kwargs.get("age", None)
52
+ self.formation = kwargs.get("formation", None)
53
+ self.sref = kwargs.get("sref", Pair(180, 0, 180, 0))
54
+ self.gref = kwargs.get("gref", Pair(180, 0, 180, 0))
55
+ self.bedding = kwargs.get("bedding", Foliation(0, 0))
56
+ self.foldaxis = kwargs.get("foldaxis", Lineation(0, 0))
57
+ self.volume = kwargs.get("volume", 1.0)
58
+ self.date = kwargs.get("date", datetime.now())
59
+ self.steps = kwargs.get("steps", [])
60
+ self.a95 = kwargs.get("a95", [])
61
+ self.comments = kwargs.get("comments", [])
62
+ self._vectors = kwargs.get("vectors", [])
63
+ self.module_units = kwargs.get("module_units", "A/m")
64
+ self.susceptibility_units = kwargs.get("susceptibility_units", "e-06 SI")
65
+ self.demag_units = kwargs.get("demag_units", "°C")
66
+
67
+ def __repr__(self):
68
+ return f"Core {self.site} {self.specimen}"
69
+
70
+ def __getitem__(self, key):
71
+ if np.issubdtype(type(key), np.integer):
72
+ key = self.steps[key]
73
+ if isinstance(key, slice):
74
+ res = deepcopy(self)
75
+ if key.start:
76
+ start_ok = key.start
77
+ else:
78
+ start_ok = self.nsteps[0]
79
+ if key.stop:
80
+ stop_ok = key.stop
81
+ else:
82
+ stop_ok = self.nsteps[-1]
83
+ ix = (self.nsteps >= start_ok) & (self.nsteps <= stop_ok)
84
+ res.steps = [val for (val, ok) in zip(self.steps, ix) if ok]
85
+ res.a95 = [val for (val, ok) in zip(self.a95, ix) if ok]
86
+ res.comments = [val for (val, ok) in zip(self.comments, ix) if ok]
87
+ res._vectors = [val for (val, ok) in zip(self._vectors, ix) if ok]
88
+ res.name = self.specimen + "({}-{})".format(start_ok, stop_ok)
89
+ return res
90
+ if isinstance(key, str):
91
+ if key in self.steps:
92
+ ix = self.steps.index(key)
93
+ return dict(
94
+ step=key,
95
+ MAG=self.MAG[ix],
96
+ V=self._vectors[ix],
97
+ geo=self.geo[ix],
98
+ tilt=self.tilt[ix],
99
+ a95=self.a95[ix],
100
+ comment=self.comments[ix],
101
+ )
102
+ else:
103
+ raise (Exception("Key {} not found.".format(key)))
104
+ else:
105
+ raise (Exception("Key of {} not supported.".format(type(key))))
106
+
107
+ @classmethod
108
+ def from_pmd(cls, filename):
109
+ """Return ``Core`` instance generated from PMD file.
110
+
111
+ Args:
112
+ filename: PMD file
113
+
114
+ """
115
+ with open(filename, encoding="latin1") as f:
116
+ d = f.read().splitlines()
117
+ data = {}
118
+ fields = {
119
+ "Xc": slice(5, 14),
120
+ "Yc": slice(15, 24),
121
+ "Zc": slice(25, 34),
122
+ "a95": slice(69, 73),
123
+ }
124
+ data["info"] = d[0].strip()
125
+ vline = d[1].strip()
126
+ data["filename"] = filename
127
+ data["specimen"] = vline[:10].strip()
128
+ data["alpha"] = float(vline[10:20].strip().split("=")[1])
129
+ data["beta"] = float(vline[20:30].strip().split("=")[1])
130
+ data["strike"] = float(vline[30:40].strip().split("=")[1])
131
+ data["dip"] = float(vline[40:50].strip().split("=")[1])
132
+ data["volume"] = float(vline[50:63].strip().split("=")[1].strip("m3"))
133
+ data["date"] = datetime.strptime(vline[63:].strip(), "%m-%d-%Y %H:%M")
134
+ data["steps"] = [ln[:4].strip() for ln in d[3:-1]]
135
+ data["comments"] = [ln[73:].strip() for ln in d[3:-1]]
136
+ data["a95"] = [float(ln[fields["a95"]].strip()) for ln in d[3:-1]]
137
+ data["vectors"] = []
138
+ for ln in d[3:-1]:
139
+ x = float(ln[fields["Xc"]].strip())
140
+ y = float(ln[fields["Yc"]].strip())
141
+ z = float(ln[fields["Zc"]].strip())
142
+ data["vectors"].append(Vector3((x, y, z)))
143
+ return cls(**data)
144
+
145
+ def write_pmd(self, filename=None):
146
+ """Save ``Core`` instance to PMD file.
147
+
148
+ Args:
149
+ filename: PMD file
150
+
151
+ """
152
+ if filename is None:
153
+ filename = self.filename
154
+ ff = os.path.splitext(os.path.basename(filename))[0][:8]
155
+ dt = self.date.strftime("%m-%d-%Y %H:%M")
156
+ infoln = "{:<8} a={:5.1f} b={:5.1f} s={:5.1f} d={:5.1f} v={}m3 {}"
157
+ ln0 = infoln.format(
158
+ ff,
159
+ self.gref.lin.geo[0],
160
+ self.gref.lin.geo[1],
161
+ self.bedding.geo[0],
162
+ self.bedding.geo[1],
163
+ eformat(self.volume, 2),
164
+ dt,
165
+ )
166
+ headln = (
167
+ "STEP Xc [Am2] Yc [Am2] Zc [Am2] MAG[A/m] Dg Ig Ds Is a95 "
168
+ )
169
+ with open(filename, "w") as pmdfile:
170
+ print("/".join([self.site, self.name]), file=pmdfile, end="\r\n")
171
+ print(ln0, file=pmdfile, end="\r\n")
172
+ print(headln, file=pmdfile, end="\r\n")
173
+ for ln in self.datatable:
174
+ print(ln, file=pmdfile, end="\r\n")
175
+ pmdfile.write(chr(26))
176
+
177
+ @classmethod
178
+ def from_rs3(cls, filename, exclude=["C", "G"]):
179
+ """Return ``Core`` instance generated from PMD file.
180
+
181
+ Args:
182
+ filename: Remasoft rs3 file
183
+
184
+ Kwargs:
185
+ exclude: Labels to be excluded. Default ['C', 'G']
186
+
187
+ """
188
+ with open(filename, encoding="windows-1250") as f:
189
+ d = f.read().splitlines()
190
+
191
+ from io import StringIO
192
+ import pandas as pd
193
+
194
+ headspec = [
195
+ [0, 9],
196
+ [10, 19],
197
+ [20, 29],
198
+ [30, 40],
199
+ [41, 50],
200
+ [51, 65],
201
+ [66, 70],
202
+ [71, 73],
203
+ [74, 79],
204
+ [80, 85],
205
+ [86, 91],
206
+ [92, 97],
207
+ [98, 103],
208
+ [104, 109],
209
+ [110, 112],
210
+ [113, 115],
211
+ [116, 118],
212
+ [119, 121],
213
+ [122, 126],
214
+ ]
215
+ bodyspec = [
216
+ [0, 2],
217
+ [3, 13],
218
+ [14, 27],
219
+ [28, 33],
220
+ [34, 39],
221
+ [40, 45],
222
+ [46, 51],
223
+ [52, 57],
224
+ [58, 63],
225
+ [64, 69],
226
+ [70, 75],
227
+ [76, 81],
228
+ [82, 95],
229
+ [96, 105],
230
+ [106, 115],
231
+ [116, 126],
232
+ ]
233
+
234
+ head = pd.read_fwf(StringIO("\n".join(d[:2])), colspecs=headspec)
235
+ body = pd.read_fwf(StringIO("\n".join(d[2:])), colspecs=bodyspec)
236
+
237
+ data = {}
238
+ data["site"] = head["Site"][0] if not pd.isna(head["Site"][0]) else ""
239
+ data["filename"] = filename
240
+ data["specimen"] = head["Name"][0] if not pd.isna(head["Name"][0]) else ""
241
+ data["longitude"] = (
242
+ float(head["Longitude"][0]) if not pd.isna(head["Longitude"][0]) else None
243
+ )
244
+ data["latitude"] = (
245
+ float(head["Latitude"][0]) if not pd.isna(head["Latitude"][0]) else None
246
+ )
247
+ data["height"] = (
248
+ float(head["Height"][0]) if not pd.isna(head["Height"][0]) else None
249
+ )
250
+ data["rock"] = head["Rock"][0] if not pd.isna(head["Rock"][0]) else ""
251
+ data["age"] = head["Age"][0] if not pd.isna(head["Age"][0]) else ""
252
+ data["formation"] = head["Fm"][0] if not pd.isna(head["Fm"][0]) else ""
253
+ data["sref"] = Pair(180, 0, 180, 0)
254
+ data["gref"] = Pair(
255
+ float(head["SDec"][0]),
256
+ float(head["SInc"][0]),
257
+ float(head["SDec"][0]),
258
+ float(head["SInc"][0]),
259
+ )
260
+ data["bedding"] = (
261
+ Foliation(float(head["BDec"][0]), float(head["BInc"][0]))
262
+ if not pd.isna(head["BDec"][0]) and not pd.isna(head["BInc"][0])
263
+ else None
264
+ )
265
+ data["foldaxis"] = (
266
+ Lineation(float(head["FDec"][0]), float(head["FInc"][0]))
267
+ if not pd.isna(head["FDec"][0]) and not pd.isna(head["FInc"][0])
268
+ else None
269
+ )
270
+ data["date"] = datetime.now()
271
+ ix = body.iloc[:, 0].apply(lambda x: x not in exclude)
272
+ data["steps"] = body[ix].iloc[:, 1].astype(int).to_list()
273
+ data["comments"] = body[ix]["Note"].to_list()
274
+ data["a95"] = body[ix]["Prec"].to_list()
275
+ data["vectors"] = []
276
+ for n, r in body[ix].iterrows():
277
+ data["vectors"].append(r.iloc[2] * Vector3(r["Dsp"], r["Isp"]))
278
+ return cls(**data)
279
+
280
+ def write_rs3(self, filename=None):
281
+ """Save ``Core`` instance to RS3 file.
282
+
283
+ Args:
284
+ filename: RS3 file
285
+
286
+ """
287
+ if filename is None:
288
+ filename = self.filename
289
+
290
+ head = "Name Site Latitude Longitude Height Rock Age Fm SDec SInc BDec BInc FDec FInc P1 P2 P3 P4 Note"
291
+ step_lbl = f"Step[{self.demag_units}]"
292
+ module_lbl = f"M[{self.module_units}]"
293
+ susceptibility_lbl = f"K[{self.susceptibility_units}]"
294
+ subhead = f"ID {step_lbl:<10} {module_lbl:>12} Dsp Isp Dge Ige Dtc Itc Dfc Ifc Prec {susceptibility_lbl:>13} Limit1 Limit2 Note "
295
+ latitude = self.latitude if self.latitude is not None else ""
296
+ longitude = self.longitude if self.longitude is not None else ""
297
+ height = self.height if self.height is not None else ""
298
+ sdec, sinc = (round(self.gref.fol.geo[0]), round(self.gref.fol.geo[1]))
299
+ bdec, binc = (
300
+ (round(self.bedding.geo[0]), round(self.bedding.geo[1]))
301
+ if self.bedding is not None
302
+ else ("", "")
303
+ )
304
+ fdec, finc = (
305
+ (round(self.foldaxis.geo[0]), round(self.foldaxis.geo[1]))
306
+ if self.foldaxis is not None
307
+ else ("", "")
308
+ )
309
+ hline = f"{self.specimen:9} {self.site:9} {latitude:<9} {longitude:<10} {height:<9} {self.rock:14} {self.age:<7} {sdec:<5} {sinc:<5} {bdec:<5} {binc:<5} {fdec:<5} {finc:<5} 12 0 6 0 "
310
+ prefix = "T" if self.demag_units == "°C" else "M"
311
+ with open(filename, "w", encoding="windows-1250") as res3file:
312
+ print(head, file=res3file, end="\r\n")
313
+ print(hline, file=res3file, end="\r\n")
314
+ print(subhead, file=res3file, end="\r\n")
315
+
316
+ ids = ["N"] + (len(self.steps) - 1) * [prefix]
317
+ for id, step, MAG, V, geo, tilt, a95, comment in zip(
318
+ ids,
319
+ self.steps,
320
+ self.MAG,
321
+ self.V,
322
+ self.geo,
323
+ self.tilt,
324
+ self.a95,
325
+ self.comments,
326
+ ):
327
+ ln = f"{id:2} {step:<10} {MAG:>13g} {V.geo[0]:>5.1f} {V.geo[1]:> 5.1f} {geo.geo[0]:>5.1f} {geo.geo[1]:> 5.1f} {tilt.geo[0]:>5.1f} {tilt.geo[1]:> 5.1f} {a95:>5.1f} {comment:10}"
328
+ print(ln, file=res3file, end="\r\n")
329
+
330
+ @property
331
+ def datatable(self):
332
+ """Return data list of strings"""
333
+ tb = []
334
+ for step, MAG, V, geo, tilt, a95, comment in zip(
335
+ self.steps,
336
+ self.MAG,
337
+ self.V,
338
+ self.geo,
339
+ self.tilt,
340
+ self.a95,
341
+ self.comments,
342
+ ):
343
+ ln = "{:<4} {: 9.2E} {: 9.2E} {: 9.2E} {: 9.2E} {:5.1f} {:5.1f} {:5.1f} {:5.1f} {:4.1f} {}".format(
344
+ step,
345
+ V.x,
346
+ V.y,
347
+ V.z,
348
+ MAG,
349
+ geo.geo[0],
350
+ geo.geo[1],
351
+ tilt.geo[0],
352
+ tilt.geo[1],
353
+ a95,
354
+ comment,
355
+ )
356
+ tb.append(ln)
357
+ return tb
358
+
359
+ def show(self):
360
+ """Show data"""
361
+ print(
362
+ "site:{} specimen:{} file:{}\nbedding:{} volume:{}m3 {}".format(
363
+ self.site,
364
+ self.specimen,
365
+ os.path.basename(self.filename),
366
+ self.bedding,
367
+ eformat(self.volume, 2),
368
+ self.date.strftime("%m-%d-%Y %H:%M"),
369
+ )
370
+ )
371
+ print(
372
+ "STEP Xc [Am2] Yc [Am2] Zc [Am2] MAG[A/m] Dge Ige Dtc Itc a95 "
373
+ )
374
+ print("\n".join(self.datatable))
375
+
376
+ @property
377
+ def MAG(self):
378
+ """Returns numpy array of MAG values"""
379
+ return np.array([abs(v) / self.volume for v in self._vectors])
380
+
381
+ @property
382
+ def nsteps(self):
383
+ """Returns steps as numpy array of numbers"""
384
+ pp = [re.findall(r"\d+", str(s)) for s in self.steps]
385
+ return np.array([int(s[0]) if s else 0 for s in pp])
386
+
387
+ @property
388
+ def V(self):
389
+ """Returns ``Vector3Set`` of vectors in sample (or core) coordinates system"""
390
+ return Vector3Set([v / self.volume for v in self._vectors], name=self.specimen)
391
+
392
+ @property
393
+ def geo(self):
394
+ """Returns ``Vector3Set`` of vectors in in-situ coordinates system"""
395
+ H = DeformationGradient3.from_two_pairs(self.sref, self.gref)
396
+ return self.V.transform(H)
397
+
398
+ @property
399
+ def tilt(self):
400
+ """Returns ``Vector3Set`` of vectors in tilt‐corrected coordinates system"""
401
+ return self.geo.rotate(
402
+ Lineation(self.bedding.geo[0] - 90, 0), -self.bedding.geo[1]
403
+ )
404
+
405
+ def pca(self, kind="geo", origin=False):
406
+ """
407
+ PCA analysis to calculate principal component and MAD
408
+
409
+ Keyword Args:
410
+ kind (str): "V", "geo" or "tilt". Default "geo"
411
+ origin (bool): Whether to include origin. Default False
412
+ """
413
+ data = getattr(self, kind)
414
+ if not origin:
415
+ r = data.R(mean=True) / len(data)
416
+ data = Vector3Set([v - r for v in data])
417
+ ot = data.ortensor()
418
+ if ot.shape > 1:
419
+ pca = ot.V1
420
+ if pca.angle(r) > 90:
421
+ pca = -pca
422
+ else:
423
+ pca = ot.V3
424
+ mad = ot.MAD()
425
+ return pca, mad