-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Eliminate traps associated with accessing ThermoPhase objects in use by Reactors #201
Comments
@speth ... this certainly looks like something we should try to make more user friendly. Regarding use cases: the first issue (ODE integration) could likely be handled by automatically calling For the second case, we could also do some thing like
instead of
and handle things internally. The exact terminology/API is obviously debatable. At the same time, tackling what amounts to a copy constructor for |
At the end of what? At the end of
This is the main problem: While the Don't read too much into my pseudocode structure. I wrote it with some of the functions effectively "inlined" for what I was hoping would improve clarity, but perhaps didn't. I fully expect the manipulation of the |
This is indeed the crux of the problem. From where I stand, it is poor practice if a user has expectations that a shared object should hold a specific state (or attempts to use that implicit pathway to update reactor contents). It is unfortunate that the current design paradigm allows for this. It is getting 'abused' and people report 'unexpected behavior'. Overall, I don't think that this is a path we should encourage. My concern is that disallowing shared objects in some cases will break existing code (example: it is fairly common to use the same As we're already breaking things, we may as well create a clean break and shift to a different paradigm where |
@ischoegl commented in Cantera/cantera#1788 (comment):
The reason I suggested having
|
I am not sure that I agree: from my perspective, a lot of the issues with 'traps' this issue is documenting arise due to allowing 'sharing' at all. As I have stated in an earlier comment above, I think that the expectation to create a
Most of the instances where a
I think that we should consider a switch of paradigms here. I believe that we are 'oversharing' some of the objects we use. In the case of I think that overall, there appear to be some fundamental issues that need further discussion. I'd suggest to shelve Cantera/cantera#1788 for the time being. While this may prolong deprecation cycles, I don't want this to unnecessarily delay the release of 3.1. |
As a friendly amendment to the evaluation logic posted on top, I am proposing the following pseudo-code, which assumes existence of universal def eval(t, y, ydot):
# ReactorNet::updateState
for reactor in all_reactors:
# Reactor.updateState
reactor.thermo.set_TPY(y[...])
# Calculation of reactor-local variables is optional, for efficiency only
# new handling of reactor interactions
boundary_transfers = np.zeros_like(ydot)
for connector in all_connectors:
# update interactions across reactor boundaries
connector.update(boundary_transfers[...])
# ReactorNet::eval
for reactor in all_reactors:
# Evaluation can directly access thermo from this reactor only (!)
ydot[...] = f(reactor.thermo, reactor.kinetics, reactor.local_vars)
ydot += boundary_transfers This approach would ensure that cyclic pointer references are avoided by design. The underlying Beyond, I believe this would also be a workable approach to resolve #202. Considering #61, there is the interesting question on how to handle reactor surfaces ... while I had reclassified |
Providing regular copy constructors would introduce a ton of additional boilerplate code, not to mention testing requirements. I'd really like to see how effective the I agree that we should provide working serialization/round trip capabilities for all models that are provided as part of Cantera. What I'm concerned about is creating a technical requirement for that capability that applies not only to the built-in models but all user-provided models as well (including those implemented using
That's true that you could avoid cloning
I like the idea of moving the calculation of
Yes, I can see how this approach to the connectors makes a |
@speth ... thanks for your constructive comments. I believe that overall it should be relatively straightforward to work out a consensus for a path forward. I am totally 👍 with sticking to the |
Abstract
For a
ThermoPhase
in use by aReactor
, the state of theThermoPhase
does not necessarily correspond to the state of theReactor
at the current time of theReactorNet
. This can lead to users extracting incorrect state information during integration (see this UG post, for example). In addition, the current requirement that a singleThermoPhase
can be shared across multipleReactor
s increases the complexity of evaluating theReactor
's governing equations.Motivation
After an integration step by$t_{int}$ . However, this time may be beyond the output time specified by the user (using the $t_{int}$ , in contrast to getting the state at $t_{user}$ by accessing it through the $t_{int}$ , introducing a large amount of noise. This is the phenomenon dubbed "Bjarne's Trap" by @rwest, and has been a source of confusion for a long time.
ReactorNet
, the state of theThermoPhase
object will generally be set to the latest time reached by the CVODES integrator,advance(t_user)
method). If the user accesses theThermoPhase
object directly, they will get the state atreactor.thermo
property, in which CVODES will provide a result interpolated back to the correct time. For simple usage, like plotting the state as a function of time, getting the state at a slightly different time won't noticeably affect results. However, for cases like trying to evaluate sensitivities using a finite difference approach, two reactor networks will not have reached the same value ofBecause the current structure allows multiple reactors to share a
ThermoPhase
object, there is a significant amount of complexity in the logic for evaluating the governing equations for all reactors in the network.The current evaluation logic amounts to the following (in Python pseudocode):
This complexity has resulted in difficulties for users who want to implement modified governing equations using the
ExtensibleReactor
framework. For example, see this problem posted on the UG, and this one too.Possible Solutions
I think the solution to both of these problems to stop having multiple reactors share
ThermoPhase
/Kinetics
objects, and to haveReactorNet.step
andReactorNet.advance
automatically synchronize the state of the reactors after each call (by default).Thanks to the implementation of
AnyMap
-based serialization, we can still allow users to use a singleSolution
object to set up multiple reactors. When initializing theReactorNet
, we can then check to see if anySolution
object is being used multiple times, and then clone it to create a distinctSolution
for eachReactor
.thermo
properties of the reactor objects. We will still automatically synchronize these after each step -- not rely on the magic call tosyncState
when the getter is used.The automatic synchronization can be disabled by passing a
sync=False
option toadvance()
orstep()
.The "simple" alternative is to force this on the user -- we just check and tell the user that they can't use the same
Solution
object multiple times, but I think the automatic cloning is much more user friendly and shouldn't be that difficult to implement.After this change, the evaluation logic would look more like:
I'd like to make sure that any changes to how the work of evaluating the reactor governing equations is divided up would provide relatively clean solutions to a few scenarios that have come up on the Users' Group. Namely:
ExtensibleReactor
override override ofupdate_state
The text was updated successfully, but these errors were encountered: