Skip to content

Commit 503b344

Browse files
committed
fix ci
1 parent a2642e8 commit 503b344

8 files changed

Lines changed: 445 additions & 114 deletions

File tree

config.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@
168168
config.tracers.knn_refresh_interval = 10; % Steps between full KNN rebuild (IDW mode)
169169
config.tracers.periodic = true; % Periodic wrap on domain boundaries
170170

171+
% Particle dynamics (one-way Stokes drag coupling)
172+
config.tracers.dynamics = 'massless'; % 'massless' (default, current behavior) or 'stokes'
173+
config.tracers.rho_f = 1.0; % Fluid density (dimensionless)
174+
config.tracers.rho_p = 1000.0; % Particle density (dimensionless)
175+
config.tracers.diameter = 1e-3; % Particle diameter (dimensionless)
176+
config.tracers.gravity_enable = false; % Enable gravitational/buoyancy force
177+
config.tracers.g = [0, 0]; % Gravitational acceleration vector (dimensionless)
178+
config.tracers.vel_init = 'fluid'; % Initial particle velocity: 'fluid' or 'zero'
179+
171180
% Live visualization (disabled in CI/tests)
172181
config.visualization.live_enable = true; % Show live heatmap during simulation
173182
config.visualization.live_frequency = 50; % Update every N steps

example_stokes_particles.m

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
%% Example: One-way Stokes Drag Particle Simulation
2+
% This script demonstrates the new Stokes particle functionality
3+
% which implements one-way coupling with drag and optional gravity.
4+
5+
clear; clc;
6+
7+
% Add required paths
8+
addpath('src');
9+
addpath('src/geometry');
10+
11+
%% Configure Stokes Particles
12+
cfg = config('cylinder');
13+
14+
% Enable Stokes particle dynamics (default is 'massless')
15+
cfg.tracers.dynamics = 'stokes';
16+
17+
% Particle properties (dimensionless)
18+
cfg.tracers.rho_f = 1.0; % Fluid density
19+
cfg.tracers.rho_p = 1000.0; % Particle density (heavy particles)
20+
cfg.tracers.diameter = 1e-3; % Particle diameter
21+
22+
% Initial particle velocity
23+
cfg.tracers.vel_init = 'fluid'; % 'fluid' or 'zero'
24+
25+
% Optional gravity/buoyancy (disabled by default)
26+
cfg.tracers.gravity_enable = false;
27+
cfg.tracers.g = [0, -0.1]; % Gravitational acceleration (if enabled)
28+
29+
% Simulation parameters
30+
cfg.tracers.num_particles = 100;
31+
cfg.simulation.num_time_steps = 50;
32+
cfg.simulation.time_step = 0.01;
33+
34+
% Visualization
35+
cfg.visualization.live_enable = true;
36+
cfg.visualization.live_frequency = 10;
37+
38+
%% Display Stokes Parameters
39+
nu = cfg.simulation.viscosity;
40+
rho_f = cfg.tracers.rho_f;
41+
rho_p = cfg.tracers.rho_p;
42+
dp = cfg.tracers.diameter;
43+
mu = nu * rho_f;
44+
tau_p = (rho_p * dp^2) / (18 * mu);
45+
46+
fprintf('=== Stokes Particle Parameters ===\n');
47+
fprintf('Particle dynamics: %s\n', cfg.tracers.dynamics);
48+
fprintf('Reynolds number: %.0f\n', 1/nu);
49+
fprintf('Particle density ratio ρ_p/ρ_f: %.1f\n', rho_p/rho_f);
50+
fprintf('Particle diameter: %.3e\n', dp);
51+
fprintf('Response time τ_p: %.3e\n', tau_p);
52+
fprintf('Time step dt: %.3e\n', cfg.simulation.time_step);
53+
fprintf('Stokes number St = τ_p/dt: %.2f\n', tau_p/cfg.simulation.time_step);
54+
55+
if cfg.tracers.gravity_enable
56+
fprintf('Gravity: [%.3f, %.3f]\n', cfg.tracers.g(1), cfg.tracers.g(2));
57+
fprintf('Buoyancy factor (1-ρ_f/ρ_p): %.3f\n', 1 - rho_f/rho_p);
58+
else
59+
fprintf('Gravity: disabled\n');
60+
end
61+
fprintf('===================================\n\n');
62+
63+
%% Run Simulation
64+
fprintf('Running Stokes particle simulation...\n');
65+
fprintf('- Particles will have their own velocity that relaxes toward fluid velocity\n');
66+
fprintf('- Drag force: F = 3πμd(u-V) where u=fluid velocity, V=particle velocity\n');
67+
fprintf('- Particle equation: dV/dt = (u-V)/τ_p + (1-ρ_f/ρ_p)g\n\n');
68+
69+
% Save configuration and run
70+
save('stokes_config.mat', 'cfg');
71+
setenv('LOAD_CFG_FILE', 'stokes_config.mat');
72+
73+
% Run the simulation
74+
simulate;
75+
76+
% Clean up
77+
delete('stokes_config.mat');
78+
79+
fprintf('\n=== Simulation Complete ===\n');
80+
fprintf('The visualization shows Stokes particles colored by their velocity magnitude.\n');
81+
fprintf('Compare this to massless tracers by setting cfg.tracers.dynamics = ''massless''.\n');

