@@ -409,6 +409,107 @@ function find_perfect_aliases!(
409409 return aliases
410410end
411411
412+ """
413+ $TYPEDSIGNATURES
414+
415+ If a row in `mm` only contains a single variable, that variable is identically zero.
416+ Eliminate such any such zero variables which can be identified from `mm`. Return:
417+ - `zero_vars`: A `Set{Int}` of variables identified to be identically zero.
418+ - `zero_eqs`: A `Set{Int}` of equations identifying such variables to be zero.
419+ - `eqs_to_substitute`: A `Set{Int}` of equations in `state` which contain variables
420+ in `zero_vars`. These equations should be substituted to remove the zero variables.
421+
422+ Also update `mm` in-place to remove such zero variables/equations.
423+ """
424+ function eliminate_zero_variables! (state:: TearingState , mm:: CLIL.SparseMatrixCLIL )
425+ # Inverse mapping of `mm.nzrows`
426+ nzrow_to_idx = Dict {Int, Int} ()
427+ sizehint! (nzrow_to_idx, length (mm. nzrows))
428+ for (i, eqidx) in enumerate (mm. nzrows)
429+ nzrow_to_idx[eqidx] = i
430+ end
431+ # Variables we can identify are zero via `mm`
432+ zero_vars = Set {Int} ()
433+ # Equations in `mm` of the form `var ~ 0`
434+ zero_eqs = Set {Int} ()
435+ # Equations that `zero_vars` are incident on and need to be substituted
436+ eqs_to_substitute = Set {Int} ()
437+ # Queue of indices in `mm.nzrows` containing rows to check for being
438+ # `var ~ 0`
439+ queue = Queue {Int} ()
440+ # Initially check all rows with just one element
441+ for i in eachindex (mm. nzrows)
442+ isone (length (mm. row_cols[i])) && push! (queue, i)
443+ end
444+
445+ while ! isempty (queue)
446+ row_i = popfirst! (queue)
447+ eqidx = mm. nzrows[row_i]
448+ # Skip rows we already processed in case they show up twice
449+ eqidx in zero_eqs && continue
450+
451+ # If a row only contains one non-zero element, that element is also zero.
452+ # We filter `rcol` and `rval` to remove any zero elements. This could be
453+ # done in the `for nbor in nbors` loop below. However, it's possible we process
454+ # two zero variables both present in the same row before processing that row.
455+ # This approach avoids the row being processed by each of the two zero variables
456+ # individually in their `nbors` loop. We delay removing all zero elements to
457+ # the latest possible time to aggregate updates.
458+ rcol = mm. row_cols[row_i]
459+ rval = mm. row_vals[row_i]
460+ ptr = firstindex (rcol)
461+ nnz = 0
462+ for i in eachindex (rcol)
463+ rcol[i] in zero_vars && continue
464+ rcol[ptr] = rcol[i]
465+ rval[ptr] = rval[i]
466+ ptr = nextind (rcol, ptr)
467+ nnz += 1
468+ end
469+ resize! (rcol, nnz)
470+ resize! (rval, nnz)
471+ # `mm` does have rows with empty `rcol` on entry to this function, but they
472+ # should never be reachable by this search.
473+ @assert ! iszero (nnz) " `mm` should not be singular at this point!"
474+ isone (nnz) || continue
475+
476+ zvar = rcol[1 ]
477+ push! (zero_vars, zvar)
478+ push! (zero_eqs, eqidx)
479+
480+ nbors = 𝑑neighbors (state. structure. graph, zvar)
481+ for nbor in nbors
482+ nzrow_idx = get (nzrow_to_idx, nbor, 0 )
483+ # If `nbor` is not present in `mm`, it must be substituted later
484+ if iszero (nzrow_idx)
485+ push! (eqs_to_substitute, nbor)
486+ continue
487+ end
488+ # If it is present, process it
489+ push! (queue, nzrow_idx)
490+ end
491+ end
492+
493+ # Remove zero equations from `mm`
494+ if ! isempty (zero_eqs)
495+ ptr = firstindex (mm. nzrows)
496+ for i in eachindex (mm. nzrows)
497+ eqidx = mm. nzrows[i]
498+ eqidx in zero_eqs && continue
499+ mm. nzrows[ptr] = mm. nzrows[i]
500+ mm. row_cols[ptr] = mm. row_cols[i]
501+ mm. row_vals[ptr] = mm. row_vals[i]
502+ ptr = nextind (mm. nzrows, ptr)
503+ end
504+ nnz = length (mm. nzrows) - length (zero_eqs)
505+ resize! (mm. nzrows, nnz)
506+ resize! (mm. row_cols, nnz)
507+ resize! (mm. row_vals, nnz)
508+ end
509+
510+ return zero_vars, zero_eqs, eqs_to_substitute
511+ end
512+
412513function alias_elimination! (
413514 state:: TearingState ; fully_determined = true ,
414515 print_underconstrained_variables = false , kwargs...
@@ -438,6 +539,10 @@ function alias_elimination!(
438539 fullvars_to_idx = Dict {SymbolicT, Int} (Iterators. map (reverse, enumerate (fullvars)))
439540 original_eqs = state. original_eqs
440541
542+ zero_vars, zero_eqs, eqs_to_substitute = eliminate_zero_variables! (state, mm)
543+ append! (vars_to_rm, zero_vars)
544+ append! (eqs_to_rm, zero_eqs)
545+
441546 for (ieq, eq) in enumerate (mm. nzrows)
442547 rcol = mm. row_cols[ieq]
443548 rval = mm. row_vals[ieq]
@@ -475,6 +580,18 @@ function alias_elimination!(
475580 end
476581 end
477582
583+ subrules = Dict {SymbolicT, SymbolicT} ()
584+ sizehint! (subrules, length (zero_vars))
585+ for v in zero_vars
586+ subrules[fullvars[v]] = Symbolics. COMMON_ZERO
587+ push! (state. additional_observed, fullvars[v] ~ Symbolics. COMMON_ZERO)
588+ end
589+ subber = SU. IRSubstituter {true} (get_irstructure (sys), subrules)
590+ for ieq in eqs_to_substitute
591+ eqs[ieq] = subber (eqs[ieq])
592+ original_eqs[ieq] = subber (original_eqs[ieq])
593+ end
594+
478595 @set! sys. eqs = eqs
479596 state. sys = sys
480597
0 commit comments