reframodel 2025.11.1__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 (31) hide show
  1. reframodel-2025.11.1/LICENSE +21 -0
  2. reframodel-2025.11.1/MANIFEST.in +7 -0
  3. reframodel-2025.11.1/PKG-INFO +90 -0
  4. reframodel-2025.11.1/README.md +58 -0
  5. reframodel-2025.11.1/RefraModel/__init__.py +4 -0
  6. reframodel-2025.11.1/RefraModel/__main__.py +7 -0
  7. reframodel-2025.11.1/RefraModel/editors/body_editor.py +20 -0
  8. reframodel-2025.11.1/RefraModel/editors/node_editor.py +20 -0
  9. reframodel-2025.11.1/RefraModel/editors/property_editor.py +24 -0
  10. reframodel-2025.11.1/RefraModel/geometry/bodies.py +83 -0
  11. reframodel-2025.11.1/RefraModel/geometry/lines.py +94 -0
  12. reframodel-2025.11.1/RefraModel/geometry/points.py +96 -0
  13. reframodel-2025.11.1/RefraModel/inversion/forward_model.py +208 -0
  14. reframodel-2025.11.1/RefraModel/inversion/inversion.py +846 -0
  15. reframodel-2025.11.1/RefraModel/inversion/mesh_builder.py +13 -0
  16. reframodel-2025.11.1/RefraModel/main.py +343 -0
  17. reframodel-2025.11.1/RefraModel/model_builder.py +2910 -0
  18. reframodel-2025.11.1/RefraModel/ui/dialogs.py +870 -0
  19. reframodel-2025.11.1/RefraModel/ui/menu.py +1045 -0
  20. reframodel-2025.11.1/RefraModel/ui/plot_manager.py +17 -0
  21. reframodel-2025.11.1/RefraModel/utils/file_io.py +23 -0
  22. reframodel-2025.11.1/RefraModel/utils/geometry_utils.py +9 -0
  23. reframodel-2025.11.1/pyproject.toml +53 -0
  24. reframodel-2025.11.1/reframodel.egg-info/PKG-INFO +90 -0
  25. reframodel-2025.11.1/reframodel.egg-info/SOURCES.txt +29 -0
  26. reframodel-2025.11.1/reframodel.egg-info/dependency_links.txt +1 -0
  27. reframodel-2025.11.1/reframodel.egg-info/entry_points.txt +2 -0
  28. reframodel-2025.11.1/reframodel.egg-info/requires.txt +9 -0
  29. reframodel-2025.11.1/reframodel.egg-info/top_level.txt +1 -0
  30. reframodel-2025.11.1/setup.cfg +4 -0
  31. reframodel-2025.11.1/setup.py +10 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 [Your Name]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ recursive-include RefraModel *.py
