diff --git a/config.m b/config.m index 92fc735..ae52437 100644 --- a/config.m +++ b/config.m @@ -150,6 +150,12 @@ config.visualization.plot_tick_x = [-5, 0, 5, 10, 15]; config.visualization.color_axis_range = 1e-0; + % Live visualization (disabled in CI/tests) + config.visualization.live_enable = true; % Show live heatmap during simulation + config.visualization.live_frequency = 10; % Update every N steps + config.visualization.heatmap_nx = 200; % Heatmap grid resolution in x + % ny is inferred from domain aspect ratio to keep pixels square-ish + %% Logging and Debug Parameters % Master debug switch - set to true to enable all debug features debug_master = false; % Master switch for all debug features diff --git a/simulate.m b/simulate.m index 4a92c78..18287ce 100644 --- a/simulate.m +++ b/simulate.m @@ -732,6 +732,12 @@ end end + % Live max-|V| heatmap every N steps (interactive only) + if doPlot && ~isCI && ~isTest && isfield(cfg.visualization, 'live_enable') && cfg.visualization.live_enable && ... + mod(j, cfg.visualization.live_frequency) == 0 + visualization('live_heatmap', cfg, xy1, W(:, j + 1), j, x_min, x_max, y_min, y_max); + end + % Advance simulation time (using current dt, which may have been adapted) current_time = current_time + dt; end @@ -840,4 +846,4 @@ fprintf('=====================================\n\n'); %% 11) Visualization of final results -visualize_final(cfg, doPlot, xy1, W0, Nt, x_min, x_max, y_min, y_max, Dx, Dy); +visualization('final', cfg, doPlot, xy1, W0, Nt, x_min, x_max, y_min, y_max, Dx, Dy); diff --git a/src/visualization.m b/src/visualization.m new file mode 100644 index 0000000..f3873b9 --- /dev/null +++ b/src/visualization.m @@ -0,0 +1,390 @@ +function varargout = visualization(action, varargin) + %VISUALIZATION Consolidated visualization module for RBF Navier-Stokes simulation + % + % This module provides all visualization functionality including final results + % plotting and live heatmap updates during simulation. + % + % USAGE: + % visualization('final', cfg, doPlot, xy1, W_final, ~, x_min, x_max, y_min, y_max, Dx, Dy) + % visualization('live_heatmap', cfg, xy1, W_step, step_idx, x_min, x_max, y_min, y_max) + % visualization('apply_axes', cfg, ax, x_min, x_max, y_min, y_max) + % + % ACTIONS: + % 'final' - Create final simulation result plots (vorticity, U, V, |V|) + % 'live_heatmap' - Update live velocity magnitude heatmap during simulation + % 'apply_axes' - Apply consistent axis configuration to current axes + % + % This consolidates the functionality from: + % - visualize_final.m + % - visualize_live_heatmap.m + % - visualization_utils.m + + % Initialize output arguments (fixes unset return value warning) + varargout = {}; + + switch lower(action) + case 'final' + visualize_final_results(varargin{:}); + + case 'live_heatmap' + visualize_live_heatmap(varargin{:}); + + case 'apply_axes' + apply_xy_axes(varargin{:}); + + otherwise + error('Unknown visualization action: %s. Valid actions: final, live_heatmap, apply_axes', action); + end +end + +function visualize_final_results(cfg, doPlot, xy1, W_final, ~, x_min, x_max, y_min, y_max, Dx, Dy) + %VISUALIZE_FINAL_RESULTS Create visualization of final simulation results + % + % This function creates plots of the final vorticity field and velocity components. + % + % INPUTS: + % cfg - Configuration structure + % doPlot - Boolean indicating if plotting should be enabled + % xy1 - Complete velocity grid coordinates + % W_final - Final velocity solution vector [U; V] (single time step) + % ~ - Number of time steps (unused, kept for compatibility) + % x_min - Minimum x-coordinate of domain + % x_max - Maximum x-coordinate of domain + % y_min - Minimum y-coordinate of domain + % y_max - Maximum y-coordinate of domain + % Dx - RBF-FD differentiation matrix for d/dx operator + % Dy - RBF-FD differentiation matrix for d/dy operator + + % Only plot if plotting is enabled + if ~doPlot + return + end + + % Extract coordinates + x1 = xy1(:, 1); % x-coordinates of velocity nodes + y1 = xy1(:, 2); % y-coordinates of velocity nodes + + % Extract velocity components from final solution vector + % W_final is structured as [U; V] stacked vertically (single time step) + n_nodes = length(xy1); + U_final = W_final(1:n_nodes); % Final u-velocity + V_final = W_final(n_nodes + 1:end); % Final v-velocity + + %% Plot 1: Vorticity Field + figure('Name', 'Vorticity Field (1/Re = 1e-2)'); + colormap(jet); + + % Compute vorticity: omega = dv/dx - du/dy using RBF-FD operators + % These operators are designed for scattered nodes and use local stencils + dvdx = Dx * V_final; % dv/dx using RBF-FD operator on scattered nodes + dudy = Dy * U_final; % du/dy using RBF-FD operator on scattered nodes + vorticity = dvdx - dudy; % Vorticity field: ω = ∂v/∂x - ∂u/∂y + + % Validate vorticity computation + fprintf('Vorticity field statistics:\n'); + fprintf(' Min vorticity: %.6f\n', min(vorticity)); + fprintf(' Max vorticity: %.6f\n', max(vorticity)); + fprintf(' Mean vorticity: %.6f\n', mean(vorticity)); + fprintf(' RMS vorticity: %.6f\n', sqrt(mean(vorticity.^2))); + + % Plot vorticity field with improved visualization + scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), vorticity, 'filled'); + hold on; + + % Set reasonable color limits to avoid extreme values dominating the plot + vort_std = std(vorticity); + vort_mean = mean(vorticity); + color_limit = max(abs(vort_mean) + 3 * vort_std, max(abs(vorticity)) * 0.8); + + % Handle case where vorticity is all zeros or NaN + if color_limit == 0 || ~isfinite(color_limit) + color_limit = 1.0; % Default color range + fprintf('Warning: Vorticity field is zero or contains NaN values. Using default color range.\n'); + end + clim([-color_limit, color_limit]); + + % Apply consistent axis configuration + apply_xy_axes(cfg, gca, x_min, x_max, y_min, y_max); + title(sprintf('Vorticity Field: ω = ∂v/∂x - ∂u/∂y (Re = %.0f)', 1 / cfg.simulation.viscosity)); + xlabel('x'); + ylabel('y'); + colorbar; % Add colorbar for vorticity + grid on; + grid minor; + drawnow; % Update display immediately + + %% Plot 2: U-Velocity Component + figure('Name', 'U-Velocity Component'); + scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), U_final, 'filled'); + hold on; + + % Set reasonable color limits for u-velocity + u_std = std(U_final); + u_mean = mean(U_final); + u_color_limit = max(abs(u_mean) + 3 * u_std, max(abs(U_final)) * 0.8); + + % Handle case where u-velocity is all zeros or NaN + if u_color_limit == 0 || ~isfinite(u_color_limit) + u_color_limit = 1.0; % Default color range + fprintf('Warning: U-velocity field is zero or contains NaN values. Using default color range.\n'); + end + clim([-u_color_limit, u_color_limit]); + + apply_xy_axes(cfg, gca, x_min, x_max, y_min, y_max); + title(sprintf('U-Velocity Component (Re = %.0f)', 1 / cfg.simulation.viscosity)); + xlabel('x'); + ylabel('y'); + colorbar; + colormap(jet); + grid on; + grid minor; + + fprintf('U-velocity field statistics:\n'); + fprintf(' Min u-velocity: %.6f\n', min(U_final)); + fprintf(' Max u-velocity: %.6f\n', max(U_final)); + fprintf(' Mean u-velocity: %.6f\n', mean(U_final)); + fprintf(' RMS u-velocity: %.6f\n', sqrt(mean(U_final.^2))); + drawnow; + + %% Plot 3: V-Velocity Component + figure('Name', 'V-Velocity Component'); + scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), V_final, 'filled'); + hold on; + + % Set reasonable color limits for v-velocity + v_std = std(V_final); + v_mean = mean(V_final); + v_color_limit = max(abs(v_mean) + 3 * v_std, max(abs(V_final)) * 0.8); + + % Handle case where v-velocity is all zeros or NaN + if v_color_limit == 0 || ~isfinite(v_color_limit) + v_color_limit = 1.0; % Default color range + fprintf('Warning: V-velocity field is zero or contains NaN values. Using default color range.\n'); + end + clim([-v_color_limit, v_color_limit]); + + apply_xy_axes(cfg, gca, x_min, x_max, y_min, y_max); + title(sprintf('V-Velocity Component (Re = %.0f)', 1 / cfg.simulation.viscosity)); + xlabel('x'); + ylabel('y'); + colorbar; + colormap(jet); + grid on; + grid minor; + + fprintf('V-velocity field statistics:\n'); + fprintf(' Min v-velocity: %.6f\n', min(V_final)); + fprintf(' Max v-velocity: %.6f\n', max(V_final)); + fprintf(' Mean v-velocity: %.6f\n', mean(V_final)); + fprintf(' RMS v-velocity: %.6f\n', sqrt(mean(V_final.^2))); + drawnow; + + %% Plot 4: Velocity Magnitude + velocity_magnitude = sqrt(U_final.^2 + V_final.^2); + + figure('Name', 'Velocity Magnitude'); + scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), velocity_magnitude, 'filled'); + hold on; + + % Color limits for velocity magnitude (always positive) + mag_max = max(velocity_magnitude); + + % Handle case where velocity magnitude is all zeros or NaN + if mag_max == 0 || ~isfinite(mag_max) + mag_max = 1.0; % Default color range + fprintf('Warning: Velocity magnitude is zero or contains NaN values. Using default color range.\n'); + end + clim([0, mag_max]); + + apply_xy_axes(cfg, gca, x_min, x_max, y_min, y_max); + title(sprintf('Velocity Magnitude |V| (Re = %.0f)', 1 / cfg.simulation.viscosity)); + xlabel('x'); + ylabel('y'); + colorbar; + colormap(jet); + grid on; + grid minor; + + fprintf('Velocity magnitude statistics:\n'); + fprintf(' Min |V|: %.6f\n', min(velocity_magnitude)); + fprintf(' Max |V|: %.6f\n', max(velocity_magnitude)); + fprintf(' Mean |V|: %.6f\n', mean(velocity_magnitude)); + fprintf(' RMS |V|: %.6f\n', sqrt(mean(velocity_magnitude.^2))); + drawnow; +end + +function visualize_live_heatmap(cfg, xy1, W_step, step_idx, x_min, x_max, y_min, y_max) + %VISUALIZE_LIVE_HEATMAP Live interpolated velocity magnitude heatmap + % + % This function creates a live-updating heatmap showing the velocity magnitude + % interpolated over the entire domain during simulation. Uses scattered data + % interpolation to create a smooth field over a regular grid. + % + % INPUTS: + % cfg - Configuration struct containing visualization parameters + % xy1 - Velocity grid coordinates [Nv x 2] + % W_step - Current velocity vector [2*Nv x 1] at this step (stacked [U; V]) + % step_idx - Current time step index (for title) + % x_min, x_max, y_min, y_max - Domain bounds + + % Extract velocity components + Nv = size(xy1, 1); + U = W_step(1:Nv); + V = W_step(Nv + 1:end); + + % Compute velocity magnitude at scattered points + vel_mag = sqrt(U.^2 + V.^2); + + % Create regular grid for interpolation + nx = cfg.visualization.heatmap_nx; + Lx = x_max - x_min; + Ly = y_max - y_min; + + % Safety check for domain bounds + if Lx <= 0 || Ly <= 0 + warning('Invalid domain bounds for heatmap visualization'); + return + end + + % Calculate ny to maintain square-ish pixels + ny = max(1, round(nx * (Ly / Lx))); + + % Create regular grid + x_grid = linspace(x_min, x_max, nx); + y_grid = linspace(y_min, y_max, ny); + [X_grid, Y_grid] = meshgrid(x_grid, y_grid); + + % Interpolate velocity magnitude to regular grid + try + % Use scattered interpolant for smooth interpolation + F = scatteredInterpolant(xy1(:, 1), xy1(:, 2), vel_mag, 'linear', 'nearest'); + H = F(X_grid, Y_grid); + + % Handle any NaN values that might occur at boundaries + if any(isnan(H(:))) + % Fill NaNs with nearest neighbor interpolation + F_nearest = scatteredInterpolant(xy1(:, 1), xy1(:, 2), vel_mag, 'nearest', 'nearest'); + nan_mask = isnan(H); + H(nan_mask) = F_nearest(X_grid(nan_mask), Y_grid(nan_mask)); + end + + catch ME + % Fallback to griddata if scatteredInterpolant fails + warning('VISUALIZATION:ScatteredInterpolantFailed', 'scatteredInterpolant failed, using griddata: %s', ME.message); + try + H = griddata(xy1(:, 1), xy1(:, 2), vel_mag, X_grid, Y_grid, 'linear'); + + % Fill NaNs with nearest neighbor + if any(isnan(H(:))) + H_nearest = griddata(xy1(:, 1), xy1(:, 2), vel_mag, X_grid, Y_grid, 'nearest'); + nan_mask = isnan(H); + H(nan_mask) = H_nearest(nan_mask); + end + catch ME2 + warning('VISUALIZATION:InterpolationFailed', 'Interpolation failed: %s', ME2.message); + return + end + end + + % Determine robust color limit (avoid single outliers) + if all(~isfinite(H(:))) + cmax = 1.0; % Default fallback + else + finite_vals = H(isfinite(H)); + if isempty(finite_vals) + cmax = 1.0; + else + % Use 99th percentile to avoid outliers, but ensure we capture the range + cmax = max(1e-12, prctile(finite_vals, 99)); + end + end + + % Create or update figure + fig = findobj('Type', 'figure', 'Tag', 'MaxVelocityHeatmap'); + if isempty(fig) + % Create new figure + fig = figure('Name', 'Velocity Magnitude Heatmap', 'Tag', 'MaxVelocityHeatmap'); + ax = axes('Parent', fig); + + % Set up colormap and image + colormap(ax, jet); + imagesc(ax, x_grid, y_grid, H); + set(ax, 'YDir', 'normal'); + + % Apply consistent axis configuration + apply_xy_axes(cfg, ax, x_min, x_max, y_min, y_max); + + % Add labels and colorbar + colorbar(ax); + title(ax, sprintf('Velocity Magnitude |V| — step %d', step_idx)); + xlabel(ax, 'x'); + ylabel(ax, 'y'); + + % Set color limits and grid + clim(ax, [0, cmax]); + grid(ax, 'on'); + grid(ax, 'minor'); + + else + % Update existing figure + ax = fig.CurrentAxes; + im = findobj(ax, 'Type', 'image'); + + if isempty(im) + % No image exists, create one + imagesc(ax, x_grid, y_grid, H); + set(ax, 'YDir', 'normal'); + else + % Update existing image + set(im, 'XData', x_grid, 'YData', y_grid, 'CData', H); + end + + % Update title and axis configuration + title(ax, sprintf('Velocity Magnitude |V| — step %d', step_idx)); + apply_xy_axes(cfg, ax, x_min, x_max, y_min, y_max); + clim(ax, [0, cmax]); + end + + % Update display with rate limiting to avoid excessive redraws + drawnow limitrate; +end + +function apply_xy_axes(cfg, ax, x_min, x_max, y_min, y_max) + %APPLY_XY_AXES Apply consistent x/y limits and tick marks from config + % + % This function centralizes the axis configuration used across all visualization + % functions to ensure consistent domain bounds and tick marks. + % + % INPUTS: + % cfg - Configuration struct containing visualization parameters + % ax - Axes handle (use gca if empty) + % x_min, x_max, y_min, y_max - Domain bounds + % + % The function applies: + % - xlim and ylim based on domain bounds + % - xticks and yticks from cfg.visualization.plot_tick_x/y if available + % - axis equal for proper aspect ratio + % - axis tight for optimal view + + if nargin < 2 || isempty(ax) + ax = gca; + end + + % Set domain limits + xlim(ax, [x_min, x_max]); + ylim(ax, [y_min, y_max]); + + % Apply tick marks from configuration if available + if isfield(cfg, 'visualization') + if isfield(cfg.visualization, 'plot_tick_y') && ~isempty(cfg.visualization.plot_tick_y) + yticks(ax, cfg.visualization.plot_tick_y); + end + if isfield(cfg.visualization, 'plot_tick_x') && ~isempty(cfg.visualization.plot_tick_x) + xticks(ax, cfg.visualization.plot_tick_x); + end + end + + % Ensure consistent aspect ratio and tight view + axis(ax, 'equal'); + axis(ax, 'tight'); +end diff --git a/src/visualize_final.m b/src/visualize_final.m deleted file mode 100644 index abfacff..0000000 --- a/src/visualize_final.m +++ /dev/null @@ -1,213 +0,0 @@ -function visualize_final(cfg, doPlot, xy1, W_final, ~, x_min, x_max, y_min, y_max, Dx, Dy) - %VISUALIZE_FINAL Create visualization of final simulation results - % - % This function creates a plot of the final vorticity field if plotting is enabled. - % - % INPUTS: - % cfg - Configuration structure - % doPlot - Boolean indicating if plotting should be enabled - % xy1 - Complete velocity grid coordinates - % W_final - Final velocity solution vector [U; V] (single time step) - % Nt - Number of time steps (unused, kept for compatibility) - % x_min - Minimum x-coordinate of domain - % x_max - Maximum x-coordinate of domain - % y_min - Minimum y-coordinate of domain - % y_max - Maximum y-coordinate of domain - % Dx - RBF-FD differentiation matrix for d/dx operator - % Dy - RBF-FD differentiation matrix for d/dy operator - - % Only plot if plotting is enabled - if ~doPlot - return - end - - % Extract coordinates - x1 = xy1(:, 1); % x-coordinates of velocity nodes - y1 = xy1(:, 2); % y-coordinates of velocity nodes - - % Create figure for vorticity visualization - figure('Name', 'Vorticity Field (1/Re = 1e-2)'); - colormap(jet); - - % Extract velocity components from final solution vector - % W_final is structured as [U; V] stacked vertically (single time step) - n_nodes = length(xy1); - U_final = W_final(1:n_nodes); % Final u-velocity - V_final = W_final(n_nodes + 1:end); % Final v-velocity - - % Compute vorticity: omega = dv/dx - du/dy using RBF-FD operators - % These operators are designed for scattered nodes and use local stencils - dvdx = Dx * V_final; % dv/dx using RBF-FD operator on scattered nodes - dudy = Dy * U_final; % du/dy using RBF-FD operator on scattered nodes - vorticity = dvdx - dudy; % Vorticity field: ω = ∂v/∂x - ∂u/∂y - - % Validate vorticity computation - fprintf('Vorticity field statistics:\n'); - fprintf(' Min vorticity: %.6f\n', min(vorticity)); - fprintf(' Max vorticity: %.6f\n', max(vorticity)); - fprintf(' Mean vorticity: %.6f\n', mean(vorticity)); - fprintf(' RMS vorticity: %.6f\n', sqrt(mean(vorticity.^2))); - - % Plot vorticity field with improved visualization - scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), vorticity, 'filled'); - axis equal; - axis tight; - hold on; - - % Set reasonable color limits to avoid extreme values dominating the plot - vort_std = std(vorticity); - vort_mean = mean(vorticity); - color_limit = max(abs(vort_mean) + 3 * vort_std, max(abs(vorticity)) * 0.8); - - % Handle case where vorticity is all zeros or NaN - if color_limit == 0 || ~isfinite(color_limit) - color_limit = 1.0; % Default color range - fprintf('Warning: Vorticity field is zero or contains NaN values. Using default color range.\n'); - end - clim([-color_limit, color_limit]); - - xlim([x_min, x_max]); - ylim([y_min, y_max]); - yticks(cfg.visualization.plot_tick_y); - xticks(cfg.visualization.plot_tick_x); - title(sprintf('Vorticity Field: ω = ∂v/∂x - ∂u/∂y (Re = %.0f)', 1 / cfg.simulation.viscosity)); - - xlabel('x'); - ylabel('y'); - colorbar; % Add colorbar for vorticity - - % Add grid for better visualization - grid on; - grid minor; - - drawnow; % Update display immediately - - %% Plot u-velocity component - figure('Name', 'U-Velocity Component'); - scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), U_final, 'filled'); - axis equal; - axis tight; - hold on; - - % Set reasonable color limits for u-velocity - u_std = std(U_final); - u_mean = mean(U_final); - u_color_limit = max(abs(u_mean) + 3 * u_std, max(abs(U_final)) * 0.8); - - % Handle case where u-velocity is all zeros or NaN - if u_color_limit == 0 || ~isfinite(u_color_limit) - u_color_limit = 1.0; % Default color range - fprintf('Warning: U-velocity field is zero or contains NaN values. Using default color range.\n'); - end - clim([-u_color_limit, u_color_limit]); - - xlim([x_min, x_max]); - ylim([y_min, y_max]); - yticks(cfg.visualization.plot_tick_y); - xticks(cfg.visualization.plot_tick_x); - title(sprintf('U-Velocity Component (Re = %.0f)', 1 / cfg.simulation.viscosity)); - - xlabel('x'); - ylabel('y'); - colorbar; - colormap(jet); - grid on; - grid minor; - - fprintf('U-velocity field statistics:\n'); - u_min = min(U_final); - u_max = max(U_final); - u_mean = mean(U_final); - u_rms = sqrt(mean(U_final.^2)); - fprintf(' Min u-velocity: %.6f\n', u_min); - fprintf(' Max u-velocity: %.6f\n', u_max); - fprintf(' Mean u-velocity: %.6f\n', u_mean); - fprintf(' RMS u-velocity: %.6f\n', u_rms); - - drawnow; - - %% Plot v-velocity component - figure('Name', 'V-Velocity Component'); - scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), V_final, 'filled'); - axis equal; - axis tight; - hold on; - - % Set reasonable color limits for v-velocity - v_std = std(V_final); - v_mean = mean(V_final); - v_color_limit = max(abs(v_mean) + 3 * v_std, max(abs(V_final)) * 0.8); - - % Handle case where v-velocity is all zeros or NaN - if v_color_limit == 0 || ~isfinite(v_color_limit) - v_color_limit = 1.0; % Default color range - fprintf('Warning: V-velocity field is zero or contains NaN values. Using default color range.\n'); - end - clim([-v_color_limit, v_color_limit]); - - xlim([x_min, x_max]); - ylim([y_min, y_max]); - yticks(cfg.visualization.plot_tick_y); - xticks(cfg.visualization.plot_tick_x); - title(sprintf('V-Velocity Component (Re = %.0f)', 1 / cfg.simulation.viscosity)); - - xlabel('x'); - ylabel('y'); - colorbar; - colormap(jet); - grid on; - grid minor; - - fprintf('V-velocity field statistics:\n'); - v_min = min(V_final); - v_max = max(V_final); - v_mean = mean(V_final); - v_rms = sqrt(mean(V_final.^2)); - fprintf(' Min v-velocity: %.6f\n', v_min); - fprintf(' Max v-velocity: %.6f\n', v_max); - fprintf(' Mean v-velocity: %.6f\n', v_mean); - fprintf(' RMS v-velocity: %.6f\n', v_rms); - - drawnow; - - %% Plot velocity magnitude - velocity_magnitude = sqrt(U_final.^2 + V_final.^2); - - figure('Name', 'Velocity Magnitude'); - scatter(x1, y1, cfg.visualization.scatter_size * ones(length(xy1), 1), velocity_magnitude, 'filled'); - axis equal; - axis tight; - hold on; - - % Color limits for velocity magnitude (always positive) - mag_max = max(velocity_magnitude); - - % Handle case where velocity magnitude is all zeros or NaN - if mag_max == 0 || ~isfinite(mag_max) - mag_max = 1.0; % Default color range - fprintf('Warning: Velocity magnitude is zero or contains NaN values. Using default color range.\n'); - end - clim([0, mag_max]); - - xlim([x_min, x_max]); - ylim([y_min, y_max]); - yticks(cfg.visualization.plot_tick_y); - xticks(cfg.visualization.plot_tick_x); - title(sprintf('Velocity Magnitude |V| (Re = %.0f)', 1 / cfg.simulation.viscosity)); - - xlabel('x'); - ylabel('y'); - colorbar; - colormap(jet); - grid on; - grid minor; - - fprintf('Velocity magnitude statistics:\n'); - fprintf(' Min |V|: %.6f\n', min(velocity_magnitude)); - fprintf(' Max |V|: %.6f\n', max(velocity_magnitude)); - fprintf(' Mean |V|: %.6f\n', mean(velocity_magnitude)); - fprintf(' RMS |V|: %.6f\n', sqrt(mean(velocity_magnitude.^2))); - - drawnow; - -end