simulate.m

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
clc;
2-
clear;
2+
% Clear workspace unless running in test/CI mode
3+
if ~strcmpi(getenv('MATLAB_TEST'), 'true') && ~strcmpi(getenv('CI'), 'true')
4+
clear;
5+
end
36

47
% Add required paths for src functions and lib dependencies
58
scriptDir = fileparts(mfilename('fullpath'));
@@ -239,7 +242,7 @@
239242
D0_21_x_obs = P.D0_21_x_obs;
240243
D0_21_y_obs = P.D0_21_y_obs;
241244

242-
%% 5) Build inter-grid operators (P-grid V-grid)
245+
%% 5) Build inter-grid operators (P-grid <-> V-grid)
243246
[D0_21_x, D0_21_y, D0_12_x, D0_12_y] = build_intergrid_ops(G, xy, xy1, xy_s, xy1_s, S, cfg);
244247

245248
%% 6) Build velocity operators and boundary conditions
@@ -309,6 +312,45 @@
309312
% Initialize tracers (seed outside obstacles)
310313
tracers = seed_tracers(cfg, G, isCI, isTest);
311314

315+
% Initialize particle velocities for Stokes dynamics
316+
if strcmpi(cfg.tracers.dynamics, 'stokes') && ~isempty(tracers)
317+
% Extract initial velocity field components
318+
NV = size(xy1, 1);
319+
U0 = W(1:NV, 1); % u-velocity at t=0
320+
V0 = W(NV + 1:end, 1); % v-velocity at t=0
321+
322+
% Initialize particle velocities based on configuration
323+
if strcmpi(cfg.tracers.vel_init, 'fluid')
324+
% Initialize particle velocities to local fluid velocity
325+
Fu = scatteredInterpolant(xy1(:, 1), xy1(:, 2), U0, 'linear', 'nearest');
326+
Fv = scatteredInterpolant(xy1(:, 1), xy1(:, 2), V0, 'linear', 'nearest');
327+
tracer_vel = [Fu(tracers(:, 1), tracers(:, 2)), Fv(tracers(:, 1), tracers(:, 2))];
328+
fprintf('Initialized %d Stokes particles with fluid velocity\n', size(tracers, 1));
329+
else
330+
% Initialize particle velocities to zero
331+
tracer_vel = zeros(size(tracers));
332+
fprintf('Initialized %d Stokes particles with zero velocity\n', size(tracers, 1));
333+
end
334+
335+
% Report Stokes parameters
336+
nu = cfg.simulation.viscosity;
337+
rho_f = cfg.tracers.rho_f;
338+
rho_p = cfg.tracers.rho_p;
339+
dp = cfg.tracers.diameter;
340+
mu = nu * rho_f;
341+
tau_p = (rho_p * dp^2) / (18 * mu);
342+
fprintf('Stokes parameters: tau_p = %.3e, rho_p/rho_f = %.1f, d_p = %.3e\n', tau_p, rho_p / rho_f, dp);
343+
344+
if cfg.tracers.gravity_enable
345+
fprintf('Gravity enabled: g = [%.3f, %.3f]\n', cfg.tracers.g(1), cfg.tracers.g(2));
346+
end
347+
else
348+
tracer_vel = [];
349+
if ~isempty(tracers)
350+
fprintf('Initialized %d massless tracers\n', size(tracers, 1));
351+
end
352+
end
353+
312354
% Define useful index lengths for boundary handling
313355
L_B = length(boundary_obs) + length(boundary_in); % Total special boundaries
314356
L_B_y = length(boundary_y); % Wall boundaries
@@ -388,7 +430,14 @@
388430
W_prev = W(:, j);
389431
W_now = W(:, j + 1);
390432
domain_struct = struct('x_min', x_min, 'x_max', x_max, 'y_min', y_min, 'y_max', y_max);
391-
tracers = advect_tracers(tracers, dt, xy1, W_prev, W_now, cfg, G.fd_obs, domain_struct);
433+
434+
if strcmpi(cfg.tracers.dynamics, 'stokes')
435+
% Stokes particle dynamics with velocity evolution
436+
[tracers, tracer_vel] = advect_tracers(tracers, tracer_vel, dt, xy1, W_prev, W_now, cfg, G.fd_obs, domain_struct);
437+
else
438+
% Massless tracer dynamics (original behavior)
439+
tracers = advect_tracers(tracers, dt, xy1, W_prev, W_now, cfg, G.fd_obs, domain_struct);
440+
end
392441
end
393442

