Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
import numpy as np from scipy.spatial import cKDTree class DPMSolverSplit2D: def __init__( self, nx=40, ny=40, lx=40.0, ly=40.0, dt=0.05, search_radius_factor=2.5, cos_theta_threshold=-0.1, velocity_type="quasilinear_transport", omega_0=0.04, ): """Initialize solver parameters and particle grid.""" self.nx, self.ny, self.lx, self.ly = nx, ny, lx, ly self.dt = dt self.search_radius_factor = search_radius_factor self.cos_theta_threshold = cos_theta_threshold self.velocity_type = velocity_type self.omega_0 = omega_0 valid_types = [ "quasilinear_transport", "linear_transport", "linear_rotation", "quasilinear_rotation", "rotating_density_blob", "rankine_vortex", "quasilinear_rotation_pressure", ] if velocity_type not in valid_types: raise ValueError(f"velocity_type must be one of {valid_types}") self.x0, self.y0 = lx / 2.0, ly / 2.0 self.R = min(lx, ly) / 5.0 self._initialize_particles() self._compute_initial_masses() self.initial_total_mass = self.compute_total_mass() print(f"Initialized DPM Solver ({velocity_type}) with {nx*ny} particles") def _initialize_particles(self): """Uniform grid initialization with paraboloid height.""" xs = np.linspace(0, self.lx, self.nx) ys = np.linspace(0, self.ly, self.ny) X, Y = np.meshgrid(xs, ys, indexing="ij") self.X, self.Y = X.ravel(), Y.ravel() self.N = len(self.X) r2 = (self.X - self.x0) ** 2 + (self.Y - self.y0) ** 2 self.H = np.where(r2 <= self.R**2, 1.0 - r2 / self.R**2, 0.0) self.H_initial = self.H.copy() self.dx = self.lx / (self.nx - 1) if self.nx > 1 else self.lx self.dy = self.ly / (self.ny - 1) if self.ny > 1 else self.ly if hasattr(self, "velocity_type") and self.velocity_type == "rankine_vortex": r = np.sqrt(r2) r_core = self.R / 2.0 denesity_factor = np.where( r < r_core, np.exp(-0.5 * (self.omega_0 * r) ** 2), np.exp(-0.5 * (self.omega_0 * r_core**2 / r) ** 2), ) self.H = self.H * (0.3 + 0.7 * denesity_factor) self.H_initial = self.H.copy() def velocity_field(self, X, Y, H): """Return velocity components (U,V) for given field type.""" if self.velocity_type == "quasilinear_transport": U = 0.5 * H V = 0.5 * H elif self.velocity_type == "linear_transport": U = 0.5 * np.ones_like(H) V = 0.5 * np.ones_like(H) elif self.velocity_type == "linear_rotation": dx, dy = X - self.x0, Y - self.y0 U, V = -dy * self.omega_0, dx * self.omega_0 elif self.velocity_type == "quasilinear_rotation": dx, dy = X - self.x0, Y - self.y0 omega = self.omega_0 * H U, V = -dy * omega, dx * omega elif self.velocity_type == "rotating_density_blob": dx, dy = X - self.x0, Y - self.y0 r = np.hypot(dx, dy) + 1e-10 omega = self.omega_0 * (1.0 + H) U_rot = -dy * omega V_rot = dx * omega dH_dr = -2.0 * r / self.R**2 * H strength = 0.05 U = U_rot + strength * dH_dr * (dx / r) V = V_rot + strength * dH_dr * (dy / r) elif self.velocity_type == "rankine_vortex": dx, dy = X - self.x0, Y - self.y0 r = np.hypot(dx, dy) + 1e-10 r_core = self.R / 2.0 v_theta = np.where( r < r_core, self.omega_0 * r, self.omega_0 * r_core**2 / r ) U = -dy / r * v_theta V = dx / r * v_theta elif self.velocity_type == "quasilinear_rotation_pressure": dx, dy = X - self.x0, Y - self.y0 r = np.hypot(dx, dy) + 1e-12 omega = self.omega_0 * H U_rot = -dy * omega V_rot = dx * omega alpha = 0.05 # давление # радиальный градиент H (аналитически, как у тебя в blob) dH_dr = -2.0 * r / self.R**2 * H U = U_rot - alpha * dH_dr * (dx / r) V = V_rot - alpha * dH_dr * (dy / r) return U, V def _compute_initial_masses(self): """Compute S0_ij between neighboring particles.""" self.S0 = {} self.initial_neighbors = {} tree = cKDTree(np.column_stack((self.X, self.Y))) R = max(self.dx, self.dy) * self.search_radius_factor for i in range(self.N): neighbor_list = [] for j in tree.query_ball_point([self.X[i], self.Y[i]], R): if j <= i: continue dist = np.hypot(self.X[i] - self.X[j], self.Y[i] - self.Y[j]) S0_ij = 0.5 * (self.H_initial[i] + self.H_initial[j]) * dist self.S0[(i, j)] = S0_ij self.S0[(j, i)] = S0_ij neighbor_list.append(j) self.initial_neighbors[i] = neighbor_list # Also add reverse connections for j in neighbor_list: if i not in self.initial_neighbors.get(j, []): self.initial_neighbors.setdefault(j, []).append(i) def _compute_impact_parameter(self, xi, yi, ui, vi, xj, yj, uj, vj): """Return cos(theta) between particles i and j.""" dx, dy = xj - xi, yj - yi dist = np.hypot(dx, dy) + 1e-14 du, dv = uj - ui, vj - vi v_rel = np.hypot(du, dv) + 1e-14 return (dx * du + dy * dv) / (dist * v_rel) def predictor_step(self): """Advance particle coordinates AND density by RK2 scheme.""" H, X, Y = self.H, self.X, self.Y # Step 1: Compute velocity and divergence at t U1, V1 = self.velocity_field(X, Y, H) div1 = self._compute_divergence_field(X, Y, H) # Half-step for X, Y, H X_half = X + 0.5 * self.dt * U1 Y_half = Y + 0.5 * self.dt * V1 H_half = H * (1.0 - 0.5 * self.dt * div1) # NEW: evolve H # Step 2: Compute velocity at half-step U2, V2 = self.velocity_field(X_half, Y_half, H_half) div2 = self._compute_divergence_field(X_half, Y_half, H_half) # Full step self.X_new = X + self.dt * U2 self.Y_new = Y + self.dt * V2 self.H_pred = H * (1.0 - self.dt * div2) # NEW: predicted H self.U_curr, self.V_curr = U2, V2 def _compute_divergence_field(self, X, Y, H): """Compute analytical divergence for known velocity fields""" if self.velocity_type == "quasilinear_rotation_pressure": # Analytical: div(v) = 4*alpha/R² alpha = 0.05 return np.full_like(H, 4.0 * alpha / self.R**2) elif self.velocity_type == "rankine_vortex": # div(v) = 0 for incompressible vortex return np.zeros_like(H) elif self.velocity_type == "linear_rotation": # div(v) = 0 for solid body rotation return np.zeros_like(H) elif self.velocity_type == "quasilinear_transport": # v = (0.5*H, 0.5*H) # div(v) = 0.5*(∂H/∂x + ∂H/∂y) # Numerical computation needed return self._compute_divergence_numerical(X, Y, H) else: # Fallback to numerical return self._compute_divergence_numerical(X, Y, H) def _compute_divergence_numerical(self, X, Y, H): """Numerical divergence computation""" div_v = np.zeros_like(H) for i in range(self.N): neighbors = self.initial_neighbors.get(i, []) if len(neighbors) < 3: continue A = [] b_u = [] b_v = [] U, V = self.velocity_field(X, Y, H) for j in neighbors: dx = X[j] - X[i] dy = Y[j] - Y[i] du = U[j] - U[i] dv = V[j] - V[i] A.append([dx, dy]) b_u.append(du) b_v.append(dv) try: A = np.array(A) grad_u = np.linalg.lstsq(A, np.array(b_u), rcond=None)[0] grad_v = np.linalg.lstsq(A, np.array(b_v), rcond=None)[0] div_v[i] = grad_u[0] + grad_v[1] except: pass return div_v def _compute_are_effective_radius(self, i, X, Y): neighbors = self.initial_neighbors.get(i, []) if len(neighbors) == 0: return None r_avg = 0.0 for j in neighbors: r = np.hypot(X[i] - X[j], Y[i] - Y[j]) r_avg += r r_avg /= len(neighbors) return np.pi * r_avg**2 def corrector_step(self): H_new = self.H_pred.copy() S_prev = {} for i in range(self.N): if i not in self.initial_neighbors: continue for j in self.initial_neighbors[i]: if (i, j) in S_prev: # Already computed continue # Distance at OLD positions (before this step) dist_old = np.hypot(self.X[i] - self.X[j], self.Y[i] - self.Y[j]) S_prev[(i, j)] = 0.5 * (self.H[i] + self.H[j]) * dist_old S_prev[(j, i)] = S_prev[(i, j)] corrections_applied = 0 all_cos_values = [] triggering_cos_values = [] passing_sign_check = 0 for i in range(self.N): if i not in self.initial_neighbors: continue neighbors = self.initial_neighbors[i] if len(neighbors) == 0: continue best_j = -1 min_cos = 2.0 for j in neighbors: cos_t = self._compute_impact_parameter( self.X_new[i], self.Y_new[i], self.U_curr[i], self.V_curr[i], self.X_new[j], self.Y_new[j], self.U_curr[j], self.V_curr[j], ) all_cos_values.append(cos_t) if cos_t < min_cos: min_cos, best_j = cos_t, j if best_j == -1 or min_cos >= self.cos_theta_threshold: continue triggering_cos_values.append(min_cos) S_target = S_prev[(i, best_j)] dist_new = np.hypot( self.X_new[i] - self.X_new[best_j], self.Y_new[i] - self.Y_new[best_j] ) if dist_new < 1e-14: continue S_current = 0.5 * (self.H[i] + self.H[best_j]) * dist_new if (self.H[i] - self.H[best_j]) * (S_current - S_target) <= 0: continue passing_sign_check += 1 h_corr = (2.0 * S_target / dist_new) - self.H[best_j] h_corr = max(h_corr, 0.0) damping = 1.0 H_new[i] = (1.0 - damping) * self.H[i] + damping * h_corr corrections_applied += 1 if hasattr(self, "_step") and self._step % 100 == 0: print(f"\n=== Step {self._step} Diagnostics ===") print(f"All cos(θ) values:") print(f" Min: {np.min(all_cos_values):.4f}") print(f" Max: {np.max(all_cos_values):.4f}") print(f" Mean: {np.mean(all_cos_values):.4f}") print(f" Median: {np.median(all_cos_values):.4f}") print( f"\nParticles passing cos threshold (<{self.cos_theta_threshold}): {len(triggering_cos_values)}" ) if len(triggering_cos_values) > 0: print( f" Their cos(θ) range: [{np.min(triggering_cos_values):.4f}, {np.max(triggering_cos_values):.4f}]" ) print(f"Particles passing sign check: {passing_sign_check}") print(f"Final corrections applied: {corrections_applied}\n") if corrections_applied > 0: print(f" Applied {corrections_applied} corrections") self.X = self.X_new.copy() self.Y = self.Y_new.copy() self.H = H_new def compute_total_mass(self): """Return total mass in the system.""" return np.sum(self.H) * self.dx * self.dy def check_cfl(self): """Compute CFL number and velocity statistics.""" U, V = self.velocity_field(self.X, self.Y, self.H) v_max = np.hypot(U, V).max() h_min = min(self.dx, self.dy) return (v_max * self.dt / h_min if v_max > 0 else 0.0), v_max, h_min def solve(self, t_final=10.0, save_every=10, verbose=True): """Integrate system until t_final.""" t, results = 0.0, { "times": [0.0], "X": [self.X.copy()], "Y": [self.Y.copy()], "H": [self.H.copy()], "masses": [self.initial_total_mass], "cfl": [], } cfl, v_max, h_min = self.check_cfl() if verbose: print(f"Start: CFL={cfl:.4f}, v_max={v_max:.4f}") for step in range(1, int(np.ceil(t_final / self.dt)) + 1): self._step = step self.predictor_step() self.corrector_step() t += self.dt cfl, v_max, _ = self.check_cfl() if step % save_every == 0 or step == int(np.ceil(t_final / self.dt)): m = self.compute_total_mass() results["times"].append(t) results["X"].append(self.X.copy()) results["Y"].append(self.Y.copy()) results["H"].append(self.H.copy()) results["masses"].append(m) results["cfl"].append(cfl) if verbose and ( step % (save_every * 10) == 0 or step == int(np.ceil(t_final / self.dt)) ): err = (m - self.initial_total_mass) / self.initial_total_mass * 100 print( f"Step {step:5d} | t={t:6.2f} | max(H)={self.H.max():.4f} | mass_err={err:+.25f}% | CFL={cfl:.4f}" ) if verbose: print(f"Done: t={t:.2f}, steps={step}") return results
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)