packages feed

cpsa-2.1.1: src/CPSA/Graph/Tree.hs

-- Generate an SVG drawing of a tree of preskeletons

-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.

module CPSA.Graph.Tree (Tree (..), Forest, forest, tree) where

import qualified Data.Map as M
import CPSA.Graph.XMLOutput
import CPSA.Graph.Config
import CPSA.Graph.SVG
import CPSA.Graph.Loader

-- The preskeletons in the output are assembled together for display
-- into trees based on the parent relation.  In reality, the
-- relationship between preskeletons is not tree-like, but includes
-- other edges as a result of a preskeleton having cohort members that
-- have been seen before.  These members are called a tree node's
-- duplicates, and their children are displayed somewhere else in the
-- display.

data Tree = Tree
    { vertex :: Preskel,
      duplicates :: Forest,     -- Preskeletons already seen
      children :: Forest,       -- Freshly discovered preskeletons
      alive :: Bool,            -- Is preskeleton alive?
      width :: Int,             -- Number of leaf nodes
      height :: Int }           -- Longest distance to a leaf plus one
    deriving Show

instance Eq Tree where
    t0 == t1 = vertex t0 == vertex t1

instance Ord Tree where
    compare t0 t1 = compare (vertex t0) (vertex t1)

makeTree :: Preskel -> [Tree] -> [Tree] -> Tree
makeTree k dups kids =
    Tree { vertex = k, duplicates = dups, children = kids,
           alive = True,        -- The correct value is set later
           width = x (dups ++ kids), height = y (dups ++ kids) }
    where
      x [] = 1                  -- Compute the width and height
      x kids = sum (map width kids)
      y kids = 1 + foldl max 0 (map height kids)

type Forest = [Tree]

-- Assemble preskeletons into a forest and then set the alive flag
forest :: [Preskel] -> Forest
forest ks = map setLiveness (assemble ks)

-- Assemble preskeletons into a forest
assemble :: [Preskel] -> Forest
assemble ks =
    map loops (spanning ks)
    where
      loops t =                 -- Add in the other edges
          let k = vertex t in   -- in the forest
          makeTree k (duplicates k) (map loops (children t))
      duplicates k =
          [ makeTree k' [] []
            | tag <- seen k,
              k' <- maybe [] (:[]) (M.lookup tag table) ]
      table = M.fromList $ map (\k -> (label k, k)) ks

-- Compute the spanning forest of the preskeletons
spanning :: [Preskel] -> Forest
spanning [] = []
spanning (k:ks) =
    makeTree k [] kids : rest
    where
      (kids, rest) = partition (label k) (assemble ks)
      partition _ [] = ([], [])
      partition tag (t:ts) =
          let (kids, rest) = partition tag ts in
          case parent (vertex t) of
            Just parent' ->
                if parent' == tag then
                    (t:kids, rest)
                else
                    (kids, t:rest)
            Nothing ->
                (kids, t:rest)

-- Set the alive flag in each preskeleton.
setLiveness :: Tree -> Tree
setLiveness t = updateLiveness (live t) t

-- Extract the non-dead preskeletons from a tree.  A preskeleton is
-- dead if it is known to be unrealized, and all of its children are
-- unrealized.  Because of duplicates, process of computing the list
-- must be iterated.
live :: Tree -> [Preskel]
live t =
    loop []
    where
      decend ks t =
          let ks' = foldl decend ks (kids t) in
          if contain ks' (vertex t) || dead ks' t then
              ks'
          else
              vertex t : ks'
      dead ks t =
          maybe False (not . null) (unrealized (vertex t))
          && all (not . contain ks . vertex) (kids t)
      loop old =
          let new = decend old t in
          if length new == length old then
              old
          else
              loop new
      kids t = duplicates t ++ children t

updateLiveness :: [Preskel] -> Tree -> Tree
updateLiveness live t =
    t { duplicates = map (updateLiveness live) (duplicates t),
        children = map (updateLiveness live) (children t),
        alive = contain live (vertex t) }

-- Does list contain a given preskeleton?
contain :: [Preskel] -> Preskel -> Bool
contain [] _ = False
contain (k:ks) k' =
    label k == label k' || contain ks k'

-- Draw tree view of preskeleton relations
tree :: Config -> Tree -> (Float, Float, [Element])
tree conf t =
    if compact conf then
        vtree conf t
    else
        htree conf t

-- Draw a vertical tree
vtree :: Config -> Tree -> (Float, Float, [Element])
vtree conf t =
    (w, h, snd $ folddup (loop x y) (mx conf, top) t)
    where
      tw = tx conf * fromIntegral (width t - 1)  -- Tree width
      th = ty conf * fromIntegral (height t - 1) -- Tree height
      x = mx conf + tw / 2
      y = my conf
      top = [button conf x y False t]
      loop :: Float -> Float -> Bool -> (Float, [Element]) ->
              Tree -> (Float, [Element])
      loop x1 y1 dup (w, es) t =
          (w + tx conf + tw, es')
          where
            x2 = w + tw / 2
            y2 = y1 + ty conf
            es'' = button conf x2 y2 dup t :
                   line conf x1 (y1 + td conf) x2 (y2 - ta conf) : es
            es' = snd $ folddup (loop x2 y2) (w, es'') t
            tw = tx conf * fromIntegral (width t - 1)
      w = 2 * mx conf + tw                       -- Diagram width
      h = 2 * my conf + th                       -- Diagram height

-- Draw a horizontal tree
htree :: Config -> Tree -> (Float, Float, [Element])
htree conf t =
    (w, h, snd $ folddup (loop x y) (my conf, top) t)
    where
      tw = tx conf * fromIntegral (height t - 1)  -- Tree width
      th = ty conf * fromIntegral (width t - 1)   -- Tree height
      x = mx conf
      y = my conf + th / 2
      top = [button conf x (y - td conf) False t]
      loop :: Float -> Float -> Bool -> (Float, [Element]) ->
              Tree -> (Float, [Element])
      loop x1 y1 dup (h, es) t =
          (h + ty conf + th, es')
          where
            x2 = x1 + tx conf
            y2 = h + th / 2
            es'' = button conf x2 (y2 - td conf) dup t :
                   line conf x1 y1 x2 y2 : es
            es' = snd $ folddup (loop x2 y2) (h, es'') t
            th = ty conf * fromIntegral (width t - 1)
      w = 2 * mx conf + tw                       -- Diagram width
      h = 2 * my conf + th                       -- Diagram height

folddup :: (Bool -> a -> Tree -> a) -> a -> Tree -> a
folddup f z t =
    foldl (f False) (foldl (f True) z (duplicates t)) (children t)

button :: Config -> Float -> Float -> Bool -> Tree -> Element
button conf x y dup t =
    kbutton conf x y kind (label (vertex t))
    where
      kind =
          case (alive t, dup) of
            (True, False) -> AliveTree
            (True, True) -> AliveDup
            (False, False) -> DeadTree
            (False, True) -> DeadDup