Skip to content

Experimental clib .NET#1888

Merged
ischoegl merged 22 commits intoCantera:mainfrom
ischoegl:experimental-clib-contd
May 25, 2025
Merged

Experimental clib .NET#1888
ischoegl merged 22 commits intoCantera:mainfrom
ischoegl:experimental-clib-contd

Conversation

@ischoegl
Copy link
Copy Markdown
Member

@ischoegl ischoegl commented May 20, 2025

Changes proposed in this pull request

This PR refactors sourcegen for clarity

  • Split HeaderGenerator from CLibSourceGenerator (which accounts for a large portion of the diff)
  • Use clib_experimental rather than the traditional clib for .NET
  • Add samples/clib_experimental/demo.c example

For the dotnet build step, the current approach uses a Doxygen tree that is saved as an artifact.

A secondary refactoring step to make the file structure more Pythonic will be addressed in a follow-up.

If applicable, fill in the issue number this pull request is fixing

Addresses Cantera/enhancements#220

If applicable, provide an example illustrating new features this pull request is introducing

Checklist

  • The pull request includes a clear description of this code change
  • Commit messages have short titles and reference relevant issues
  • Build passes (scons build & scons test) and unit tests address code coverage
  • Style & formatting of contributed code follows contributing guidelines
  • The pull request is ready for review

@ischoegl ischoegl force-pushed the experimental-clib-contd branch 6 times, most recently from 0f7d6b4 to 5691afc Compare May 21, 2025 13:54
@ischoegl ischoegl force-pushed the experimental-clib-contd branch 4 times, most recently from 3ab3666 to 9769e5e Compare May 21, 2025 17:18
@ischoegl ischoegl marked this pull request as ready for review May 21, 2025 17:40
@ischoegl ischoegl requested a review from a team May 21, 2025 17:41
@ischoegl ischoegl force-pushed the experimental-clib-contd branch from 9769e5e to 56592d6 Compare May 21, 2025 17:46
@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented May 21, 2025

@speth ... this is ready for a review. A large portion of the line count is due to splitting the header generation from the CLib source generation. There are a couple of loose ends still, but I am trying to keep individual PR's manageable. As an aside: both failing tests appear to be due to a MATLAB regression.

Copy link
Copy Markdown
Member

@speth speth left a comment

Choose a reason for hiding this comment

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

Thanks, @ischoegl. I think this looks good. I had just a few questions / comments for your consideration before merging.

Comment on lines -253 to -265

