1function [all_jumps_red, rateBase_red, eventIdx_red, state_map] = ...
2 ode_eliminate_immediate(all_jumps, rateBase, eventIdx, sn, options)
3% [ALL_JUMPS_RED, RATEBASE_RED, EVENTIDX_RED, STATE_MAP] = ...
4% ODE_ELIMINATE_IMMEDIATE(ALL_JUMPS, RATEBASE, EVENTIDX, SN, OPTIONS)
6% Eliminate immediate transitions from Fluid ODE system
using stochastic
7% complementation to reduce stiffness
10% all_jumps: [n_states x n_transitions] matrix of state changes
11% rateBase: [n_transitions x 1] vector of base rates
12% eventIdx: [n_transitions x 1] vector of state indices
for rate gating
14% options: Solver options
17% all_jumps_red: Reduced jump matrix
18% rateBase_red: Reduced rate vector
19% eventIdx_red: Reduced
event index vector
20% state_map: Mapping from reduced to original state indices
22% Copyright (c) 2012-2026, Imperial College London
25% Get immediate detection threshold
using GlobalConstants.Immediate with tolerance
26% A transition
is considered immediate
if its rate
is within 1% of GlobalConstants.Immediate
27imm_ref = GlobalConstants.Immediate;
29imm_tol = imm_ref * (1 - rel_tol);
31% Identify immediate transitions (rates close to or above GlobalConstants.Immediate)
32imm_idx = find(rateBase >= imm_tol);
34% If no immediate transitions,
return unchanged
36 all_jumps_red = all_jumps;
37 rateBase_red = rateBase;
38 eventIdx_red = eventIdx;
39 state_map = 1:size(all_jumps, 1);
43% Report detected immediate transitions
44if isfield(options,
'verbose') && options.verbose > 0
45 line_printf(sprintf(
'Eliminating %d immediate transitions (out of %d total)\n', ...
46 length(imm_idx), length(rateBase)));
50 % Apply stochastic complement elimination
51 [all_jumps_red, rateBase_red, eventIdx_red, state_map] = ...
52 eliminate_via_stochcomp(all_jumps, rateBase, eventIdx, imm_idx, options);
55 % Elimination failed - fall back to original system
56 warning(
'LINE:Fluid:EliminationFailed', ...
57 'Immediate transition elimination failed: %s. Using original system.', ME.message);
58 all_jumps_red = all_jumps;
59 rateBase_red = rateBase;
60 eventIdx_red = eventIdx;
61 state_map = 1:size(all_jumps, 1);
64end % ode_eliminate_immediate
67function [all_jumps_red, rateBase_red, eventIdx_red, state_map] = ...
68 eliminate_via_stochcomp(all_jumps, rateBase, eventIdx, imm_idx, options)
69% Eliminate immediate transitions
using stochastic complementation
71n_states = size(all_jumps, 1);
72n_transitions = length(rateBase);
74% Step 1: Construct infinitesimal generator matrix from jumps and rates
75W = sparse(n_states, n_states);
77for t = 1:n_transitions
78 src_state = eventIdx(t);
79 jump = all_jumps(:, t);
81 % Find destination state (where jump > 0)
82 dst_states = find(jump > 0);
84 if ~isempty(dst_states)
85 for d = 1:length(dst_states)
86 dst_state = dst_states(d);
87 % Add transition rate from src to dst
88 W(src_state, dst_state) = W(src_state, dst_state) + rateBase(t) * jump(dst_state);
93% Make W a proper generator (row sums = 0)
94W = W - diag(sum(W, 2));
96% Step 2: Identify immediate vs timed states
97% Immediate states are those that have outgoing immediate transitions
98imm_states = unique(eventIdx(imm_idx));
99timed_states = setdiff(1:n_states, imm_states);
101% Check that we have some timed states remaining
102if isempty(timed_states)
103 error('LINE:Fluid:AllImmediate', ...
104 'All states have immediate transitions - cannot eliminate');
107% Step 3: Apply stochastic complement
108[W_red, ~, ~, ~, Q22, ~] = ctmc_stochcomp(W, timed_states);
110% Check conditioning of Q22
111if rcond(full(Q22)) < 1e-12
112 warning('LINE:Fluid:IllConditioned', ...
113 'Immediate transition matrix ill-conditioned (rcond = %g)', rcond(full(Q22)));
116% Step 4: Convert reduced generator back to jump/rate representation
117[all_jumps_red_local, rateBase_red, eventIdx_red_local] = generator_to_jumps(W_red);
119% Step 5: Set up state mapping and expand to original state space
120state_map = timed_states;
122% Expand jump matrix and eventIdx to original state space dimension
123% This ensures compatibility with ode_rates_closing which uses original q_indices
124all_jumps_red = zeros(n_states, length(rateBase_red));
125for t = 1:length(rateBase_red)
126 jump_local = all_jumps_red_local(:, t);
127 % Map local indices to original indices
128 for s = 1:length(state_map)
129 if jump_local(s) ~= 0
130 all_jumps_red(state_map(s), t) = jump_local(s);
135% Map eventIdx from reduced to original state space
136eventIdx_red = state_map(eventIdx_red_local);
139if isfield(options, 'verbose') && options.verbose > 0
140 reduction_pct = 100 * (1 - length(timed_states)/n_states);
141 line_printf(sprintf('State space reduced from %d to %d states (%.1f%% reduction)\n', ...
142 n_states, length(timed_states), reduction_pct));
145end % eliminate_via_stochcomp
148function x_full = ode_expand_state(x_reduced, state_map, n_original)
149% X_FULL = ODE_EXPAND_STATE(X_REDUCED, STATE_MAP, N_ORIGINAL)
151% Expand reduced state vector back to original dimension
154% x_reduced: [n_reduced x 1] reduced state vector
155% state_map: [n_reduced x 1] mapping from reduced to original indices
156% n_original: original state space dimension
159% x_full: [n_original x 1] full state vector
160% Immediate states (not in state_map) are set to 0
162x_full = zeros(n_original, 1);
163x_full(state_map) = x_reduced;
165end % ode_expand_state