Skip to content

Commit b985fea

Browse files
authored
Merge pull request #4 from sbryngelson/tracers
2 parents 1d827c5 + 75b1dcb commit b985fea

21 files changed

+1697
-325
lines changed

.cursorrules

Lines changed: 401 additions & 167 deletions
Large diffs are not rendered by default.

.github/scripts/lint.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,6 @@
6161

6262
fclose(fid);
6363
if hasIssues
64-
error('Code Analyzer found issues. See .github/scripts/lint.log.');
64+
error('Code Analyzer found issues.');
6565
end
6666
end

config.m

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,37 @@
150150
config.visualization.plot_tick_x = [-5, 0, 5, 10, 15];
151151
config.visualization.color_axis_range = 1e-0;
152152

153+
%% Tracers (Lagrangian point particles advected by the flow)
154+
config.tracers.enable = true; % Enable tracer particles
155+
config.tracers.num_particles = 100; % Number of tracers (normal runs)
156+
config.tracers.num_particles_ci = 30; % Fewer tracers for CI/tests
157+
158+
% Particle dynamics (one-way Stokes drag coupling)
159+
config.tracers.dynamics = 'tracer'; % 'tracer' (default, massless tracers) or 'stokes'
160+
config.tracers.rho_f = 1.0; % Fluid density (dimensionless)
161+
config.tracers.rho_p = 1000.0; % Particle density (dimensionless)
162+
config.tracers.diameter = 1e-3; % Particle diameter (dimensionless)
163+
config.tracers.gravity_enable = false; % Enable gravitational/buoyancy force
164+
config.tracers.g = [0, -1]; % Gravitational acceleration vector (dimensionless)
165+
config.tracers.vel_init = 'fluid'; % Initial particle velocity: 'fluid' or 'zero'
166+
167+
% Particle parameters
168+
config.tracers.k_neighbors = 10; % IDW neighbors for velocity interpolation
169+
config.tracers.idw_power = 2; % IDW power (2 = standard)
170+
config.tracers.obstacle_margin = 3 * config.mesh.boundary_eps; % Seed clearance from obstacles
171+
config.tracers.wall_margin = 3 * config.mesh.boundary_eps; % Seed clearance from walls/domain
172+
config.tracers.max_seed_tries = 20; % Try this many batches before stopping early
173+
config.tracers.integrator = 'heun'; % 'heun' for trapezoidal method
174+
config.tracers.pushback_on_obstacle = true; % Prevent crossing into obstacle during advection
175+
config.tracers.max_pushback_iters = 5; % Limit for step shrinking when crossing obstacle
176+
config.tracers.interp_method = 'tri_bary'; % 'tri_bary' (fastest), 'scattered' (fast), or 'idw' (original)
177+
config.tracers.knn_refresh_interval = 10; % Steps between full KNN rebuild (IDW mode)
178+
config.tracers.periodic = true; % Periodic wrap on domain boundaries
179+
153180
% Live visualization (disabled in CI/tests)
154181
config.visualization.live_enable = true; % Show live heatmap during simulation
155-
config.visualization.live_frequency = 10; % Update every N steps
156-
config.visualization.heatmap_nx = 200; % Heatmap grid resolution in x
182+
config.visualization.live_frequency = 50; % Update every N steps
183+
config.visualization.heatmap_nx = 300; % Heatmap grid resolution in x
157184
% ny is inferred from domain aspect ratio to keep pixels square-ish
158185

159186
%% Logging and Debug Parameters

lint.sh

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ BLUE='\033[0;34m'
1414
NC='\033[0m' # No Color
1515

1616
echo -e "${BLUE}[LINT] MATLAB Code Analyzer${NC}"
17-
echo "=========================="
1817

