{- | 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 ()
}
}