diff --git a/concraft.cabal b/concraft.cabal
--- a/concraft.cabal
+++ b/concraft.cabal
@@ -1,5 +1,5 @@
 name:               concraft
-version:            0.6.0
+version:            0.7.0
 synopsis:           Morphological disambiguation based on constrained CRFs
 description:
     A morphological disambiguation library based on
@@ -47,11 +47,12 @@
 
     exposed-modules:
         NLP.Concraft
-      , NLP.Concraft.Morphosyntax
       , NLP.Concraft.Analysis
       , NLP.Concraft.Schema
       , NLP.Concraft.Guess
       , NLP.Concraft.Disamb
+      , NLP.Concraft.Morphosyntax
+      , NLP.Concraft.Morphosyntax.Accuracy
 
     other-modules:
         NLP.Concraft.Disamb.Positional
diff --git a/src/NLP/Concraft/Morphosyntax/Accuracy.hs b/src/NLP/Concraft/Morphosyntax/Accuracy.hs
new file mode 100644
--- /dev/null
+++ b/src/NLP/Concraft/Morphosyntax/Accuracy.hs
@@ -0,0 +1,109 @@
+-- | Accuracy statistics.
+
+
+module NLP.Concraft.Morphosyntax.Accuracy
+(
+-- * Stats
+  Stats (..)
+, accuracy
+
+-- * Accuracy
+, weakLB
+, weakUB
+, strongLB
+, strongUB
+) where 
+
+
+import           Data.List (foldl')
+import qualified Data.Set as S
+import qualified Data.Map as M
+import qualified Data.Tagset.Positional as P
+import           NLP.Concraft.Morphosyntax
+import           NLP.Concraft.Morphosyntax.Align
+
+
+-- | Statistics.
+data Stats = Stats
+    { good :: Int   -- ^ Number of correct tags
+    , gold :: Int } -- ^ Number of segments in gold corpus
+
+
+-- | Add stats,
+(.+.) :: Stats -> Stats -> Stats
+Stats x y .+. Stats x' y' = Stats (x + x') (y + y')
+
+
+-- | Accuracy given stats.
+accuracy :: Stats -> Double
+accuracy s
+    = fromIntegral (good s)
+    / fromIntegral (gold s)
+
+
+-- | Accuracy weak lower bound.
+weakLB :: Word w => P.Tagset -> [Seg w P.Tag] -> [Seg w P.Tag] -> Stats
+weakLB tagset ref other =
+    foldl' (.+.) (Stats 0 0) . map (uncurry stats) $ align ref other
+  where
+    stats [x] [y]
+        | S.null (xTags `S.intersection` yTags) = Stats 0 1
+        | otherwise = Stats 1 1
+      where
+        xTags = choice tagset x
+        yTags = choice tagset y
+    stats xs _ = Stats 0 (length xs)
+
+
+-- | Accuracy strong lower bound.
+strongLB :: Word w => P.Tagset -> [Seg w P.Tag] -> [Seg w P.Tag] -> Stats
+strongLB tagset ref other =
+    foldl' (.+.) (Stats 0 0) . map (uncurry stats) $ align ref other
+  where
+    stats [x] [y]
+        | xTags == yTags = Stats 1 1
+        | otherwise = Stats 0 1
+      where
+        xTags = choice tagset x
+        yTags = choice tagset y
+    stats xs _ = Stats 0 (length xs)
+
+
+-- | Accuracy weak upper bound.
+weakUB :: Word w => P.Tagset -> [Seg w P.Tag] -> [Seg w P.Tag] -> Stats
+weakUB tagset ref other =
+    foldl' (.+.) (Stats 0 0) . map (uncurry stats) $ align ref other
+  where
+    stats [x] [y]
+        | S.null (xTags `S.intersection` yTags) = Stats 0 1
+        | otherwise = Stats 1 1
+      where
+        xTags = choice tagset x
+        yTags = choice tagset y
+    stats xs _ = Stats (length xs) (length xs)
+
+
+-- | Accuracy strong upper bound.
+strongUB :: Word w => P.Tagset -> [Seg w P.Tag] -> [Seg w P.Tag] -> Stats
+strongUB tagset ref other =
+    foldl' (.+.) (Stats 0 0) . map (uncurry stats) $ align ref other
+  where
+    stats [x] [y]
+        | xTags == yTags = Stats 1 1
+        | otherwise = Stats 0 1
+      where
+        xTags = choice tagset x
+        yTags = choice tagset y
+    stats xs _ = Stats (length xs) (length xs)
+    
+
+-- | All tags are expanded here.
+choice :: P.Tagset -> Seg w P.Tag -> S.Set P.Tag
+choice tagset = S.fromList . concatMap (P.expand tagset) . positive
+
+
+-- | Positive tags.
+positive :: Seg w t -> [t]
+positive seg =
+    let xs = M.toList . unWMap . tags
+    in  [x | (x, v) <- xs seg, v > 0]
diff --git a/src/NLP/Concraft/Morphosyntax/Align.hs b/src/NLP/Concraft/Morphosyntax/Align.hs
--- a/src/NLP/Concraft/Morphosyntax/Align.hs
+++ b/src/NLP/Concraft/Morphosyntax/Align.hs
@@ -1,12 +1,15 @@
 {-# LANGUAGE TupleSections #-}
 
+
 -- | Alignment and synchronization.  Currently works only with positional tagsets.
 
+
 module NLP.Concraft.Morphosyntax.Align
 ( align
 , sync
 ) where
 
+
 import           Control.Applicative ((<|>))
 import           Data.Maybe (fromJust)
 import           Data.List (find)
@@ -18,6 +21,7 @@
 
 import           NLP.Concraft.Morphosyntax
 
+
 -- | Synchronize two datasets, taking disamb tags from the first one
 -- and the rest of information form the second one.
 -- In case of differences in token-level segmentation, reference segmentation
@@ -26,6 +30,7 @@
 sync :: Word w => P.Tagset -> [Seg w P.Tag] -> [Seg w P.Tag] -> [Seg w P.Tag]
 sync tagset xs ys = concatMap (uncurry (moveDisamb tagset)) (align xs ys)
 
+
 -- | If both arguments contain only one segment, insert disamb interpretations
 -- from the first segment into the second segment.  Otherwise, the first list
 -- of segments will be returned unchanged.
@@ -50,6 +55,7 @@
 -- Do nothing in this case.
 moveDisamb _ xs _ = xs
 
+
 -- | Align two lists of segments.
 align :: Word w => [Seg w t] -> [Seg w t] -> [([Seg w t], [Seg w t])]
 align [] [] = []
@@ -59,6 +65,7 @@
     let (x, y) = match xs ys
         rest   = align (drop (length x) xs) (drop (length y) ys)
     in  (x, y) : rest
+
 
 -- | Find the shortest, length-matching prefixes in the two input lists.
 match :: Word w => [Seg w t] -> [Seg w t] -> ([Seg w t], [Seg w t])
