relativus commited on
Commit
8a7adc1
·
verified ·
1 Parent(s): bdfca84

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +305 -0
app.py ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ import plotly.graph_objects as go
4
+ from scipy.interpolate import LinearNDInterpolator
5
+ from scipy.spatial import Delaunay
6
+ import plotly.express as px
7
+
8
+ def barycentric_to_cartesian(df):
9
+ """
10
+ Преобразует барицентрические координаты в декартовы для тетраэдра.
11
+ """
12
+ # Вершины правильного тетраэдра
13
+ vertices = np.array([
14
+ [0, 0, 0], # v0
15
+ [1, 0, 0], # v1
16
+ [0.5, np.sqrt(3)/2, 0], # v2
17
+ [0.5, np.sqrt(3)/6, np.sqrt(6)/3] # v3
18
+ ])
19
+
20
+ # Преобразование координат
21
+ bary_coords = df[['x1', 'x2', 'x3', 'x4']].values
22
+ cartesian_coords = np.dot(bary_coords, vertices)
23
+
24
+ result_df = df.copy()
25
+ result_df['x'] = cartesian_coords[:, 0]
26
+ result_df['y'] = cartesian_coords[:, 1]
27
+ result_df['z'] = cartesian_coords[:, 2]
28
+
29
+ return result_df
30
+
31
+
32
+ def add_tetrahedron_wireframe(fig, vertices):
33
+ """Добавляет каркас тетраэдра и подписи вершин"""
34
+ edges = [
35
+ [0, 1], [0, 2], [0, 3],
36
+ [1, 2], [1, 3], [2, 3]
37
+ ]
38
+
39
+ for edge in edges:
40
+ x_line = [vertices[edge[0]][0], vertices[edge[1]][0]]
41
+ y_line = [vertices[edge[0]][1], vertices[edge[1]][1]]
42
+ z_line = [vertices[edge[0]][2], vertices[edge[1]][2]]
43
+
44
+ fig.add_trace(go.Scatter3d(
45
+ x=x_line, y=y_line, z=z_line,
46
+ mode='lines',
47
+ line=dict(color='black', width=4),
48
+ showlegend=False
49
+ ))
50
+
51
+ vertex_labels = ['V₁ (x₁=1)', 'V₂ (x₂=1)', 'V₃ (x₃=1)', 'V₄ (x₄=1)']
52
+ fig.add_trace(go.Scatter3d(
53
+ x=vertices[:, 0],
54
+ y=vertices[:, 1],
55
+ z=vertices[:, 2],
56
+ mode='text+markers',
57
+ text=vertex_labels,
58
+ textposition="top center",
59
+ marker=dict(size=8, color='red'),
60
+ showlegend=False
61
+ ))
62
+
63
+
64
+ def create_tetrahedron_isosurfaces_correct(df, temperatures, resolution=60):
65
+ """
66
+ Правильное построение изоповерхностей с помощью Marching Tetrahedra.
67
+ """
68
+ df_cartesian = barycentric_to_cartesian(df)
69
+
70
+ # Вершины тетраэдра
71
+ vertices = np.array([
72
+ [0, 0, 0],
73
+ [1, 0, 0],
74
+ [0.5, np.sqrt(3)/2, 0],
75
+ [0.5, np.sqrt(3)/6, np.sqrt(6)/3]
76
+ ])
77
+
78
+ # Создаем интерполятор в ДЕКАРТОВЫХ координатах
79
+ points = df_cartesian[['x', 'y', 'z']].values
80
+ temp_values = df_cartesian['T, C'].values
81
+
82
+ valid_mask = ~np.isnan(temp_values)
83
+ points = points[valid_mask]
84
+ temp_values = temp_values[valid_mask]
85
+
86
+ interpolator = LinearNDInterpolator(points, temp_values)
87
+
88
+ # Создаем регулярную сетку в барицентрических координатах
89
+ u = np.linspace(0, 1, resolution)
90
+ v = np.linspace(0, 1, resolution)
91
+ w = np.linspace(0, 1, resolution)
92
+
93
+ U, V, W = np.meshgrid(u, v, w, indexing='ij')
94
+
95
+ # Фильтруем точки внутри тетраэдра (u+v+w <= 1)
96
+ mask = (U + V + W) <= 1
97
+ U_valid = U[mask]
98
+ V_valid = V[mask]
99
+ W_valid = W[mask]
100
+ T_valid = 1 - U_valid - V_valid - W_valid
101
+
102
+ # Преобразуем в декартовы координаты
103
+ bary_coords = np.column_stack([U_valid, V_valid, W_valid, T_valid])
104
+ grid_points = np.dot(bary_coords, vertices)
105
+
106
+ # Интерполируем температуру в ДЕКАРТОВЫХ координатах
107
+ grid_temps = interpolator(grid_points)
108
+
109
+ # Создаем триангуляцию для регулярной сетки
110
+ print("Создаем триангуляцию для регулярной сетки...")
111
+ try:
112
+ tri = Delaunay(grid_points)
113
+ print(f"Создано {len(tri.simplices)} тетраэдров")
114
+ except Exception as e:
115
+ print(f"Ошибка триангуляции: {e}")
116
+ return None
117
+
118
+ fig = go.Figure()
119
+
120
+ for temp in temperatures:
121
+ print(f"Строим изоповерхность для {temp}°C")
122
+
123
+ vertices_list = []
124
+ faces_list = []
125
+
126
+ # Для каждого тетраэдра в сетке
127
+ for tetra in tri.simplices:
128
+ tetra_points = grid_points[tetra]
129
+ tetra_temps = grid_temps[tetra]
130
+
131
+ # Определяем, какие вершины выше/ниже изоповерхности
132
+ above = tetra_temps >= temp
133
+ below = tetra_temps < temp
134
+
135
+ # Если все вершины с одной стороны - пропускаем
136
+ if np.all(above) or np.all(below):
137
+ continue
138
+
139
+ # Находим пересечения изоповерхности с р��брами тетраэдра
140
+ intersections = []
141
+ intersection_edges = []
142
+
143
+ # Проверяем все ребра тетраэдра
144
+ edges = [(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)]
145
+ for i, (idx1, idx2) in enumerate(edges):
146
+ temp1, temp2 = tetra_temps[idx1], tetra_temps[idx2]
147
+ point1, point2 = tetra_points[idx1], tetra_points[idx2]
148
+
149
+ # Если ребро пересекает изоповерхность
150
+ if (temp1 >= temp and temp2 < temp) or (temp1 < temp and temp2 >= temp):
151
+ # Линейная интерполяция
152
+ t = (temp - temp1) / (temp2 - temp1)
153
+ intersection_point = point1 + t * (point2 - point1)
154
+ intersections.append(intersection_point)
155
+ intersection_edges.append((idx1, idx2))
156
+
157
+ # Формируем полигоны в зависимости от количества пересечений
158
+ if len(intersections) == 3:
159
+ # Один треугольник
160
+ start_idx = len(vertices_list)
161
+ vertices_list.extend(intersections)
162
+ faces_list.append([start_idx, start_idx+1, start_idx+2])
163
+
164
+ elif len(intersections) == 4:
165
+ # Четырехугольник - разбиваем на 2 треугольника
166
+ start_idx = len(vertices_list)
167
+ vertices_list.extend(intersections)
168
+
169
+ # Первый вариант разбиения (диагональ 0-2)
170
+ faces_list.append([start_idx, start_idx+1, start_idx+2])
171
+ faces_list.append([start_idx, start_idx+2, start_idx+3])
172
+
173
+ # Альтернативный вариант разбиения (диагональ 1-3)
174
+ # faces_list.append([start_idx, start_idx+1, start_idx+3])
175
+ # faces_list.append([start_idx+1, start_idx+2, start_idx+3])
176
+
177
+ # Добавляем изоповерхность
178
+ if vertices_list:
179
+ vertices_array = np.array(vertices_list)
180
+ x = vertices_array[:, 0]
181
+ y = vertices_array[:, 1]
182
+ z = vertices_array[:, 2]
183
+
184
+ i_list, j_list, k_list = [], [], []
185
+ for face in faces_list:
186
+ i_list.append(face[0])
187
+ j_list.append(face[1])
188
+ k_list.append(face[2])
189
+
190
+ color_idx = temperatures.index(temp) % len(px.colors.qualitative.Set1)
191
+ fig.add_trace(go.Mesh3d(
192
+ x=x, y=y, z=z,
193
+ i=i_list, j=j_list, k=k_list,
194
+ color=px.colors.qualitative.Set1[color_idx],
195
+ opacity=0.7,
196
+ name=f'{temp}°C',
197
+ flatshading=True,
198
+ lighting=dict(diffuse=0.8, ambient=0.3),
199
+ lightposition=dict(x=100, y=100, z=100)
200
+ ))
201
+ print(f"Добавлено {len(faces_list)} треугольников для температуры {temp}°C")
202
+ else:
203
+ print(f"Не найдено пересечений для температуры {temp}°C")
204
+
205
+ # Добавляем каркас тетраэдра
206
+ add_tetrahedron_wireframe(fig, vertices)
207
+
208
+ fig.update_layout(
209
+ title='Правильные изоповерхности в тетраэдре',
210
+ scene=dict(
211
+ xaxis_title='X',
212
+ yaxis_title='Y',
213
+ zaxis_title='Z',
214
+ aspectmode='data',
215
+ camera=dict(eye=dict(x=1.5, y=1.5, z=1.5))
216
+ ),
217
+ width=1000,
218
+ height=800
219
+ )
220
+
221
+ return fig
222
+
223
+
224
+ # 🎯 Создание тестовых данных
225
+ def generate_test_data(n_points=100):
226
+ """
227
+ Генерирует случайные барицентрические координаты и температуры
228
+ """
229
+ # Генерируем случайные барицентрические координаты
230
+ # x1 + x2 + x3 + x4 = 1, все >= 0
231
+ data = np.random.dirichlet([1, 1, 1, 1], size=n_points)
232
+
233
+ # Создаем DataFrame
234
+ df = pd.DataFrame(data, columns=['x1', 'x2', 'x3', 'x4'])
235
+
236
+ # Генерируем температуры в разумном диапазоне
237
+ # Например, температура зависит от барицентрических координат
238
+ df['T, C'] = 50 + 30 * (df['x1'] * 0.5 + df['x2'] * 0.3 + df['x3'] * 0.2 + df['x4'] * 0.1) + \
239
+ np.random.normal(0, 2, size=n_points) # добавляем шум
240
+
241
+ return df
242
+
243
+
244
+ # 🚀 Основной код для Streamlit
245
+ if __name__ == "__main__":
246
+ import streamlit as st
247
+
248
+ st.title("Изоповерхности в тетраэдре")
249
+
250
+ # 📁 Загрузка Excel-ф��йла
251
+ uploaded_file = st.file_uploader(
252
+ "Загрузите Excel файл с данными (x1, x2, x3, x4, T, C)",
253
+ type=["xlsx", "xls"],
254
+ help="Файл должен содержать столбцы: x1, x2, x3, x4, T, C"
255
+ )
256
+
257
+ if uploaded_file is not None:
258
+ # Читаем Excel-файл
259
+ try:
260
+ data = pd.read_excel(uploaded_file)
261
+ st.success("Файл успешно загружен!")
262
+
263
+ # Проверяем, что все нужные столбцы есть
264
+ required_columns = ['x1', 'x2', 'x3', 'x4', 'T, C']
265
+ missing_cols = [col for col in required_columns if col not in data.columns]
266
+
267
+ if missing_cols:
268
+ st.error(f"⚠️ В файле отсутствуют следующие столбцы: {missing_cols}")
269
+ st.stop()
270
+
271
+ st.write("Пример данных из файла:")
272
+ st.dataframe(data.head(10))
273
+
274
+ except Exception as e:
275
+ st.error(f"Ошибка при чтении файла: {e}")
276
+ st.stop()
277
+ else:
278
+ # Если файл не загружен - используем тестовые данные
279
+ st.info("Файл не загружен. Используются тестовые данные.")
280
+ data = generate_test_data(n_points=200)
281
+
282
+
283
+ # Параметры
284
+ available_temps = sorted(data['T, C'].dropna().unique())
285
+ if len(available_temps) > 0:
286
+ default_temps = [available_temps[0]] if len(available_temps) == 1 else [available_temps[0], available_temps[-1]]
287
+ else:
288
+ default_temps = [64.5, 81]
289
+
290
+ target_temperatures = st.multiselect(
291
+ "Выберите температуры для изоповерхностей",
292
+ options=sorted(data['T, C'].dropna().unique()),
293
+ default=default_temps
294
+ )
295
+
296
+ resolution = st.slider("Разрешение сетки", min_value=20, max_value=100, value=60)
297
+
298
+ if st.button("Построить изоповерхности"):
299
+ with st.spinner("Идет построение..."):
300
+ fig = create_tetrahedron_isosurfaces_correct(data, target_temperatures, resolution=resolution)
301
+
302
+ if fig:
303
+ st.plotly_chart(fig, use_container_width=True)
304
+ else:
305
+ st.error("Не удалось построить изоповерхности")