1918
# Function to run MATLAB linting
2019
run_matlab_lint() {
@@ -24,10 +23,6 @@ run_matlab_lint() {
2423
return 0
2524
else
2625
echo -e "${RED}[FAILED] MATLAB Code Analyzer found issues${NC}"
27-
if [ -f ".github/scripts/lint.log" ]; then
28-
echo -e "${YELLOW}Issues found:${NC}"
29-
cat .github/scripts/lint.log
30-
fi
3126
return 1
3227
fi
3328
}
@@ -42,9 +37,5 @@ else
4237
echo ""
4338
echo -e "${RED}[FAILED] MATLAB Code Analyzer found issues!${NC}"
4439
echo -e "${YELLOW}Please fix the issues above before committing.${NC}"
45-
echo ""
46-
echo -e "${BLUE}Additional checks:${NC}"
47-
echo -e " * For style issues: ${YELLOW}./format.sh${NC} (check/fix formatting)"
48-
echo -e " * For auto-fixes: ${YELLOW}./fix.sh${NC} (attempt automatic fixes)"
4940
exit 1
5041
fi

lint_fix.sh

Lines changed: 0 additions & 101 deletions
This file was deleted.

simulate.m

Lines changed: 77 additions & 9 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
@@ -306,6 +309,48 @@
306309
end
307310
[W, p0] = init_state(xy1, xy1_s, boundary_obs, Nt_alloc);
308311

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+
309354
% Define useful index lengths for boundary handling
310355
L_B = length(boundary_obs) + length(boundary_in); % Total special boundaries
311356
L_B_y = length(boundary_y); % Wall boundaries
@@ -380,6 +425,21 @@
380425
rethrow(ME);
381426
end
382427

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+
383443
% Comprehensive instability detection
384444
if cfg.logging.immediate_nan_check || cfg.logging.comprehensive_instability_check
385445
instability_detected = false;
@@ -435,7 +495,7 @@
435495
instability_type = failure_type;
436496

437497
% 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);
439499

440500
% Find representative bad index based on failure type
441501
if contains(failure_type, 'NaN/Inf in velocity')
@@ -522,7 +582,7 @@
522582
else
523583
fprintf('RECOMMENDATION: Lower CFL thresholds or reduce initial time step\n');
524584
end
525-
elseif strcmp(instability_type, 'Velocity collapse (all velocities 0)')
585+
elseif strcmp(instability_type, 'Velocity collapse (all velocities -> 0)')
526586
fprintf('LIKELY CAUSE: Numerical damping or boundary condition issues\n');
527587
fprintf('RECOMMENDATION: Check boundary conditions and reduce viscosity\n');
528588
elseif contains(instability_type, 'explosion')
@@ -643,7 +703,7 @@
643703
[is_valid, failure_type, failure_details] = check_solution_validity(W(:, j + 1), p_for_check, 1e-12);
644704

645705
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);
647707
fprintf('Step %d: Time step was reduced from %.3e to %.3e but solution is still corrupted.\n', ...
648708
j, dt / cfg.adaptive_dt.reduction_factor, dt);
649709
fprintf('This indicates the solution cannot be recovered by time step reduction alone.\n');
@@ -735,7 +795,11 @@
735795
% Live max-|V| heatmap every N steps (interactive only)
736796
if doPlot && ~isCI && ~isTest && isfield(cfg.visualization, 'live_enable') && cfg.visualization.live_enable && ...
737797
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
739803
end
740804

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

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

805869
% Detailed failure analysis
@@ -834,7 +898,7 @@
834898

835899
error('SIMULATION FAILED: %s at step %d. Solution is corrupted.', failure_type, j);
836900
else
837-
fprintf(' Final solution validation: PASSED\n');
901+
fprintf('[PASS] Final solution validation: PASSED\n');
838902
fprintf(' Velocity nodes: %d (all valid)\n', failure_details.velocity_nodes);
839903
fprintf(' Velocity field: max=%.3e, mean=%.3e\n', failure_details.velocity_max, failure_details.velocity_mean);
840904
if ~isempty(p_for_check)
@@ -846,4 +910,8 @@
846910
fprintf('=====================================\n\n');
847911

848912
%% 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

Comments
 (0)