Skip to content

Create variables to track non-passive species, so that they can be excluded from the Rosenbrock error norm (in int/rosenbrock*.f90 integrators only) - #154

Merged
yantosca merged 12 commits into
devfrom
feature/kpp-ros-errornorm
Jul 8, 2026
Merged

yantosca merged 12 commits into
devfrom
feature/kpp-ros-errornorm

Conversation

@yantosca

@yantosca yantosca commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Overview

This is the companion PR to #66 and #144. In this PR we create variables that can be used to exclude dummy species (i.e. those added to chemical reactions for the purpose of obtaining diagnostic information) from the Rosenbrock error norm computation. This should prevent the issue where integration results are changed depending on the number of dummy species in the mechanism (cf geoschem/geos-chem#3175).

Technical description

Two new module-level variables have been added to the ROOT_Global module (for F90 only). These are:

  1. NonPassiveSpc_Count: The number of non-passive species
  2. NonPassiveSpc_Indices: The KPP species indices for non-passive species

These optional variables (which are stored in ROOT_Global) are constructed when the Initialize routine is called.

  • If Initialize is called with the PassiveSpc_ATOL_Threshold optional argument, then all species that have an absolute tolerance (ATOL) greater than or equal to PassiveSpc_ATOL_Threshold will be denoted as passive species. In this case, NonPassiveSpc_Count will be equal to NVAR - the number of passive species and NonPassiveSpc_Indices will contain the index list of only the non-passive species.

  • If Initialize is called without any optional arguments, then NonPassiveSpc_Count will be equal to NVAR and NonPassiveSpc_Indices will contain the index list of all variable species.

We have also updated the algorithm in the Ros_ErrorNorm function (in each of the int/rosenbrock*.f90 files) to compute the error norm on the basis of non-passive species. We have also optimized the function to facilitate vectorization and to remove unnecessary ELSE blocks, which can be a bottleneck:

!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  KPP_REAL FUNCTION ros_ErrorNorm ( Y, Ynew, Yerr, &
                               AbsTol, RelTol, VectorTol )
!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!~~~> Computes the "scaled norm" of the error vector Yerr
!~~~>
!~~~> Now uses separate loops for scalar and vector tolerances,
!~~~> as this facilitates loop vectorization for better performance.
!~~~>
!~~~> Also uses NonPassiveSpc_Count and NonPassiveSpc_Indices (constructed
!~~~> in Initialize), so that we can exclude "passive" (e.g. prod/loss)
!~~~> species from the error norm computation.
!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   IMPLICIT NONE

! Input arguments
   KPP_REAL, INTENT(IN) :: Y(N), Ynew(N), Yerr(N), AbsTol(N), RelTol(N)
   LOGICAL, INTENT(IN) ::  VectorTol
! Local variables
   KPP_REAL :: Err, Scale, Ymax
   INTEGER  :: I, IDX

   Err = ZERO

   !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   !~~~> Vector Tolerances (per-species AbsTol & RelTol)
   !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   IF ( VectorTol ) THEN

      DO IDX = 1, NonPassiveSpc_Count
         I     = NonPassiveSpc_Indices(IDX)
         Ymax  = MAX( ABS( Y(I) ), ABS( Ynew(I) ) )
         Scale = AbsTol(I) + RelTol(I) * Ymax
         Err   = Err + ( Yerr(I) / Scale )**2
      ENDDO

      ! Normalize the error norm by the number of non-dummy species
      ! and prevent it from getting smaller than 1e-10
      Err           = SQRT( Err / NonPassiveSpc_Count )
      ros_ErrorNorm = MAX( Err, 1.0d-10 )
      RETURN

   ENDIF

   !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   !~~~> Scalar Tolerance (same AbsTol & RelTol for all species)
   !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   DO IDX = 1, NonPassiveSpc_Count
      I     = NonPassiveSpc_Indices(IDX)
      Ymax  = MAX( ABS( Y(I) ), ABS( Ynew(I) ) )
      Scale = AbsTol(1) + RelTol(1) * Ymax
      Err   = Err + ( Yerr(I) / Scale )**2
   ENDDO
   
   ! Normalize the error norm by the number of non-dummy species
   ! and prevent it from getting smaller than 1e-10
   Err           = SQRT( Err / NonPassiveSpc_Count )
   ros_ErrorNorm = MAX( Err, 1.0d-10 )

  END FUNCTION ros_ErrorNorm

Other modifications bundled into this PR:

  • Added C-I test F90_ros_passivespc, which adds 3 passive species to the small_strato mechanism.
  • Added new driver program drv/general_passivespc.f90, which executes CALL INITIALIZE( PassiveSpc_ATOL_Threshold = 1.0d25 ). This is used in the F90_ros_passivespc C-I test.
  • Added KPP internal function F90_FunctionBeginNoArgsDecl (in src/code_f90.c). This is similar to F90_FunctionBegin, except that it does not write any subroutine arguments. This allows us to manually declare subroutine below any F90 USE statements.
  • Added macro DefElmO in src/code.h, which defines a scalar F90 variable with the OPTIONAL attribute.
  • Added GNU 14, 15, 16 compilers to the "Run C-I tests" GitHub action.
  • Set Hacc = ZERO; and ErrOld = ZERO; in int/runge_kutta.c to avoid generating compiler warnings during C-I tests
  • Updated ICNTRL and RCNTRL comment headers in the int/rosenbrock*.f90 integrator files.
  • Updated ReadtheDocs documentation accordingly.

Related GitHub issues

Tagging @RolfSander @msl3v @jimmielin @obin1 @fsolmon @ujongebloed @mje1398

@yantosca yantosca added this to the 3.4.1 milestone Jun 30, 2026
@yantosca yantosca self-assigned this Jun 30, 2026
@yantosca yantosca added integrators Related to numerical integrators bugfix Fixes a bug or a technical issue labels Jun 30, 2026
@RolfSander

Copy link
Copy Markdown
Contributor

It's good to see that there is now a possibility to exclude dummy
species from the Rosenbrock error calculation! I do have a suggestion
though:

With the current implementation, you can use the optional argument only
for the four selected integrators. When you switch to another integrator
and forget to remove the optional argument again, the compiler will
produce an error. Of course, we could add the new optional arguments to
all integrators but that's a bit tedious.

My suggestion is that we put NonDummySpc_Count into ICNTRL(19). The
calculation of NonDummySpc_Indices could be done by KPP automatically at
the end of SUBROUTINE Initialize (which is generated by
GenerateInitialize() in gen.c).

Would that work?

@yantosca

yantosca commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @RolfSander, I think that's a good idea. I'll try it.

@yantosca

yantosca commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@RolfSander: I just noticed that there is the #DUMMYINDEX option that lets you set the indices of species that aren't in the mechanism to zero. I was thinking I should rename e.g. NonDumSpc_Count etc. so as not to confuse our nomenclature. Maybe I should refer to these as "diagnostic species" (e.g. NonDiagSpc_Count, etc)? Or maybe "actual" species?

@RolfSander

Copy link
Copy Markdown
Contributor

NonDiagSpc_Count

Yes, we should avoid confusion. Unfortunately, diag occurs quite often
in the KPP code, meaning diagonal, not diagnostic.

ActualSpec_Count would be ok.

Here is yet another idea: During the discussion, @obin1 used the name
"P/L species". So maybe ProdLossSpc_Count?

@obin1

obin1 commented Jul 1, 2026

Copy link
Copy Markdown
Member

I like ProdLossSpc_Count! This is a good name if we only use them for the production/loss diagnostics.

If we are looking for something more general, would PassiveSpc_Count work? I would imagine we want all passive species (not necessarily inert, but passive diagnostic species) to be excluded from the adaptive timestepping strategy.

@yantosca

yantosca commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @RolfSander. I think NonPassive_SpcCount etc is more general. I'll try that.

@yantosca
yantosca force-pushed the feature/kpp-ros-errornorm branch 3 times, most recently from ac46cd9 to d8b35b8 Compare July 2, 2026 14:07
yantosca added 2 commits July 2, 2026 17:09
code_f90.c
- In function F90_Decl:
  - Modified the ELM case block to write a F90 scalar argument with the
    OPTIONAL attribute when ATTR_F90_OPT is specified
  - Updated comments (cosmetic changes)
- Added F90_FunctionBeginNoArgDecl, which is similar to F90_FunctionBegin
  except that it doesn't automatically write the F90 function args
  immediately below the subroutine header.
- In routine Use_F90:
  - Point FunctionBeginNoArgDecl to F90_FunctionBeginNoArgDecl

src/code.c
- Added function prototype for FunctionBeginNoArgDecl

src/code.h
- Added macro DefElmO, which writes an OPTIONAL F90 scalar variable
- Added an extern function prototype for FunctionBeginNoArgDecl

src/code_c.c
src/code_f77.c
src/code_matlab.c
- Point FunctionBeginNoArgDecl to NULL in order to avoid
  uninitialized-variable compiler warnings

CHANGELOG.md
- Updated accordingly

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
src/gen.c
- In routine InitGen:
  - Added variables NON_PASV_SPC_CT and NON_PASV_SPC_IND, which will
    be used to write F90 variables (NonPassiveSpc_Count and
    NonPassiveSpc_indices) to keep track of non-passive species
  - Updated comments for clarity + other cosmetic changes
- In routine GenerateUpdateRconst
  - Now use FunctionBeginNoArgsDecl to write the subroutine interface
    for UPDATE_RCONST with YIN as optional input argument
  - Updated comments
- In routine GenerateGlobalHeader
  - Write NonPassiveSpc_Count and NonPassiveSpc_Indices to ROOT_Global
    (for F90 only)
  - Now write the autoreduction variables following the non-THREADPRIVATE
    variables
  - Updated comments, cosmetic changes
- In routine GenerateInitialize
  - Use FunctionBeginNoArgsDecl to write the ROOT_Initialize subroutine
    header with PassiveSpc_ATOL_Threshold optional variable
  - Write the PassiveSpc_ATOL_Threshold variable declaration
    following all F90 USE statements
  - For F90 only, add inlined code that initializes the variables
    NonPassiveSpc_Count and NonPassiveSpc_Indices by testing which
    species have ATOL < PassiveSpc_ATOL_Threshold
  - Updated comments for clarity

int/rosenbrock.f90
int/rosenbrock_adj.f90
int/rosenbrock_autoreduce.f90
int/rosenbrock_tlm.f90
- Rewrote the ros_ErrorNorm function to use NonPassiveSpc_Count and
  NonPassiveSpc_Indices so that passive species won't influence
  the Rosenbrock error norm
- Use separate loops for vector tolerance and scalar tolerance,
  to faciltate vectorization

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
@yantosca
yantosca force-pushed the feature/kpp-ros-errornorm branch from d8b35b8 to 66c2796 Compare July 2, 2026 21:32
@yantosca yantosca changed the title Add optional arguments to int/rosenbrock*.f90 integrators to exclude dummy species from the Rosenbrock error norm Create variables to track non-passive species, so that they can be excluded from the Rosenbrock error norm (in int/rosenbrock*.f90 integrators only) Jul 2, 2026
yantosca added 3 commits July 3, 2026 11:46
int/runge_kutta.c
- Set Hacc and ErrOldl to zero, which avoids compiler warnings
  for uninitialized variables

CHANGELOG.md
- Updated accordingly

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
src/gen.c
- At top of file:
  - Removed NON_PASV_SPC_CT and NON_PASV_SPC_IND integer variables
- In function GenerateUpdateRconst:
  - Set YIN and Y to zero when F90 if useLang != F90_LANG
  - Free YIN and Y when F90 at function's end if useLang != F90_LANG
    (this fixes a segfault)
- In function GenerateGlobalHeader
  - Declare NON_PASV_SPC_CT and NON_PASV_SPC_IND as local variables
  - Free NON_PASV_SPC_CT and NON_PASV_SPC_IND if useLang != F90_LANG

CHANGELOG.md
- Updated accordingly

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
.github/workflows/run-ci-tests
- Added GCC 14 and GCC 15 to the build matrix

CHANGELOG.md
- Updated accordingly

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
@RolfSander

Copy link
Copy Markdown
Contributor

Thanks, @yantosca! The name NonPassiveSpc_Count is fine. However, I
still see one problem: The new code is not at the end of SUBROUTINE Initialize but before the inlined code from F90_INIT.

As I am using F90_INIT to define my tolerances, this means that
NonPassiveSpc_Count will be calculated using potentially obsolete
ATOL values.

Would it be okay to move the new code after the inlined F90_INIT
block?

@yantosca

yantosca commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @RolfSander. Yes, I can make that change. I'll also update the documentation as well. Stay tuned.

yantosca added 6 commits July 7, 2026 09:55
.github/workflows/run-ci-tests.yml
- Added code to manually install the GCC 15 compiler, since it
  does not come pre-installed with Ubuntu 24.04.

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
src/gen.c
- In routine GenerateInitialize:
  - Moved the initialization of NonPassiveSpc_Count and
    NonPassiveSpc_Indices after the F90_INIT section.
    This will prevent clobbering of inlined ATOL values.

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
.ci-pipelines/ci-common-defs.sh
- Added F90_ros_passivespc to the list of tests

ci-tests/F90_ros_passivespc.kpp
- Added this KPP definition file for the F90_ros_passivespc C-I test.
  This is the "small strato" mechanism plus 3 passive species.

docs/source/getting-started/00_revision_history.rst
- Added to the list of recent updates under "Unreleased"

docs/source/tech_info/06_info_for_kpp_developers.rst
- Converted KPP source code files table into a list-table
- Added F90_ros_passivespc to the C-I tests table
- Added a 5th column ("What this tests") with links to the C-I tests
  table

docs/source/tech_info/07_numerical_methods.rst
- Renamed "rk_method_comparison" anchor to "rk-methods-3stage"
- Added "rk-methods-radau5", "rk_methods-sdirk", "rk-methods-sdirk4",
  "rk-methods-seulex" anchors

src/gen.c
- Updated syntax in comment

CHANGELOG.md
- Updated accordingly

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
.github/workflows/run-ci-tests.yml
- Added GCC 16 to the GCC versions matrix for the Run C-I tests action

docs/source/index.rst
- Converted HTML in header to ReST

CHANGELOG.md
- Updated accordingly

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
docs/source/using_kpp/04_input_for_kpp.rst
- Added "Filtering out passive species with an absolute tolerance
  threshold" section
- Shortened some ~~~ underlines

docs/source/using_kpp/05_output_from_kpp.rst
- Added a note under ROOT_Initialize pointing the user to the
  "Filtering out passive species with an absolute tolerance threshold"
  section
- Converted tables to list-tables

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
.github/workflows/run-ci-tests.yml
- Remove unneeded stuff from packages.microsoft.com from the GitHub
  Actions runner.  This will make sure that a failure in checking
  out these packages does not take down the entire job.

CHANGELOG.md
- Updated accordingly

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
@yantosca
yantosca force-pushed the feature/kpp-ros-errornorm branch from 77ebaa6 to 3d5c96a Compare July 7, 2026 21:34
int/rosenbrock.f90
int/rosenbrock_adj.f90
int/rosenbrock_autoreduce.f90
int/rosenbrock_tlm.f90
- Now use consistent formatting in the comment headers for
  ICNTRL and RCNTRL

Signed-off-by: Bob Yantosca <yantosca@seas.harvard.edu>
@yantosca
yantosca force-pushed the feature/kpp-ros-errornorm branch from 93df5cb to c0a9b02 Compare July 7, 2026 22:06
@yantosca
yantosca marked this pull request as ready for review July 7, 2026 22:21
@RolfSander

Copy link
Copy Markdown
Contributor

Thanks, @yantosca!, it looks good now!

@yantosca yantosca modified the milestones: 3.4.1, 3.5.0 Jul 8, 2026
@yantosca
yantosca merged commit e1da51f into dev Jul 8, 2026
17 checks passed
@yantosca
yantosca deleted the feature/kpp-ros-errornorm branch July 10, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fixes a bug or a technical issue integrators Related to numerical integrators

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants