Skip to content

New IO capabilities via FieldAPI IO #121

Draft
piotrows wants to merge 36 commits into
developfrom
napz-hdf5io-pr
Draft

New IO capabilities via FieldAPI IO #121
piotrows wants to merge 36 commits into
developfrom
napz-hdf5io-pr

Conversation

@piotrows
Copy link
Copy Markdown
Contributor

@piotrows piotrows commented Jun 14, 2025

This PR adds HDF5 IO feature to a FieldAPI variant of CLOUDSC. The changes proposed are:

  1. Extra routine TEST_FIELD_HDF5 placed next to the field's driver version of the Fortran feature, performing FieldAPI HDF5 calls to write and subsequently read CLOUDSC input data. The routine queries for host pointers of the FieldAPI variables, then stores all in the HDF5 file (or files, in case of MPI variant), and ultimately reads them again to verify if the write/read was performed correctly.
  2. Fiat is added as a FieldAPI dependency, since FieldAPI IO module depends on MPI for generality. Fiat-specific files duplicated in CLOUDSC dwarf are compiled only if Fiat is FieldAPI is requested to be disabled.

@reuterbal
Copy link
Copy Markdown
Collaborator

The HDF5 functionality has now been added to field_api main. Could this also be merged into develop_ecmwf, as that's the branch we use in cloudsc? (@awnawab)

@piotrows
Copy link
Copy Markdown
Contributor Author

The HDF5 functionality has now been added to field_api main. Could this also be merged into develop_ecmwf, as that's the branch we use in cloudsc? (@awnawab)

Yes please! I need it for further developments.

@awnawab
Copy link
Copy Markdown
Contributor

awnawab commented Jul 25, 2025

The HDF5 functionality has now been added to field_api main. Could this also be merged into develop_ecmwf, as that's the branch we use in cloudsc? (@awnawab)

Sure, I'll rebase ecmwf-develop over main.

@awnawab
Copy link
Copy Markdown
Contributor

awnawab commented Jul 25, 2025

F-API develop-ecmwf now rebased over main.

@reuterbal
Copy link
Copy Markdown
Collaborator

@piotrows Can you update the field_api branch in the bundle.yml and resolve the conflicts? Then this should be ready for a look I believe?

@piotrows piotrows changed the title Napz hdf5io pr New IO capabilities via FieldAPI IO Aug 2, 2025
@piotrows piotrows marked this pull request as ready for review August 2, 2025 12:21
@piotrows
Copy link
Copy Markdown
Contributor Author

piotrows commented Aug 2, 2025

@piotrows Can you update the field_api branch in the bundle.yml and resolve the conflicts? Then this should be ready for a look I believe?

Done, let's have a look together. I've made this development a bit more optional.

@piotrows piotrows marked this pull request as draft September 3, 2025 08:06
Copy link
Copy Markdown
Collaborator

@mlange05 mlange05 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, apologies for the delayed review comments, and second of all many thanks for taking this on. I think this is already very useful and I've run a basic example locally and things do indeed work as I'd expect them to. So, many thanks for this and much appreciated.

Next up, I've left a few comments mostly about structure and where to call what. In short, I think it would be better if we would add the file-write capabilities as utilities of the field-based state and flux objects, and call these outside of the driver layer, triggered by env variables to allow run-time switching instead of compile-time switching.

I've also noted two things:

  • Nothing in the tests actually tests the correct execution of the output file, so silent failure would not be caught. Maybe a simple check for the existence of the dumped file(s) could be added to one of the ctests?
  • Executing the same run twice only works with a data set of the same size, however changing the working set size on a re-run fails. I'm assuming we want to replace existing output files on the re-run instead of writing into the existing file?

TYPE(CLOUDSC_STATE_TYPE) ,INTENT(IN) :: TENDENCY_TMP
TYPE(CLOUDSC_STATE_TYPE) ,INTENT(IN) :: TENDENCY_LOC

REAL(KIND=JPRB), POINTER :: PAUX_PT(:,:,:)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the pointer declarations are needed here, or am I missing something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are needed for the FA IO API so the shape of the variable becomes explicit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are they truly needed there though? From my reading of https://github.com/ecmwf-ifs/field_api/blob/main/src/io/field_RANKSUFF_hdf5_module.fypp#L50 it uses SELF%PTR throughout, so there's no need to pass an externally declared pointer in. I also think this makes it look much more cumbersome than it needs to be, as the field object should have all the information locally to just write its content.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is required to have shape information embedded into the subroutine interface that allows Fortran to pick the correct variant for the field?

If the pointer was removed, something like

CALL WRITE_HDF5_PERRANK_DATA(PAUX%F_PT, PAUX_PVERVEL, "cloudsc_data", "PT",           HDFEXISTS=.TRUE.)

would then have to have either a dimension keyword

CALL WRITE_HDF5_PERRANK_DATA(PAUX%F_PT, 3, "cloudsc_data", "PT",           HDFEXISTS=.TRUE.)

or specify the actual implementation in the routine name, hypothetically paraphrasing:

CALL WRITE_HDF5_PERRANK_DATA_3RB(PAUX%F_PT, "cloudsc_data", "PT",           HDFEXISTS=.TRUE.)

I might be misinterpreting this, but if the above is the case, then pointers with a certain dimension are an unintuitive way of specifying this information. @awnawab might have a suggestion here?

write(0,1003) NUMPROC,NUMOMP,NGPTOTG,NPROMA,NGPBLKS
end if

#ifdef FIELD_API_IO
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, the option to write the input state to file would be a run-time option, and not a compile-time option. How about we rename TEST_FIELD_HDF5 to WRITE_STATE_HDF5 and make it a property of the CLOUDSC_FIELD_STATE instead of invoking it from the kernel? This way we could toggle this via an environment flag in src/cloudsc_fortran/dwarf_cloudsc.F90 right after loading the input state from the classic input files?

Similar we could then write the output by implementing the same boilerplate on CLOUDSC_FLUX_TYPE instead of adding the I/O to the kernel driver.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we rename TEST_FIELD_HDF5 to WRITE_STATE_HDF5

Could be done but what we are doing at the moment is testing (write+read, not a write alone).

Copy link
Copy Markdown
Contributor Author

@piotrows piotrows Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a property of the CLOUDSC_FIELD_STATE instead of invoking it from the kernel?

Well, this is an option, but then it looses the virtue of a top-level, clear example that teaches the FieldAPI IO. So it really depends if we want expand CLOUDSC capabilities or we would rather prefer a FA IO tutorial, or perhaps both.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this is an option, but then it looses the virtue of a top-level, clear example that teaches the FieldAPI IO. So it really depends if we want expand CLOUDSC capabilities or we would rather prefer a FA IO tutorial, or perhaps both.

I disagree on this one - having a pure procedural test routine does not make a good "tutorial". Instead, the derived types CLOUDSC_FIELD_STATE and CLOUDSC_FLUX_STATE are intended as examples of group types used in the EC-physics context. Adding independent read/write routines to those types is the main goal here, so why not illustrate this here in exactly this form?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about splitting the two into a write and read routine and calling them in this order from here?
That would work as a demonstration of how to use either mode individually.

For a full-on switch to HDF5 as inputs, we would need to replicate the inputs in a third format commited to the repository. I think that is a good idea but could also be tackled in a follow-on PR maybe?
The input reading is still very much set-up in a Serialbox first, otherwise use the legacy hdf5 read method. We could first remove that and full-on switch to HDF5, then add the field_api hdf5 reader.

Comment thread bundle.yml Outdated
help : Enable Field API IO (requires MPI) [ON|OFF]
cmake : >
FIELD_API_ENABLE_IO={{value}}
FIELD_API_ENABLE_FIAT={{value}}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not always enable FIAT now that it is part of the bundle? Also, could we sensibly always enabled Field-API-IO, or do we need that toggle for installs without hdf5?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@reuterbal was strongly opposing permanent dependence on FIAT.
Since the availability of HDF5 for non-standard compilers is not obvious, perhaps we could stick to an optional HDF5...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that makes sense. In that case please leave this as is.

Comment thread src/common/CMakeLists.txt Outdated
${COMMON_LIB_LIBS}
$<${HAVE_FIELD_API}:fiat>
$<${HAVE_FIELD_API}:field_api_${prec}>
# $<$<NOT:$<BOOL:${HAVE_FIELD_API}>>:parkind_${prec}>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug leftover?

@piotrows
Copy link
Copy Markdown
Contributor Author

piotrows commented Sep 3, 2025

  • Nothing in the tests actually tests the correct execution of the output file, so silent failure would not be caught. Maybe a simple check for the existence of the dumped file(s) could be added to one of the ctests?

Actually, there is a check. If you look closely, there is first a store to the output file and then an immediate read. If the store is corrupt, the read shall be also corrupt and the usual norms will fail.

@piotrows
Copy link
Copy Markdown
Contributor Author

piotrows commented Sep 3, 2025

  • Executing the same run twice only works with a data set of the same size, however changing the working set size on a re-run fails. I'm assuming we want to replace existing output files on the re-run instead of writing into the existing file?

This is a design question ... the current solution prevents the user from overwriting his previously stored data and was chosen deliberately. We can extend the API to support both ways, or we can change the default behavior.

@piotrows
Copy link
Copy Markdown
Contributor Author

piotrows commented Sep 3, 2025

@mlange05 @reuterbal
Quick summary of where we are, feel free to further comment:

  1. BR suggested (offline) that I should get rid of the quest for replacing Fiat files with Fiat lib, because it makes things complicated and the Atos CI runs fail due to linking errors. I am not fully convinced (perhaps we could link Fiat static to satisfy the GPU compiler needs), but certainly we have a decision to make.
  2. I have a feeling that HDF5 should remain optional for CLOUDSC at compile time, but run-time is fine to me as well (HDF5 2.x (in devel) shall get rid of autotools and be easier to install, I believe)
  3. Explicit example of FA IO vs. deeper integration with CLOUDSC aiming at expanding CLOUDSC capabilities, or both.
  4. Do we need another evaluation of the HDF5 store correctness.

@mlange05
Copy link
Copy Markdown
Collaborator

mlange05 commented Sep 4, 2025

@mlange05 @reuterbal Quick summary of where we are, feel free to further comment:

  1. BR suggested (offline) that I should get rid of the quest for replacing Fiat files with Fiat lib, because it makes things complicated and the Atos CI runs fail due to linking errors. I am not fully convinced (perhaps we could link Fiat static to satisfy the GPU compiler needs), but certainly we have a decision to make.
  2. I have a feeling that HDF5 should remain optional for CLOUDSC at compile time, but run-time is fine to me as well (HDF5 2.x (in devel) shall get rid of autotools and be easier to install, I believe)
  3. Explicit example of FA IO vs. deeper integration with CLOUDSC aiming at expanding CLOUDSC capabilities, or both.
  4. Do we need another evaluation of the HDF5 store correctness.
  1. Agreed. Let's keep this a PR for one feature and only add what's needed for that.
  2. Agreed, let's keep the compile-time option. I'd still prefer the actual use of it to mimic the binary I/O and use run-time flags for that.
  3. Deep integration was always the aim, as we don't need a second unit test (F-API unit tests this already). Ideally it would behave like the binary I/O and either optionally or always replace this field F-API enabled drivers.
  4. I don't think the in-situ test is the right way. I'd prefer to have a ctest that checks if the file was created to test the writes, and maybe let F-API-based drivers read input/reference from HDF5 to test reads (either optionally or always)?

Copy link
Copy Markdown
Collaborator

@reuterbal reuterbal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many thanks, overall this looks very nice. I agree with the API and integration points that @mlange05 raised and have also pointed out how to fix the fiat/field_api integration.

Comment thread src/cloudsc_fortran/cloudsc_driver_field_mod.F90 Outdated
TYPE(CLOUDSC_STATE_TYPE) ,INTENT(IN) :: TENDENCY_TMP
TYPE(CLOUDSC_STATE_TYPE) ,INTENT(IN) :: TENDENCY_LOC

REAL(KIND=JPRB), POINTER :: PAUX_PT(:,:,:)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is required to have shape information embedded into the subroutine interface that allows Fortran to pick the correct variant for the field?

If the pointer was removed, something like

CALL WRITE_HDF5_PERRANK_DATA(PAUX%F_PT, PAUX_PVERVEL, "cloudsc_data", "PT",           HDFEXISTS=.TRUE.)

would then have to have either a dimension keyword

CALL WRITE_HDF5_PERRANK_DATA(PAUX%F_PT, 3, "cloudsc_data", "PT",           HDFEXISTS=.TRUE.)

or specify the actual implementation in the routine name, hypothetically paraphrasing:

CALL WRITE_HDF5_PERRANK_DATA_3RB(PAUX%F_PT, "cloudsc_data", "PT",           HDFEXISTS=.TRUE.)

I might be misinterpreting this, but if the above is the case, then pointers with a certain dimension are an unintuitive way of specifying this information. @awnawab might have a suggestion here?

Comment thread src/common/CMakeLists.txt Outdated
Comment on lines +27 to +32
set(CLOUDSC_FIAT_SOURCES
module/parkind1.F90
module/ec_pmon_mod.F90
module/oml_mod.F90
module/abor1.F90
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion would be to roll back this change and restore these files in the list above.

Suggested change
set(CLOUDSC_FIAT_SOURCES
module/parkind1.F90
module/ec_pmon_mod.F90
module/oml_mod.F90
module/abor1.F90
)

Comment thread src/common/CMakeLists.txt Outdated
SOURCES
${CLOUDSC_COMMON_SOURCES}
${COMMON_LIB_SOURCES}
$<$<NOT:$<BOOL:${HAVE_FIAT}>>:${CLOUDSC_FIAT_SOURCES}>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can then also be removed

Suggested change
$<$<NOT:$<BOOL:${HAVE_FIAT}>>:${CLOUDSC_FIAT_SOURCES}>

Comment thread src/common/CMakeLists.txt Outdated
Comment thread bundle.yml Outdated
Comment thread bundle.yml
Comment thread bundle.yml
Comment thread CMakeLists.txt Outdated
write(0,1003) NUMPROC,NUMOMP,NGPTOTG,NPROMA,NGPBLKS
end if

#ifdef FIELD_API_IO
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about splitting the two into a write and read routine and calling them in this order from here?
That would work as a demonstration of how to use either mode individually.

For a full-on switch to HDF5 as inputs, we would need to replicate the inputs in a third format commited to the repository. I think that is a good idea but could also be tackled in a follow-on PR maybe?
The input reading is still very much set-up in a Serialbox first, otherwise use the legacy hdf5 read method. We could first remove that and full-on switch to HDF5, then add the field_api hdf5 reader.

@awnawab
Copy link
Copy Markdown
Contributor

awnawab commented Sep 6, 2025

F-API develop-ecmwf rebased over main and now also contains ecmwf-ifs/field_api#108.

Comment thread bundle.yml Outdated
FIELD_API_ENABLE_IO={{value}}
FIELD_API_ENABLE_FIAT={{value}}
ENABLE_FIELD_API_ENABLE_IO={{value}}
ENABLE_MPI=ON
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there really a need to forcefully enable MPI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there really a need to forcefully enable MPI?

The way it is coded in FA at the moment, yes. I am working on it on a FA side.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there really a need to forcefully enable MPI?

@reuterbal @awnawab I've made MPI/MPL fully optional, what do you think about the current state?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great, many thanks! Looks very nice on the CLOUDSC side now, but I haven't look at Field-API. I'll let @awnawab judge the F-API angle ;-)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify it a little in F-API. My comments that follow are based on what I saw in napz-IO-mpi-independent-option, please disregard if you had plans to change that.

Right now the FIELD_API IO feature requires fiat and for fiat to be built with MPI. For the moment, let's keep these two assumptions, and if in the future they become limitations we can address them then.

The only MPI related information we need in the IO functionality is the MPI communicator. The current rank and total number of ranks can be queried from the communicator. So I would propose we simply make the MPI communicator an optional argument to any IO routine that needs it. If this argument is not present, then we can assume serial IO. We can print a message either way to NULOUT so the user isn't surprised.

This allows us to remove all the IO related options: IO_SERIAL, IO_MPI, IO_MPL. FIAT links to MPI publicly, so linking to FIAT means we can import the MPI runtime library too in F-API. Since that is currently a requirement for the IO feature, we don't even need to ifdef guard it.

Apologies if I've missed something, and we can also chat about it offline if you'd like 😄

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you misunderstood me. Having fiat as a dependency when we're building against field api is perfectly valid. I just don't like having fiat always as a dependency on non-fapi targets.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just means we increment the rank by 1 by default,

If it is not set in stone in MPL, then I would see it as unsafe to encode such an implicit assumption into non-MPL code.

Copy link
Copy Markdown
Contributor Author

@piotrows piotrows Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having 4 IO related options seems like overkill

I think there are 3: SERIAL, MPI, MPL. There need to be at least two specified at the compile time (serial and parallel), the third one merely allows to be independent of FIAT.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An IO option coupled with an MPI option should be enough., and allows us to be independent of FIAT.

Again, from a domain scientist point of view, there should be an option of storing the data in an organized manner that translates in an unique way to a computational mesh, so the h5 file can be e.g. viewed in an external viewer or auto-processed in Python for postprocessing. At least until we move on to Atlas.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you misunderstood me. Having fiat as a dependency when we're building against field api is perfectly valid. I just don't like having fiat always as a dependency on non-fapi targets.

My main point was to provide an MPI-independent setup, actually. You had a good point that this should be relaxed and indeed I see that for the sake of testing new compilers it is very handy to don't rely on the accompanying MPI.

Comment thread src/cloudsc_cuda/CMakeLists.txt
Comment thread src/cloudsc_cuda/CMakeLists.txt Outdated
target_compile_options( ${TARGET}-${prec}-lib
PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:SHELL:${CLOUDSC_CUDA_OPT_FLAGS} ${CLOUDSC_CUDA_FLAGS}>
)
target_link_libraries(${TARGET}-${prec}-lib PRIVATE $<${HAVE_MPI}:MPI::MPI_C>)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be redundant to the addition above (if that is even needed)

Comment thread src/cloudsc_fortran/cloudsc_driver_field_mod.F90 Outdated
Comment thread src/common/CMakeLists.txt Outdated
Comment thread src/common/CMakeLists.txt Outdated
Comment thread CMakeLists.txt
DESCRIPTION "Support for task-level parallelism using MPI"
DEFAULT OFF
REQUIRED_PACKAGES "MPI COMPONENTS Fortran" )
REQUIRED_PACKAGES "MPI COMPONENTS Fortran C CXX" )
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still curious what makes this necessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated earlier, dependence of hdf5.h on mpi.h. I am not sure if CXX will be needed here, but it is hard to exclude at the moment.

Comment thread CMakeLists.txt Outdated
if( HAVE_FIELD_API_ENABLE_IO_MPL )
list(APPEND CLOUDSC_DEFINITIONS FIELD_API_IO_MPL)
elseif( HAVE_FIELD_API_ENABLE_IO_MPI )
find_package(MPI REQUIRED )
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to check here that the MPI feature is enabled. The bundle implicitly enforces this but a CMake-build without bundle would allow a halfway-house otherwise

Suggested change
find_package(MPI REQUIRED )
if( NOT HAVE_MPI )
ecbuild_error("Cannot enable FIELD_API_ENABLE_IO_MPI when feature MPI is disabled.")
endif()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, at the moment it is activated in FieldAPI itself:
elseif(HAVE_IO_MPI)
ecbuild_enable_mpi( COMPONENTS FORTRAN REQUIRED)

so this section of cloudsc can be deleted indeed.
However, if we link to parallel version of HDF5, it is probably necessary anyway to activate MPI, even without --with-mpi in the bundle.

Comment thread CMakeLists.txt Outdated
Comment thread README.md
Comment on lines +38 to +41
Field API is used in newer versions of the IFS. Optionally, this version also tests
Field API IO. To test IO write/read feature, add --with-field-api-io at the build stage.
Note that this enables MPI by default. The IO feature is embedded in the
cloudsc-fortran Field API test.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs a small update later.

Comment thread CMakeLists.txt Outdated
piotrows and others added 4 commits September 11, 2025 11:00
Co-authored-by: Balthasar Reuter <6384870+reuterbal@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants