# Expressions for boundary condition as a function of neural-network outputs

**URL:** <https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189>\
**Category:** Development\
**Created:** [17 November 2024 20:03 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189 "2024-11-17T20:03:46Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [17 November 2024 20:03 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/1 "2024-11-17T20:03:46Z")

</div>

Hi, I am trying to reproduce [a study](https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/artificial-neural-networks-trained-through-deep-reinforcement-learning-discover-control-strategies-for-active-flow-control/D5B80D809DFFD73760989A07F5E11039) of active flow control of the flow over a 2D cylinder with 2 jets positioned at ±90 degrees. I am solving the compressible NS equations for now. As a start, I set the boundary conditions for the jet as, for example,

```auto
[soln-bcs-jet_top]
type = sub-in-frv
theta = atan2(y,x) ; theta, not used for now
omega = 10*pi/180 ; jet width in radians
theta0_top = 90*pi/180 ; center of jet
R = 0.5 ; radius of cylinder

rho = rhoc
u = 0.09 / (%(omega)s*%(R)s*%(R)s) * x
v = 0.09 / (%(omega)s*%(R)s*%(R)s) * y
p = rhoc/(M*M*gamma) ; T = 1

```

where the “0.09” value is supposed to be the mass flow rate. With steady blowing, I get some result like the following:

 ![yvelocity](https://global.discourse-cdn.com/free1/uploads/pyfr/original/1X/4abedb1b0963784b6c590288fbe8059fc95fa58a.jpeg)

The next step is to change the “0.09” value to be a function that will change at different time steps, involving one or two values inferred from a neural network. For now, say instead of 0.09, we obtain mdot (mass flow rate) from a neural network as an output. Furthermore, at the same time, we also need to feed some input to the neural network in form of primitive variables at some chosen locations (essentially similar to what the sampler plugin does). Can someone point me to the files that I could begin to modify in the source code to achieve this? Any additional advice is welcome.

TL;DR : I am seeking advice in modifying the source code to make expressions used in the setting the boundary condition to be not only a function of space and time, but a function of some parameters that would be inferred from a neural network.

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [17 November 2024 20:44 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/2 "2024-11-17T20:44:37Z")

</div>

This is possible with a custom boundary condition. In particular, boundary conditions have a `prepare` method which is called on the host-side in advance of a time-step stage. This gives them an opportunity to compute any quantities they like and modify any arrays they like. These arrays can then be used by the boundary condition kernel.

Unfortunately, while the functionality is there none of our current boundary conditions make use of this feature and so I do not have any examples.

Regards, Freddie.

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [18 November 2024 02:17 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/3 "2024-11-18T02:17:37Z")

</div>

Line 254 of PyFR/pyfr/solvers/base/system.py has the following

```auto
    def _prepare_kernels(self, t, uinbank, foutbank):
        _, binders = self._get_kernels(uinbank, foutbank)

        for b in self._bc_inters:
            b.prepare(t)

        for b in binders:
            b(t=t)

```

Is this the one called before every-time step? Furthermore, would the next step regarding custom BCs would be to add some code in PyFR/pyfr/solvers/navstokes/inters.py ? For instance,

```auto
class NavierStokesSubInflowFrvNeuralBCInters(NavierStokesBaseBCInters):
    type = 'sub-in-frv-neural'
    cflux_state = 'ghost'

    def prepare(self, t):
         # do things here

    def __init__ (self, be, lhs, elemap, cfgsect, cfg):
        super(). __init__ (be, lhs, elemap, cfgsect, cfg)

        self.c |= self._exp_opts(
            ['rho', 'u', 'v', 'w'][:self.ndims + 1], lhs,
            default={'u': 0, 'v': 0, 'w': 0}
        )

```

Thank you for your time.

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [18 November 2024 05:13 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/4 "2024-11-18T05:13:20Z")

</div>

Pretty much. Although inside the constructor you’ll need to allocate some backend matrices (arrays) to hold whatever data you’re interested in passing to the kernel.

Then you’ll need to call `_set_external` to make those arrays visible to the kernel.

Next, inside the kernel definition itself you’ll need something like

```auto
<%pyfr:macro name='bc_rsolve_state' params='ul, nl, ur' externs='ploc, t, your_array'>

```

where `your_array` is a fill-in for whatever name you give the extern. Then, inside `prepare` you will need to call the `set` method of the matrix you’ve allocated to change its value on the backend.

Regards, Freddie.

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [19 November 2024 05:34 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/5 "2024-11-19T05:34:32Z")

</div>

So I put the following in PyFR/pyfr/solvers/navstokes/inters.py :

```auto
class NavierStokesSubInflowFrvNeuralBCInters(NavierStokesBaseBCInters):
    type = 'sub-in-frv-neural'
    cflux_state = 'ghost'

    def __init__ (self, be, lhs, elemap, cfgsect, cfg):
        self.backend = be
        super(). __init__ (be, lhs, elemap, cfgsect, cfg)

        self.c |= self._exp_opts(
            ['rho', 'u', 'v', 'w'][:self.ndims + 1], lhs,
            default={'u': 0, 'v': 0, 'w': 0}
        )

        self.nn_params = self.backend.matrix((1, 2),tags={'align'},dtype=self.backend.fpdtype)
        self._set_external('nn_params', 'fpdtype_t[1][2]',value=self.nn_params)

    def prepare(self, t):
        nn_params = self.nn_params.get()
        nn_params[0][0] = 0.09 # mdot
        #nn_params[0][1] = 0.00 # reserved for future purposes
        self.nn_params.set(nn_params)

```

and created PyFR/pyfr/solvers/navstokes/kernels/bcs/sub-in-frv-neural.mako with the following content :

```auto
<%namespace module='pyfr.backends.base.makoutil' name='pyfr'/>
<%include file='pyfr.solvers.navstokes.kernels.bcs.common'/>

<%pyfr:macro name='bc_rsolve_state' params='ul, nl, ur' externs='ploc, t, nn_params'>
    ur[0] = ${c['rho']};
% for i, v in enumerate('uvw'[:ndims]):
    ur[${i + 1}] = nn_params[0][0]*(${c['rho']}) * (${c[v]});
% endfor
    ur[${nvars - 1}] = ul[${nvars - 1}]
                     - 0.5*(1.0/ul[0])*${pyfr.dot('ul[{i}]', i=(1, ndims + 1))}
                     + 0.5*(1.0/ur[0])*${pyfr.dot('ur[{i}]', i=(1, ndims + 1))};
</%pyfr:macro>

<%pyfr:alias name='bc_ldg_state' func='bc_rsolve_state'/>
<%pyfr:alias name='bc_ldg_grad_state' func='bc_common_grad_zero'/>

```

Here I was hoping that by multiplying nn\_params[0][0] to the conserved variables corresponding to the momentum equation, I am effectively multiplying velocity by this variable. So, in the .ini file, I changed the definition of velocities to

```auto
u = 1.0 / (%(omega)s*%(R)s*%(R)s) * x
v = 1.0 / (%(omega)s*%(R)s*%(R)s) * y

```

as compared to

```auto
u = 0.09 / (%(omega)s*%(R)s*%(R)s) * x
v = 0.09 / (%(omega)s*%(R)s*%(R)s) * y

```

from before. However, I am not getting the expected results. I think the nn\_params[0][0] that I set is being accessed by the kernel because if I set this to a large value like 1000, the program crashes. Then, I think the value is being altered somewhere along the way or I put the variable at the wrong location in the mako file. Are you able to see any obvious errors in my code?

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [19 November 2024 05:49 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/6 "2024-11-19T05:49:22Z")

</div>

> [@boundaryconditions](#):
>
> `fpdtype_t[1][2]`

This means that at each flux point there are two distinct values. However, you want every flux point to see the same value. As such I suspect you want `broadcast fpdtype_t[1][2]`. (I think there are a couple of other topics on the forum regarding this which may be helpful references.)

Regards, Freddie.

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [19 November 2024 06:00 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/7 "2024-11-19T06:00:05Z")

</div>

Yes, `in broadcast fpdtype_t[1][2]` did the trick. For my purpose, the next step is to extract the primitive variables at certain locations to feed it to the machine learning library. Is it OK if I continue discussing things in this thread?

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [19 November 2024 06:08 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/8 "2024-11-19T06:08:56Z")

</div>

The point sampler plugin contains all of the required machinery for this. In future (unreleased) versions of PyFR this capability will be extracted out into its own class so anyone can use it, but for now you will need to hack the code a little.

Also, do not forget that `prepare` is being called for all MPI ranks at the same time, so when running in parallel you may need to broadcast/gather/scatter quantities. Note, however, that every ranks gets its own copy of the `nn_params` array.

Regards, Freddie.

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [21 November 2024 16:01 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/9 "2024-11-21T16:01:10Z")

</div>

Thank you, I will look into MPI once I get a serial version running. I managed to get something working [here](https://github.com/TRPrasanna/PyFR/commit/ff357f7f5726ed8d9d45957938276300ce254ae9). However, I realize that the point sampling needs to be done only once per timestep (or once every 5000 time steps or so). What currently happens in my code is that the sampling is done once each for each “sub-in-frv-neural” boundaries. My intention is to sample the points once for a time step, feed the samples to a neural network, and then obtain some output which will be stored in the nn\_params array. Then, each BC of type sub-in-frv-neural will share the values that have been stored in the nn\_params array, but use the shared values in a slightly different way, say one uses +nn\_params[0] and the other uses -nn\_params[0] (I think this is easily done by flipping the signs of velocities in my .ini file). Therefore, I think I need to do the sampling outside of the boundary condition code (to be more precise, I need to probe values at certain points, and also calculate aerodynamic forces and moments like in the fluidforces plugin). Do you have any suggestions how I could go about doing this?

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [22 November 2024 13:20 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/10 "2024-11-22T13:20:26Z")

</div>

Here you want a singleton class in Python which multiple boundary condition instances can make use of.

Regards, Freddie.

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [15 December 2024 03:16 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/11 "2024-12-15T03:16:06Z")

</div>

So I decided to create a [plugin](https://github.com/TRPrasanna/PyFR/blob/c58425fef08ab5ac51aec9c798509491ebd162b5/pyfr/plugins/reinforcementlearning.py) as a common ground for the boundary conditions to get values from. I got some decent result after my neural network model was trained:

![out](https://global.discourse-cdn.com/free1/uploads/pyfr/original/1X/46af065996bcf1537bf075f4dc74fcc787061f36.gif)

My latest boundary condition code looks like this:

```auto
class NavierStokesSubInflowFrvNeuralBCInters(NavierStokesBaseBCInters):
    type = 'sub-in-frv-neural'
    cflux_state = 'ghost'

    def __init__ (self, intg, be, lhs, elemap, cfgsect, cfg):
        self.backend = be
        self.intg = intg
        super(). __init__ (be, lhs, elemap, cfgsect, cfg)
        
        # Basic initialization
        self.c |= self._exp_opts(
            ['rho', 'u', 'v', 'w'][:self.ndims + 1], lhs,
            default={'u': 0, 'v': 0, 'w': 0}
        )

        # some config parameters
        self.jcenter = self.backend.matrix((1,1))
        self.polarity = self.backend.matrix((1,1))
        self._set_external('jcenter', 'broadcast fpdtype_t[1][1]', 
                         value=self.jcenter)
        self._set_external('polarity', 'broadcast fpdtype_t[1][1]', 
                         value=self.polarity)

        # Neural network parameters
        self.nn_params = self.backend.matrix((1, 2), tags={'align'})
        self._set_external('nn_params', 'broadcast fpdtype_t[1][2]', 
                         value=self.nn_params)

        # Initial value
        nn_params = self.nn_params.get()
        nn_params[0][0] = 0.0
        self.nn_params.set(nn_params)

        # Fixed values
        jcenter = self.jcenter.get()
        jcenter[0][0] = self.cfg.getfloat(cfgsect, 'jcenter')
        self.jcenter.set(jcenter)
        polarity = self.polarity.get()
        polarity[0][0] = self.cfg.getfloat(cfgsect, 'polarity')
        self.polarity.set(polarity)
        #print(f"jcenter: {jcenter[0][0]}, polarity: {polarity[0][0]}")

        # Time constant for control signal smoothing
        self.alpha = 5e-2

    def prepare(self, t):
        # Direct access to control signal from solver environment
        target = self.intg.system.env.current_control
        
        # Update parameters with smoothing
        nn_params = self.nn_params.get()
        nn_params[0][0] += self.intg._dt/self.alpha * (target - nn_params[0][0])
        self.nn_params.set(nn_params)
        #print(f"Control signal: {target}, Updated nn_params: {nn_params[0][0]}")

```

Unfortunately, the two lines in the prepare method that have the .get() and .set() commands for the externed variable “nn\_params” cause the entire program to slow down by a factor of atleast 4.4 times ! To my understanding this is because of the high number of calls (since prepare is called every time step) and the I/O overhead between the CPU and GPU (I have tested CUDA and HIP backends). I can think of having a CPU-side copy of nn\_params to avoid using the .get() command but I don’t see any way to avoid a .set() call (which causes the most overhead as per my measurements). Is there any obvious way I could overcome this overhead?

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [15 December 2024 16:27 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/12 "2024-12-15T16:27:23Z")

</div>

You can certainly avoid the `.get()` calls. Secondly, the overhead is likely large because your simulation is proportionally quite small. In a 3D simulation where time steps are a lot more expensive it may well be insignificant. Thirdly, you’ll want to see if you really do need to update the parameters every step. For an explicit simulation it is almost certainly excessive.

Regards, Freddie,

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [15 December 2024 16:37 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/13 "2024-12-15T16:37:40Z")

</div>

For now, I have sped things up by calling the .set() function less frequently:

```auto
class NavierStokesSubInflowFrvNeuralBCInters(NavierStokesBaseBCInters):
    type = 'sub-in-frv-neural'
    cflux_state = 'ghost'

    def __init__ (self, intg, be, lhs, elemap, cfgsect, cfg):
.
.
.
        self._set_external('nn_params', 'broadcast fpdtype_t[1][1]', 
                         value=self.nn_params)
.
.
.
    def prepare(self, t):
        # Direct access to control signal from solver environment
        target = self.intg.system.env.current_control

        new_param = self._current_param + self.intg._dt/self.alpha * (target - self._current_param)

        # Only update backend if change is significant
        if abs(new_param - self._current_param) > 1e-2:
            self.nn_params.set(np.array([[new_param]]))

        self._current_param = new_param

```

However I wish to change the way the parameters are in the near future. In particular, the parameter is changed by this line

```auto
new_param = self._current_param + self.intg._dt/self.alpha * (target - self._current_param)

```

and this has to be done every timestep. One significant way of speeding things up would be to extern `target` (needs to be updated less frequently) and `alpha` (just a constant, broadcast once), and that way I can only broadcast `target` once in a while. To do this, I need to do the calculation shown in the above equation within the Mako kernel. The only piece I am missing is the time-step size `dt`. Can this be easily obtained in a manner similar to how the current time `t` is available to the kernel?

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [15 December 2024 20:29 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/14 "2024-12-15T20:29:05Z")

</div>

Any use of \Delta t is almost certainly in error.

The prepare method is called for every _stage_ of an RK scheme. These stages occur at times between t and t + \Delta t.

Regards, Freddie.

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [15 December 2024 20:37 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/15 "2024-12-15T20:37:46Z")

</div>

Would storing t inside a variable within the kernel and calculating \Delta t based off of it be a good workaround?

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [15 December 2024 20:40 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/16 "2024-12-15T20:40:33Z")

</div>

No. Any use of \Delta t is fundamentally, numerically, wrong. This is why we prefix it with an \_ and do not make it available to kernels. Relaxation time scales need to be based on physics.

Regards, Freddie.

---

<div class="post-metadata">

**Author:** ![boundaryconditions](https://avatars.discourse-cdn.com/v4/letter/b/d6d6ee/32.png) [@boundaryconditions](https://pyfr.discourse.group/u/boundaryconditions)\
**Post date:** [16 December 2024 15:12 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/17 "2024-12-16T15:12:50Z")

</div>

I see. I do have another option proposed [here](https://www.mdpi.com/2076-0825/11/12/359) which doesn’t depend on \Delta t:  
\displaystyle Q = \frac{Q\_1-Q\_0}{T\_a}\left(t-t\_0\right) + Q\_0  
This is supposed to change the value Q at different t (in my Mako kernel Q corresponds to `control`), like described here:

 ![actuators-11-00359-g004](https://global.discourse-cdn.com/free1/uploads/pyfr/original/1X/bf955e36a9eff3b59d1f9421a3cffc427c8c9e83.jpeg)

I implemented this using:

```auto
class NavierStokesSubInflowFrvNeuralBCInters(NavierStokesBaseBCInters):
    type = 'sub-in-frv-neural'
    cflux_state = 'ghost'

    def __init__ (self, intg, be, lhs, elemap, cfgsect, cfg):
        self.backend = be
        self.intg = intg
        super(). __init__ (be, lhs, elemap, cfgsect, cfg)
        
        # Basic initialization
        self.c |= self._exp_opts(
            ['rho', 'u', 'v', 'w'][:self.ndims + 1], lhs,
            default={'u': 0, 'v': 0, 'w': 0}
        )

        # some config parameters
        self.t_act_interval = self.backend.matrix((1,1))
        self._set_external('t_act_interval', 'broadcast fpdtype_t[1][1]', 
                         value=self.t_act_interval)

        # Neural network + control parameters
        self.control_params = self.backend.matrix((1,3))
        self._set_external('control_params', 'broadcast fpdtype_t[1][3]', 
                         value=self.control_params)

        # Initial value
        self.control_params.set(np.array([[0.0, 0.0, 0.0]])) #(Q0,Q1,t0)

        # Cache current parameter value 
        self._current_target = 0.0

    def prepare(self, t):
        # Direct access to control signal from solver environment
        new_target = self.intg.system.env.current_control

        # Only update backend if there is a new new_target
        if new_target != self._current_target:
            self.t_act_interval.set(np.array([[self.intg.system.env.action_interval]]))
            self.control_params.set(np.array([[self._current_target, new_target, t]]))
            self._current_target = new_target

```

and the corresponding Mako kernel is:

```auto
<%namespace module='pyfr.backends.base.makoutil' name='pyfr'/>
<%include file='pyfr.solvers.navstokes.kernels.bcs.common'/>

<%pyfr:macro name='bc_rsolve_state' params='ul, nl, ur' externs='ploc, t, control_params, t_act_interval'>
    <% control = "(control_params[0][1]-control_params[0][0])/t_act_interval[0][0]*(t-control_params[0][2]) + control_params[0][0]"%>
    ur[0] = ${c['rho']};
% for i, v in enumerate('uvw'[:ndims]):
    ur[${i + 1}] = ${control}*(${c['rho']}) * (${c[v]});
% endfor
    ur[${nvars - 1}] = ul[${nvars - 1}]
                     - 0.5*(1.0/ul[0])*${pyfr.dot('ul[{i}]', i=(1, ndims + 1))}
                     + 0.5*(1.0/ur[0])*${pyfr.dot('ur[{i}]', i=(1, ndims + 1))};
</%pyfr:macro>

<%pyfr:alias name='bc_ldg_state' func='bc_rsolve_state'/>
<%pyfr:alias name='bc_ldg_grad_state' func='bc_common_grad_zero'/>

```

But does the Mako kernel for boundary condition run at all stages of Runge-Kutta schemes as well? In that case me updating the interface values might be wrong.

---

<div class="post-metadata">

**Author:** ![fdw](https://avatars.discourse-cdn.com/v4/letter/f/a587f6/32.png) [@fdw](https://pyfr.discourse.group/u/fdw)\
**Post date:** [16 December 2024 15:38 UTC](https://pyfr.discourse.group/t/expressions-for-boundary-condition-as-a-function-of-neural-network-outputs/1189/18 "2024-12-16T15:38:14Z")

</div>

Yes, the BC kernels run at every single RK stage.

Regards, Freddie.
