Dynamic Bayesian Network Action Set

Fault Detection in a System of Two Tanks

This section contains PROC CAS code.

Note: Input data must be accessible in your CAS session, either as a CAS table or as a transient-scope table. A CAS table has a two-level name: the first level is your CAS engine libref, and the second level is the table name. You refer to this table in the CAS procedure by specifying only the second level. For more information about two-level names, see Chapter 2, Shared Concepts (SAS Viya: Machine Learning Procedures). A transient-scope table is called directly from the action and exists in memory for the duration of the action. For more information about accessing data, see SAS Viya: System Programming Guide. For more information about PROC CAS and programming in CASL, see SAS Cloud Analytic Services: CASL Programmer’s Guide and SAS Cloud Analytic Services: CASL Reference.

This example of two tanks (see Figure 8) and associated flows is developed using a similar scenario of five tanks from Lerner et al. (2000) to illustrate the filtering algorithm. The numerical values are generated using a simulation model for this two-tank scenario. The following notation is used to build the model:

  • upper A 1 = cross-sectional area of tank 1

  • upper A 2 = cross-sectional area of tank 2

  • h 1 = height of liquid in tank 1

  • h 2 = height of liquid in tank 2

  • f 1 Subscript normal i normal n = flow rate of liquid coming into tank 1

  • f 1 Subscript normal o normal u normal t = flow rate of liquid going out of tank 1

  • upper C 1 Subscript normal o normal u normal t = conductance (reciprocal of resistance) of pipe going out of tank 1

  • upper C Baseline 12 = conductance of pipe going from tank 1 to tank 2

  • f Baseline 12 = flow rate of liquid going from tank 1 to tank 2

  • upper C Subscript normal l normal e normal a normal k = conductance of leak in pipe going from tank 1 to tank 2

  • f Subscript normal l normal e normal a normal k = flow rate of leak in pipe going from tank 1 to tank 2

  • upper C 2 Subscript normal o normal u normal t = conductance of pipe going out of tank 2

  • f 2 Subscript normal o normal u normal t = flow rate of liquid going out of tank 2

Adding the subscript t to a notation denotes its value at time period t.

Figure 8: Two-Tank Model

Two-Tank Model


Flows are computed using these relationships:

f 1 Subscript normal o normal u normal t Baseline equals upper C 1 Subscript normal o normal u normal t Baseline times h 1
f Baseline 12 equals upper C Baseline 12 times left parenthesis h 1 minus h 2 right parenthesis
f Subscript normal l normal e normal a normal k Baseline equals upper C Subscript normal l normal e normal a normal k Baseline times h 1
f 2 Subscript normal o normal u normal t Baseline equals upper C 2 Subscript normal o normal u normal t Baseline times h 2

Material balance in tank 1 is represented by this relationship:

upper A 1 times StartFraction d h 1 Over d t EndFraction equals f 1 Subscript normal i normal n Baseline minus f Baseline 12 minus f Subscript normal l normal e normal a normal k Baseline minus f 1 Subscript normal o normal u normal t

Similarly, material balance in tank 2 is represented by the following relationship:

upper A 2 times StartFraction d h 2 Over d t EndFraction equals f Baseline 12 minus f 2 Subscript normal o normal u normal t

Rearranging terms in these equations gives the following derivatives for ODEs:

StartFraction d h 1 Over d t EndFraction equals StartFraction 1 Over upper A 1 EndFraction left bracket f 1 Subscript normal i normal n Baseline minus left parenthesis upper C Baseline 12 plus upper C Subscript normal l normal e normal a normal k Baseline plus upper C 1 Subscript normal o normal u normal t Baseline right parenthesis h 1 plus left parenthesis upper C Baseline 12 right parenthesis h 2 right bracket
StartFraction d h 2 Over d t EndFraction equals StartFraction 1 Over upper A 2 EndFraction left bracket left parenthesis upper C Baseline 12 right parenthesis h 1 minus left parenthesis upper C Baseline 12 plus upper C 2 Subscript normal o normal u normal t Baseline right parenthesis h 2 right bracket

A block that develops over time in the pipe connecting tank 1 to tank 2 is specified as