/// <summary>
/// Gets or sets the mass fractions for all the species at once,
/// in the order in which they appear in this collection,
/// but does not normalize them.
/// </summary>
public void SetUnnormalizedMassFractions(double[] fractions)
{
var retval = LibCantera.thermo_setMassFractions(_handle,
(nuint) fractions.Length, fractions, InteropConsts.False);

InteropUtil.CheckReturn(retval);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does the "experimental" CLib have a way of setting unnormalized mass/mole fractions?

Copy link
Copy Markdown
Member Author

@ischoegl ischoegl May 23, 2025

Choose a reason for hiding this comment

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

It does not have this at the moment. I anticipate that this will come up again in the context of MATLAB ... I am trying to keep some mode switches out of the interface implementation. Also, while I know that it is a needed function, I view it as a relatively odd implementation detail. If we can avoid exposing this in user-facing API's, I'd be in favor of alternatives.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we will need to provide this function. It's essential for users who want to use their own integrators.

@ischoegl ischoegl force-pushed the experimental-clib-contd branch from 56592d6 to f7141cf Compare May 24, 2025 00:51
@ischoegl
Copy link
Copy Markdown
Member Author

@speth … thank you for the review. I either addressed your suggestions or left notes for follow-up work.

@ischoegl ischoegl requested a review from speth May 24, 2025 01:16
Copy link
Copy Markdown
Contributor

@burkenyo burkenyo left a comment

Choose a reason for hiding this comment

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

@ischoegl Thanks for keeping the .NET interface code generation and hand-built portions updated! I’ve added a few comments below. Addtionally, I would recommend removing the .h part of the generated file names since we are no longer using the CLib header files to inform the C# Code generation. For example Interop.Handles.Reaction.g.cs or Interop.Handles.ctrxn.g.cs instead of Interop.Handles.ctrxn.h.g.cs

Comment on lines 13 to 14
readonly SolutionHandle _sol;

Copy link
Copy Markdown
Contributor

@burkenyo burkenyo May 24, 2025

Choose a reason for hiding this comment

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

I would remove this, since we never access it. (In .NET, the mere ownership of an object will not ensure it is cleaned up. That’s the job of the Garbage Collector, and for non-GC items like objects on the native Cantera side, we need to leverage the dispose pattern for deterministic release of the resources)

Suggested change
readonly SolutionHandle _sol;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm a little uncomfortable deleting this object. The way the CLib Solution destructor is implemented, it will clean up associated ThermoPhase objects. I am not sufficiently familiar with the C# garbage collection to be sure that there aren't unintended side effects. Fwiw, here's the generated CLib destructor:

    int sol3_del(int handle)
    {
        // destructor
        try {
            auto obj = SolutionCabinet::at(handle);
            // remove all associated objects in reversed order
            if (obj->transport()) {
                int index = TransportCabinet::index(*(obj->transport()), handle);
                if (index >= 0) {
                    TransportCabinet::del(index);
                }
            }
            if (obj->kinetics()) {
                int index = KineticsCabinet::index(*(obj->kinetics()), handle);
                if (index >= 0) {
                    KineticsCabinet::del(index);
                }
            }
            if (obj->thermo()) {
                int index = ThermoPhaseCabinet::index(*(obj->thermo()), handle);
                if (index >= 0) {
                    ThermoPhaseCabinet::del(index);
                }
            }
            SolutionCabinet::del(handle);
            return 0;
        } catch (...) {
            return handleAllExceptions(-1, ERR);
        }
    }

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.

Keep it for now, see comment below!

Comment on lines +57 to +58
_sol = LibCantera.sol3_newSolution(filename, phaseName ?? "", "none");
_handle = LibCantera.sol3_thermo(_sol);
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.

Until we add a proper Solution class, we only need to temporarily create the solution object on the native side (it also appears that it would be safe to destroy it once we extract the thermo object because it uses shared pointers). And to ensure the native solution object is released, we should dispose the handle by leveraging the using statement (also guaranteeing its release if an exception is thrown later in the constructor). Also, we need to call sol.EnsureValid() before using the handle to get the proper error message in case of a missing file.

Suggested change
_sol = LibCantera.sol3_newSolution(filename, phaseName ?? "", "none");
_handle = LibCantera.sol3_thermo(_sol);
using var sol = LibCantera.sol3_newSolution(filename, phaseName ?? "", "none");
sol.EnsureValid();
_handle = LibCantera.sol3_thermo(sol);

(this assumes we remove the _sol field above)

Copy link
Copy Markdown
Member Author

@ischoegl ischoegl May 25, 2025

Choose a reason for hiding this comment

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

See above: I'm not sufficiently familiar - this may introduce side effects that go beyond my wheelhouse.

Edit: As .NET would access the storage in CLib, I'm pretty certain that _sol needs to remain. The ThermoPhase handles will be gone as soon as the Solution object is destroyed.

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.

@ischoegl I realized the handle handling is messed up in a number of ways. For example, right now when a ThermoPhaseHandle is disposed in .NET it calls thermo3_del(handle). But thermo3_del takes no arguments! I am surprised this doesn’t crash. Same problem for KineticsHandle and TransportHandle.

So I think we should merge this now, and I’ll create a follow-up to fix the these handles. In truth ThermoPhaseHandle, KineticsHandle, and TransportHandle shouldn’t be treated as such because they don’t identify an owned resource which can be closed independently. It will require some thinking on how the handle classes are scaffolded and which ones to scaffold at all, and how the wrapper classes are scaffolded.

@ischoegl ischoegl force-pushed the experimental-clib-contd branch from 4b3a6b0 to 65db458 Compare May 25, 2025 01:16
@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented May 25, 2025

Hi @burkenyo ... I truly appreciate you looking over the C# code - while I was apparently able to keep things from breaking, I don't really have much prior exposure to C#.

Regarding the 32 and 64 bit types, I am planning to dedicate a PR to a migration to int64_t (which is necessary in view of #1852) along with a clean-up of CLib cross-walks that I commented on earlier.

Other than that: I didn't touch the error logic (some of that comes out of CLib, some of that is ancient C++ code), and I am fairly certain that the Solution object does need to persist to ensure that we don't inadvertently get rid of associated ThermoPhase objects.

@ischoegl ischoegl requested a review from burkenyo May 25, 2025 01:31
@ischoegl ischoegl force-pushed the experimental-clib-contd branch from 65db458 to 55b7d90 Compare May 25, 2025 01:36
@ischoegl ischoegl force-pushed the experimental-clib-contd branch from 55b7d90 to 2a80fc3 Compare May 25, 2025 01:38
@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented May 25, 2025

@ischoegl Thanks for keeping the .NET interface code generation and hand-built portions updated! I’ve added a few comments below. Addtionally, I would recommend removing the .h part of the generated file names since we are no longer using the CLib header files to inform the C# Code generation. For example Interop.Handles.Reaction.g.cs or Interop.Handles.ctrxn.g.cs instead of Interop.Handles.ctrxn.h.g.cs

Regarding the ‘.h’ …. Technically, there aren’t any headers parsed but things are still based on header information (they are not written explicitly but are implicit to the implementation). I certainly can rename though.

@burkenyo
Copy link
Copy Markdown
Contributor

@ischoegl Thanks for keeping the .NET interface code generation and hand-built portions updated! I’ve added a few comments below. Addtionally, I would recommend removing the .h part of the generated file names since we are no longer using the CLib header files to inform the C# Code generation. For example Interop.Handles.Reaction.g.cs or Interop.Handles.ctrxn.g.cs instead of Interop.Handles.ctrxn.h.g.cs

Regarding the ‘.h’ …. Technically, there aren’t any headers parsed but things are still based on header information (they are not written explicitly but are implicit to the implementation). I certainly can rename though.

Let's keep the filenames for now so we can merge this PR. :)

Copy link
Copy Markdown
Member

@speth speth left a comment

Choose a reason for hiding this comment

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

Thanks, @ischoegl, This looks good and takes care of most of what's needed to align the .NET interface with the new CLib. I think @burkenyo has some ideas on how to further clean up the .NET interface and resolve a couple of rough edges that he'll bring in a separate PR.

@ischoegl ischoegl merged commit 3110fd1 into Cantera:main May 25, 2025
47 of 49 checks passed
@ischoegl ischoegl deleted the experimental-clib-contd branch May 25, 2025 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants