summaryrefslogblamecommitdiff
path: root/Assignment2/src/DTMC.icl
blob: 87a9130773c876be69739765c40f4932ca49dd46 (plain) (tree)
1
2
3
4
5
6
7
8
9

                          
              
               
                     
              
                         
                
               
                 
                                     
                              
                                  
                 
                         

                      
                    
 

                 
                      






























                                                                             





                                                                                







                                                                       
                                                             
             
                                                                
 
                                                                          



                                          
                                   
                              
                                                  
                                 
                                                      
                                



                                                                                        
             
                                                                                                      
                                               
                                         


                                                  
                                                                        
                                               
                                                                                              
 
                                                                                                      
                                               
                                         
                                         
                                                                      

                                                  
                                                                                                      
                                               
                                                                                              


                                                                                








                                                                 
                                      
                         
                                              


                                        

                                                                                                   
                 
                                                                   
                                                

                                                                        
                     
                                                                     
                                                                        
                                                               





                                                                               
 
                                         
                                             
                                               
                 
                       

                                                       



















                                                                                          
                                                       
     

















                                                                                          
                                          
 
                                                                
                                                  
                                          
                        
                                                                      
                                                          





                                                                             
                                              


                                            

                                                                                                
                                    
                                                                       
                                                                           
 
                                                               
                                                                     
                 





                                                                                        
                                         


                                                   
     

                                                                          
                                      
                                                                          
 




                         




                                                                     
                                                                 
                                           
                                 
                                           
                                                                               
                            
                                                                                       
                            
           
                                                    
                                            
          

                                                                              



                                                                            
       
                                    
                                                               
                         
                               

                                                   
                     
                                                    
                              
 
                    
                                
                                             
                                                           
                                        
                                                                        
                                                                                                                             
 
                         
                  
                                   
                                    
implementation module DTMC

import StdArray
import StdBool
import StdClass
import StdFile
from StdFunc import o
import StdInt
import StdList
from StdMisc import abort
import StdString
import StdTuple

import Data.Error
from Data.Func import $, mapSt, seqSt
import Data.List
import qualified Data.Map as M
from Data.Map import :: Map, alter
import Data.Maybe
import System.CommandLine
import System.File
import System.FilePath
import Text
import Text.GenParse

import Expression
import Z3

derive gParse Op, (,,)

paths :: !*DTMC -> *([[Int]], !*DTMC)
paths dtmc=:{nr_states,states}
# (inits,states) = getInitStates (nr_states-1) states
# (ps,states) = mapSt (paths` []) inits states
= (flatten ps, {dtmc & states=states})
where
	getInitStates :: !Int !*{Maybe State} -> *(![Int], !*{Maybe State})
	getInitStates i ss
	| i < 0 = ([], ss)
	# (s,ss) = ss![i]
	| isNothing s = ([], ss)
	# s = fromJust s
	| s.init
		# (inits,ss) = getInitStates (i-1) ss
		= ([i:inits],ss)
	= getInitStates (i-1) ss

	paths` :: ![Int] !Int !*{Maybe State} -> *(![[Int]], !*{Maybe State})
	paths` seen cur ss
	| isMember cur seen = ([], ss)
	# (s,ss) = ss![cur]
	| isNothing s = ([], ss)
	# s = fromJust s
	| isAbsorbing s = ([[cur]], ss)
	# succ = 'M'.keys s.transitions
	# (pss,ss) = mapSt (paths` [cur:seen]) succ ss
	= ([[cur:p] \\ ps <- pss, p <- ps], ss)

stateElimination :: !*DTMC -> *DTMC
stateElimination dtmc=:{nr_states}
# (ps,dtmc) = paths dtmc
= case [p \\ p=:[_:_:_:_] <- ps] of
	[]
		= {dtmc & states=prune (nr_states-1) dtmc.states}
	[p:_]
		# (unzs,states) = uncurry elim (penultimate_trans p) dtmc.states
		# dtmc & states = states
		# dtmc & unzero = unzs ++ dtmc.unzero
		= stateElimination dtmc
		with
			penultimate_trans [x,y,_] = (x,y)
			penultimate_trans [_:xs] = penultimate_trans xs
where
	prune :: !Int !*{Maybe State} -> *{Maybe State}
	prune i ss
	| i < 0 = ss
	# (Just s,ss) = ss![i]
	| s.init || isAbsorbing s = prune (i-1) ss
	| otherwise               = prune (i-1) $ remove i ss
	where
		remove :: !Int !*{Maybe State} -> *{Maybe State}
		remove i ss = {ss & [i]=Nothing}

	elim :: !Int !Int !*{Maybe State} -> *(![String], !*{Maybe State})
	elim s1 s ss
	# (Just s1s,ss) = ss![s1]
	# prob = 'M'.get s s1s.transitions

	# (succ,ss) = ss![s]
	| isNothing succ = ([], ss)
	# succ = fromJust succ
	| succ.init || isAbsorbing succ = ([], ss)
	# succ = succ.transitions

	# self_prob = fromMaybe "0.0" $ 'M'.get s succ
	# succ = 'M'.toList succ

	# (unzs,ss) = mapSt (uncurry $ if (s1==s) update_eq update_ne self_prob) succ ss
	# (Just s1s,ss) = ss![s1]
	# ss & [s1] = Just {s1s & transitions='M'.del s s1s.transitions}
	= (catMaybes unzs,ss)
	where
		update_eq :: !String !Int !String !*{Maybe State} -> *(!Maybe String, !*{Maybe State})
		update_eq self_prob s2 prob2 ss
		| s == s2 = (Nothing, ss)
		# (Just s1s,ss) = ss![s1]
		# ss & [s1] = Just
			{ s1s
			& transitions = 'M'.put s2
				("(" +++ prob2 +++ ")/" +++ denominator)
				s1s.transitions
			}
		= (Just $ "(not (= 0 " +++ parenthesise (parseInfix denominator) +++ "))", ss)
		where denominator = "(1.0-(" +++ self_prob +++ "))"

		update_ne :: !String !Int !String !*{Maybe State} -> *(!Maybe String, !*{Maybe State})
		update_ne self_prob s2 prob2 ss
		| s == s2 = (Nothing, ss)
		# (Just s1s,ss) = ss![s1]
		# ps1s2 = fromMaybe "0.0" $ 'M'.get s2 s1s.transitions
		# ps1s = fromMaybe "0.0" $ 'M'.get s s1s.transitions
		# ss & [s1] = Just
			{ s1s
			& transitions = 'M'.put s2
				(ps1s2 +++ "+(" +++ ps1s +++ ")*(" +++ prob2 +++ ")/" +++ denominator)
				s1s.transitions
			}
		= (Just $ "(not (= 0 " +++ parenthesise (parseInfix denominator) +++ "))", ss)
		where denominator = "(1.0-(" +++ self_prob +++ "))"

isAbsorbing :: !State -> Bool
isAbsorbing s = all ((==) s.state_id) [to \\ (to,_) <- 'M'.toList s.transitions]

parseDTMC :: !FilePath !*World -> *(!DTMC, !*World)
parseDTMC fp w
# (lines,w) = readFileLines fp w
= (parseDTMCFromLines [s % (0,size s-2) \\ s <- fromOk lines], w)

parseDTMCFromLines :: ![String] -> DTMC
parseDTMCFromLines lines
# [nr_states:_:lines] = tl $ dropWhile ((<>) "@nr_states") lines
# states = map parseState $ groupLines "\t" [] lines
=
	{ nr_states  = toInt nr_states
	, parameters = []
	, unzero     = []
	, states     = {Just s \\ s <- states}
	}
