ararpy 0.1.13__tar.gz → 0.1.15__tar.gz

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.
Files changed (72) hide show
  1. {ararpy-0.1.13 → ararpy-0.1.15}/PKG-INFO +10 -2
  2. ararpy-0.1.15/ararpy/argon_diffusion_simulator/__init__.py +12 -0
  3. ararpy-0.1.15/ararpy/argon_diffusion_simulator/main.py +631 -0
  4. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/age.py +2 -2
  5. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/basic.py +1 -1
  6. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/corr.py +8 -8
  7. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/plot.py +3 -3
  8. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/raw_funcs.py +2 -0
  9. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/regression.py +0 -5
  10. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/spectra.py +3 -1
  11. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/files/calc_file.py +17 -9
  12. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/corr.py +9 -1
  13. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/diffusion_funcs.py +34 -12
  14. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/export.py +620 -374
  15. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/initial.py +9 -0
  16. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/json.py +7 -0
  17. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/plots.py +6 -4
  18. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/sample.py +11 -7
  19. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy.egg-info/PKG-INFO +10 -2
  20. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy.egg-info/SOURCES.txt +2 -0
  21. {ararpy-0.1.13 → ararpy-0.1.15}/setup.py +1 -1
  22. {ararpy-0.1.13 → ararpy-0.1.15}/LICENSE +0 -0
  23. {ararpy-0.1.13 → ararpy-0.1.15}/README.md +0 -0
  24. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/__init__.py +0 -0
  25. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/__init__.py +0 -0
  26. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/arr.py +0 -0
  27. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/err.py +0 -0
  28. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/histogram.py +0 -0
  29. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/isochron.py +0 -0
  30. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/calc/jvalue.py +0 -0
  31. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/022_VU124-M11a.ahd +0 -0
  32. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/20WHA0103.age +0 -0
  33. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/22WHA0078.xls +0 -0
  34. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/22WHA0433.age +0 -0
  35. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/22WHA0433.arr +0 -0
  36. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/22WHA0433.full.xls +0 -0
  37. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/24WHN0001-51-592.XLS +0 -0
  38. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/AHD.input-filter +0 -0
  39. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/ArAr.calc +0 -0
  40. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/ArArCALC.age +0 -0
  41. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/NGX-600 - Copy.TXT +0 -0
  42. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/NGX-600.TXT +0 -0
  43. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/NGX-XLS.input-filter +0 -0
  44. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/Qtegra-exported-xls.input-filter +0 -0
  45. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/S01-239.csv +0 -0
  46. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/WH01.irra +0 -0
  47. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/WHA.pdf +0 -0
  48. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/raw_example.xls +0 -0
  49. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/examples/sample-default.smp +0 -0
  50. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/files/__init__.py +0 -0
  51. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/files/arr_file.py +0 -0
  52. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/files/basic.py +0 -0
  53. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/files/new_file.py +0 -0
  54. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/files/raw_file.py +0 -0
  55. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/files/xls.py +0 -0
  56. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/EXPORT_TO_PDF_DATA_PROPERTIES.py +0 -0
  57. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/__init__.py +0 -0
  58. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/basic.py +0 -0
  59. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/calculation.py +0 -0
  60. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/consts.py +0 -0
  61. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/info.py +0 -0
  62. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/raw.py +0 -0
  63. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/style.py +0 -0
  64. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/smp/table.py +0 -0
  65. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy/test.py +0 -0
  66. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy.egg-info/dependency_links.txt +0 -0
  67. {ararpy-0.1.13 → ararpy-0.1.15}/ararpy.egg-info/top_level.txt +0 -0
  68. {ararpy-0.1.13 → ararpy-0.1.15}/setup.cfg +0 -0
  69. {ararpy-0.1.13 → ararpy-0.1.15}/tests/test.py +0 -0
  70. {ararpy-0.1.13 → ararpy-0.1.15}/tests/test2.py +0 -0
  71. {ararpy-0.1.13 → ararpy-0.1.15}/tests/test_error_correlation.py +0 -0
  72. {ararpy-0.1.13 → ararpy-0.1.15}/tests/test_regression_methods.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: ararpy
3
- Version: 0.1.13
3
+ Version: 0.1.15
4
4
  Summary: A project for Ar-Ar geochronology
5
5
  Home-page: https://github.com/wuyangchn/ararpy.git
6
6
  Author: Yang Wu
@@ -12,6 +12,14 @@ Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.5
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: requires-python
22
+ Dynamic: summary
15
23
 
16
24
  # ArArPy
17
25
 
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ # ==========================================
5
+ # Copyright 2025 Yang
6
+ # webarar - __init__.py
7
+ # ==========================================
8
+ #
9
+ #
10
+ #
11
+ """
12
+ from . import main
@@ -0,0 +1,631 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ # ==========================================
5
+ # Copyright 2024 Yang
6
+ # ArgonDiffusionRandomWalk - main
7
+ # ==========================================
8
+ #
9
+ #
10
+ #
11
+ """
12
+ import os
13
+ import pickle
14
+ import numpy as np
15
+ import math
16
+ import copy
17
+ import time
18
+ from typing import List
19
+
20
+ np.set_printoptions(precision=18, threshold=10000, linewidth=np.inf)
21
+
22
+ SETUP_PRINT = "natoms = {0}, nsteps = {1}, grain_size = {2}, time_scale = {3}, size_scale = {4}, number_scale = {5}"
23
+
24
+
25
+ def _uniform_sphere_step(step_length, shape):
26
+ """
27
+ 在三维空间中生成均匀分布的球面样本,且步长严格为指定长度。
28
+
29
+ Parameters:
30
+ step_length (array, float): 步长的固定长度, (n, m)
31
+ num_samples (tuple): 步长shape, (n, m)
32
+
33
+ Returns:
34
+ np.ndarray: 形状为 (num_samples, 3) 的数组,每行是一个随机步长向量
35
+ """
36
+ # 随机生成方位角 phi 和极角的 cos(theta)
37
+ num_samples, dimension = shape
38
+ phi = np.random.uniform(0, 2 * np.pi, num_samples)
39
+ cos_theta = np.random.uniform(-1, 1, num_samples)
40
+ sin_theta = np.sqrt(1 - cos_theta ** 2) # sin(theta) = sqrt(1 - cos^2(theta))
41
+
42
+ # 将球面坐标转换为笛卡尔坐标
43
+ x = sin_theta * np.cos(phi)
44
+ y = sin_theta * np.sin(phi)
45
+ z = cos_theta
46
+
47
+ # 构建单位向量
48
+ unit_vectors = np.vstack((x, y, z)).T
49
+
50
+ # 乘以步长,得到最终的步长向量
51
+ steps = step_length * unit_vectors
52
+ return steps
53
+
54
+
55
+ def walker(pos, step_length, total_nsteps, min_bound, max_bound, scale=0., compensation=0, remove=True,
56
+ conditions=None, boundary_factor=1, decay=0, parent=0):
57
+
58
+ if len(pos) == 0:
59
+ return pos
60
+
61
+ dimension = pos.shape[1] if len(pos.shape) > 1 else 1
62
+
63
+ sigma = step_length * math.sqrt(scale)
64
+
65
+ if conditions is None:
66
+ if dimension == 3:
67
+ conditions = np.array([[-50, -50, -50, 50, 50, 50, 1]])
68
+ else:
69
+ conditions = np.array([[-50, -50, 50, 50, 1]])
70
+
71
+ num_big_steps = int(total_nsteps // scale) if int(scale) > 1 else 0
72
+ num_small_steps = int(total_nsteps % scale) if int(scale) > 1 else int(total_nsteps)
73
+
74
+ for _ in range(num_big_steps):
75
+ # print(f"new walker {_ = } {len(pos) = }")
76
+ # 边界判断(布尔掩码)
77
+ in_boundary_mask = np.all(
78
+ (pos >= (min_bound + sigma * compensation)) & (pos <= (max_bound - sigma * compensation)), axis=1)
79
+ in_core = pos[in_boundary_mask]
80
+ in_boundary = pos[~in_boundary_mask]
81
+
82
+ # 核心粒子随机行走
83
+ coefficients = np.ones(len(in_core))
84
+ for each in conditions:
85
+ # each = x1, y1, z1, x2, y2, z2, coeff for dimension=3
86
+ cond = np.all(
87
+ (in_core >= np.array(each[:dimension])) & (in_core <= np.array(each[dimension:int(2*dimension)])),
88
+ axis=1)
89
+ coefficients = np.where(cond, each[-1] ** 0.5, coefficients)
90
+ steps = np.random.normal(0, sigma, size=in_core.shape)
91
+ in_core += steps * coefficients[:, None]
92
+
93
+ # 将边界粒子以更小 scale 的步长模拟
94
+ if scale > 1:
95
+ in_boundary = walker(in_boundary, step_length, scale, min_bound, max_bound, scale // 2,
96
+ compensation, remove, conditions, boundary_factor)
97
+
98
+ # 拼接核心和边界粒子
99
+ pos = np.concatenate((in_boundary, in_core))
100
+
101
+ # 移除超出范围的粒子
102
+ if remove:
103
+ pos = pos[np.all((pos >= min_bound) & (pos <= max_bound), axis=1)]
104
+
105
+ # 最后处理 scale < 1 的小步模拟, boundary_factor用来缩放这里的处理步数,1 < boundary_factor <= 1如果 boundary_factor=1,将完全吻合实际
106
+ # boundary_factor < 1 将加快一些速度, boundary_factor=0 将忽略小步数
107
+ k = boundary_factor * max(conditions[:, -1])
108
+ for _ in range(int(num_small_steps * k)):
109
+ coefficients = np.ones(len(pos))
110
+ for each in conditions:
111
+ cond = np.all((pos >= np.array(each[:dimension])) & (pos <= np.array(each[dimension:int(2*dimension)])),
112
+ axis=1)
113
+ coefficients = np.where(cond, (each[-1] / k) ** 0.5, coefficients)
114
+ # coefficients = np.where(cond, coeff ** 0.5, coefficients)
115
+ steps = np.random.normal(0, step_length, size=np.shape(pos))
116
+ pos += steps * coefficients[:, None]
117
+
118
+ if remove:
119
+ pos = pos[np.all((pos >= min_bound) & (pos <= max_bound), axis=1)]
120
+
121
+ return pos
122
+
123
+
124
+ def walker2(pos: np.ndarray, duration, step_length, min_bound, max_bound, time_scale, frequency,
125
+ conditions=None, remove: bool = True, decay=0, parent=0):
126
+ """
127
+ :return:
128
+ """
129
+
130
+ if len(pos) == 0:
131
+ return pos
132
+
133
+ dimension = pos.shape[-1] if len(pos.shape) > 1 else 1
134
+
135
+
136
+ dt = time_scale
137
+ if conditions is None:
138
+ if dimension == 3:
139
+ conditions = np.array([[-50, -50, -50, 50, 50, 50, 1]])
140
+ else:
141
+ conditions = np.array([[-50, -50, 50, 50, 1]])
142
+
143
+ for step in range(int(1e16)):
144
+
145
+ n = parent - int(parent * math.exp(-1 * decay * dt / (3600 * 24 * 365.2425)))
146
+ # print(f"number of new pos = {n}")
147
+ parent = parent - n
148
+ new_pos = np.random.uniform(max(max_bound), min(min_bound), size=(int(n), dimension))
149
+ new_pos = new_pos[np.all((new_pos >= min_bound) & (new_pos <= max_bound), axis=1)]
150
+ pos = np.concatenate((pos, new_pos), axis=0)
151
+
152
+ res = np.zeros(0).reshape(0, dimension)
153
+ for index, each in enumerate(conditions):
154
+ # each = (x1, y1, z1, x2, y2, z2, gamma)
155
+ d = step_length * math.sqrt(each[-1] * frequency * time_scale)
156
+
157
+ locs = np.ones(len(pos)) * 999
158
+ for _index, _each in enumerate(conditions):
159
+ cond = np.all((pos >= np.array(_each[:dimension])) & (pos <= np.array(_each[dimension:int(2*dimension)])), axis=1)
160
+ locs = np.where(cond, _index, locs)
161
+
162
+ partial_pos = pos[locs == index]
163
+
164
+ # c = len(partial_pos)
165
+ # # 初始化步长
166
+ # steps = np.zeros(partial_pos.shape, dtype=float)
167
+ # # 移动方向, x or y or z
168
+ # directions = np.random.choice(list(range(dimension)), size=c)
169
+ # # 方向, positive or negative, 步长
170
+ # steps[np.arange(c), directions] = np.random.choice([-d, d], size=c, p=[0.5, 0.5])
171
+ steps = np.random.normal(0, d, size=partial_pos.shape)
172
+
173
+ partial_pos += steps
174
+
175
+ if remove:
176
+ partial_pos = partial_pos[np.all((partial_pos >= min_bound) & (partial_pos <= max_bound), axis=1)]
177
+
178
+ res = np.concatenate((res, partial_pos))
179
+
180
+ pos = copy.deepcopy(res)
181
+
182
+ _t = (step + 1) * dt
183
+
184
+ if abs(_t - duration) < dt:
185
+ break
186
+
187
+ return pos
188
+
189
+
190
+ class OverEpsilonError(Exception):
191
+ def __init__(self, i, d, e):
192
+ self.i = i
193
+ self.d = d
194
+ self.e = e
195
+
196
+ def __str__(self):
197
+ return f"OverEpsilonError: Index = {self.i}, d = {self.d} > e = {self.e}"
198
+
199
+
200
+ class Domain:
201
+ def __init__(self, dimension: int = 3, atom_density: float = 1e14, fraction: float = 1.0,
202
+ min_bound=None, max_bound=None, **kwargs):
203
+
204
+ self.dimension = dimension
205
+ self.size = ...
206
+ self.atom_density = atom_density
207
+ self.fraction = fraction
208
+ self.time_scale = 1
209
+ self.size_scale = 0.05
210
+
211
+ self.energy = 120 * 1000 # J/mol
212
+ self.density = 0
213
+ self.fracture = 0
214
+ self.boundary = 0
215
+ self.defect = 0
216
+ self.pho = 1
217
+ self.step_length = 0.0001 # 1 A = 0.1 nm = 0.0001 μm
218
+
219
+ self.current_temp = 273.15
220
+
221
+ self.inclusions: List[Domain] = []
222
+ self.natoms: int = 0
223
+ self.positions = []
224
+ self.min_bound = min_bound
225
+ self.max_bound = max_bound
226
+ self.remained_per_step = []
227
+ self.released_per_step = []
228
+
229
+ self.attr = {}
230
+
231
+ for key, val in kwargs.items():
232
+ if hasattr(self, key):
233
+ setattr(self, key, val)
234
+
235
+ self.setup()
236
+
237
+ def setup(self):
238
+ """
239
+ Used to set or reset positions of atoms
240
+ :return:
241
+ """
242
+
243
+ # 初始化粒子位置,随机分布在网格内
244
+ # 初始原子个数 1e14 原子密度 个/立方厘米 1e-4是单位换算 cm/µm
245
+ natoms = int(self.atom_density ** (self.dimension / 3) * (abs(max(self.max_bound) - min(self.min_bound)) * 1e-4) ** self.dimension)
246
+ natoms = 1 if natoms == 0 else natoms
247
+ self.positions = np.random.uniform(max(self.max_bound), min(self.min_bound), size=(int(natoms), self.dimension))
248
+ self.positions = self.positions[np.all((self.positions >= self.min_bound) & (self.positions <= self.max_bound), axis=1)]
249
+ for dom in self.inclusions:
250
+ self.positions = self.positions[~np.all((self.positions >= dom.min_bound) & (self.positions <= dom.max_bound), axis=1)]
251
+ self.natoms = len(self.positions)
252
+
253
+ self.remained_per_step = []
254
+ self.released_per_step = []
255
+
256
+ def get_gamma(self, energy: float = None, temperature: float = None):
257
+ r = 8.31446261815324 # J / (K * mol)
258
+ T = self.current_temp if temperature is None else temperature
259
+ e = self.energy if energy is None else energy
260
+ alpha = 1 - self.density
261
+ p1 = math.exp(- e / (r * T))
262
+ p2 = math.exp(- e / 2 / (r * T)) # p2是给定活化能的一半,用于假定晶面扩散的活化能
263
+ gamma = p1 * alpha * (1 - self.fracture) + self.fracture * p2 + self.boundary + self.defect
264
+ return gamma
265
+
266
+ def get_natoms(self, pos=None):
267
+ if pos is None:
268
+ return len(self.positions)
269
+ pos = pos[np.all((pos >= self.min_bound) & (pos <= self.max_bound), axis=1)]
270
+ for dom in self.inclusions:
271
+ pos = pos[~np.all((pos >= dom.min_bound) & (pos <= dom.max_bound), axis=1)]
272
+ return len(pos)
273
+
274
+ def set_attr(self, name, value, index=None):
275
+ if index is None:
276
+ self.attr.update({name: value})
277
+ else:
278
+ if name not in self.attr.keys():
279
+ self.attr.update({name: {}})
280
+ self.attr[name].update({index: value})
281
+
282
+
283
+ class DiffSimulation:
284
+ def __init__(self):
285
+ self.name = "example"
286
+ self.loc = "examples"
287
+ self.score = 0
288
+ self.end_at = 0
289
+ self.dimension = 3
290
+
291
+ self.size_scale = 1
292
+ self.length_scale = 1
293
+
294
+ self.atom_density = 1e14 # 原子密度 个/立方厘米
295
+ self.frequency = 1e13 # 每秒振动的次数, Debye frequency
296
+
297
+ self.grain_size = 300 # 0.3 mm = 300 μm = 300000 nm
298
+ self.grid_size = self.grain_size * self.size_scale # 网格大小
299
+ self.nsteps: int = ...
300
+ self.natoms: int = ...
301
+ self.epsilon = 1e-5
302
+ self.step_length = 0.0001 * 2.5 # 1 A = 0.1 nm = 0.0001 μm
303
+
304
+ self.seq: [List, np.ndarray] = []
305
+ self.domains: List[Domain] = []
306
+ self.positions: [List, np.ndarray] = []
307
+
308
+ self.remained_per_step = []
309
+ self.released_per_step = []
310
+
311
+ def setup(self):
312
+ """
313
+ Used to set or reset positions of atoms
314
+ :return:
315
+ """
316
+ # 网格大小和颗粒边界
317
+ self.grid_size = self.grain_size * self.size_scale # 网格大小
318
+ center = np.array([0 for i in range(self.dimension)])
319
+ self.min_bound = center - self.grid_size / 2
320
+ self.max_bound = center + self.grid_size / 2
321
+
322
+ # 粒子位置
323
+ self.positions = np.concatenate([dom.positions for dom in self.domains])
324
+ # 粒子数目
325
+ self.natoms = len(self.positions)
326
+
327
+ self.remained_per_step = []
328
+ self.released_per_step = []
329
+
330
+ def run_sequence(self, times, temperatures, statuses, targets, domains: List[Domain], nsteps_factor=None,
331
+ simulating: bool = False, epsilon=0.001, start_time=0, k=1.2, use_walker1=True,
332
+ scoring: bool = False, decay=0, parent=0):
333
+ pos = copy.deepcopy(self.positions)
334
+ self.seq = [] # 用于记录参数
335
+
336
+ for index, (duration, temperature, target, status) in enumerate(zip(times, temperatures, targets, statuses)):
337
+
338
+ self.seq.append({})
339
+
340
+ loop = 0
341
+ e_low = 100 * 1000
342
+ e_up = 300 * 1000
343
+
344
+ _start = time.time()
345
+ heating_duration = duration - start_time # in seconds
346
+ start_time = duration
347
+ temperature = temperature + 273.15 # in Kelvin
348
+
349
+ while loop < 50:
350
+
351
+ e = (e_low + e_up) / 2
352
+
353
+ total_steps = self.frequency * heating_duration # 振动次数
354
+ conditions = np.array(
355
+ [[*dom.min_bound, *dom.max_bound,
356
+ dom.get_gamma(temperature=temperature, energy=e if simulating else None)] for dom in domains if dom.energy != 0 and not simulating])
357
+
358
+ if use_walker1:
359
+
360
+ # print(f"调整前: {nsteps_factor = }, gamma = {conditions[0][-1]}, {total_steps = }")
361
+ nsteps_factor = 10 ** math.floor(
362
+ -math.log10(max(conditions[:, -1]))) * 1000 # 用来调节次数和步长的巨大差异,次数太大,但步程太小
363
+ while int(total_steps / nsteps_factor) < 1:
364
+ nsteps_factor /= 10
365
+ while int(total_steps / nsteps_factor) > 10000:
366
+ nsteps_factor *= 10
367
+ total_steps = int(total_steps / nsteps_factor) # 振动次数
368
+ conditions[:, -1] *= nsteps_factor
369
+ compensation = max(3, *(np.ceil(np.sqrt(conditions[:, -1])) * 3))
370
+ boundary_factor = 1
371
+ if max(conditions[:, -1]) > 1000:
372
+ # k = 0时,bf = 1, 完全无缩放 # k = 1时,bf <= 0.5 小步行走将至少缩放一半 # 推荐值 k = 1.2
373
+ boundary_factor = 0.1 ** (k * math.log10(1 + (max(conditions[:, -1]) // 1000)))
374
+ step_length = self.step_length / np.sqrt(pos.shape[1] if len(pos.shape) > 1 else 1)
375
+ scale = int(total_steps)
376
+ # print(f"调整后: {nsteps_factor = }, gamma = {conditions[0][-1]}, {total_steps = }, {compensation = }, {boundary_factor = }")
377
+
378
+ _pos = walker(
379
+ copy.deepcopy(pos), step_length=step_length, total_nsteps=total_steps,
380
+ min_bound=self.min_bound, max_bound=self.max_bound, scale=scale,
381
+ compensation=compensation, remove=True, conditions=conditions, boundary_factor=boundary_factor,
382
+ decay=decay, parent=parent
383
+ )
384
+
385
+ # self.seq[index].update({
386
+ # "duration": duration, "temperature": temperature, "target": target, "step_length": step_length,
387
+ # "scale": scale, "compensation": compensation, "boundary_factor": boundary_factor, "e": e,
388
+ # "simulation": simulating, "nsteps_factor": nsteps_factor, "conditions": conditions
389
+ # })
390
+
391
+ else:
392
+ step_length = self.step_length / np.sqrt(pos.shape[1] if len(pos.shape) > 1 else 1)
393
+ scale = k
394
+ _pos = walker2(copy.deepcopy(pos), duration=heating_duration, step_length=step_length,
395
+ min_bound=self.min_bound, max_bound=self.max_bound, time_scale=scale,
396
+ frequency=self.frequency, conditions=conditions, remove=True, decay=decay, parent=parent)
397
+ boundary_factor = 1
398
+ compensation = 0
399
+
400
+ self.seq[index].update({
401
+ "duration": duration, "temperature": temperature, "target": target, "step_length": step_length,
402
+ "scale": scale, "compensation": compensation, "boundary_factor": boundary_factor, "e": e,
403
+ "simulation": simulating, "nsteps_factor": nsteps_factor, "conditions": conditions
404
+ })
405
+
406
+ released = 1 - len(_pos) / self.natoms
407
+ d = released - target
408
+ if simulating:
409
+ loop += 1
410
+ print(f"{e = }, {d = }, {abs(d) <= epsilon}")
411
+ if abs(d) <= epsilon:
412
+ break
413
+ elif d > 0:
414
+ e_low = e
415
+ else:
416
+ e_up = e
417
+ elif scoring:
418
+ self.score += (d * 100) ** 2
419
+ print(f"{self.score = }, {d = }")
420
+ if self.score > 4 * (index + 1):
421
+ raise OverEpsilonError(index, self.score, 4 * (index + 1))
422
+ else:
423
+ break
424
+ else:
425
+ break
426
+
427
+ pos = _pos
428
+
429
+ if status:
430
+ # status == True: argon collection, False: pumping
431
+ for dom in self.domains:
432
+ _c = dom.get_natoms(pos)
433
+ dom.remained_per_step.append(_c)
434
+ dom.released_per_step.append(dom.natoms - _c)
435
+ dom.set_attr("energy", float(e), index=index)
436
+
437
+ self.remained_per_step.append(len(pos))
438
+ self.released_per_step.append(self.natoms - len(pos))
439
+
440
+ print(f"{index = } {duration} - {heating_duration = } - {temperature = } - {total_steps = } - conc = {len(pos) / self.natoms * 100:.2f}% - {time.time() - _start:.5f}s")
441
+
442
+ self.positions = copy.deepcopy(pos)
443
+
444
+ def run_persecond(self, times, temperatures, statuses, targets, domains: List[Domain], scale=None,
445
+ simulation: bool = False, epsilon=0.001, start_time=0):
446
+ seq = []
447
+ for i in range(1, int(times[-1] + 1), 300):
448
+ for index, time in enumerate(times):
449
+ if (times[index-1] if index > 0 else 0) < i <= times[index]:
450
+ seq.append([i, temperatures[index], statuses[index], targets[index]])
451
+ seq = np.transpose(seq)
452
+ return self.run_sequence(*seq, domains=domains, nsteps_factor=scale, simulating=simulation,
453
+ epsilon=epsilon, start_time=start_time)
454
+
455
+
456
+ def run(times, temps, statuses, energies, fractions, ndoms: int = 1, grain_szie=275, atom_density=1e10, frequency=1e13,
457
+ dimension: int = 3, targets: list = None, epsilon: float = 0.001, simulation: bool = False,
458
+ file_name: str = "Y70", ignore_error: bool = False, positions=None, **kwargs):
459
+ """
460
+ :param times:
461
+ :param temps:
462
+ :param statuses:
463
+ :param energies:
464
+ :param fractions:
465
+ :param dimension:
466
+ :param ndoms:
467
+ :param targets:
468
+ :param epsilon:
469
+ :param simulation:
470
+ :param file_name:
471
+ :param ignore_error:
472
+ :return:
473
+ """
474
+
475
+ demo = DiffSimulation()
476
+
477
+ def _(n, es, fs):
478
+ # fs 应从大到小,父空间在前,子空间在后
479
+
480
+ # demo.grain_size = 300
481
+ # demo.size_scale = 0.05
482
+ # demo.atom_density = 1e14 # 原子密度 个/立方厘米
483
+ demo.dimension = dimension
484
+
485
+ demo.size_scale = 1
486
+ demo.grain_size = grain_szie
487
+ demo.atom_density = atom_density # 原子密度 个/立方厘米
488
+ demo.frequency = frequency
489
+
490
+ # domains应该从外到内
491
+ domains = []
492
+ for i in range(n-1, 0-1, -1):
493
+ size = int(demo.grain_size * fs[i]) * demo.size_scale
494
+ center = np.zeros(demo.dimension)
495
+ if i == 2:
496
+ ad = demo.atom_density * 5 / 4
497
+ else:
498
+ ad = demo.atom_density
499
+ dom = Domain(
500
+ dimension=demo.dimension, atom_density=ad, min_bound=center - size / 2, max_bound=center + size / 2,
501
+ energy=es[i], fraction=fs[i], inclusions=[domains[-1]] if len(domains) >= 1 else []
502
+ )
503
+ domains.append(dom)
504
+ # domains应该从外到内, 上面为了inclusion以及方便不同扩散域设置不同的密度,要按照从小到大的顺序生成,但是后面行走的时候要根据不同条件设置系数,要从外到内
505
+ demo.domains = sorted(domains, key=lambda dom: dom.fraction, reverse=True)
506
+
507
+ demo.setup()
508
+
509
+ demo.name = f"{file_name}"
510
+
511
+ print(f"Total Atoms: {demo.natoms}, atoms in each dom: {[dom.natoms for dom in demo.domains]} filename: {demo.name}")
512
+
513
+ if positions is not None:
514
+ demo.positions = positions
515
+ demo.natoms = len(positions)
516
+
517
+ demo.run_sequence(times=times, temperatures=temps, statuses=statuses, targets=targets, domains=demo.domains,
518
+ epsilon=epsilon, simulating=simulation, **kwargs)
519
+ # demo.run_persecond(times=times, temperatures=temps, domains=demo.domains, targets=target,
520
+ # epsilon=epsilon, simulation=simulation)
521
+
522
+ return demo
523
+
524
+ try:
525
+ return _(ndoms, energies, fractions), True
526
+ except OverEpsilonError as e:
527
+ if ignore_error:
528
+ return demo, False
529
+ else:
530
+ raise
531
+
532
+
533
+ def run_40ar(times, temps, statuses, energies, fractions, ndoms: int = 1, grain_szie=275, atom_density=1e10, frequency=1e13,
534
+ dimension: int = 3, targets: list = None, epsilon: float = 0.001, simulation: bool = False,
535
+ file_name: str = "Y70", ignore_error: bool = False, **kwargs):
536
+ """
537
+ :param times:
538
+ :param temps:
539
+ :param statuses:
540
+ :param energies:
541
+ :param fractions:
542
+ :param dimension:
543
+ :param ndoms:
544
+ :param targets:
545
+ :param epsilon:
546
+ :param simulation:
547
+ :param file_name:
548
+ :param ignore_error:
549
+ :return:
550
+ """
551
+
552
+ demo = DiffSimulation()
553
+
554
+ def _(n, es, fs):
555
+ # fs 应从大到小,父空间在前,子空间在后
556
+
557
+ # demo.grain_size = 300
558
+ # demo.size_scale = 0.05
559
+ # demo.atom_density = 1e14 # 原子密度 个/立方厘米
560
+ demo.dimension = dimension
561
+
562
+ demo.size_scale = 1
563
+ demo.grain_size = grain_szie
564
+ demo.atom_density = atom_density # 原子密度 个/立方厘米
565
+ demo.frequency = frequency
566
+
567
+ # domains应该从外到内
568
+ domains = []
569
+ for i in range(n-1, 0-1, -1):
570
+ size = int(demo.grain_size * fs[i]) * demo.size_scale
571
+ center = np.zeros(demo.dimension)
572
+ dom = Domain(
573
+ dimension=demo.dimension, atom_density=demo.atom_density, min_bound=center - size / 2, max_bound=center + size / 2,
574
+ energy=es[i], fraction=fs[i], inclusions=[domains[-1]] if len(domains) >= 1 else []
575
+ )
576
+ domains.append(dom)
577
+ # domains应该从外到内, 上面为了inclusion以及方便不同扩散域设置不同的密度,要按照从小到大的顺序生成,但是后面行走的时候要根据不同条件设置系数,要从外到内
578
+ demo.domains = sorted(domains, key=lambda dom: dom.fraction, reverse=True)
579
+
580
+ demo.setup()
581
+
582
+ demo.name = f"{file_name}"
583
+
584
+ print(f"Total Atoms: {demo.natoms}, atoms in each dom: {[dom.natoms for dom in demo.domains]} filename: {demo.name}")
585
+
586
+ demo.run_sequence(times=times, temperatures=temps, statuses=statuses, targets=targets, domains=demo.domains,
587
+ epsilon=epsilon, simulating=simulation, **kwargs)
588
+ # demo.run_persecond(times=times, temperatures=temps, domains=demo.domains, targets=target,
589
+ # epsilon=epsilon, simulation=simulation)
590
+
591
+ return demo
592
+
593
+ try:
594
+ return _(ndoms, energies, fractions), True
595
+ except OverEpsilonError as e:
596
+ if ignore_error:
597
+ return demo, False
598
+ else:
599
+ raise
600
+
601
+
602
+ def save_ads(ads: DiffSimulation, dir_path: str = None, name: str = None):
603
+ if dir_path is None:
604
+ dir_path = ""
605
+ if name is None:
606
+ name = ads.name
607
+ if not name.endswith(".ads"):
608
+ name = f"{name}.ads"
609
+ dir_path = os.path.join(dir_path, name)
610
+ with open(dir_path, 'wb') as f:
611
+ f.write(pickle.dumps(ads))
612
+ return name
613
+
614
+
615
+ def read_ads(file_path: str) -> DiffSimulation:
616
+
617
+ class RenameUnpickler(pickle.Unpickler):
618
+ def find_class(self, module: str, name: str):
619
+ if "argon_diffusion_simulator" in module:
620
+ return super(RenameUnpickler, self).find_class(DiffSimulation().__module__, name)
621
+ else:
622
+ return super(RenameUnpickler, self).find_class(module, name)
623
+
624
+ def renamed_load(file_obj) -> DiffSimulation:
625
+ return RenameUnpickler(file_obj).load()
626
+
627
+ try:
628
+ with open(file_path, 'rb') as f:
629
+ return renamed_load(f)
630
+ except (Exception, BaseException):
631
+ raise ValueError(f"ModuleNotFoundError")
@@ -20,6 +20,7 @@ import numpy as np
20
20
  def calc_age_min(F, sF, **kwargs) -> tuple:
21
21
  """
22
22
  Calculate ArAr ages using Min et al. (2000) equation.
23
+ Note that the Ar/K of primary standards is not working currently
23
24
  Parameters
24
25
  ----------
25
26
  F : array-like
@@ -68,7 +69,6 @@ def calc_age_min(F, sF, **kwargs) -> tuple:
68
69
  # recalculation using Min et al.(2000) equation
69
70
  # lmd = A * W * Y / (f * No)
70
71
  V = f * No / ((Ab + Ae) * W * Y)
71
- sf = 0 # the error of f was not considered by Koppers
72
72
  sV = pow((V / f * sf) ** 2 + (V / No * sNo) ** 2 + (V / (Ab + Ae)) ** 2 * (sAb ** 2 + sAe ** 2) +
73
73
  (V / W * sW) ** 2 + (V / Y * sY) ** 2, 0.5)
74
74
  # standard age in year, change to Ma
@@ -87,7 +87,7 @@ def calc_age_min(F, sF, **kwargs) -> tuple:
87
87
  KK = np.exp(t / V) - 1 # 40Arr / 40K Use standard age
88
88
  XX = BB * KK * R + 1
89
89
  k0 = V * np.log(XX)
90
- e1 = (np.log(XX) * V / f - V * BB * KK * R / (f * XX)) ** 2 * sf ** 2 # sFr
90
+ e1 = (np.log(XX) * V / f - V * BB * KK * R / (f * XX)) ** 2 * sf ** 2 # sF
91
91
  e2 = (np.log(XX) * V / No) ** 2 * sNo ** 2 # sNo
92
92
  e3 = (-1 * np.log(XX) * V / A + BB * KK * R / (A * XX)) ** 2 * sAb ** 2 # sAb
93
93
  e4 = (-1 * np.log(XX) * V / A - Ab * KK * R / (Ae ** 2 * XX)) ** 2 * sAe ** 2 # sAe