ABSTRACT (original article): Despite being the dominant risk analysis paradigm, event guessing is useless for systems design. In management, no event guessing has ever preempted the launch of policies that are decided, not designed. In engineering, events are not guessed; rather, they are created for testing purposes. Events provide inputs to which systems respond according to their structure, as described by state-space or equivalent System Dynamics models. A new risk analysis framework draws design support information from model attributes. Risk-informed dynamic models help design physical architectures or organizational policies that capably respond to arbitrary events. The approach builds on the notion that all inputs carry energy. Physical or policy systems change states by trading energy with the surroundings, through expected transactions and unexpected disturbances. A non-probabilistic risk framework supports the design by showing that the system exhibits intended functionality when responding to arbitrary inputs. Instead of guessing countless hypothetical events, the framework systematically and comprehensively analyzes weaknesses in the system model using a programmed algorithm. It applies to any state-space dynamic model by defining risk as a function of the energy needed to move the system from an acceptable to a faulty state. Robust systems dissipate excess energy, whereas vulnerable systems lose functionality. Fuses and cushions are generic classes of protections. Placing them into system models at identified weak points helps improve design. Two published simple models of business policies illustrate the framework, which is extended to define measures for consequence and uncertainty as functions of acceptable and faulty states. CITATION (original article): Mendes, JP. Model-based risk analysis for system design. Systems Engineering. 2023; 1-16. https://doi.org/10.1002/sys.21704

Introduction

Background

This code was written only to support the analysis in “Model-Based Risk Analysis for System Design” (Systems Engineering). The code contains two sections. The first section converts a Vensim model saved in text view to Wolfram Language (WL). The second section executes a risk analysis algorithm. All subsections are titled and commented.

Origin

The first section draws from a Rogelio Oliva resource page featuring a PERL code translator, <http://iops.tamu.edu/faculty/roliva/research/sd/>, originally referred by «Saleh M, Oliva R, Kampmann CE, Davidsen PI. 2010. A comprehensive analytical approach for policy analysis of system dynamics models. European Journal of Operational Research 203(3): 673–683.»
Miguel Aleluia (CENTEC/Archon Strategy Engineering) revised that code in 2014 to remove some limitations. This WL procedure removes a few more.

Input

A pop-up window asks the user for the .txt Vensim file.

Output

Writes the analysis results into “MBSRA.csv”, in the same directory of the .txt Vensim file.

Working variables

The following lists of variables captures the structure of the Vensim model:
sim - simulation environment
par - parameters and constants
var - auxiliaries
lev - levels (stocks)
levInit - initial level values.

Limitations


History


Vensim to Wolfram Language Procedure

Scaffolding

Defining these separately makes the code easier to read and debug. They could as well be embedded where used.
Block[{},​​cmd=l___~~f__~~"("~~Shortest[b:(___~~("("~~___~~("("~~__~~")"~~___)...~~")"~~___)...)]​​~~")"~~r___];(*Patterntotranslatefunctionexpressions.*)​​eof="\\\\\\---/// Sketch information - do not modify anything except names";(*SignalstheendofusefulVensimtext.*)​​funcs={"Min","Max","If","Abs","ArcCos","ArcSin","ArcTan","Cos","Cosh","Exp","Floor","Log","Mod","Power",​​"Quotient","Sin","Sinh","Sqrt","Tan","Tanh"};​​ops={"*","/","+","-","=",",","(","<",">"};(*PossibleVensimlineterminationsinamulti-lineexpression.*)

Input the model

In[]:=
ClearAll["Global`*"];​​riskAnalysis::stop="The system fails without disturbances.";​​mdl=SystemDialogInput["FileOpen",".txt",WindowTitle"Please select the model file in text format"];​​SetDirectory[DirectoryName[mdl]];

Read and prune

The SD model is now read into a string, called wrk, which is stripped out of all comments, units, and other text used by Vensim and not required by the risk simulation engine . Further parsing is done on this string.
In[]:=
Block[{},​​wrk=StringTrim[ReadList[mdl,String]];​​For[k=Length[wrk],k>0&&MemberQ[wrk,eof],k--,​​wrk=Most[wrk]];wrk=wrk/.x_String/;StringMatchQ[x,{("\\*")..,".Control","Simulation Control Parameters"}]Nothing;wrk=StringReplace[wrk,(StartOfString~~Whitespace)|(Whitespace~~EndOfString)""];wrk=StringReplace[wrk,"\\"""];​​For[i=Length[wrk],i>1,i--,​​If[StringContainsQ[wrk[[i]],"= INTEG ("],​​wrk[[i]]=StringReplace[wrk[[i]],"= INTEG (""="];wrk[[i+1]]="INTEG ("<>wrk[[i+1]]]];​​For[k=1,StringFreeQ[wrk[[k]],{x__~~"="}],k++];​​wrk=Drop[wrk,k-1]];

Glue multi-line commands

Some expressions may take several lines of text. Vensim marks continuation lines together with comments and information about units. The intention is to create a single string for each command, no matter how long.
In[]:=
Block[{},​​For[k=Length[wrk],k>1,k--,​​If[SameQ["|",Last[Characters[wrk[[k]]]]],​​If[!SameQ["~",First[Characters[wrk[[k]]]]],​​wrk[[k-1]]=wrk[[k-1]]<>wrk[[k]];​​wrk[[k]]=Nothing]]];wrk=wrk/.x_String/;StringMatchQ[x,{"~"~~___~~"|"}]Nothing;For[k=Length[wrk],k>=1,k--,If[MemberQ[ops,Last[Characters[wrk[[k-1]]]]]||MemberQ[ops,First[Characters[wrk[[k]]]]]||SameQ[")",First[Characters[wrk[[k]]]]],​​wrk[[k-1]]=wrk[[k-1]]<>wrk[[k]];​​wrk[[k]]=Nothing]];​​wrk=wrk/.x_String/;StringMatchQ[x,___~~"~"~~___]Nothing;​​For[k=1,k≤Length[wrk],k++,​​wrk=StringReplace[wrk,l__~~Shortest["("~~__~~")\""~~r___]l~~r]];wrk=StringReplace[wrk,{Whitespace~~"\"","\""~~Whitespace}"\""];wrk=StringReplace[wrk,{Whitespace~~"=","="~~Whitespace}"="]];

Replace special functions and keywords


Rewrite variable names to camelCase


Parse Vensim statements

This Block separates stocks, auxiliaries, and all components of a simulation model.
(Obs.: I' d love to have indented each option in the Which[] statement. Maybe in a later version of Mathematica...)
In[]:=
Block[{temp},​​lev=levInit=var=par=sim={};​​For[i=1,i≤lw,i++,​​wrk[[i]]=StringReplace[wrk[[i]],{Whitespace""}];​​Which[​​StringContainsQ[wrk[[i]],{"initialTime=","finalTime=","dT=","savePer="}],AppendTo[sim,StringReplace[wrk[[i]],v:("initialTime="|"finalTime="|"dT="|"savePer=")~~x:NumberStringv~~x]],​​StringContainsQ[wrk[[i]],__~~"#"~~__],​​PrependTo[par,wrk[[i]]];​​temp=StringSplit[wrk[[i]],"="];​​PrependTo[funcs,temp[[1]]],​​StringContainsQ[wrk[[i]],__~~"="~~__?DigitQ~~"E"~~___?DigitQ],AppendTo[par,StringReplace[wrk[[i]],v:WordCharacter..~~"="~~x:NumberString~~"E"~~y:NumberStringv~~"="~~x~~"E^"~~y]],​​StringMatchQ[wrk[[i]],__~~"="~~NumberString],​​AppendTo[par,StringReplace[wrk[[i]],v:WordCharacter..~~"="~~x:NumberStringv~~"="~~x]],StringMatchQ[wrk[[i]],__~~"="~~("INTEG "|"INTEG")~~"("~~__~~","~~__~~")"],​​temp=StringSplit[wrk[[i]],{"=","(",",",")"}];AppendTo[lev,{temp[[1]],temp[[3]]}];AppendTo[levInit,{temp[[1]],temp[[4]]}],​​StringMatchQ[wrk[[i]],__~~"="~~__],​​temp=StringSplit[wrk[[i]],"="];​​AppendTo[var,{temp[[1]],temp[[2]]}]]]];

Replace function parentheses


Final housekeeping


Initialize the simulation


Risk Analysis Procedure
Uses the WL version of the Vensim model.
Reads from the same directory a «.csv» file containing the camelCase name of the state variables and their bounds. Variables whose oscillatory behavior crosses the x-axis are not supported.
Provides the output into “MBSRA.csv”, also in the same directory.

Simulation Procedure

Content

This is a non-optimised, interpreted System Dynamics engine that also checks for violations in the bounds of state variables (stocks).
In[]:=
ClearAll[simul8];​​simul8[lv_,auxl_]:=​​Block[{fail,x=lv,y=auxl,netx,xWk,yWk,ts,t,t0,end,i,j,k},​​simul8::usage="The function performs a SD simulation run and detects disturbance-caused boundary violations. The inputs are: lv - seed values of level (stock) variables auxl - seed values of auxiliary variables Each simulation iteration updates: netx - current net rate value yWk - working value for each auxiliary variable xWk - working value for each level (stock) The output is a list with: a)fail - a vector with 0s if all state variables stayed within bounds, plus lev - final values of all state variables var - final values of auxiliaries b)fail - a vector with 0s and a 1 or -1 in the positition of the failed variable, plus stk - name of failed variable xWk - values of state variables at the failure point.";​​fail=xWk=Table[0,n];​​yWk=Table[0,m];​​ts=Table[j,{j,initialTime,finalTime,dT}];​​t0=Extract[Flatten[Position[ts,initialTime+dT]],1];​​end=Length[ts];​​For[t=t0,t≤end,t++,​​time=ts[[t]];​​For[j=1,j<=m,j++,​​yWk[[j]]=var[[j,2]];​​Do[yWk[[j]]=yWk[[j]]/.aux[[k]]->y[[k]],{k,m}];(*Executethealgebra;*)​​Do[yWk[[j]]=yWk[[j]]/.stk[[k]]->x[[k]],{k,n}]];​​For[i=1,i<=n,i++,​​netx=lev[[i,2]];​​Do[netx=netx/.aux[[k]]->yWk[[k]],{k,m}];​​Do[netx=netx/.stk[[k]]->x[[k]],{k,n}];​​xWk[[i]]=x[[i]]+netx*dT;(*Updatethevalueofeachstatevariable;*)​​If[xWk[[i]]>xMax[[i]],fail[[i]]=1];(*Checkifthestockiswithinboundaries,*)​​If[xWk[[i]]<xMin[[i]],fail[[i]]=-1];​​If[fail[[i]]≠0, (*verifyfailure,*)​​Return[{fail,stk[[i]],xWk}]]];(*andexit.*)​​x=xWk;(*Otherwise,proceed.*)​​y=yWk];​​{fail,xWk,yWk}];

Set-up risk analysis

The «.csv» file contains the camelCase name of the state variables and their bounds.

Add or drain energy

The variable switch controls for energy addition (0) or removal (1).

CITE THIS NOTEBOOK

Model-based risk analysis for system design​
by J. Pedro Mendes​
Wolfram Community, STAFF PICKS, May 3, 2024
​https://community.wolfram.com/groups/-/m/t/3169362