fargopy 0.3.15__py3-none-any.whl → 0.4.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.
- fargopy/__init__.py +2 -1
- fargopy/fields.py +561 -0
- fargopy/flux.py +808 -0
- fargopy/fsimulation.py +299 -473
- fargopy/simulation.py +224 -36
- fargopy/version.py +1 -1
- {fargopy-0.3.15.dist-info → fargopy-0.4.0.dist-info}/METADATA +4 -1
- fargopy-0.4.0.dist-info/RECORD +17 -0
- {fargopy-0.3.15.dist-info → fargopy-0.4.0.dist-info}/WHEEL +1 -1
- fargopy-0.3.15.dist-info/RECORD +0 -16
- {fargopy-0.3.15.data → fargopy-0.4.0.data}/scripts/ifargopy +0 -0
- {fargopy-0.3.15.dist-info → fargopy-0.4.0.dist-info}/entry_points.txt +0 -0
- {fargopy-0.3.15.dist-info → fargopy-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {fargopy-0.3.15.dist-info → fargopy-0.4.0.dist-info}/top_level.txt +0 -0
fargopy/fsimulation.py
CHANGED
|
@@ -2,63 +2,207 @@ import pandas as pd
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import fargopy as fp
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
import matplotlib.pyplot as plt
|
|
7
6
|
import plotly.figure_factory as ff
|
|
8
7
|
from plotly.subplots import make_subplots
|
|
9
8
|
import plotly.graph_objects as go
|
|
10
9
|
from matplotlib.animation import FFMpegWriter
|
|
11
10
|
|
|
12
|
-
from ipywidgets import interact, FloatSlider,IntSlider
|
|
11
|
+
from ipywidgets import interact, FloatSlider, IntSlider
|
|
13
12
|
from celluloid import Camera
|
|
14
|
-
from IPython.display import HTML
|
|
15
|
-
from IPython.display import Video
|
|
13
|
+
from IPython.display import HTML, Video
|
|
16
14
|
|
|
17
15
|
from scipy.interpolate import griddata
|
|
18
16
|
from scipy.integrate import solve_ivp
|
|
19
17
|
from tqdm import tqdm
|
|
20
18
|
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class DataHandler:
|
|
20
|
+
class FieldInterpolate:
|
|
25
21
|
def __init__(self, sim):
|
|
26
22
|
self.sim = sim
|
|
27
23
|
self.df = None
|
|
28
24
|
self.plane = None
|
|
25
|
+
self.angle = None
|
|
26
|
+
|
|
27
|
+
def load_data(self, field=None, plane=None, angle=None, snapshots=None):
|
|
28
|
+
"""
|
|
29
|
+
Loads data in 2D or 3D depending on the provided parameters.
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
field (list of str, optional): List of fields to load (e.g., ["gasdens", "gasv"]).
|
|
33
|
+
plane (str, optional): Plane for 2D data ('XZ', 'XY', 'YZ'). Required for 2D.
|
|
34
|
+
angle (float, optional): Angle for the 2D slice. Required for 2D.
|
|
35
|
+
snapshots (list or int, optional): List of snapshot indices or a single snapshot to load. Required for both 2D and 3D.
|
|
36
|
+
Returns:
|
|
37
|
+
pd.DataFrame: DataFrame containing the loaded data.
|
|
38
|
+
"""
|
|
39
|
+
if field is None:
|
|
40
|
+
raise ValueError("You must specify at least one field to load using the 'fields' parameter.")
|
|
41
|
+
|
|
42
|
+
if (plane and not angle) or (angle and not plane):
|
|
43
|
+
raise ValueError("Both 'plane' and 'angle' must be provided for 2D data.")
|
|
44
|
+
|
|
45
|
+
if angle and not isinstance(angle, str):
|
|
46
|
+
raise ValueError("'angle' must be a str example: angle='theta=1.5' [rad]")
|
|
47
|
+
|
|
48
|
+
if not isinstance(snapshots, (int, list, tuple)):
|
|
49
|
+
raise ValueError("'snapshots' must be an integer, a list, or a tuple.")
|
|
50
|
+
|
|
51
|
+
if isinstance(snapshots, (list, tuple)) and len(snapshots) == 2:
|
|
52
|
+
if snapshots[0] > snapshots[1]:
|
|
53
|
+
raise ValueError("The range in 'snapshots' is invalid. The first value must be less than or equal to the second.")
|
|
54
|
+
|
|
55
|
+
if not hasattr(self.sim, "domains") or self.sim.domains is None:
|
|
56
|
+
raise ValueError("Simulation domains are not loaded. Ensure the simulation data is properly initialized.")
|
|
57
|
+
|
|
58
|
+
# Convert a single snapshot to a list
|
|
59
|
+
if isinstance(snapshots, int):
|
|
60
|
+
snapshots = [snapshots]
|
|
61
|
+
|
|
62
|
+
# Handle the case where snapshots is a single value or a list with one value
|
|
63
|
+
if len(snapshots) == 1:
|
|
64
|
+
snaps = snapshots
|
|
65
|
+
time_values = [0] # Single snapshot corresponds to a single time value
|
|
66
|
+
else:
|
|
67
|
+
snaps = np.arange(snapshots[0], snapshots[1] + 1)
|
|
68
|
+
time_values = np.linspace(0, 1, len(snaps))
|
|
29
69
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
df_snapshots
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
if plane and angle: # Load 2D data
|
|
71
|
+
|
|
72
|
+
# Map plane to coordinate names
|
|
73
|
+
plane_map = {
|
|
74
|
+
"XY": ("x", "y", "vx", "vy"),
|
|
75
|
+
"XZ": ("x", "z", "vx", "vz"),
|
|
76
|
+
"YZ": ("y", "z", "vy", "vz")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if plane not in plane_map:
|
|
80
|
+
raise ValueError(f"Invalid plane '{plane}'. Valid options are 'XY', 'XZ', 'YZ'.")
|
|
81
|
+
|
|
82
|
+
coord1, coord2, vel1, vel2 = plane_map[plane]
|
|
83
|
+
|
|
84
|
+
# Dynamically create DataFrame columns based on the fields
|
|
85
|
+
columns = ["snapshot", "time", coord1, coord2]
|
|
86
|
+
if field=="gasdens":
|
|
87
|
+
print(f"Loading 2D density data for plane {plane} at angle {angle} rad.")
|
|
88
|
+
columns.append("gasdens")
|
|
89
|
+
if field=="gasv":
|
|
90
|
+
columns.extend([vel1, vel2])
|
|
91
|
+
print(f"Loading 2D gas velocity data for plane {plane} at angle {angle} rad.")
|
|
92
|
+
|
|
93
|
+
if field=="gasenergy":
|
|
94
|
+
columns.append("gasenergy")
|
|
95
|
+
print(f"Loading 2D gas energy data for plane {plane} at angle {angle} rad.")
|
|
96
|
+
df_snapshots = pd.DataFrame(columns=columns)
|
|
97
|
+
|
|
98
|
+
for i, snap in enumerate(snaps):
|
|
99
|
+
row = {"snapshot": snap, "time": time_values[i]}
|
|
100
|
+
|
|
101
|
+
# Assign coordinates for all fields
|
|
102
|
+
gasv = self.sim.load_field('gasv', snapshot=snap, type='vector')
|
|
103
|
+
_, mesh = gasv.meshslice(slice=angle)
|
|
104
|
+
coord1_vals, coord2_vals = getattr(mesh, coord1), getattr(mesh, coord2)
|
|
105
|
+
row[coord1] = coord1_vals
|
|
106
|
+
row[coord2] = coord2_vals
|
|
107
|
+
|
|
108
|
+
if field=="gasdens" :
|
|
109
|
+
gasd = self.sim.load_field('gasdens', snapshot=snap, type='scalar')
|
|
110
|
+
gasd_slice, _ = gasd.meshslice(slice=angle)
|
|
111
|
+
row["gasdens"] = gasd_slice
|
|
112
|
+
|
|
113
|
+
if field=="gasv":
|
|
114
|
+
gasvx, gasvy, gasvz = gasv.to_cartesian()
|
|
115
|
+
vel1_slice, _ = getattr(gasvx, f"meshslice")(slice=angle)
|
|
116
|
+
vel2_slice, _ = getattr(gasvy, f"meshslice")(slice=angle)
|
|
117
|
+
row[vel1] = vel1_slice
|
|
118
|
+
row[vel2] = vel2_slice
|
|
119
|
+
|
|
120
|
+
if field=="gasenergy":
|
|
121
|
+
gasenergy = self.sim.load_field('gasenergy', snapshot=snap, type='scalar')
|
|
122
|
+
gasenergy_slice, _ = gasenergy.meshslice(slice=angle)
|
|
123
|
+
row["gasenergy"] = gasenergy_slice
|
|
124
|
+
|
|
125
|
+
# Convert the row to a DataFrame and concatenate it
|
|
126
|
+
row_df = pd.DataFrame([row])
|
|
127
|
+
df_snapshots = pd.concat([df_snapshots, row_df], ignore_index=True)
|
|
128
|
+
|
|
129
|
+
self.df = df_snapshots
|
|
130
|
+
return df_snapshots
|
|
131
|
+
|
|
132
|
+
elif plane is None and angle is None: # Load 3D data
|
|
133
|
+
print("Loading 3D data.")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Generate 3D mesh
|
|
137
|
+
theta, r, phi = np.meshgrid(self.sim.domains.theta, self.sim.domains.r, self.sim.domains.phi, indexing='ij')
|
|
138
|
+
x, y, z = r * np.sin(theta) * np.cos(phi), r * np.sin(theta) * np.sin(phi), r * np.cos(theta)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Create an empty DataFrame for the current field
|
|
142
|
+
columns = ["snapshot", "time", "x", "y", "z"]
|
|
143
|
+
if field == "gasv":
|
|
144
|
+
columns.extend(["vx", "vy", "vz"])
|
|
145
|
+
else:
|
|
146
|
+
columns.append(field)
|
|
147
|
+
|
|
148
|
+
df_snapshots = pd.DataFrame(columns=columns)
|
|
149
|
+
|
|
150
|
+
for i, snap in enumerate(snaps):
|
|
151
|
+
row = {"snapshot": snap, "time": time_values[i], "x": x.ravel(), "y": y.ravel(), "z": z.ravel()}
|
|
152
|
+
|
|
153
|
+
if field == "gasdens":
|
|
154
|
+
gasd = self.sim.load_field("gasdens", snapshot=snap, type="scalar")
|
|
155
|
+
row[field] = gasd.data.ravel()
|
|
156
|
+
|
|
157
|
+
elif field == "gasv":
|
|
158
|
+
gasv = self.sim.load_field("gasv", snapshot=snap, type="vector")
|
|
159
|
+
gasvx, gasvy, gasvz = gasv.to_cartesian()
|
|
160
|
+
row["vx"] = gasvx.data.ravel()
|
|
161
|
+
row["vy"] = gasvy.data.ravel()
|
|
162
|
+
row["vz"] = gasvz.data.ravel()
|
|
163
|
+
|
|
164
|
+
elif field == "gasenergy":
|
|
165
|
+
gasenergy = self.sim.load_field("gasenergy", snapshot=snap, type="scalar")
|
|
166
|
+
row[field] = gasenergy.data.ravel()
|
|
167
|
+
|
|
168
|
+
# Append the row to the DataFrame
|
|
169
|
+
df_snapshots = pd.concat([df_snapshots, pd.DataFrame([row])], ignore_index=True)
|
|
170
|
+
|
|
171
|
+
self.df = df_snapshots
|
|
172
|
+
# Return the single DataFrame
|
|
173
|
+
return df_snapshots
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def evaluate(self, time, var1, var2, var3=None):
|
|
177
|
+
"""
|
|
178
|
+
Interpolates a field in 2D or 3D depending on the provided parameters.
|
|
179
|
+
|
|
180
|
+
Parameters:
|
|
181
|
+
time (float): Time at which to interpolate.
|
|
182
|
+
var1, var2 (numpy.ndarray or float): Spatial coordinates in 2D.
|
|
183
|
+
var3 (numpy.ndarray or float, optional): Additional coordinate for 3D. If None, 2D is assumed.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
numpy.ndarray or float: Interpolated field values at the given coordinates.
|
|
187
|
+
If velocity fields are present, returns a tuple (vx, vy, vz) or (vx, vy).
|
|
188
|
+
"""
|
|
189
|
+
# Automatically determine the field to interpolate
|
|
190
|
+
if "gasdens" in self.df.columns:
|
|
191
|
+
field_name = "gasdens"
|
|
192
|
+
elif "gasenergy" in self.df.columns:
|
|
193
|
+
field_name = "gasenergy"
|
|
194
|
+
elif {"vx", "vy", "vz"}.issubset(self.df.columns): # 3D velocity
|
|
195
|
+
field_name = ["vx", "vy", "vz"]
|
|
196
|
+
elif {"vx", "vy"}.issubset(self.df.columns): # 2D velocity (vx, vy)
|
|
197
|
+
field_name = ["vx", "vy"]
|
|
198
|
+
elif {"vx", "vz"}.issubset(self.df.columns): # 2D velocity (vx, vz)
|
|
199
|
+
field_name = ["vx", "vz"]
|
|
200
|
+
elif {"vy", "vz"}.issubset(self.df.columns): # 2D velocity (vy, vz)
|
|
201
|
+
field_name = ["vy", "vz"]
|
|
202
|
+
else:
|
|
203
|
+
raise ValueError("No valid field found in the DataFrame for interpolation.")
|
|
204
|
+
|
|
205
|
+
# Sort the DataFrame by time
|
|
62
206
|
df_sorted = self.df.sort_values("time")
|
|
63
207
|
idx = df_sorted["time"].searchsorted(time) - 1
|
|
64
208
|
if idx == -1:
|
|
@@ -70,49 +214,122 @@ class DataHandler:
|
|
|
70
214
|
if factor < 0:
|
|
71
215
|
factor = 0
|
|
72
216
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
217
|
+
# Check if the input is a single point or a mesh
|
|
218
|
+
is_scalar = np.isscalar(var1) and np.isscalar(var2) and (var3 is None or np.isscalar(var3))
|
|
219
|
+
if is_scalar:
|
|
220
|
+
result_shape = ()
|
|
221
|
+
else:
|
|
222
|
+
result_shape = var1.shape # Preserve the shape of the input mesh
|
|
223
|
+
|
|
224
|
+
def interp(idx, field):
|
|
225
|
+
if var3 is not None: # 3D interpolation
|
|
226
|
+
coord_x = np.array(df_sorted.iloc[idx]["x"])
|
|
227
|
+
coord_y = np.array(df_sorted.iloc[idx]["y"])
|
|
228
|
+
coord_z = np.array(df_sorted.iloc[idx]["z"])
|
|
229
|
+
points = np.column_stack((coord_x.ravel(), coord_y.ravel(), coord_z.ravel()))
|
|
230
|
+
data = np.array(df_sorted.iloc[idx][field]).ravel()
|
|
231
|
+
return griddata(points, data, (var1, var2, var3), method='nearest', fill_value=0.0)
|
|
232
|
+
else: # 2D interpolation
|
|
233
|
+
if 'x' in self.df.columns and 'y' in self.df.columns:
|
|
234
|
+
coord1 = np.array(df_sorted.iloc[idx]["x"])
|
|
235
|
+
coord2 = np.array(df_sorted.iloc[idx]["y"])
|
|
236
|
+
elif 'x' in self.df.columns and 'z' in self.df.columns:
|
|
237
|
+
coord1 = np.array(df_sorted.iloc[idx]["x"])
|
|
238
|
+
coord2 = np.array(df_sorted.iloc[idx]["z"])
|
|
239
|
+
elif 'y' in self.df.columns and 'z' in self.df.columns:
|
|
240
|
+
coord1 = np.array(df_sorted.iloc[idx]["y"])
|
|
241
|
+
coord2 = np.array(df_sorted.iloc[idx]["z"])
|
|
242
|
+
else:
|
|
243
|
+
raise ValueError("Insufficient spatial coordinates for interpolation.")
|
|
244
|
+
points = np.column_stack((coord1.ravel(), coord2.ravel()))
|
|
245
|
+
data = np.array(df_sorted.iloc[idx][field]).ravel()
|
|
246
|
+
return griddata(points, data, (var1, var2), method='linear', fill_value=0.0)
|
|
247
|
+
|
|
248
|
+
# Preallocate arrays for results
|
|
249
|
+
if isinstance(field_name, list): # Velocity (multiple fields)
|
|
250
|
+
results = []
|
|
251
|
+
for field in field_name:
|
|
252
|
+
interpolated = (1 - factor) * interp(idx, field) + factor * interp(idx_after, field)
|
|
253
|
+
if is_scalar:
|
|
254
|
+
results.append(interpolated.item()) # Extract scalar value
|
|
255
|
+
else:
|
|
256
|
+
results.append(interpolated)
|
|
257
|
+
return results
|
|
258
|
+
else: # Scalar field (gasdens or gasenergy)
|
|
259
|
+
interpolated = (1 - factor) * interp(idx, field_name) + factor * interp(idx_after, field_name)
|
|
260
|
+
if is_scalar:
|
|
261
|
+
return interpolated.item() # Extract scalar value
|
|
262
|
+
else:
|
|
263
|
+
return interpolated
|
|
81
264
|
|
|
82
|
-
def interpolate_velocity(self, time, var1, var2):
|
|
83
|
-
v1 = self.interpolate_field(time, var1, var2, "vel1")
|
|
84
|
-
v2 = self.interpolate_field(time, var1, var2, "vel2")
|
|
85
|
-
return v1, v2
|
|
86
265
|
|
|
87
|
-
def interpolate_density(self, time, var1, var2):
|
|
88
|
-
return self.interpolate_field(time, var1, var2, "gasdens")
|
|
89
266
|
|
|
90
267
|
|
|
91
|
-
class
|
|
268
|
+
class fluidmotion:
|
|
92
269
|
def __init__(self, plane, angle, num_snapshots, dir_path):
|
|
270
|
+
"""
|
|
271
|
+
Initializes the Simulation class.
|
|
272
|
+
|
|
273
|
+
Parameters:
|
|
274
|
+
plane (str): The plane for 2D data ('XZ', 'XY', 'YZ').
|
|
275
|
+
angle (float): The angle for the 2D slice.
|
|
276
|
+
num_snapshots (int): Number of snapshots for 2D data.
|
|
277
|
+
dir_path (str): Directory path where the simulation data is stored.
|
|
278
|
+
"""
|
|
93
279
|
self.sim = fp.Simulation(output_dir=dir_path)
|
|
94
280
|
self.data_handler = DataHandler(self.sim)
|
|
95
281
|
self.data_handler.load_data(plane, angle, num_snapshots)
|
|
96
282
|
|
|
97
283
|
def velocity_field(self, t, y):
|
|
284
|
+
"""
|
|
285
|
+
Computes the velocity field at a given time and position.
|
|
286
|
+
|
|
287
|
+
Parameters:
|
|
288
|
+
t (float): Time at which to compute the velocity field.
|
|
289
|
+
y (array-like): Position [var1, var2] where the velocity is computed.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
numpy.ndarray: Velocity vector [v1, v2] at the given position and time.
|
|
293
|
+
"""
|
|
98
294
|
var1, var2 = y
|
|
99
295
|
v1, v2 = self.data_handler.interpolate_velocity(t, np.array([var1]), np.array([var2]))
|
|
100
296
|
return np.array([v1[0], v2[0]])
|
|
101
297
|
|
|
102
298
|
def integrate_particles(self, particle_pos, time, dt=0.01):
|
|
103
|
-
"""
|
|
299
|
+
"""
|
|
300
|
+
Integrates all particles using an explicit Euler step.
|
|
301
|
+
|
|
302
|
+
Parameters:
|
|
303
|
+
particle_pos (numpy.ndarray): Array of particle positions (shape: [n_particles, 2]).
|
|
304
|
+
time (float): Current time of the simulation.
|
|
305
|
+
dt (float): Time step for integration.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
numpy.ndarray: Updated particle positions after integration.
|
|
309
|
+
"""
|
|
104
310
|
if len(particle_pos) == 0:
|
|
105
311
|
return np.array([])
|
|
106
312
|
|
|
107
313
|
v1, v2 = self.data_handler.interpolate_velocity(time, particle_pos[:, 0], particle_pos[:, 1])
|
|
108
314
|
|
|
109
|
-
#
|
|
315
|
+
# Euler step: x_{n+1} = x_n + v * dt
|
|
110
316
|
particle_pos[:, 0] += v1 * dt
|
|
111
317
|
particle_pos[:, 1] += v2 * dt
|
|
112
318
|
|
|
113
319
|
return particle_pos
|
|
114
320
|
|
|
115
321
|
def generate_uniform_particles(self, var1_min, var1_max, var2_min, var2_max, num_particles):
|
|
322
|
+
"""
|
|
323
|
+
Generates uniformly distributed particles within a specified region.
|
|
324
|
+
|
|
325
|
+
Parameters:
|
|
326
|
+
var1_min, var1_max (float): Range for the first coordinate.
|
|
327
|
+
var2_min, var2_max (float): Range for the second coordinate.
|
|
328
|
+
num_particles (int): Number of particles to generate.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
numpy.ndarray: Array of particle positions (shape: [num_particles, 2]).
|
|
332
|
+
"""
|
|
116
333
|
grid_size = int(np.sqrt(num_particles))
|
|
117
334
|
var1_candidates = np.linspace(var1_min + 0.01, var1_max - 0.01, grid_size)
|
|
118
335
|
var2_candidates = np.linspace(var2_min + 0.001, var2_max - 0.001, grid_size)
|
|
@@ -133,6 +350,21 @@ class Simulation:
|
|
|
133
350
|
return new_particles
|
|
134
351
|
|
|
135
352
|
def run_simulation(self, res, var1_min, var1_max, var2_min, var2_max, ts, npi, max_lifetime, generation_interval):
|
|
353
|
+
"""
|
|
354
|
+
Runs the particle simulation and generates an animation.
|
|
355
|
+
|
|
356
|
+
Parameters:
|
|
357
|
+
res (int): Resolution of the grid for density interpolation.
|
|
358
|
+
var1_min, var1_max (float): Range for the first coordinate.
|
|
359
|
+
var2_min, var2_max (float): Range for the second coordinate.
|
|
360
|
+
ts (int): Number of time steps for the simulation.
|
|
361
|
+
npi (int): Number of particles to generate at each interval.
|
|
362
|
+
max_lifetime (int): Maximum lifetime of particles.
|
|
363
|
+
generation_interval (int): Interval for generating new particles.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
IPython.display.Video: Animation of the particle simulation.
|
|
367
|
+
"""
|
|
136
368
|
var1_reg, var2_reg = np.linspace(var1_min, var1_max, res), np.linspace(var2_min, var2_max, res)
|
|
137
369
|
VAR1_reg, VAR2_reg = np.meshgrid(var1_reg, var2_reg, indexing='ij')
|
|
138
370
|
|
|
@@ -143,14 +375,14 @@ class Simulation:
|
|
|
143
375
|
lifetimes = np.empty(0)
|
|
144
376
|
new_particles = self.generate_uniform_particles(var1_min, var1_max, var2_min, var2_max, npi)
|
|
145
377
|
|
|
146
|
-
|
|
378
|
+
# Determine the y-axis label based on the plane
|
|
147
379
|
plane = self.data_handler.plane
|
|
148
|
-
y_label = "Z [AU]" if plane == "XZ" else
|
|
380
|
+
y_label = "Z [AU]" if plane == "XZ" else "Y [AU]"
|
|
149
381
|
|
|
150
382
|
fig, ax = plt.subplots(figsize=(8, 8))
|
|
151
383
|
camera = Camera(fig)
|
|
152
384
|
|
|
153
|
-
with tqdm(total=len(t_eval), desc="
|
|
385
|
+
with tqdm(total=len(t_eval), desc="Generating animation", unit="frame") as pbar:
|
|
154
386
|
for frame in range(len(t_eval)):
|
|
155
387
|
time = t_eval[frame]
|
|
156
388
|
if frame % generation_interval == 0:
|
|
@@ -167,437 +399,31 @@ class Simulation:
|
|
|
167
399
|
|
|
168
400
|
lifetimes_normalized = lifetimes / max_lifetime
|
|
169
401
|
|
|
170
|
-
#
|
|
402
|
+
# Add density interpolation as background
|
|
171
403
|
gasd_interpolated = self.data_handler.interpolate_density(time, VAR1_reg, VAR2_reg)
|
|
172
404
|
c = ax.pcolormesh(VAR1_reg, VAR2_reg, np.log10(gasd_interpolated * self.sim.URHO * 1e3),
|
|
173
|
-
|
|
405
|
+
cmap="viridis", shading='auto')
|
|
174
406
|
|
|
175
|
-
#
|
|
407
|
+
# Plot particles
|
|
176
408
|
if len(particle_pos) > 0:
|
|
177
409
|
ax.scatter(particle_pos[:, 0], particle_pos[:, 1], c='lightgray', alpha=lifetimes_normalized, s=1.0)
|
|
178
410
|
|
|
179
411
|
ax.set_xlim(var1_min, var1_max)
|
|
180
412
|
ax.set_ylim(var2_min, var2_max)
|
|
181
|
-
ax.set_xlabel(r"$r \ [AU]$",size=12)
|
|
182
|
-
ax.set_ylabel(y_label,size=12)
|
|
413
|
+
ax.set_xlabel(r"$r \ [AU]$", size=12)
|
|
414
|
+
ax.set_ylabel(y_label, size=12)
|
|
183
415
|
camera.snap()
|
|
184
416
|
|
|
185
417
|
pbar.update(1)
|
|
186
418
|
|
|
187
|
-
#
|
|
419
|
+
# Add color bar
|
|
188
420
|
fig.colorbar(c, ax=ax, label=r'$\log_{10}(\rho)$ [kg/m$^3$]')
|
|
189
421
|
plt.close(fig)
|
|
190
422
|
animation = camera.animate()
|
|
191
|
-
video_filename = '
|
|
423
|
+
video_filename = 'particles.mp4'
|
|
192
424
|
animation.save(video_filename, writer=FFMpegWriter(fps=10, codec='libx264', bitrate=5000))
|
|
193
425
|
|
|
194
|
-
#
|
|
426
|
+
# Display the video in the interactive environment
|
|
195
427
|
return Video(video_filename, embed=True)
|
|
196
428
|
|
|
197
429
|
|
|
198
|
-
class Visualize:
|
|
199
|
-
def __init__(self, data_handler):
|
|
200
|
-
"""
|
|
201
|
-
Inicializa la clase Visualize con una instancia de DataHandler.
|
|
202
|
-
"""
|
|
203
|
-
self.data_handler = data_handler
|
|
204
|
-
|
|
205
|
-
def density(self, var1_min, var1_max, var2_min, var2_max, res, time):
|
|
206
|
-
"""
|
|
207
|
-
Grafica un mapa de contornos de densidad interpolada en un tiempo dado.
|
|
208
|
-
"""
|
|
209
|
-
# Crear una cuadrícula regular para las coordenadas
|
|
210
|
-
var1 = np.linspace(var1_min, var1_max, res)
|
|
211
|
-
var2 = np.linspace(var2_min, var2_max, res)
|
|
212
|
-
VAR1, VAR2 = np.meshgrid(var1, var2, indexing='ij')
|
|
213
|
-
|
|
214
|
-
# Interpolar la densidad en el tiempo dado
|
|
215
|
-
density = self.data_handler.interpolate_density(time, VAR1, VAR2).T
|
|
216
|
-
|
|
217
|
-
# Determinar el label del eje y en función del plano
|
|
218
|
-
plane = self.data_handler.plane
|
|
219
|
-
y_label = "Z [AU]" if plane == "XZ" else r"$\phi$ [rad]"
|
|
220
|
-
|
|
221
|
-
# Crear el gráfico de contorno
|
|
222
|
-
fig = go.Figure(
|
|
223
|
-
data=go.Contour(
|
|
224
|
-
z=np.log10(density * self.data_handler.sim.URHO * 1e3),
|
|
225
|
-
x=var1,
|
|
226
|
-
y=var2,
|
|
227
|
-
colorscale="Spectral_r",
|
|
228
|
-
contours=dict(coloring="heatmap"),
|
|
229
|
-
colorbar=dict(title="log10(ρ) [kg/m³]")
|
|
230
|
-
)
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
# Configurar el diseño del gráfico
|
|
234
|
-
fig.update_layout(
|
|
235
|
-
title=f"Mapa de Contornos de Densidad (t = {time:.2f})",
|
|
236
|
-
xaxis_title="r [AU]",
|
|
237
|
-
yaxis_title=y_label,
|
|
238
|
-
width=600,
|
|
239
|
-
height=600
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
# Mostrar el gráfico
|
|
243
|
-
fig.show()
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def animate_density(self, var1_min, var1_max, var2_min, var2_max, res, time_array):
|
|
249
|
-
|
|
250
|
-
# Crear una cuadrícula regular para las coordenadas
|
|
251
|
-
var1 = np.linspace(var1_min, var1_max, res)
|
|
252
|
-
var2 = np.linspace(var2_min, var2_max, res)
|
|
253
|
-
VAR1, VAR2 = np.meshgrid(var1, var2, indexing='ij')
|
|
254
|
-
|
|
255
|
-
# Precalcular el campo de densidad para todos los tiempos
|
|
256
|
-
precalculated_density = [
|
|
257
|
-
self.data_handler.interpolate_density(time, VAR1, VAR2).T
|
|
258
|
-
for time in time_array
|
|
259
|
-
]
|
|
260
|
-
# Determinar el label del eje y en función del plano
|
|
261
|
-
plane = self.data_handler.plane
|
|
262
|
-
y_label = "Z [AU]" if plane == "XZ" else r"$\phi$ [rad]"
|
|
263
|
-
|
|
264
|
-
# Crear la figura inicial
|
|
265
|
-
initial_density = precalculated_density[0]
|
|
266
|
-
fig = go.Figure(
|
|
267
|
-
data=go.Contour(
|
|
268
|
-
z=np.log10(initial_density * self.data_handler.sim.URHO * 1e3),
|
|
269
|
-
x=var1,
|
|
270
|
-
y=var2,
|
|
271
|
-
colorscale="Spectral_r",
|
|
272
|
-
contours=dict(coloring="heatmap"),
|
|
273
|
-
colorbar=dict(title="log10(ρ) [kg/m³]")
|
|
274
|
-
)
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
# Crear los frames para la animación
|
|
278
|
-
frames = []
|
|
279
|
-
for i, time in enumerate(time_array):
|
|
280
|
-
density = precalculated_density[i]
|
|
281
|
-
frames.append(go.Frame(
|
|
282
|
-
data=go.Contour(
|
|
283
|
-
z=np.log10(density * self.data_handler.sim.URHO * 1e3),
|
|
284
|
-
x=var1,
|
|
285
|
-
y=var2,
|
|
286
|
-
colorscale="Spectral_r", # Este colormap será actualizado dinámicamente
|
|
287
|
-
contours=dict(coloring="heatmap")
|
|
288
|
-
),
|
|
289
|
-
name=f"{time:.2f}"
|
|
290
|
-
))
|
|
291
|
-
|
|
292
|
-
fig.frames = frames
|
|
293
|
-
|
|
294
|
-
# Configurar el slider
|
|
295
|
-
sliders = [
|
|
296
|
-
dict(
|
|
297
|
-
steps=[
|
|
298
|
-
dict(
|
|
299
|
-
method="animate",
|
|
300
|
-
args=[[f"{time:.2f}"], dict(mode="immediate", frame=dict(duration=100, redraw=True), transition=dict(duration=0))],
|
|
301
|
-
label=f"{time:.2f}"
|
|
302
|
-
)
|
|
303
|
-
for time in time_array
|
|
304
|
-
],
|
|
305
|
-
transition=dict(duration=0),
|
|
306
|
-
currentvalue=dict(font=dict(size=16), prefix="Tiempo: ", visible=True),
|
|
307
|
-
len=0.9
|
|
308
|
-
)
|
|
309
|
-
]
|
|
310
|
-
|
|
311
|
-
# Configurar los botones de reproducción y pausa
|
|
312
|
-
updatemenus = [
|
|
313
|
-
dict(
|
|
314
|
-
type="buttons",
|
|
315
|
-
showactive=False,
|
|
316
|
-
buttons=[
|
|
317
|
-
dict(label="Play",
|
|
318
|
-
method="animate",
|
|
319
|
-
args=[None, dict(frame=dict(duration=100, redraw=True), fromcurrent=True)]),
|
|
320
|
-
dict(label="Pause",
|
|
321
|
-
method="animate",
|
|
322
|
-
args=[[None], dict(frame=dict(duration=0, redraw=False), mode="immediate")])
|
|
323
|
-
]
|
|
324
|
-
)
|
|
325
|
-
]
|
|
326
|
-
|
|
327
|
-
# Actualizar el diseño de la figura
|
|
328
|
-
fig.update_layout(
|
|
329
|
-
title="Evolución del Campo de Densidad",
|
|
330
|
-
xaxis_title='r [AU]',
|
|
331
|
-
yaxis_title=y_label,
|
|
332
|
-
width=600,
|
|
333
|
-
height=600,
|
|
334
|
-
sliders=sliders,
|
|
335
|
-
updatemenus=updatemenus
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
# Mostrar la animación
|
|
339
|
-
fig.show()
|
|
340
|
-
|
|
341
|
-
def velocity(self, var1_min, var1_max, var2_min, var2_max, res, time):
|
|
342
|
-
"""
|
|
343
|
-
Grafica un mapa de magnitud de velocidad interpolada en un tiempo dado.
|
|
344
|
-
Las zonas donde la interpolación da 0 no serán coloreadas.
|
|
345
|
-
"""
|
|
346
|
-
# Crear una cuadrícula regular para las coordenadas
|
|
347
|
-
var1 = np.linspace(var1_min, var1_max, res)
|
|
348
|
-
var2 = np.linspace(var2_min, var2_max, res)
|
|
349
|
-
VAR1, VAR2 = np.meshgrid(var1, var2, indexing='ij')
|
|
350
|
-
|
|
351
|
-
# Interpolar el campo de velocidad en el tiempo dado
|
|
352
|
-
velocity_x, velocity_y = self.data_handler.interpolate_velocity(time, VAR1, VAR2)
|
|
353
|
-
|
|
354
|
-
# Calcular la magnitud de la velocidad
|
|
355
|
-
velocity_magnitude = np.sqrt(velocity_x**2 + velocity_y**2).T * self.data_handler.sim.UV / (1e5) # Convertir a km/s
|
|
356
|
-
|
|
357
|
-
# Establecer las zonas donde velocity_x y velocity_y son 0 como None
|
|
358
|
-
velocity_magnitude[velocity_magnitude==0] = None
|
|
359
|
-
|
|
360
|
-
# Determinar el label del eje y en función del plano
|
|
361
|
-
plane = self.data_handler.plane
|
|
362
|
-
y_label = "Z [AU]" if plane == "XZ" else r"$\phi$ [rad]"
|
|
363
|
-
|
|
364
|
-
# Crear el gráfico de contorno
|
|
365
|
-
fig = go.Figure(
|
|
366
|
-
data=go.Contour(
|
|
367
|
-
z=velocity_magnitude,
|
|
368
|
-
x=var1,
|
|
369
|
-
y=var2,
|
|
370
|
-
colorscale="Viridis",
|
|
371
|
-
contours=dict(coloring="heatmap"),
|
|
372
|
-
colorbar=dict(title="|v| [km/s]"),
|
|
373
|
-
zmin=np.nanmin(velocity_magnitude),
|
|
374
|
-
zmax=np.nanmax(velocity_magnitude),
|
|
375
|
-
showscale=True
|
|
376
|
-
)
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
# Configurar el diseño del gráfico
|
|
380
|
-
fig.update_layout(
|
|
381
|
-
title=f"Mapa de Velocidad (t = {time:.2f})",
|
|
382
|
-
xaxis_title="r [AU]",
|
|
383
|
-
yaxis_title=y_label,
|
|
384
|
-
width=600,
|
|
385
|
-
height=600,
|
|
386
|
-
plot_bgcolor="white" # Fondo blanco para que las zonas no coloreadas sean visibles
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
# Mostrar el gráfico
|
|
390
|
-
fig.show()
|
|
391
|
-
|
|
392
|
-
def animate_velocity(self, var1_min, var1_max, var2_min, var2_max, res, time_array):
|
|
393
|
-
"""
|
|
394
|
-
Crea una animación del mapa de magnitud de velocidad interpolada en función del tiempo.
|
|
395
|
-
Las zonas donde la magnitud de la velocidad es 0 no serán coloreadas.
|
|
396
|
-
"""
|
|
397
|
-
# Crear una cuadrícula regular para las coordenadas
|
|
398
|
-
var1 = np.linspace(var1_min, var1_max, res)
|
|
399
|
-
var2 = np.linspace(var2_min, var2_max, res)
|
|
400
|
-
VAR1, VAR2 = np.meshgrid(var1, var2, indexing='ij')
|
|
401
|
-
|
|
402
|
-
# Determinar el label del eje y en función del plano
|
|
403
|
-
plane = self.data_handler.plane
|
|
404
|
-
y_label = "Z [AU]" if plane == "XZ" else r"$\phi$ [rad]"
|
|
405
|
-
|
|
406
|
-
# Precalcular la magnitud del campo de velocidad para todos los tiempos
|
|
407
|
-
precalculated_velocity_magnitude = []
|
|
408
|
-
for time in time_array:
|
|
409
|
-
velocity_x, velocity_y = self.data_handler.interpolate_velocity(time, VAR1, VAR2)
|
|
410
|
-
velocity_magnitude = np.sqrt(velocity_x**2 + velocity_y**2).T * self.data_handler.sim.UV / (1e5) # Convertir a km/s
|
|
411
|
-
velocity_magnitude[velocity_magnitude == 0] = None # Reemplazar las zonas con magnitud 0 por None
|
|
412
|
-
precalculated_velocity_magnitude.append(velocity_magnitude)
|
|
413
|
-
|
|
414
|
-
# Crear la figura inicial
|
|
415
|
-
initial_velocity_magnitude = precalculated_velocity_magnitude[0]
|
|
416
|
-
fig = go.Figure(
|
|
417
|
-
data=go.Contour(
|
|
418
|
-
z=initial_velocity_magnitude,
|
|
419
|
-
x=var1,
|
|
420
|
-
y=var2,
|
|
421
|
-
colorscale="Viridis",
|
|
422
|
-
contours=dict(coloring="heatmap"),
|
|
423
|
-
colorbar=dict(title="|v| [km/s]")
|
|
424
|
-
)
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
# Crear los frames para la animación
|
|
428
|
-
frames = []
|
|
429
|
-
for i, time in enumerate(time_array):
|
|
430
|
-
velocity_magnitude = precalculated_velocity_magnitude[i]
|
|
431
|
-
frames.append(go.Frame(
|
|
432
|
-
data=go.Contour(
|
|
433
|
-
z=velocity_magnitude,
|
|
434
|
-
x=var1,
|
|
435
|
-
y=var2,
|
|
436
|
-
colorscale="Viridis",
|
|
437
|
-
contours=dict(coloring="heatmap")
|
|
438
|
-
),
|
|
439
|
-
name=f"{time:.2f}"
|
|
440
|
-
))
|
|
441
|
-
|
|
442
|
-
fig.frames = frames
|
|
443
|
-
|
|
444
|
-
# Configurar el slider
|
|
445
|
-
sliders = [
|
|
446
|
-
dict(
|
|
447
|
-
steps=[
|
|
448
|
-
dict(
|
|
449
|
-
method="animate",
|
|
450
|
-
args=[[f"{time:.2f}"], dict(mode="immediate", frame=dict(duration=150, redraw=True), transition=dict(duration=0))],
|
|
451
|
-
label=f"{time:.2f}"
|
|
452
|
-
)
|
|
453
|
-
for time in time_array
|
|
454
|
-
],
|
|
455
|
-
transition=dict(duration=0),
|
|
456
|
-
currentvalue=dict(font=dict(size=16), prefix="Tiempo: ", visible=True),
|
|
457
|
-
len=0.9
|
|
458
|
-
)
|
|
459
|
-
]
|
|
460
|
-
|
|
461
|
-
# Configurar los botones de reproducción y pausa
|
|
462
|
-
updatemenus = [
|
|
463
|
-
dict(
|
|
464
|
-
type="buttons",
|
|
465
|
-
showactive=False,
|
|
466
|
-
buttons=[
|
|
467
|
-
dict(label="Play",
|
|
468
|
-
method="animate",
|
|
469
|
-
args=[None, dict(frame=dict(duration=100, redraw=True), fromcurrent=True)]),
|
|
470
|
-
dict(label="Pause",
|
|
471
|
-
method="animate",
|
|
472
|
-
args=[[None], dict(frame=dict(duration=0, redraw=False), mode="immediate")])
|
|
473
|
-
]
|
|
474
|
-
)
|
|
475
|
-
]
|
|
476
|
-
|
|
477
|
-
# Actualizar el diseño de la figura
|
|
478
|
-
fig.update_layout(
|
|
479
|
-
title="Evolución del Campo de Velocidad",
|
|
480
|
-
xaxis_title='r [AU]',
|
|
481
|
-
yaxis_title=y_label,
|
|
482
|
-
width=600,
|
|
483
|
-
height=600,
|
|
484
|
-
sliders=sliders,
|
|
485
|
-
updatemenus=updatemenus
|
|
486
|
-
)
|
|
487
|
-
|
|
488
|
-
# Mostrar la animación
|
|
489
|
-
fig.show()
|
|
490
|
-
|
|
491
|
-
def vel_streamlines(self, var1_min, var1_max, var2_min, var2_max, res, time):
|
|
492
|
-
"""
|
|
493
|
-
Grafica las streamlines del campo de velocidad con la densidad de fondo para un tiempo dado.
|
|
494
|
-
"""
|
|
495
|
-
# Crear una cuadrícula regular para las coordenadas
|
|
496
|
-
var1 = np.linspace(var1_min, var1_max, res)
|
|
497
|
-
var2 = np.linspace(var2_min, var2_max, res)
|
|
498
|
-
VAR1, VAR2 = np.meshgrid(var1, var2, indexing='ij')
|
|
499
|
-
|
|
500
|
-
# Interpolar el campo de densidad y velocidad en el tiempo dado
|
|
501
|
-
density = self.data_handler.interpolate_density(time, VAR1, VAR2)
|
|
502
|
-
velocity_x, velocity_y = self.data_handler.interpolate_velocity(time, VAR1, VAR2)
|
|
503
|
-
|
|
504
|
-
# Calcular la magnitud de la velocidad
|
|
505
|
-
v_mag = np.sqrt(velocity_x**2 + velocity_y**2).T * self.data_handler.sim.UV / (1e5) # Convertir a km/s
|
|
506
|
-
|
|
507
|
-
# Determinar el label del eje y en función del plano
|
|
508
|
-
plane = self.data_handler.plane
|
|
509
|
-
y_label = "Z [AU]" if plane == "XZ" else r"$\phi$ [rad]"
|
|
510
|
-
|
|
511
|
-
# Crear el gráfico
|
|
512
|
-
plt.figure(figsize=(6, 6))
|
|
513
|
-
plt.pcolormesh(var1, var2, np.log10(density * self.data_handler.sim.URHO * 1e3).T, cmap="Spectral_r", shading='auto')
|
|
514
|
-
plt.streamplot(var1, var2, velocity_x.T, velocity_y.T, color=v_mag, linewidth=0.7, density=3.0, cmap='viridis')
|
|
515
|
-
plt.colorbar(label="|v| [km/s]")
|
|
516
|
-
plt.title(f"Streamlines (t = {time:.2f})")
|
|
517
|
-
plt.xlabel("r [AU]")
|
|
518
|
-
plt.ylabel(y_label)
|
|
519
|
-
plt.show()
|
|
520
|
-
|
|
521
|
-
def vel_streamlines_slide(self, var1_min, var1_max, var2_min, var2_max, res, time_array):
|
|
522
|
-
|
|
523
|
-
# Crear una cuadrícula regular para las coordenadas
|
|
524
|
-
var1 = np.linspace(var1_min, var1_max, res)
|
|
525
|
-
var2 = np.linspace(var2_min, var2_max, res)
|
|
526
|
-
VAR1, VAR2 = np.meshgrid(var1, var2, indexing='ij')
|
|
527
|
-
# Determinar el label del eje y en función del plano
|
|
528
|
-
plane = self.data_handler.plane
|
|
529
|
-
y_label = "Z [AU]" if plane == "XZ" else r"$\phi$ [rad]"
|
|
530
|
-
|
|
531
|
-
# Precalcular densidad y velocidades para todos los tiempos
|
|
532
|
-
precalculated_data = [
|
|
533
|
-
{
|
|
534
|
-
"density": self.data_handler.interpolate_density(time, VAR1, VAR2),
|
|
535
|
-
"velocity": self.data_handler.interpolate_velocity(time, VAR1, VAR2)
|
|
536
|
-
}
|
|
537
|
-
for time in time_array
|
|
538
|
-
]
|
|
539
|
-
|
|
540
|
-
def update_plot(time_index):
|
|
541
|
-
"""
|
|
542
|
-
Actualiza el gráfico para un índice de tiempo dado.
|
|
543
|
-
"""
|
|
544
|
-
data = precalculated_data[time_index]
|
|
545
|
-
density = data["density"]
|
|
546
|
-
velocity_x = data["velocity"][0]
|
|
547
|
-
velocity_y = data["velocity"][1]
|
|
548
|
-
v_mag = np.sqrt(velocity_x**2 + velocity_y**2).T*self.data_handler.sim.UV/(1e5)
|
|
549
|
-
# Crear el gráfico
|
|
550
|
-
plt.figure(figsize=(6, 6))
|
|
551
|
-
plt.pcolormesh(var1, var2, np.log10(density * self.data_handler.sim.URHO * 1e3).T, cmap="Spectral_r", shading='auto')
|
|
552
|
-
plt.streamplot(var1, var2, velocity_x.T, velocity_y.T, color=v_mag, linewidth=0.7, density=3.0,cmap='viridis')
|
|
553
|
-
plt.colorbar(label="|v| [km/s]")
|
|
554
|
-
plt.title(f"Streamlines (t = {time_array[time_index]:.2f})")
|
|
555
|
-
plt.xlabel("r [AU]")
|
|
556
|
-
plt.ylabel(y_label)
|
|
557
|
-
plt.show()
|
|
558
|
-
|
|
559
|
-
# Crear un slider interactivo para el tiempo (con índices enteros)
|
|
560
|
-
interact(update_plot, time_index=IntSlider(value=0, min=0, max=len(time_array) - 1, step=1))
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
def vel_streamlines_vid(self, var1_min, var1_max, var2_min, var2_max, res, time_array,output_file="streamlines_animation.mp4"):
|
|
564
|
-
|
|
565
|
-
# Crear una cuadrícula regular para las coordenadas
|
|
566
|
-
var1 = np.linspace(var1_min, var1_max, res)
|
|
567
|
-
var2 = np.linspace(var2_min, var2_max, res)
|
|
568
|
-
VAR1, VAR2 = np.meshgrid(var1, var2, indexing='ij')
|
|
569
|
-
# Determinar el label del eje y en función del plano
|
|
570
|
-
plane = self.data_handler.plane
|
|
571
|
-
y_label = "Z [AU]" if plane == "XZ" else r"$\phi$ [rad]"
|
|
572
|
-
|
|
573
|
-
# Configurar la figura y la cámara para la animación
|
|
574
|
-
fig, ax = plt.subplots(figsize=(6, 6))
|
|
575
|
-
camera = Camera(fig)
|
|
576
|
-
|
|
577
|
-
# Generar los frames de la animación
|
|
578
|
-
for time in time_array:
|
|
579
|
-
# Interpolar el campo de densidad y velocidad
|
|
580
|
-
density = self.data_handler.interpolate_density(time, VAR1, VAR2)
|
|
581
|
-
velocity_x, velocity_y = self.data_handler.interpolate_velocity(time, VAR1, VAR2)
|
|
582
|
-
v_mag = np.sqrt(velocity_x**2 + velocity_y**2).T*self.data_handler.sim.UV/(1e5)
|
|
583
|
-
|
|
584
|
-
# Crear el gráfico para el frame actual
|
|
585
|
-
c = ax.pcolormesh(var1, var2, np.log10(density * self.data_handler.sim.URHO * 1e3).T, cmap="Spectral_r", shading='auto')
|
|
586
|
-
strm=ax.streamplot(var1, var2, velocity_x.T, velocity_y.T, color=v_mag, linewidth=0.7, density=3.0,cmap='viridis')
|
|
587
|
-
ax.set_xlabel("r [AU]")
|
|
588
|
-
ax.set_ylabel(y_label)
|
|
589
|
-
camera.snap()
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
# Crear la animación
|
|
593
|
-
fig.colorbar(strm.lines, ax=ax, label="|v| [km/s]")
|
|
594
|
-
plt.close(fig)
|
|
595
|
-
animation = camera.animate()
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
# Guardar la animación en un archivo
|
|
599
|
-
animation.save(output_file, writer="ffmpeg", fps=10)
|
|
600
|
-
|
|
601
|
-
plt.close(fig)
|
|
602
|
-
return Video(output_file, embed=True)
|
|
603
|
-
|