5
+ recursive-exclude RefraModel __pycache__
6
+ recursive-exclude RefraModel *.pyc
7
+ recursive-exclude RefraModel *.pyo
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: reframodel
3
+ Version: 2025.11.1
4
+ Summary: Seismic refraction modeling and inversion tool
5
+ Author-email: Hermann Zeyen <hermann.zeyen@universite-paris-saclay.fr>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/HZeyen/reframodel
8
+ Project-URL: Documentation, https://github.com/HZeyen/reframodel/wiki
9
+ Project-URL: Repository, https://github.com/HZeyen/reframodel
10
+ Project-URL: Issues, https://github.com/HZeyen/reframodel/issues
11
+ Keywords: seismic,refraction,geophysics,inversion,modeling
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Physics
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: numpy>=1.20.0
24
+ Requires-Dist: matplotlib>=3.3.0
25
+ Requires-Dist: PyQt5>=5.15.0
26
+ Requires-Dist: pygimli>=1.3.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
29
+ Requires-Dist: black>=22.0.0; extra == "dev"
30
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # RefraModel
34
+
35
+ Seismic refraction modeling and inversion tool using PyGIMLi.
36
+
37
+ ## Features
38
+
39
+ - Interactive geological model builder
40
+ - Visual model editing and manipulation
41
+ - Forward modeling of seismic refraction data
42
+ - Inversion with flexible regularization
43
+ - Body-specific velocity constraints
44
+ - Anisotropic smoothing support
45
+
46
+ ## Installation
47
+
48
+ ### From source
49
+
50
+ ```bash
51
+ git clone https://github.com/yourusername/reframodel.git
52
+ cd reframodel
53
+ pip install -e .
54
+ ```
55
+
56
+ ### From PyPI (when published)
57
+
58
+ ```bash
59
+ pip install reframodel
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ After installation, run the application:
65
+
66
+ ```bash
67
+ reframodel
68
+ ```
69
+
70
+ Or as a Python module:
71
+
72
+ ```bash
73
+ python -m RefraModel
74
+ ```
75
+
76
+ ## Requirements
77
+
78
+ - Python >= 3.8
79
+ - numpy >= 1.20.0
80
+ - matplotlib >= 3.3.0
81
+ - PyQt5 >= 5.15.0
82
+ - pygimli >= 1.3.0
83
+
84
+ ## License
85
+
86
+ MIT License
87
+
88
+ ## Author
89
+
90
+ Hermann Zeyen, University Paris-Saclay <hermann.zeyen@universite-paris-saclay.fr>
@@ -0,0 +1,58 @@
1
+ # RefraModel
2
+
3
+ Seismic refraction modeling and inversion tool using PyGIMLi.
4
+
5
+ ## Features
6
+
7
+ - Interactive geological model builder
8
+ - Visual model editing and manipulation
9
+ - Forward modeling of seismic refraction data
10
+ - Inversion with flexible regularization
11
+ - Body-specific velocity constraints
12
+ - Anisotropic smoothing support
13
+
14
+ ## Installation
15
+
16
+ ### From source
17
+
18
+ ```bash
19
+ git clone https://github.com/yourusername/reframodel.git
20
+ cd reframodel
21
+ pip install -e .
22
+ ```
23
+
24
+ ### From PyPI (when published)
25
+
26
+ ```bash
27
+ pip install reframodel
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ After installation, run the application:
33
+
34
+ ```bash
35
+ reframodel
36
+ ```
37
+
38
+ Or as a Python module:
39
+
40
+ ```bash
41
+ python -m RefraModel
42
+ ```
43
+
44
+ ## Requirements
45
+
46
+ - Python >= 3.8
47
+ - numpy >= 1.20.0
48
+ - matplotlib >= 3.3.0
49
+ - PyQt5 >= 5.15.0
50
+ - pygimli >= 1.3.0
51
+
52
+ ## License
53
+
54
+ MIT License
55
+
56
+ ## Author
57
+
58
+ Hermann Zeyen, University Paris-Saclay <hermann.zeyen@universite-paris-saclay.fr>
@@ -0,0 +1,4 @@
1
+ """
2
+ RefraModel - Seismic refraction modeling and inversion
3
+ """
4
+ __version__ = "25.11.1"
@@ -0,0 +1,7 @@
1
+ """
2
+ Entry point for running RefraModel as a module: python -m reframodel
3
+ """
4
+ from .main import main
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -0,0 +1,20 @@
1
+ """
2
+ Body editing operations for geological model
3
+ """
4
+ from ..geometry.bodies import BodyManager
5
+
6
+
7
+ class BodyEditor:
8
+ """Handles editing of geological bodies"""
9
+
10
+ def __init__(self):
11
+ self.body_manager = BodyManager()
12
+
13
+ def edit_body(self, body_index, new_properties):
14
+ """Edit properties of a specific body"""
15
+ if 0 <= body_index < len(self.body_manager.bodies):
16
+ body = self.body_manager.bodies[body_index]
17
+ body.update(new_properties)
18
+ print(f"Updated body {body_index} with properties: {new_properties}")
19
+ else:
20
+ print("Body index out of range.")
@@ -0,0 +1,20 @@
1
+ """
2
+ Node editing operations for geological model
3
+ """
4
+ from ..geometry.points import PointManager
5
+
6
+
7
+ class NodeEditor:
8
+ """Handles editing of nodes (points) in the model"""
9
+
10
+ def __init__(self):
11
+ self.point_manager = PointManager(0, 100, 0, 100, 1, 1)
12
+
13
+ def edit_node(self, node_index, new_coordinates):
14
+ """Edit coordinates of a specific node"""
15
+ if 0 <= node_index < len(self.point_manager.points):
16
+ point = self.point_manager.points[node_index]
17
+ point["x"], point["y"] = new_coordinates
18
+ print(f"Updated node {node_index} to coordinates: {new_coordinates}")
19
+ else:
20
+ print("Node index out of range.")
@@ -0,0 +1,24 @@
1
+ """
2
+ Property editing operations for geological model
3
+ """
4
+ from ..geometry.bodies import BodyManager
5
+
6
+
7
+ class PropertyEditor:
8
+ """Handles editing of properties for geological bodies"""
9
+
10
+ def __init__(self):
11
+ self.body_manager = BodyManager()
12
+
13
+ def edit_property(self, body_index, property_name, new_value):
14
+ """Edit a specific property of a body"""
15
+ if 0 <= body_index < len(self.body_manager.bodies):
16
+ body = self.body_manager.bodies[body_index]
17
+ if property_name in body["prop_names"]:
18
+ prop_index = body["prop_names"].index(property_name)
19
+ body["props"][prop_index] = new_value
20
+ print(f"Updated property '{property_name}' of body {body_index} to {new_value}")
21
+ else:
22
+ print(f"Property '{property_name}' not found in body {body_index}.")
23
+ else:
24
+ print("Body index out of range.")
@@ -0,0 +1,83 @@
1
+ """
2
+ Body management for geological model
3
+ """
4
+ import numpy as np
5
+ from matplotlib.path import Path
6
+
7
+
8
+ class BodyManager:
9
+ """Manages geological bodies in the model"""
10
+
11
+ def __init__(self):
12
+ self.bodies = []
13
+ self.nbody = -1
14
+
15
+ def append_body(self, lines, directions, para_names, para_values=None, name=None):
16
+ """Add a new body to the list of bodies"""
17
+ self.nbody += 1
18
+ print(f"Body {self.nbody} created")
19
+
20
+ body = {
21
+ "lines": lines,
22
+ "sense": directions,
23
+ "name": name or f"Body_{self.nbody}",
24
+ "prop_names": para_names,
25
+ "props": para_values or [0.] * len(para_names),
26
+ "cuts": [],
27
+ "constraint": "free",
28
+ "limits": [0., 10000.],
29
+ "corr_lengths": [0., 0.]
30
+ }
31
+
32
+ self.bodies.append(body)
33
+
34
+ def get_polygon(self, nb, points, lines):
35
+ """Get polygon coordinates for a body"""
36
+ b = self.bodies[nb]
37
+ line = lines[b["lines"][0]]
38
+
39
+ if b["sense"][0] > 0:
40
+ x = [points[line["point1"]]["x"]]
41
+ y = [points[line["point1"]]["y"]]
42
+ else:
43
+ x = [points[line["point2"]]["x"]]
44
+ y = [points[line["point2"]]["y"]]
45
+
46
+ for j, lin in enumerate(b["lines"]):
47
+ line = lines[lin]
48
+ if b["sense"][j] > 0:
49
+ x.append(points[line["point2"]]["x"])
50
+ y.append(points[line["point2"]]["y"])
51
+ else:
52
+ x.append(points[line["point1"]]["x"])
53
+ y.append(points[line["point1"]]["y"])
54
+
55
+ return x, y
56
+
57
+ def inside_body(self, xp, yp, points, lines):
58
+ """Check whether a point is inside a body"""
59
+ for i, b in enumerate(self.bodies):
60
+ x, y = self.get_polygon(i, points, lines)
61
+ path = Path(list(zip(x, y)))
62
+ if xp is None:
63
+ return -1, path
64
+ if path.contains_point((xp, yp)):
65
+ return i, path
66
+ return -1, None
67
+
68
+ def get_vel_limits(self):
69
+ """Calculate minimum and maximum velocities"""
70
+ if not self.bodies:
71
+ return 1000., 5000.
72
+
73
+ vmin = self.bodies[0]["props"][0]
74
+ vmax = vmin
75
+ for b in self.bodies:
76
+ vmin = min(vmin, b["props"][0])
77
+ vmax = max(vmax, b["props"][0])
78
+
79
+ if vmin == vmax:
80
+ vmin -= 100.
81
+ vmax += 100.
82
+
83
+ return vmin, vmax
@@ -0,0 +1,94 @@
1
+ """
2
+ Line management for geological model
3
+ """
4
+
5
+
6
+ class LineManager:
7
+ """Manages lines connecting points"""
8
+
9
+ def __init__(self):
10
+ self.lines = []
11
+ self.nline = -1
12
+
13
+ def append_line(self, pt1, pt2, body=None):
14
+ """Append a line to the list"""
15
+ self.nline += 1
16
+
17
+ line = {
18
+ "point1": pt1,
19
+ "point2": pt2,
20
+ "bodies": body or [],
21
+ "topo": False,
22
+ "bottom": False,
23
+ "left": False,
24
+ "right": False
25
+ }
26
+
27
+ self.lines.append(line)
28
+
29
+ def find_nearest_line(self, xp, yp, points):
30
+ """Find line nearest to a point in screen coordinates"""
31
+ lmin = -1
32
+ dmin = 1000000000.
33
+
34
+ for i, lin in enumerate(self.lines):
35
+ pt = points[lin["point1"]]
36
+ x1 = pt["xscreen"]
37
+ y1 = pt["yscreen"]
38
+ pt = points[lin["point2"]]
39
+ x2 = pt["xscreen"]
40
+ y2 = pt["yscreen"]
41
+
42
+ xcross, ycross, x_flag = self.crossing(x1, y1, x2, y2, xp, yp)
43
+ if x_flag:
44
+ d = (xp - xcross) ** 2 + (yp - ycross) ** 2
45
+ if d < dmin:
46
+ dmin = d
47
+ lmin = i
48
+
49
+ return lmin
50
+
51
+ @staticmethod
52
+ def crossing(x1, y1, x2, y2, xp, yp):
53
+ """Calculate crossing point of perpendicular line"""
54
+ dx = x2 - x1
55
+ dy = y2 - y1
56
+
57
+ if dx == dy == 0:
58
+ return None, None, False
59
+
60
+ vx = xp - x1
61
+ vy = yp - y1
62
+
63
+ t = (vx * dx + vy * dy) / (dx ** 2 + dy ** 2)
64
+ xq = x1 + t * dx
65
+ yq = y1 + t * dy
66
+
67
+ flag = True
68
+ if dx > dy:
69
+ if xq < min(x1, x2) or xq > max(x1, x2):
70
+ flag = False
71
+ else:
72
+ if yq < min(y1, y2) or yq > max(y1, y2):
73
+ flag = False
74
+
75
+ return xq, yq, flag
76
+
77
+ def check_line_exist(self, x, y, points):
78
+ """Check if a line already exists"""
79
+ import numpy as np
80
+
81
+ for i, lin in enumerate(self.lines):
82
+ x1 = points[lin["point1"]]["x"]
83
+ x2 = points[lin["point2"]]["x"]
84
+ y1 = points[lin["point1"]]["y"]
85
+ y2 = points[lin["point2"]]["y"]
86
+
87
+ if (np.isclose(x[0], x1) and np.isclose(y[0], y1) and
88
+ np.isclose(x[1], x2) and np.isclose(y[1], y2)):
89
+ return i, 1
90
+ elif (np.isclose(x[1], x1) and np.isclose(y[1], y1) and
91
+ np.isclose(x[0], x2) and np.isclose(y[0], y2)):
92
+ return i, -1
93
+
94
+ return -1, None
@@ -0,0 +1,96 @@
1
+ """
2
+ Point management for geological model
3
+ """
4
+ import numpy as np
5
+
6
+
7
+ class PointManager:
8
+ """Manages points in the model"""
9
+
10
+ def __init__(self, xmin, xmax, ymin, ymax, eps_x, eps_y):
11
+ self.points = []
12
+ self.npoint = -1
13
+ self.xmin = xmin
14
+ self.xmax = xmax
15
+ self.ymin = ymin
16
+ self.ymax = ymax
17
+ self.eps_x = eps_x
18
+ self.eps_y = eps_y
19
+
20
+ def append_point(self, xp, yp, xs, ys, body=None, line=None):
21
+ """Append a point to the list of points"""
22
+ self.npoint += 1
23
+ # Round coordinates to 2 decimals at creation
24
+ try:
25
+ xp = float(round(xp, 2))
26
+ yp = float(round(yp, 2))
27
+ except Exception:
28
+ pass
29
+
30
+ point = {
31
+ "x": xp,
32
+ "y": yp,
33
+ "xscreen": xs,
34
+ "yscreen": ys,
35
+ "lines": line or [],
36
+ "bodies": body or [],
37
+ "topo": False,
38
+ "bottom": False,
39
+ "left": False,
40
+ "right": False
41
+ }
42
+
43
+ # Check special positions
44
+ if np.isclose(yp, self.ymin):
45
+ point["bottom"] = True
46
+ if xp <= self.xmin + 0.1:
47
+ point["left"] = True
48
+ if xp >= self.xmax - 0.01:
49
+ point["right"] = True
50
+
51
+ self.points.append(point)
52
+
53
+ def find_nearest_point(self, xp, yp):
54
+ """Find the nearest point to given coordinates"""
55
+ if not self.points:
56
+ return -1
57
+
58
+ pmin = -1
59
+ dmin = 1000000000.
60
+
61
+ for i, p in enumerate(self.points):
62
+ d = (p["x"] - xp) ** 2 + (p["y"] - yp) ** 2
63
+ if d < dmin:
64
+ pmin = i
65
+ dmin = d
66
+
67
+ return pmin
68
+
69
+ def find_nearest_screen_point(self, xp, yp):
70
+ """Find nearest point in screen coordinates"""
71
+ if not self.points:
72
+ return -1
73
+
74
+ pmin = -1
75
+ dmin = 1000000000.
76
+
77
+ for i, p in enumerate(self.points):
78
+ d = (p["xscreen"] - xp) ** 2 + (p["yscreen"] - yp) ** 2
79
+ if d < dmin:
80
+ pmin = i
81
+ dmin = d
82
+
83
+ return pmin
84
+
85
+ def check_close(self, x, y):
86
+ """Check if point is near existing point"""
87
+ if not self.points:
88
+ return x, y, -1
89
+
90
+ for i, p in enumerate(self.points):
91
+ xb = p["x"]
92
+ yb = p["y"]
93
+ if abs(x - xb) < self.eps_x and abs(y - yb) < self.eps_y:
94
+ return xb, yb, i
95
+
96
+ return x, y, -1