where
	parseState :: ![String] -> State
	parseState [head:actions] =
		{ state_id    = toInt $ trim $ head % (5,size head-1)
		, transitions = 'M'.unions $ map (snd o parseAction) $ groupLines "\t\t" [] actions
		, init        = endsWith "init" head
		}
	where
		parseAction :: ![String] -> (!Int, !Map Int String)
		parseAction [head:transitions] =
			( toInt (head % (8, size head-1))
			, 'M'.fromList $ map parseTransition transitions
			)
		where
			parseTransition :: !String -> (!Int, !String)
			parseTransition s = case split " : " (trim s) of
				[id:prob:_] -> (toInt id, prob)

	groupLines :: !String ![String] ![String] -> [[String]]
	groupLines head current [] = [reverse current]
	groupLines head current [line:lines]
	| startsWith head line = groupLines head [line:current] lines
	| isEmpty current      = groupLines head [line] lines
	| otherwise            = [reverse current:groupLines head [line] lines]

printDTMC :: !*DTMC -> *(!String, !*DTMC)
printDTMC dtmc=:{nr_states,parameters,states}
# (state_strings,states) = printStates 0 states
# s = join "\n" $
	[ "@type: DTMC"
	, "@parameters"
	] ++ if (isEmpty parameters) [""] parameters ++
	[ "@reward_models\n"
	, "@nr_states"
	, toString nr_states
	, "@model"
	: state_strings
	]
= (s, {dtmc & states=states})
where
	printStates :: !Int !*{Maybe State} -> *(![String], !*{Maybe State})
	printStates i ss
	| i == nr_states = ([], ss)
	# (s,ss) = ss![i]
	| isNothing s = printStates (i+1) ss
	# s = fromJust s
	# repr =
		[ "state " <+ s.state_id
		, "\taction 0"
		: ["\t\t" <+ to <+ " : " <+ prob \\ (to,prob) <- 'M'.toList s.transitions]
		]
	# (reprs,ss) = printStates (i+1) ss
	= (repr ++ reprs,ss)

addVars :: !*DTMC -> *DTMC
addVars dtmc=:{nr_states} = addVarsS (nr_states-1) dtmc
where
	addVarsS :: !Int !*DTMC -> *DTMC
	addVarsS i dtmc=:{states}
	| i < 0 = dtmc
	# (s,states) = states![i]
	# dtmc & states = states
	| isNothing s = dtmc
	# s = fromJust s
	# dtmc = seqSt (uncurry $ addVar i) ('M'.toList s.transitions) dtmc
	= addVarsS (i-1) dtmc

	addVar :: !Int !Int !String !*DTMC -> *DTMC
	addVar f t prob dtmc=:{parameters}
	# (s,dtmc) = dtmc!states.[f]
	# s = fromJust s
	# s & transitions = 'M'.put t ("((" <+ prob <+ ") + " <+ var <+ ")") s.transitions
	# dtmc & states.[f] = Just s
	# dtmc & parameters = [var:parameters]
	= dtmc
	where
		var = "v" <+ f <+ "x" <+ t

repairConfig :: !CostFunction !*DTMC -> *(!RepairConfig, !*DTMC)
repairConfig cf dtmc=:{nr_states,parameters,states}
# (ss,states) = collectStates (nr_states-1) states
# transs = concatMap collectTransitions ss
# dtmc & states = states
# config =
	{ totality_restrictions   = map totality ss
	, transition_restrictions = concatMap probabilityBounds transs
	, minimalisation_goal     = minimise cf parameters
	}
= (config, dtmc)
where
	collectStates :: !Int !*{Maybe State} -> *(![State], !*{Maybe State})
	collectStates i ss
	| i < 0 = ([], ss)
	# (s,ss) = ss![i]
	| isNothing s = collectStates (i-1) ss
	# s = fromJust s
	# (rest,ss) = collectStates (i-1) ss
	= ([s:rest],ss)

	collectTransitions :: !State -> [(!Int,!Int,!String)]
	collectTransitions s = [(s.state_id, to, prob) \\ (to,prob) <- 'M'.toList s.transitions]

	totality :: !State -> String
	totality s = "(= 1.0 " <+ parenthesise (parseInfix expr) <+ ")"
	where
		expr = "(" <+ (join ")+(" $ 'M'.elems s.transitions) <+ ")"

	probabilityBounds :: !(!Int, !Int, !String) -> [String]
	probabilityBounds (_,_,prob) =
		[ "(<= 0.0 " <+ parenthesise (parseInfix prob) <+ ")"
		, "(>= 1.0 " <+ parenthesise (parseInfix prob) <+ ")"
		]

	minimise :: !CostFunction ![String] -> String
	minimise cf params = foldr1 (\a b -> "(+ " <+ a <+ " " <+ b <+ ")") $ case cf of
		NumberModifiedTransitions ->
			["(if (= 0.0 " <+ p <+ ") 0.0 1.0)" \\ p <- params]
		SumSquaredErrors ->
			["(* " <+ p <+ " " <+ p <+ ")" \\ p <- params]

findInitial :: !*DTMC -> (!State, !*DTMC)
findInitial dtmc=:{nr_states,states}
# (init,states) = findInitial` (nr_states-1) states
# dtmc & states = states
= (init, dtmc)
where
	findInitial` :: !Int !*{Maybe State} -> *(!State, !*{Maybe State})
	findInitial` i states
	| i < 0 = abort "No initial state"
	# (state, states) = states![i]
	| isJust state && (fromJust state).init = (fromJust state, states)
	| otherwise = findInitial` (i - 1) states

instance toString Op
where
	toString LT = "<"
	toString EQ = "="
	toString GT = ">"

assertProperties :: !Properties !*DTMC !Z3 !*World -> (*DTMC, *World)
assertProperties [] dtmc _ w = (dtmc,w)
assertProperties [prop:props] dtmc z3 w
# (dtmc, w) = assertProperty prop dtmc z3 w
= assertProperties props dtmc z3 w

assertProperty :: !Property !*DTMC !Z3 !*World -> (*DTMC, *World)
assertProperty (op, prob, target) dtmc z3 w
# (init, dtmc) = findInitial dtmc
# formula = 'M'.get target init.transitions
| isNothing formula = abort "Cannot reach property target from initial state\n"
# formula = fromJust formula
# formula = "(" <+ op <+ " " <+ prob <+ " " <+ parenthesise (parseInfix formula) <+ ")"
# w = addAssert z3 formula w
= (dtmc, w)

z3AddVars :: !*DTMC !Z3 !*World -> (!*DTMC, !*World)
z3AddVars dtmc z3 w
# (params,dtmc) = dtmc!parameters
# w = seqSt (addVariable z3 "Real") params w
= (dtmc,w)

parenthesise :: !a -> String | toString a
parenthesise x = let s = toString x in if (s.[0] == '(') s ("(" +++ s +++ ")")

parseProperty :: !String -> Property
parseProperty s = case parseString s of
	Nothing -> abort $ "Could not parse '" +++ s +++ "' as a property\n"
	Just p  -> p

Start w
# ([prog:args],w) = getCommandLine w
| length args <> 2 = "Usage: " +++ prog +++ " DRN PROPERTIES\n"
# [drn_in,props:_] = args

# (dtmc,w) = parseDTMC drn_in w
# (props,w) = readFileLines props w
# props = map (parseProperty o trim) $ fromOk props

# dtmc = addVars dtmc
# (config,dtmc) = repairConfig SumSquaredErrors dtmc
# dtmc = stateElimination dtmc

# (z3,w) = startZ3 w
# (dtmc,w) = z3AddVars dtmc z3 w
# (dtmc,w) = assertProperties props dtmc z3 w
# w = seqSt (addAssert z3) config.totality_restrictions w
# w = seqSt (addAssert z3) config.transition_restrictions w
# w = seqSt (addAssert z3) dtmc.unzero w
# w = addVariable z3 "Real" "cost" w
# w = addAssert z3 ("(= cost " +++ config.minimalisation_goal +++ ")") w
//# w = addMinimize z3 "cost" w // NOTE: z3 fails with this, but you can approach a bound by asserting an upper bound on cost

# (sat,w) = checkSat z3 w
| not (sat=:Unsat)
	# (model,w) = getModel z3 w
	= model
= "Could not satisfy the property\n"