Skip to content

Commit 576974a

Browse files
committed
added phase diagram creator
1 parent ea1321f commit 576974a

File tree

6 files changed

+1196
-26
lines changed

6 files changed

+1196
-26
lines changed

burnman/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323
from . import unitcell
2424
from . import geotherm
2525
from . import anisotropy
26+
from . import plotting

burnman/utils/plotting.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# MIT License
2+
3+
# Copyright (c) 2017 Michal Haták
4+
# Copyright (c) 2025 Bob Myhill
5+
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
13+
# The above copyright notice and this permission notice shall be included in all
14+
# copies or substantial portions of the Software.
15+
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
# SOFTWARE.
23+
24+
import numpy as np
25+
import heapq
26+
27+
28+
def _get_seg_dist_sq(px, py, a, b):
29+
"""
30+
Compute the squared distance from point (px, py) to the line segment defined by points a and b.
31+
32+
:param px: X-coordinate of the point.
33+
:param py: Y-coordinate of the point.
34+
:param a: First point of the segment as a tuple (x, y).
35+
:param b: Second point of the segment as a tuple (x, y).
36+
:return: Squared distance from point to segment.
37+
:rtype: float
38+
"""
39+
ax, ay = a
40+
bx, by = b
41+
dx, dy = bx - ax, by - ay
42+
43+
if dx != 0 or dy != 0:
44+
t = ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)
45+
if t > 1:
46+
ax, ay = bx, by
47+
elif t > 0:
48+
ax += dx * t
49+
ay += dy * t
50+
51+
return (px - ax) ** 2 + (py - ay) ** 2
52+
53+
54+
def _point_to_polygon_distance(x, y, polygon):
55+
"""
56+
Compute the signed distance from a point to the nearest polygon edge.
57+
58+
:param x: X-coordinate of the point.
59+
:param y: Y-coordinate of the point.
60+
:param polygon: List of rings (each ring is a numpy array of shape (N, 2)).
61+
:return: Signed distance; positive if inside, negative if outside.
62+
:rtype: float
63+
"""
64+
inside = False
65+
min_dist_sq = np.inf
66+
67+
for ring in polygon:
68+
b = ring[-1]
69+
for a in ring:
70+
if ((a[1] > y) != (b[1] > y)) and (
71+
x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0]
72+
):
73+
inside = not inside
74+
min_dist_sq = min(min_dist_sq, _get_seg_dist_sq(x, y, a, b))
75+
b = a
76+
77+
distance = np.sqrt(min_dist_sq)
78+
return distance if inside else -distance
79+
80+
81+
class Cell:
82+
"""
83+
Represents a square cell used during the polygon center search.
84+
85+
:param x: X-coordinate of the cell center.
86+
:param y: Y-coordinate of the cell center.
87+
:param h: Half-size of the cell.
88+
:param polygon: The input polygon as a list of rings.
89+
"""
90+
91+
def __init__(self, x, y, h, polygon):
92+
self.x = x
93+
self.y = y
94+
self.h = h
95+
self.d = _point_to_polygon_distance(x, y, polygon)
96+
self.max = self.d + h * np.sqrt(2)
97+
98+
def __lt__(self, other):
99+
return self.max > other.max
100+
101+
102+
def _get_centroid_cell(polygon):
103+
"""
104+
Estimate the polygon's centroid as an initial guess.
105+
106+
:param polygon: List of polygon rings.
107+
:return: A Cell object located at the estimated centroid.
108+
:rtype: Cell
109+
"""
110+
points = polygon[0]
111+
area = 0.0
112+
cx = 0.0
113+
cy = 0.0
114+
b = points[-1]
115+
116+
for a in points:
117+
f = a[0] * b[1] - b[0] * a[1]
118+
cx += (a[0] + b[0]) * f
119+
cy += (a[1] + b[1]) * f
120+
area += f * 3
121+
b = a
122+
123+
if area == 0:
124+
return Cell(points[0][0], points[0][1], 0.0, polygon)
125+
126+
return Cell(cx / area, cy / area, 0.0, polygon)
127+
128+
129+
def visual_center_of_polygon(polygon_rings, precision=1.0, with_distance=False):
130+
"""
131+
Compute the pole of inaccessibility (visual center) of a polygon with the specified precision.
132+
133+
:param polygon_rings: A polygon represented as a list of rings. Each ring is a numpy array of shape (N, 2).
134+
:param precision: Desired precision. Stops when improvement is less than this value.
135+
:param with_distance: If True, also return the distance to the closest edge.
136+
:return: The [x, y] coordinates of the center, and optionally the distance.
137+
:rtype: list or tuple
138+
"""
139+
coords = polygon_rings[0]
140+
if coords.ndim != 2 or coords.shape[1] != 2:
141+
raise ValueError("Expected polygon ring to be an Nx2 array")
142+
143+
min_x, min_y = np.min(coords, axis=0)
144+
max_x, max_y = np.max(coords, axis=0)
145+
146+
width = max_x - min_x
147+
height = max_y - min_y
148+
cell_size = min(width, height)
149+
max_dim = max(width, height)
150+
151+
h = cell_size / 2.0
152+
153+
# If the cell is much longer than it is wide (or vice-versa),
154+
# just return the mean of x and y.
155+
if cell_size < max_dim / 100:
156+
mean_x = (max_x + min_x) / 2.0
157+
mean_y = (max_y + min_y) / 2.0
158+
return ([mean_x, mean_y], 0.0) if with_distance else [mean_x, mean_y]
159+
160+
# Initialize priority queue
161+
queue = []
162+
heapq.heapify(queue)
163+
164+
x_coords = np.arange(min_x, max_x, cell_size)
165+
y_coords = np.arange(min_y, max_y, cell_size)
166+
167+
for x in x_coords:
168+
for y in y_coords:
169+
heapq.heappush(queue, Cell(x + h, y + h, h, polygon_rings))
170+
171+
best_cell = _get_centroid_cell(polygon_rings)
172+
173+
bbox_cell = Cell(min_x + width / 2, min_y + height / 2, 0.0, polygon_rings)
174+
if bbox_cell.d > best_cell.d:
175+
best_cell = bbox_cell
176+
177+
while queue:
178+
cell = heapq.heappop(queue)
179+
180+
if cell.d > best_cell.d:
181+
best_cell = cell
182+
183+
if cell.max - best_cell.d <= precision:
184+
continue
185+
186+
h = cell.h / 2
187+
for dx in [-h, h]:
188+
for dy in [-h, h]:
189+
heapq.heappush(queue, Cell(cell.x + dx, cell.y + dy, h, polygon_rings))
190+
191+
result = [best_cell.x, best_cell.y]
192+
return (result, best_cell.d) if with_distance else result

contrib/perplex/create_lo_res_table.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
import burnman
55
from perplex_utils import databases, make_build_file
66
from perplex_utils import run_vertex, run_pssect
7-
from perplex_utils import create_perplex_table
7+
from perplex_utils import create_perplex_class_table
88

99

10-
def create_table(
10+
def run_perplex(
1111
perplex_bindir,
1212
project_name,
1313
database,
@@ -46,7 +46,7 @@ def create_table(
4646

4747
# Create the BurnMan-readable table from the vertex output
4848
print("* Creating BurnMan-readable table...")
49-
create_perplex_table(
49+
create_perplex_class_table(
5050
perplex_bindir,
5151
project_name,
5252
outfile,
@@ -148,7 +148,7 @@ def create_table(
148148
f"Creating table [{i_P + 1}/{n_splits_pressure}, {i_T + 1}/{n_splits_temperature}]"
149149
)
150150
# Create the table for the current split
151-
create_table(
151+
run_perplex(
152152
perplex_bindir,
153153
split_project_name,
154154
database,

0 commit comments

Comments
 (0)