|
1 | 1 | 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 |
3 | 6 |
|
4 | 7 | % Add required paths for src functions and lib dependencies |
5 | 8 | scriptDir = fileparts(mfilename('fullpath')); |
|
239 | 242 | D0_21_x_obs = P.D0_21_x_obs; |
240 | 243 | D0_21_y_obs = P.D0_21_y_obs; |
241 | 244 |
|
242 | | -%% 5) Build inter-grid operators (P-grid ↔ V-grid) |
| 245 | +%% 5) Build inter-grid operators (P-grid <-> V-grid) |
243 | 246 | [D0_21_x, D0_21_y, D0_12_x, D0_12_y] = build_intergrid_ops(G, xy, xy1, xy_s, xy1_s, S, cfg); |
244 | 247 |
|
245 | 248 | %% 6) Build velocity operators and boundary conditions |
|
306 | 309 | end |
307 | 310 | [W, p0] = init_state(xy1, xy1_s, boundary_obs, Nt_alloc); |
308 | 311 |
|
| 312 | +% Initialize tracers (seed outside obstacles) |
| 313 | +tracers = seed_tracers(cfg, G, isCI, isTest); |
| 314 | + |
| 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 tracer particles\n', size(tracers, 1)); |
| 351 | + end |
| 352 | +end |
| 353 | + |
309 | 354 | % Define useful index lengths for boundary handling |
310 | 355 | L_B = length(boundary_obs) + length(boundary_in); % Total special boundaries |
311 | 356 | L_B_y = length(boundary_y); % Wall boundaries |
|
380 | 425 | rethrow(ME); |
381 | 426 | end |
382 | 427 |
|
| 428 | + % Advect tracers with latest velocity field (Heun: uses W(:,j) and W(:,j+1)) |
| 429 | + if cfg.tracers.enable && ~isempty(tracers) |
| 430 | + W_prev = W(:, j); |
| 431 | + W_now = W(:, j + 1); |
| 432 | + domain_struct = struct('x_min', x_min, 'x_max', x_max, 'y_min', y_min, 'y_max', y_max); |
| 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 | + % Tracer particle dynamics (original behavior) |
| 439 | + tracers = advect_tracers(tracers, dt, xy1, W_prev, W_now, cfg, G.fd_obs, domain_struct); |
| 440 | + end |
| 441 | + end |
| 442 | + |
383 | 443 | % Comprehensive instability detection |
384 | 444 | if cfg.logging.immediate_nan_check || cfg.logging.comprehensive_instability_check |
385 | 445 | instability_detected = false; |
|
435 | 495 | instability_type = failure_type; |
436 | 496 |
|
437 | 497 | % Immediate notification of detection |
438 | | - 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); |
439 | 499 |
|
440 | 500 | % Find representative bad index based on failure type |
441 | 501 | if contains(failure_type, 'NaN/Inf in velocity') |
|
522 | 582 | else |
523 | 583 | fprintf('RECOMMENDATION: Lower CFL thresholds or reduce initial time step\n'); |
524 | 584 | end |
525 | | - elseif strcmp(instability_type, 'Velocity collapse (all velocities → 0)') |
| 585 | + elseif strcmp(instability_type, 'Velocity collapse (all velocities -> 0)') |
526 | 586 | fprintf('LIKELY CAUSE: Numerical damping or boundary condition issues\n'); |
527 | 587 | fprintf('RECOMMENDATION: Check boundary conditions and reduce viscosity\n'); |
528 | 588 | elseif contains(instability_type, 'explosion') |
|
643 | 703 | [is_valid, failure_type, failure_details] = check_solution_validity(W(:, j + 1), p_for_check, 1e-12); |
644 | 704 |
|
645 | 705 | if ~is_valid |
646 | | - 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); |
647 | 707 | fprintf('Step %d: Time step was reduced from %.3e to %.3e but solution is still corrupted.\n', ... |
648 | 708 | j, dt / cfg.adaptive_dt.reduction_factor, dt); |
649 | 709 | fprintf('This indicates the solution cannot be recovered by time step reduction alone.\n'); |
|
735 | 795 | % Live max-|V| heatmap every N steps (interactive only) |
736 | 796 | if doPlot && ~isCI && ~isTest && isfield(cfg.visualization, 'live_enable') && cfg.visualization.live_enable && ... |
737 | 797 | mod(j, cfg.visualization.live_frequency) == 0 |
738 | | - visualization('live_heatmap', cfg, xy1, W(:, j + 1), j, x_min, x_max, y_min, y_max); |
| 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 |
739 | 803 | end |
740 | 804 |
|
741 | 805 | % Advance simulation time (using current dt, which may have been adapted) |
|
799 | 863 | [is_valid, failure_type, failure_details] = check_solution_validity(W(:, j), p_for_check, 1e-12); |
800 | 864 |
|
801 | 865 | if ~is_valid |
802 | | - fprintf('🚨 CRITICAL: %s in final solution!\n', failure_type); |
| 866 | + fprintf('[CRITICAL] %s in final solution!\n', failure_type); |
803 | 867 | fprintf('The simulation appeared to complete but the solution is corrupted.\n'); |
804 | 868 |
|
805 | 869 | % Detailed failure analysis |
|
834 | 898 |
|
835 | 899 | error('SIMULATION FAILED: %s at step %d. Solution is corrupted.', failure_type, j); |
836 | 900 | else |
837 | | - fprintf('✅ Final solution validation: PASSED\n'); |
| 901 | + fprintf('[PASS] Final solution validation: PASSED\n'); |
838 | 902 | fprintf(' Velocity nodes: %d (all valid)\n', failure_details.velocity_nodes); |
839 | 903 | fprintf(' Velocity field: max=%.3e, mean=%.3e\n', failure_details.velocity_max, failure_details.velocity_mean); |
840 | 904 | if ~isempty(p_for_check) |
|
846 | 910 | fprintf('=====================================\n\n'); |
847 | 911 |
|
848 | 912 | %% 11) Visualization of final results |
849 | | -visualization('final', cfg, doPlot, xy1, W0, Nt, x_min, x_max, y_min, y_max, Dx, Dy); |
| 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