StartFraction d upper C Baseline 12 Over d t EndFraction equals minus b l o c k bar r a t e

where b l o c k bar r a t e is the rate at which the block is developing. A leak that develops over time in the pipe connecting tank 1 to tank 2 is specified as

StartFraction d upper C Subscript normal l normal e normal a normal k Baseline Over d t EndFraction equals l e a k bar r a t e

where l e a k bar r a t e is the rate at which the leak is developing. The following branches are used to consider behavior under different scenarios for the pipe connecting tank 1 and tank 2:

  • L1Working: no block or leak in the pipe

  • L2Blocked: a sudden block appearing in the pipe that reduces the conductance to half of normal conductance

  • L3BwDrift: a gradual block appearing in the pipe

  • L4LwDrift: a gradual leak appearing in the pipe

  • L5B_LwD: a sudden block with a gradual leak appearing in the pipe

  • L6BwD_LwD: a gradual block with a gradual leak appearing in the pipe

The two-time-period Bayesian network for the two-tank model given in Figure 8 is shown in Figure 9, where f 1 Subscript normal o normal u normal t normal upper R normal d Sub Subscript t and f 2 Subscript normal o normal u normal t normal upper R normal d Sub Subscript t are observed variables at time t. The models for f 1 Subscript normal o normal u normal t normal upper R normal d Sub Subscript t and f 2 Subscript normal o normal u normal t normal upper R normal d Sub Subscript t are as follows:

f 1 Subscript normal o normal u normal t normal upper R normal d Sub Subscript t Subscript Baseline equals upper C 1 Subscript normal o normal u normal t Baseline times h Subscript 1 Sub Subscript t Subscript Baseline plus epsilon 1 comma where epsilon 1 tilde script upper N left parenthesis 0 comma sigma 1 squared right parenthesis and sigma 1 squared is the variance in the sensor to observe f 1 Subscript normal o normal u normal t Baseline
f 2 Subscript normal o normal u normal t normal upper R normal d Sub Subscript t Subscript Baseline equals upper C 2 Subscript normal o normal u normal t Baseline times h Subscript 2 Sub Subscript t Subscript Baseline plus epsilon 2 comma where epsilon 2 tilde script upper N left parenthesis 0 comma sigma 2 squared right parenthesis and sigma 2 squared is the variance in the sensor to observe f 2 Subscript normal o normal u normal t Baseline

The clique tree for the Bayesian network in Figure 9 is shown in Figure 10.

Figure 9: Two-Period Tank Model

Two-Period Tank Model


Figure 10: Two-Tank Clique Tree

Two-Tank Clique Tree


The dynBnet action requires multiple input data tables. These statements assume that your CAS engine libref is named casuser, but you can substitute any appropriately defined CAS engine libref.

The variables in the two-time-period Bayesian network and their roles are defined in the variableRoles data table as follows:

data casuser.variableRoles;
   length varname $20;
   input  varname $        varrole $;
datalines;
Time                TIMEID
H1                  HIDDEN
H2                  HIDDEN
C12                 HIDDEN
Cleak               HIDDEN
Fault               HIDDEN
F1out_rd            OBSERVED
F2out_rd            OBSERVED
;

Levels of nominal variables are defined in the variableLevels data table as follows:

data casuser.variableLevels;
   length varname $20;
   length varlevel $20;
   input  varname $   varlevel $;
datalines;
Fault           L1Working
Fault           L2Blocked
Fault           L3BwDrift
Fault           L4LwDrift
Fault           L5B_LwD
Fault           L6BwD_LwD
;

The parent-child relationships in the two-time-period Bayesian network are defined using the links data table as follows, where stage defines the parent to be in the current time period (1) or previous time period (2):

data casuser.links;
   length parent $20 child $20;
   input  parent $ child $ stage;
datalines;
Time            C12         1
C12             C12         2
Fault           C12         1
Time            Cleak       1
Cleak           Cleak       2
Fault           Cleak       1
Time            H1          1
C12             H1          2
Cleak           H1          2
H1              H1          2
H2              H1          2
Time            H2          1
C12             H2          2
H1              H2          2
H2              H2          2
H1              F1out_rd    1
H2              F2out_rd    1
;

The systems of ordinary differential equations in the two-time-period Bayesian network are defined using the variableOdes data table as follows:

data casuser.variableOdes;
	length varname $20;
	input  varname $  system;
datalines;
C12         1
Cleak       1
H1          1
H2          1
;

The initial beliefs that come from the previous time period are defined in the initialBeliefs data table as follows. For nominal variables, incoming probabilities are defined for the levels. For interval variables, the incoming mean and standard deviation are optionally defined by levels of nominal variables.

data casuser.initialBeliefs;
   length intervalvar $20   nominalvar $20    varlevel $20;
   input  Time    intervalvar $    mean    sdev        nominalvar $   varlevel $;
datalines;
1       H1         20               0.001           .               .
1       H2          2               0.001           .               .
1       C12         1               0.0001          .               .
1       C12         0.5             0.0001          Fault           L2Blocked
1       C12         0.5             0.0001          Fault           L5B_LwD
1       Cleak       0               0.0001          .               .
1       .           0.95             .              Fault           L1Working
1       .           0.01             .              Fault           L2Blocked
1       .           0.01             .              Fault           L3BwDrift
1       .           0.01             .              Fault           L4LwDrift
1       .           0.01             .              Fault           L5B_LwD
1       .           0.01             .              Fault           L6BwD_LwD
;

The observations in the tank_data data table, shown in the following code, are for the two observed variables, and they cover the time periods from 1 to 10. Time period 1 corresponds to the maximum time period in the initialBeliefs data table. Hence, the future observations are from time periods 2 to 10. The action generates outgoing beliefs for time periods 2 to 10, given the input beliefs from time period 1 and the observations from time periods 2 to 10. If a model of any child variable uses a parent that is an observed variable from the previous time period, then the observed values from time period 1 are also used by the action.

data casuser.tank_data;
   input Time f1out_rd f2out_rd;
datalines;
1       10.000        2.000
2        9.955        2.069
3        9.910        2.137
4        9.866        2.203
5        9.822        2.268
6        9.779        2.332
7        9.736        2.394
8        9.692        2.455
9        9.650        2.514
10       9.607        2.572
;

You need to load the fcmpact action set and invoke the addRoutines action as follows to define the models in the two-time-period Bayesian network:

proc cas;
 session sascas1;
 loadactionset "fcmpact";

 setSessOpt{cmplib="casuser.nonlinears"}; run;

 fcmpact.addRoutines /
   saveTable = true,
   funcTable = {name="nonlinears", caslib="casuser", replace=true},
   package = "tank",
   routineCode =
   "
     /****************************************************/
     /* subroutine to get derivative of variable C12     */
     /* over different branches of nominal variable      */
     /* sdev is the noise in the ODE model of C12        */
     /****************************************************/
     subroutine d_C12(Time, C12, Fault $, d_C12, sdev);
         outargs d_C12, sdev;
         if Fault = 'L1Working' or Fault = 'L2Blocked' or Fault = 'L4LwDrift'
           or Fault = 'L5B_LwD' then do;
             d_C12 = 0;
             sdev = 0.0001;
         end;
         else do;
             d_C12 = -1.0/120;
             sdev = 0.0001;
         end;
     endsub;

     /****************************************************/
     /* subroutine to get derivative of variable Cleak   */
     /* over different branches of nominal variable      */
     /* sdev is the noise in the ODE model of Cleak      */
     /****************************************************/
     subroutine d_Cleak(Time, Cleak, Fault $, d_Cleak, sdev);
         outargs d_Cleak, sdev;
         if Fault = 'L4LwDrift' or Fault = 'L5B_LwD' or Fault = 'L6BwD_LwD' then do;
             d_Cleak = 0.5 / 60;
             sdev = 0.0001;
         end;
         else do;
             d_Cleak = 0;
             sdev = 0.0001;
         end;
     endsub;

     /**********************************************************/
     /* subroutine to get derivative of variable H1            */
     /* as a function of variables TIME, C12, Cleak, H1 and H2 */
     /* sdev is the noise in the ODE model of H1               */
     /**********************************************************/
     subroutine d_H1(TIME, C12, Cleak, H1, H2, d_H1, sdev);
         outargs d_H1, sdev;
         A1 = 200;
         f1in = 1;
         C1out = 0.5;
         d_H1 = (1/A1)*f1in - (C12+Cleak+C1out)/A1 * H1
                 + (C12)/A1 * H2;
         sdev = 0.001;
     endsub;

     /**********************************************************/
     /* subroutine to get derivative of variable H2            */
     /* as a function of variables TIME, C12, H1 and H2        */
     /* sdev is the noise in the ODE model of H2               */
     /**********************************************************/
     subroutine d_H2(TIME, C12, H1, H2, d_H2, sdev);
         outargs d_H2, sdev;
         A2 = 100;
         C2out = 1;
         d_H2 = C12/A2 * H1
                 - ((C12+C2out)/A2) * H2;
         sdev = 0.001;
     endsub;

     /**********************************************************/
     /* subroutine to get value of variable F1out_rd           */
     /* as a function of variable H1                           */
     /* sdev is the noise in this function                     */
     /**********************************************************/
     subroutine F1out_rd(H1, mean, sdev);
         outargs mean, sdev;
         C1out = 0.5;
         mean = H1 * C1out;
         sdev = 0.0001;
     endsub;

     /**********************************************************/
     /* subroutine to get value of variable F2out_rd           */
     /* as a function of variable H2                           */
     /* sdev is the noise in this function                     */
     /**********************************************************/
     subroutine F2out_rd(H2, mean, sdev);
         outargs mean, sdev;
         C2out = 1;
         mean = H2 * C2out;
         sdev = 0.0001;
     endsub;
   ";
quit;

Next, you need to load the dynamicBayesianNetwork action set and invoke the dynBnet action as follows:

proc cas;
   loadactionset "dynamicBayesianNetwork";
   /* invoke dynBnet action, where action returns status code in result = res */
   /* status code indicates the success or the cause of failure if any       */
   action dynBnet result = res /
       /* values of observed variables  */
       table = {data = {name = "tank_data" caslib = "casuser"}}

       /* variables in Bayesian network */
       varroles = {data = {name = "variableRoles" caslib = "casuser"}}

       /* levels of nominal variables   */
       varlevels = {data = {name = "variableLevels" caslib = "casuser"}}

       /* parent-child relationships  */
       links = {data = {name = "links" caslib = "casuser"}}

       /* ODEs */
       varodes = {data = {name = "variableOdes" caslib = "casuser"}}

       /* incoming beliefs              */
       initbeliefs = {data = {name = "initialBeliefs" caslib = "casuser"}}

       /* output tables                 */
       output = {data = {name = "tank_out" caslib = "casuser" replace = 1}}

       outdetails = {data = {name = "tank_outd" caslib = "casuser" replace = 1}}

       outputState = {data = {name = "tank_state" caslib = "casuser" replace=1}}

       /* method to get linear approx   */
       linApprox = "EXTENDED"

       /* method to approx ODE solution */
       odeApprox = "NONE"
   ;
   print res;
run;

Output 12.1.1 shows the results.

Output 12.1.1: dynBnet Action Results

res: Results from dynamicBayesianNetwork.dynBnet

RowIdDescriptioncValueValue
RUNSTATUSstatus of filter runsuccess0
NTHREADSnumber of threads used 1


You need to invoke the ASTORE procedure as follows to save the state in a file on disk so that it can be accessed as input in future invocations of the dynBnet action:

 proc astore;
     download rstore=casuser.tank_state store='\Documents\tank_state';
 run;

The tank_data data table contains values of observed variables every minute from t equals 2 to t equals 10 minutes. The data are obtained by simulating the fault for the branch L2Blocked, where the fault appears at t equals 2 minutes and remains. Models are linearized using the extended approximation method by specifying linApprox="EXTENDED", and ODEs are not approximated by specifying odeApprox="NONE".

The incoming beliefs specify the L1Working (branch 1) to have a probability of 0.95 and all other branches to have a probability of 0.01.

The following code displays the probabilities of branches over time:

 data tank_out;
 set casuser.tank_out;
 by Time;
 Branch = "L1Working";
 prob = Fault_L1Working;
 output;
 Branch = "L2Blocked";
 prob = Fault_L2Blocked;
 output;
 Branch = "L3BwDrift";
 prob = Fault_L3BwDrift;
 output;
 Branch = "L4LwDrift";
 prob = Fault_L4LwDrift;
 output;
 Branch = "L5B_LwD";
 prob = Fault_L5B_LwD;
 output;
 Branch = "L6BwD_LwD";
 prob = Fault_L6BwD_LwD;
 output;
 run;
ods graphics on / attrpriority=none;
proc sgplot data = tank_out;
   series x = Time y = prob / group = Branch lineattrs=(thickness=4);
   xaxis label = "Time (mins)";
   yaxis label = "Probability";
run;

Output 12.1.2 shows the results.

Output 12.1.2: Probabilities of Fault Branches

Probabilities of Fault Branches


At t equals 3 minutes, the probability of L2Blocked (branch 2) is 0.40 and the probability of L5B_Lwd (branch 5) is 0.60. A division of probabilities between L2Blocked and L5B_Lwd is explained by the fact that L5B_Lwd also has a block in the pipe that connects tank 1 and tank 2. At t equals 4 minutes, the probability of L2Blocked is 0.97 and the probability of L5B_Lwd is 0.03. The higher probability of L2Blocked is explained by the fact that the observations of the variables f 1 Subscript normal o normal u normal t and f 2 Subscript normal o normal u normal t at time t equals 4 minutes are closer to the L2Blocked branch than to the L5B_Lwd branch.

Fault Detection in a System of Two Tanks

This section contains Lua code for the analysis in the CASL version of this example, which contains details about the results.

Note: In order to run this code, the data that are described in the CASL version need to be accessible to the CAS server. One way to do this is to convert the inputtable data to the comma-separated-value (CSV) file inputtable.csv and then use the following code to load the CSV file into CAS:

s:loadtable{casLib="casuser", path="inputtable.csv"}

For more information about coding in Lua, see Getting Started with SAS Viya for Lua and SAS Viya: System Programming Guide.

The dynBnet action requires multiple input data tables. You need to load each data table by replacing inputtable.csv with the appropriate CSV file, such as variableRoles.csv. These statements assume that your CAS engine libref is named casuser, but you can substitute any appropriately defined CAS engine libref.

The models for child variables that relate values of child variables to the values of parent variables are specified using the fcmpact action set.

-- Load fcmpact action set

s:loadactionset{actionset='fcmpact'}
s:sessionProp_setSessOpt{cmplib="casuser.nonlinears"}

-- Specify models for child variables

s:fcmpact_addRoutines{   saveTable = true,
subroutine d_C12(Time, C12, Fault $, d_C12, sdev);        \
  outargs d_C12, sdev;              \
  if Fault = 'L1Working' or Fault = 'L2Blocked' or Fault = 'L4LwDrift'  \
    or Fault = 'L5B_LwD' then do;         \
    d_C12 = 0;                \
    sdev = 0.0001;              \
  end;                  \
  else do;                \
    d_C12 = -1.0/120;             \
    sdev = 0.0001;              \
  end;                  \
endsub;                 \
subroutine d_Cleak(Time, Cleak, Fault $, d_Cleak, sdev);        \
  outargs d_Cleak, sdev;                \
  if Fault = 'L4LwDrift' or Fault = 'L5B_LwD' or Fault = 'L6BwD_LwD' then do; \
    d_Cleak = 0.5 / 60;         \
    sdev = 0.0001;            \
  end;                \
  else do;              \
    d_Cleak = 0;            \
    sdev = 0.0001;            \
  end;                \
endsub;               \
subroutine d_H1(TIME, C12, Cleak, H1, H2, d_H1, sdev);    \
  outargs d_H1, sdev;           \
  A1 = 200;             \
  f1in = 1;             \
  C1out = 0.5;              \
  d_H1 = (1/A1)*f1in - (C12+Cleak+C1out)/A1 * H1      \
    + (C12)/A1 * H2;          \
  sdev = 0.001;             \
endsub;               \
subroutine d_H2(TIME, C12, H1, H2, d_H2, sdev);     \
  outargs d_H2, sdev;           \
  A2 = 100;             \
  C2out = 1;              \
  d_H2 = C12/A2 * H1            \
    - ((C12+C2out)/A2) * H2;        \
  sdev = 0.001;             \
endsub;               \
subroutine F1out_rd(H1, mean, sdev);        \
  outargs mean, sdev;           \
  C1out = 0.5;              \
  mean = H1 * C1out;            \
  sdev = 0.0001;              \
endsub;               \
subroutine F2out_rd(H2, mean, sdev);        \
  outargs mean, sdev;           \
  C2out = 1;              \
  mean = H2 * C2out;            \
  sdev = 0.0001;              \
endsub;"
}

The dynBnet action is invoked as follows to generate new beliefs by filtering the incoming observations;

-- Load dynamicBayesianNetwork actionset

s:loadactionset{actionset='dynamicBayesianNetwork'}

-- Call dynBnet action to generate outgoing beliefs in CAS table output

myres = s:dynamicBayesianNetwork_dynBnet{table = 'tank_data', \
                varRoles = 'variableRoles', \
                varLevels = 'variableLevels', \
                links = 'links' \
                varodes = 'variableOdes', \
                initBeliefs = 'initialBeliefs', \
                odeApprox = 'NONE', \
                output = 'output' }

Fault Detection in a System of Two Tanks

This section contains Python code for the analysis in the CASL version of this example, which contains details about the results.

Note: In order to run this code, the data that are described in the CASL version need to be accessible to the CAS server. One way to do this is to convert the inputtable data to the comma-separated-value (CSV) file inputtable.csv and then use the following code to load the CSV file into CAS:

s.upload_file('inputtable.csv')

For more information about coding in Python, see Getting Started with SAS Viya for Python and SAS Viya: System Programming Guide.

The dynBnet action requires multiple input data tables. You need to load each data table by replacing inputtable.csv with the appropriate CSV file, such as variableRoles.csv. These statements assume that your CAS engine libref is named casuser, but you can substitute any appropriately defined CAS engine libref.

The models for child variables that relate values of child variables to the values of parent variables are specified using the fcmpact action set.

 s.fcmpact.addRoutines(saveTable="true", \
    funcTable = {"name":"nonlinears", "caslib":"casuser", "replace":"true"}, \
    package="tank", \
    routineCode = {"\
    subroutine d_C12(Time, C12, Fault $, d_C12, sdev); \
       outargs d_C12, sdev; \
          if Fault = 'L1Working' or Fault = 'L2Blocked' or Fault = 'L4LwDrift' \
             or Fault = 'L5B_LwD' then do; \
             d_C12 = 0; \
             sdev = 0.0001; \
          end; \
          else do; \
             d_C12 = -1.0/120; \
             sdev = 0.0001; \
          end; \
    endsub; \
    subroutine d_Cleak(Time, Cleak, Fault $, d_Cleak, sdev); \
       outargs d_Cleak, sdev; \
          if Fault = 'L4LwDrift' or Fault = 'L5B_LwD' or Fault = 'L6BwD_LwD' then do; \
             d_Cleak = 0.5 / 60; \
             sdev = 0.0001; \
          end; \
          else do; \
             d_Cleak = 0; \
             sdev = 0.0001; \
          end; \
    endsub; \
    subroutine d_H1(TIME, C12, Cleak, H1, H2, d_H1, sdev); \
       outargs d_H1, sdev; \
          A1 = 200; \
          f1in = 1; \
          C1out = 0.5; \
          d_H1 = (1/A1)*f1in - (C12+Cleak+C1out)/A1 * H1 \
               + (C12)/A1 * H2; \
          sdev = 0.001; \
    endsub; \
    subroutine d_H2(TIME, C12, H1, H2, d_H2, sdev); \
       outargs d_H2, sdev; \
          A2 = 100; \
          C2out = 1; \
          d_H2 = C12/A2 * H1 \
               - ((C12+C2out)/A2) * H2; \
          sdev = 0.001; \
    endsub; \
    subroutine F1out_rd(H1, mean, sdev); \
       outargs mean, sdev; \
          C1out = 0.5; \
          mean = H1 * C1out; \
          sdev = 0.0001; \
    endsub; \
    subroutine F2out_rd(H2, mean, sdev); \
       outargs mean, sdev; \
          C2out = 1; \
          mean = H2 * C2out; \
          sdev = 0.0001; \
    endsub;"})

The dynBnet action is invoked as follows to generate new beliefs by filtering the incoming observations;

  myres = s.dynamicBayesianNetwork.dynBnet(table = 'tank_data', \
     varRoles = 'variableRoles', \
     varLevels = 'variableLevels', \
     links = 'links', \
     varodes = 'variableOdes', \
     initBeliefs = 'initialBeliefs', \
     odeApprox = 'NONE', \
     output = 'output')

Fault Detection in a System of Two Tanks

This section contains R code for the analysis in the CASL version of this example, which contains details about the results.

Note: In order to run this code, the data that are described in the CASL version need to be accessible to the CAS server. One way to do this is to convert the inputtable data to the comma-separated-value (CSV) file inputtable.csv and then use the following code to load the CSV file into CAS:

m <- cas.read.csv(s, "inputtable.csv", casOut=list(name="inputtable"))

For more information about coding in R, see Getting Started with SAS Viya for R and SAS Viya: System Programming Guide.

The dynBnet action requires multiple input data tables. You need to load each data table by replacing inputtable.csv with the appropriate CSV file, such as variableRoles.csv. These statements assume that your CAS engine libref is named casuser, but you can substitute any appropriately defined CAS engine libref.

The models for child variables that relate values of child variables to the values of parent variables are specified using the fcmpact action set.

  results <- cas.fcmpact.addRoutines(s,
     saveTable=TRUE,
     funcTable = c(name = "nonlinears",
     caslib = "casuser", replace = TRUE),
     package = "tank",
     routineCode = c("\
        subroutine d_C12(Time, C12, Fault $, d_C12, sdev); \
           outargs d_C12, sdev; \
           if Fault = 'L1Working' or Fault = 'L2Blocked' or Fault = 'L4LwDrift' \
           or Fault = 'L5B_LwD' then do; \
             d_C12 = 0; \
             sdev = 0.0001; \
           end; \
           else do; \
              d_C12 = -1.0/120; \
              sdev = 0.0001; \
           end; \
        endsub; \
        subroutine d_Cleak(Time, Cleak, Fault $, d_Cleak, sdev); \
           outargs d_Cleak, sdev; \
           if Fault = 'L4LwDrift' or Fault = 'L5B_LwD' or Fault = 'L6BwD_LwD' then do; \
              d_Cleak = 0.5 / 60; \
              sdev = 0.0001; \
           end; \
           else do; \
              d_Cleak = 0; \
              sdev = 0.0001; \
           end; \
        endsub; \
        subroutine d_H1(TIME, C12, Cleak, H1, H2, d_H1, sdev); \
           outargs d_H1, sdev; \
              A1 = 200; \
              f1in = 1; \
              C1out = 0.5; \
              d_H1 = (1/A1)*f1in - (C12+Cleak+C1out)/A1 * H1 \
                   + (C12)/A1 * H2; \
              sdev = 0.001; \
        endsub; \
        subroutine d_H2(TIME, C12, H1, H2, d_H2, sdev); \
           outargs d_H2, sdev; \
              A2 = 100; \
              C2out = 1; \
              d_H2 = C12/A2 * H1 \
                   - ((C12+C2out)/A2) * H2; \
              sdev = 0.001; \
        endsub; \
        subroutine F1out_rd(H1, mean, sdev); \
           outargs mean, sdev; \
              C1out = 0.5; \
              mean = H1 * C1out; \
              sdev = 0.0001; \
        endsub; \
        subroutine F2out_rd(H2, mean, sdev); \
           outargs mean, sdev; \
              C2out = 1; \
              mean = H2 * C2out; \
              sdev = 0.0001; \
        endsub;"))

The dynBnet action is invoked as follows to generate new beliefs by filtering the incoming observations;

  myres = cas.dynamicBayesianNetwork.dynbnet(s, table = 'tank_data',
     varRoles = 'variableRoles',
     varLevels = 'variableLevels',
     links = 'links',
     varodes = 'variableOdes',
     initBeliefs = 'initialBeliefs',
     odeApprox = 'NONE',
     output = 'output')
Last updated: August 04, 2026