394443
% Comprehensive instability detection
@@ -446,7 +495,7 @@
446495
instability_type = failure_type;
447496

448497
% Immediate notification of detection
449-
fprintf('\n🚨 IMMEDIATE DETECTION at step %d: %s\n', j, failure_type);
498+
fprintf('\n[ALERT] IMMEDIATE DETECTION at step %d: %s\n', j, failure_type);
450499

451500
% Find representative bad index based on failure type
452501
if contains(failure_type, 'NaN/Inf in velocity')
@@ -533,7 +582,7 @@
533582
else
534583
fprintf('RECOMMENDATION: Lower CFL thresholds or reduce initial time step\n');
535584
end
536-
elseif strcmp(instability_type, 'Velocity collapse (all velocities 0)')
585+
elseif strcmp(instability_type, 'Velocity collapse (all velocities -> 0)')
537586
fprintf('LIKELY CAUSE: Numerical damping or boundary condition issues\n');
538587
fprintf('RECOMMENDATION: Check boundary conditions and reduce viscosity\n');
539588
elseif contains(instability_type, 'explosion')
@@ -654,7 +703,7 @@
654703
[is_valid, failure_type, failure_details] = check_solution_validity(W(:, j + 1), p_for_check, 1e-12);
655704

656705
if ~is_valid
657-
fprintf('\n🚨 FATAL ERROR: %s after time step reduction!\n', failure_type);
706+
fprintf('\n[FATAL] ERROR: %s after time step reduction!\n', failure_type);
658707
fprintf('Step %d: Time step was reduced from %.3e to %.3e but solution is still corrupted.\n', ...
659708
j, dt / cfg.adaptive_dt.reduction_factor, dt);
660709
fprintf('This indicates the solution cannot be recovered by time step reduction alone.\n');
@@ -746,7 +795,11 @@
746795
% Live max-|V| heatmap every N steps (interactive only)
747796
if doPlot && ~isCI && ~isTest && isfield(cfg.visualization, 'live_enable') && cfg.visualization.live_enable && ...
748797
mod(j, cfg.visualization.live_frequency) == 0
749-
visualization('live_heatmap', cfg, xy1, W(:, j + 1), j, x_min, x_max, y_min, y_max, tracers);
798+
if strcmpi(cfg.tracers.dynamics, 'stokes') && exist('tracer_vel', 'var')
799+
visualization('live_heatmap', cfg, xy1, W(:, j + 1), j, x_min, x_max, y_min, y_max, tracers, tracer_vel);
800+
else
801+
visualization('live_heatmap', cfg, xy1, W(:, j + 1), j, x_min, x_max, y_min, y_max, tracers);
802+
end
750803
end
751804

752805
% Advance simulation time (using current dt, which may have been adapted)
@@ -810,7 +863,7 @@
810863
[is_valid, failure_type, failure_details] = check_solution_validity(W(:, j), p_for_check, 1e-12);
811864

812865
if ~is_valid
813-
fprintf('🚨 CRITICAL: %s in final solution!\n', failure_type);
866+
fprintf('[CRITICAL] %s in final solution!\n', failure_type);
814867
fprintf('The simulation appeared to complete but the solution is corrupted.\n');
815868

816869
% Detailed failure analysis
@@ -845,7 +898,7 @@
845898

846899
error('SIMULATION FAILED: %s at step %d. Solution is corrupted.', failure_type, j);
847900
else
848-
fprintf(' Final solution validation: PASSED\n');
901+
fprintf('[PASS] Final solution validation: PASSED\n');
849902
fprintf(' Velocity nodes: %d (all valid)\n', failure_details.velocity_nodes);
850903
fprintf(' Velocity field: max=%.3e, mean=%.3e\n', failure_details.velocity_max, failure_details.velocity_mean);
851904
if ~isempty(p_for_check)
@@ -857,4 +910,8 @@
857910
fprintf('=====================================\n\n');
858911

859912
%% 11) Visualization of final results
860-
visualization('final', cfg, doPlot, xy1, W0, Nt, x_min, x_max, y_min, y_max, Dx, Dy, tracers);
913+
if strcmpi(cfg.tracers.dynamics, 'stokes') && exist('tracer_vel', 'var')
914+
visualization('final', cfg, doPlot, xy1, W0, Nt, x_min, x_max, y_min, y_max, Dx, Dy, tracers, tracer_vel);
915+
else
916+
visualization('final', cfg, doPlot, xy1, W0, Nt, x_min, x_max, y_min, y_max, Dx, Dy, tracers);
917+
end

0 commit comments

Comments
 (0)