Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - LukeLabrie/msrDynamics: Python API to delay differential equation solver for dynamic modeling of molten salt reactors · GitHub
Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LukeLabrie/msrDynamics: Python API to delay differential equation solver for dynamic modeling of molten salt reactors · GitHub
Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LukeLabrie/msrDynamics: Python API to delay differential equation solver for dynamic modeling of molten salt reactors · GitHub
Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - LukeLabrie/msrDynamics: Python API to delay differential equation solver for dynamic modeling of molten salt reactors · GitHub
Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LukeLabrie/msrDynamics: Python API to delay differential equation solver for dynamic modeling of molten salt reactors · GitHub
Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LukeLabrie/msrDynamics: Python API to delay differential equation solver for dynamic modeling of molten salt reactors · GitHub
Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - LukeLabrie/msrDynamics: Python API to delay differential equation solver for dynamic modeling of molten salt reactors · GitHub
Skip to content

Repository files navigation

msrDynamics

Documentation Status

msrDynamics is an object-oriented API to JiTCDDE, a delay differential equation solver, written with emulation of simulink-style solvers for molten salt reactor (MSR) systems in mind (see Singh et al), but can be extended to other fission and/or thermal hydraulic systems. The goal of this package is to streamline the implemetation of such nodal models for more complex systems, where direct handling of the equations can become cumbersome.

Installation

The project can be installed with pip from your preferred python environment.

python -m pip install msrDynamics

If you plan on making changes to the source code, clone the repository, and install in developer mode.

git clone https://github.com/LukeLabrie/msrDynamics.git
cd msrDynamics
python -m pip install -e .

Methodology

The API is designed for building nodal systems of the form discussed in the examples below, whereby variables representing system properties or masses are aggregated into nodes with associated properties, and which only interact with other nodes through these properties. The system can then be described as a first-order system of differential equations, suitable for a numerical solver. The nodes therefore, are essentially representations of the state variables of the system.

For more detail on this approach and its applications, see:

Usage

Nodes are represented by the Node() object which stores properties associated with the node, and contains helper methods to define certian dynamics like convective and advective heat transfer as well as neutron kinetics. For example, fuel flow through a core in direct contact with a moderator material (e.g. graphite), could be set up as follows.

importparamerersimportnumpyasnp# instantiate system msr=System()
# define nodes with mass m, scpecific heat capacity scp, and mass flow Wf1=Node(m=m_f1, scp=scp_f, W=W_f)
f2=Node(m=m_f2, scp=scp_f, W=W_f)
g=Node(m=m_g, scp=scp_g)
# add nodes to system msr.add_nodes([f1,f2,g])
# define dynamics f1.set_dTdt_advective(source=f_in)
f1.set_dTdt_convective(source=g.y(), hA= [hA_fg])
f2.set_dTdt_advective(source=f_1.y())
f2.set_dTdt_convective(source=g.y(), hA= [hA_fg])
g.set_dTdt_convective(source= [f1.y(), f2.y()], hA= [hA_fg, hA_fg])
# solve T=np.arange(0,100,0.01)
msr.solve(T)

Note, for any system, a System() object is required for proper handling of the global indexing required for the JiTCDDE backend. Nodes need to be added to the system object before dynamics are defined. This is because certain global system information is required in order to index the variables properly for the backend.

Nodes can also represent state variables associated with neutron kinetics, like neutron concentration $n(t)$ delayed neutorn precursor concentrations $C_i(t)$, and therefore does not need to be associated with a thermal mass. The helper methods take other nodes to which a given node is coupled, along with relevant system parameters, as arguments, and sets up the symbolic expressions representing the equations govenring the dynamics of the node. These symbolic expressions are the input to the JiTCDDE backend. Alternatively, if the user wishes to circumvent the helper methods, or would like to define other dynamics, a node's dynamics can be set directly with a user-defined symbolic expression through the Node.dydt attribute, e.g.

# instantiate system f=System()
# define nodesx=Node(m=m_x)
y=Node(m=m_y)
# add nodes to systemf.add_nodes([x,y])
# define dynamics x.dydt=x.y() -y.y()
y.dydt=-x.y() +y.y() 

Helper methods currently encompass the following effects:

  • Point kinetics, including modified point kinetics for MSRs
  • Convective heat transfer
  • Advective heat transfer (mass flow)

Simple Example

The diagram below describes a simple MSR system. The notebook for the example below can be found in examples/toyModel.ipynb.

First, Node and System objects are instantiated, with relevant parameters to describe the state of the node. Note, the parameters for this example are mostly borrowed from the Aircraft Reactor Experiment (ARE), see examples/are.

fromtoyParametersimport*fromjitcddeimporttimportmsrDynamicsimportmatplotlib.pyplotasplt# MSR system MSR=System()
# core nodescf_in=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f1) # core fuel inletcf_out=Node(m=m_f_c/2, scp=scp_f, W=W_f, y0=T0_c_f2) # core fuel outletcm=Node(m=m_m_c, scp=scp_m, y0=T0_c_m) # core moderatorn=Node(y0=n_frac0) # fractional neutron densityC1=Node(y0=C0[0]) # precursor group 1C2=Node(y0=C0[1]) # precursor group 2C3=Node(y0=C0[2]) # precursor group 3C4=Node(y0=C0[3]) # precursor group 4C5=Node(y0=C0[4]) # precursor group 5C6=Node(y0=C0[5]) # precursor group 6rho=Node(y0=0.0) # reactivity# heat exchanger nodes hx_p_in=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f1) # hx primary circuit inlethx_p_out=Node(m=m_f_hx, scp=scp_f, W=W_f, y0=T0_hfh_f2) # hx primary circuit outlethx_t=Node(m=m_t_hxfh, scp=scp_t, y0=T0_hfh_t1) # hx tubeshx_s_in=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h1) # hx secondary circuit inlethx_s_out=Node(m=m_h_hxfh, scp=scp_h, W=W_h_fh, y0=T0_hfh_h2) # hx secondary circuit outlet

Nodes are added to the System object which takes care of instantiation and indexing for the JiTCDDE backend.

MSR.add_nodes([cf_in,cf_out,cm,n,C1,C2,C3,C4,C5,C6,rho,
hx_p_in,hx_p_out,hx_t,hx_s_in,hx_s_out])

Once nodes are added to the System object, dynamics can be defined. Variables can be accessed by calling their associated y function from JiTCDDE, i.e. node_name.y(). To access a variable at a previous time $t-tau$, simply provide the time as an argument, i.e. node_name.y(t-tau). simply

# core# corecf_in.set_dTdt_advective(source=hx_p_out.y(t-tau_hx_c_f)) cf_in.set_dTdt_internal(source=n.y(), k=k_f1*P)
cf_in.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cf_out.set_dTdt_advective(source=cf_in.y()) cf_out.set_dTdt_internal(source=n.y(), k=k_f2*P)
cf_out.set_dTdt_convective(source= [cm.y()], hA= [hA_ft_c/2])
cm.set_dTdt_internal(source=n.y(), k=k_m*P)
cm.set_dTdt_convective(source= [cf_in.y(), cf_out.y()], hA= [hA_mc_c/2]*2)
n.set_dndt(rho.y(), beta_t, Lam, lam, [C1.y(), C2.y(), C3.y(), C4.y(), C5.y(), C6.y()])
C1.set_dcdt(n.y(), beta[0], Lam, lam[0], tau_c, tau_l)
C2.set_dcdt(n.y(), beta[1], Lam, lam[1], tau_c, tau_l)
C3.set_dcdt(n.y(), beta[2], Lam, lam[2], tau_c, tau_l)
C4.set_dcdt(n.y(), beta[3], Lam, lam[3], tau_c, tau_l)
C5.set_dcdt(n.y(), beta[4], Lam, lam[4], tau_c, tau_l)
C6.set_dcdt(n.y(), beta[5], Lam, lam[5], tau_c, tau_l)
rho.set_drdt([cf_in.dydt(),cf_out.dydt(),cm.dydt()],[a_f/2,a_f/2,a_b])
# heat exchangerhx_p_in.set_dTdt_advective(source=cf_out.y(t-tau_c_hx_f))
hx_p_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_p_out.set_dTdt_advective(source=hx_p_in.y())
hx_p_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ft_hx])
hx_t.set_dTdt_convective(source= [hx_p_in.y(),hx_p_out.y(),hx_s_in.y(),hx_s_out.y()],
hA= [hA_ft_hx, hA_ft_hx, hA_ht_hx, hA_ht_hx])
hx_s_in.set_dTdt_advective(source=50)
hx_s_in.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])
hx_s_out.set_dTdt_advective(source=hx_s_in.y())
hx_s_out.set_dTdt_convective(source= [hx_t.y()], hA= [hA_ht_hx])

Note, nodes can represent thermal masses as well as parameters related to point-kinetics. Now the system can be solved.

sol_jit=MSR.solve(T)

Results for the above system are shown below.

Solutions can be accessed from the y_out attribute of the associated node. The snippet below is used for the plot above.

# Paxs[0].plot(T, [k*Pforkinn.y_out])
axs[0].set_xlim(t0,tf)
axs[0].set_title("Power (MW)")
axs[0].set_xlabel(r"$t$ (s)")
axs[0].set_ylabel("MW")

Other Examples

See the notebooks below for more detailed examples of usage, as well as comparison to experimental data.

Comparison of results from the Molten Salt Reactor Experiment (MSRE) generated with msrDynamics against experimental data, and similar work by Singh et al.

About

Python API to delay differential equation solver for dynamic modeling of molten salt reactors

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages