{- | Module Main Emping 0.5 (provisional) Module Main performs the GUI and I\/O tasks for the reduction of heuristic rules. The input consists of facts in a comma separated file (.csv) format as produced by the Open Office Calc spreadsheet. The rule output is in the same format and can be read by OO Calc. Dependencies between reduced rules may be shown as a graph in .dot format, which can be viewed with a GraphViz viewer like dotty or gzviewer. The user can open a .csv file, select an attribute which is to be the consequent, and Emping will find all shortest rules which predict the values of the selected attribute. The user can set whether the facts will be checked for duplicates, and for ambiguous rules (those with the same antecedent but a different consequent). Graph may be displayed as entailments (top down) or implications (the reversed graph). See the white paper \"Deriving Heuristic Rules from Facts\" (January 2007) for more on the foundation and algorithm. Wed 16 Apr 2008 01:14:12 PM CEST -} module Main (main) where import Graphics.UI.Gtk import Control.Concurrent import Data.IORef import Data.Array import Data.Graph.Inductive import Aux import CSVParse import Codec import CSVTable import Reduce import Abduce main :: IO () main = do initGUI timeoutAddFull (yield >> return True) priorityDefaultIdle 50 window <- windowNew set window [windowTitle := "Emping", windowDefaultWidth := 400, windowDefaultHeight := 100] vbox1 <- vBoxNew False 0 containerAdd window vbox1 opensrc <- actionNew "OpenSrc" "Open" (Just "Open a source .csv file") (Just stockOpen) options <- actionNew "OptionMenu" "Options" (Just "Check for duplicates and ambiguities") (Just stockProperties) selcons <- actionNew "SelCons" "Consequent" (Just "Select the attribute which will be the rule consequent") (Just stockNew) reduact <- actionNew "ReduAct" "Reduce" (Just "Get the reduced normal form of the rules") (Just stockConvert) abdutop <- actionNew "AbduTop" "Top" (Just "Get only the top level of all implications") (Just stockGotoTop) filemen <- actionNew "FileMenu" "File" (Just "Open and exit") (Just stockFile) quitact <- actionNew "QuitAct" "Quit" Nothing (Just stockQuit) grphmen <- actionNew "GrphMen" "Rule Graphs" (Just "Display rule graphs or legend") (Just stockGotoBottom) attgrph <- actionNew "AttGrph" "Attribute" (Just "Get the graph of implications for the attribute") (Just stockZoom100) valgrph <- actionNew "ValGrph" "Per Value" (Just "Get the graph of implications for an attribute value") (Just stockZoomIn) glegend <- actionNew "GleGend" "Legend" (Just "Legend of all nodes in the implication graph") (Just stockUnderline) duptogg <- toggleActionNew "DupCheck" "Check Data" (Just "Check facts for duplicates") Nothing ambtogg <- toggleActionNew "AmbCheck" "Check Rules" (Just "Check rules for ambiguity") Nothing revtogg <- toggleActionNew "RevGraph" "Reverse" (Just "Get the graphs in reverse direction") Nothing toggleActionSetActive duptogg True toggleActionSetActive ambtogg True actgrp <- actionGroupNew "ActGrp" mapM_ (actionGroupAddAction actgrp) [opensrc,options,selcons,reduact,abdutop,filemen,quitact,grphmen,attgrph,valgrph,glegend] mapM_ (actionGroupAddAction actgrp) [duptogg,ambtogg,revtogg] manager <- uiManagerNew uiManagerAddUiFromString manager uiDecl uiManagerInsertActionGroup manager actgrp 0 mbMenubar <- uiManagerGetWidget manager "ui/menubar" let menubar = case mbMenubar of Just x -> x Nothing -> error "Main: Cannot get menubar from String" boxPackStart vbox1 menubar PackNatural 0 mbToolbar <- uiManagerGetWidget manager "ui/toolbar" let toolbar = case mbToolbar of Just x -> x Nothing -> error "Main: Cannot get toolbar from String" boxPackStart vbox1 toolbar PackNatural 0 status <- statusbarNew boxPackStart vbox1 status PackNatural 0 gencontext <- statusbarGetContextId status "General" filesrc <- fileChooserDialogNew (Just "Source File in CSV") (Just window) FileChooserActionOpen [("Cancel", ResponseCancel), ("Open", ResponseAccept)] filesave <- fileChooserDialogNew (Just "Save File") (Just window) FileChooserActionSave [("Cancel", ResponseCancel), ("Save", ResponseAccept)] fileChooserSetDoOverwriteConfirmation filesave True csvfilt <- fileFilterNew fileFilterAddPattern csvfilt "*.csv" fileFilterSetName csvfilt "CSV Source" fileChooserAddFilter filesrc csvfilt attpopman <- uiManagerNew attpopgrp <- actionGroupNew "Popup Att Select" valpopman <- uiManagerNew valpopgr <- actionGroupNew "Popup Value Select" mapM_ ((flip actionSetSensitive) False) [selcons, reduact,abdutop,attgrph,valgrph,glegend] mvrNameArray <- newEmptyMVar mvrSingleFacts <- newEmptyMVar mvrSelCons <- newEmptyMVar mvrRules <- newEmptyMVar mvrReductions <- newEmptyMVar mvrNodeGroup <- newEmptyMVar mvrValPopup <- newEmptyMVar hasValPopup <- newIORef False onActionActivate opensrc $ do mbf1 <- mygetFileName filesrc case mbf1 of Nothing -> return () Just fpath1 -> do { statusbarPush status gencontext "Reading and parsing source..." ;forkIO $ do { ;strl <- getTable fpath1 ;let stfacts = tableCode strl stnamearray = tableToArray strl ;stsinglefacts <- getNoDuplicates duptogg filesave status gencontext stnamearray stfacts ;putMVar mvrNameArray stnamearray ;putMVar mvrSingleFacts stsinglefacts ;actionSetSensitive selcons True } ;actionSetSensitive opensrc False } onActionActivate selcons $ do { slnamearray <- readMVar mvrNameArray ;singlefacts <- readMVar mvrSingleFacts ;let choice = map (\ind -> (RadioActionEntry (show ind) (fst (slnamearray ! ind)) Nothing Nothing Nothing ind)) (indices slnamearray) -- dummy because of radioActionGetCurrentValue and RadioActionEntry. Selection of already selected does nothing. dummy = [RadioActionEntry (show lst) "None" Nothing Nothing Nothing lst] where lst = length (indices slnamearray) dumchoice = concat [choice,dummy] popmstr = "" ++ (concatMap itstr dumchoice) ++ "" itstr x = "" myChange ra = do { cons <- radioActionGetCurrentValue ra ;let consname = (fst (slnamearray ! cons)) ;statusbarPush status gencontext ("The consequent attribute is: " ++ consname) ;let rules = facts2Rules cons singlefacts ;noamb <- noAmbiguities ambtogg filesave status gencontext slnamearray cons rules ;if noamb then do { putMVar mvrSelCons cons ;putMVar mvrRules rules ;actionSetSensitive selcons False ;actionSetSensitive reduact True } else return () } ;uiManagerInsertActionGroup attpopman attpopgrp 0 ;actionGroupAddRadioActions attpopgrp dumchoice ((length dumchoice) -1) myChange ;uiManagerAddUiFromString attpopman popmstr ;mbpopup <- uiManagerGetWidget attpopman "/ui/popup" ;let attpopup = case mbpopup of Nothing -> error "Main: no popup menu for consequent attribute selection" Just x -> x ;menuPopup (castToMenu attpopup) Nothing ;return () } onActionActivate reduact $ do mbf2 <- mygetFileName filesave case mbf2 of Nothing -> return () Just fpath2 -> do { statusbarPush status gencontext "Reducing rules, this may take a while..." ;forkIO $ do { rdnamearray <- readMVar mvrNameArray ;rdrules <- readMVar mvrRules ;rdcons <- readMVar mvrSelCons ;let rdconsname = (fst (rdnamearray ! rdcons)) reductions = reduceAll rdrules ;writeFile fpath2 (rnf2CSVTb rdnamearray rdrules reductions) ;statusbarPush status gencontext ("Finished reduction for " ++ rdconsname) ;putMVar mvrReductions reductions ;actionSetSensitive abdutop True ;actionSetSensitive glegend True } ;actionSetSensitive reduact False } onActionActivate abdutop $ do mbf3 <- mygetFileName filesave case mbf3 of Nothing -> return () Just fpath3 -> do { statusbarPush status gencontext "Checking for rule dependencies.." ;forkIO $ do { abnamearray <- readMVar mvrNameArray ;abrules <- readMVar mvrRules ;abcons <- readMVar mvrSelCons ;abreductions <- readMVar mvrReductions ;let abconsname = (fst (abnamearray ! abcons)) topsgroup = abduceTopAll abrules abreductions ;if not (hasDependencies topsgroup abreductions) then do { statusbarPush status gencontext ("There are no dependencies in reductions for " ++ abconsname) ;actionSetSensitive abdutop False } else do { writeFile fpath3 (allTops2CSVTb abnamearray abrules topsgroup) ;statusbarPush status gencontext ("Saved most general reductions for " ++ abconsname) ;return () } } ;actionSetSensitive abdutop False } onActionActivate glegend $ do mbf4 <- mygetFileName filesave ;case mbf4 of Nothing -> return () Just fpath4 -> do { forkIO $ do { glnamearray <- readMVar mvrNameArray ;glcons <- readMVar mvrSelCons ;glrules <- readMVar mvrRules ;glreductions <- readMVar mvrReductions ;let glconsname = (fst (glnamearray ! glcons)) nodegroup = orgReg2Ndg glrules glreductions ;statusbarPush status gencontext "Getting graph legend, this may take a while..." ;let graphlegend = nodeLegend glreductions nodegroup ;writeFile fpath4 (allNodes2CSVTb glnamearray glrules graphlegend) ;statusbarPush status gencontext ("Saved graph legend for " ++ glconsname) ;putMVar mvrNodeGroup nodegroup } ;actionSetSensitive attgrph True ;actionSetSensitive valgrph True ;actionSetSensitive glegend False } onActionActivate attgrph $ do mbf5 <- mygetFileName filesave case mbf5 of Nothing -> return () Just fpath5 -> do { statusbarPush status gencontext "Building rule dependency graph..." ;forkIO $ do { agnamearray <- readMVar mvrNameArray ;agcons <- readMVar mvrSelCons ;agnodegroup <- readMVar mvrNodeGroup ;let agconsname = (fst (agnamearray ! agcons)) ;revset <- get revtogg toggleActionActive ;let fullgraph = implicGraphAll agnodegroup displaygraph | revset = grev fullgraph | otherwise = fullgraph ;writeFile fpath5 (graph2DOT displaygraph agconsname) ;statusbarPush status gencontext ("Saved rule dependency graph for " ++ agconsname) ;return () } ;return () } onActionActivate valgrph $ do { ppres <- readIORef hasValPopup ;if ppres then return () else do { vgnamearray <- readMVar mvrNameArray ;vgcons <- readMVar mvrSelCons ;vgnodegroup <- readMVar mvrNodeGroup ;let grphvalues = snd (vgnamearray ! vgcons) -- define the value menu for valgrph here valindices = [0..((length grphvalues)-1)] valchoice = map (\ind -> (RadioActionEntry (show ind) (grphvalues !! ind) Nothing Nothing Nothing ind)) valindices -- dummy value, as with selcons valdummy = [RadioActionEntry (show lst) "None" Nothing Nothing Nothing lst] where lst = length valindices valdumchoice = concat [valchoice,valdummy] valpopmstr = "" ++ (concatMap vitstr valdumchoice) ++ "" -- itstr defined above vitstr x = "" ;actionGroupAddRadioActions valpopgr valdumchoice ((length valdumchoice) -1) (myValChange vgnodegroup filesave status gencontext vgnamearray vgcons revtogg) ;uiManagerAddUiFromString valpopman valpopmstr ;uiManagerInsertActionGroup valpopman valpopgr 0 ;mbvalpopup <- uiManagerGetWidget valpopman "/ui/popup" ;let valpopup = case mbvalpopup of Nothing -> error "Main: no popup menu for value selection of graph" Just x -> x ;putMVar mvrValPopup valpopup ;writeIORef hasValPopup True } -- end else ;vgvalpopup <- readMVar mvrValPopup ;menuPopup (castToMenu vgvalpopup) Nothing ;return () } widgetShowAll window onActionActivate quitact (widgetDestroy window) onDestroy window mainQuit mainGUI uiDecl :: String uiDecl = "\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " -- | test for duplicates if the user wants, let the user save a .csv file of duplicates with frequencies, finally remove duplicates from facts getNoDuplicates :: ToggleAction -> FileChooserDialog -> Statusbar -> ContextId -> Array Int (String,[String])-> [[AVp]] -> IO [[AVp]] getNoDuplicates check fs status dupcontext attvarr rawfcts = do { duptrue <- get check toggleActionActive ; if duptrue then do { ;statusbarPush status dupcontext "Checking for duplicates..." ; let prt = partDups rawfcts ;if checkNoDups prt then do { statusbarPush status dupcontext "No duplicates found in the facts" ;return () } else do { mbf <- mygetFileName fs ;case mbf of Just fpath -> writeFile fpath (((allDup2CSVTb attvarr) . factsDups) prt) Nothing -> return () ;statusbarPush status dupcontext "Duplicates have been removed from the fact list" ;return () } ;return (factsUniques prt) } else do { statusbarPush status dupcontext "Source file read and parsed" ;return rawfcts } } -- returns True if there are NO ambiguous rules for the selected consequent attribute (and if the test was skipped) noAmbiguities :: ToggleAction -> FileChooserDialog -> Statusbar -> ContextId -> Array Int (String,[String]) -> Int -> [[Rule]] -> IO Bool noAmbiguities check fs status ambcontext attvarr consatt rules = do { let cnsnm = (fst (attvarr ! consatt)) ;ambtest <- get check toggleActionActive ; if ambtest then do { let ambs = getAmbiguous rules ; if ambs == [] then do { statusbarPush status ambcontext ("No ambiguous rules for " ++ (fst (attvarr ! consatt))) ;return True } else do { statusbarPush status ambcontext ( "Ambiguities: " ++ (show (length ambs)) ++ " for " ++ (fst (attvarr ! consatt))) ;mbf <- mygetFileName fs ;case mbf of Nothing -> return () Just fpath -> writeFile fpath (allAmb2CSVTb attvarr ambs) ;return False } } else do { statusbarPush status ambcontext ("No ambiguities check. The consequent attribute is: " ++ cnsnm) ;return True } } mygetFileName :: FileChooserDialog -> IO (Maybe FilePath) mygetFileName fs = do resp <- dialogRun fs widgetHide fs case resp of ResponseAccept -> fileChooserGetFilename fs (_) -> return Nothing myValChange :: [[LNode RuRe]] -> FileChooserDialog -> Statusbar -> ContextId -> Array Int (String,[String])-> Int -> ToggleAction -> RadioAction -> IO () myValChange ndgrp fs status valcontext attvarr cns check ra = do { statusbarPush status valcontext "Building value rule graph.." ;valind <- radioActionGetCurrentValue ra ;revset <- get check toggleActionActive ;let valuegraph = implicGraphOne (ndgrp !! valind) displaygraph | revset = grev valuegraph | otherwise = valuegraph ;mbf <- mygetFileName fs ;case mbf of Nothing -> do { statusbarPush status valcontext "Cancelled" ;return () } Just fpath -> do { forkIO $ do { let consname = (fst (attvarr ! cns)) valname = (snd (attvarr ! cns)) !! valind ;writeFile fpath (graph2DOT displaygraph "ValueGraph") ;statusbarPush status valcontext (consname ++ " : " ++ valname ++ " rule graph saved") ;return () } ;return () } }