diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2014, Elliot Robinson
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Elliot Robinson nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Math/IRT/Fisher.hs b/Math/IRT/Fisher.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Fisher.hs
@@ -0,0 +1,74 @@
+module Math.IRT.Fisher
+    ( FisherInfo(..)
+    , fisherInfoObserved
+    , fisherInfoExpected
+    , l'
+    , l''
+    ) where
+
+import Statistics.Distribution
+import Math.IRT.Internal.Distribution
+
+
+data FisherInfo = FisherInfo { items :: [Double]
+                             , test  :: !Double
+                             , sem   :: !Double
+                             } deriving (Show)
+
+
+-- |The observed Fisher Information
+--  Applies a min/max prior to ensure a real-valued SEM
+fisherInfoObserved :: (ContDistr d, DensityDeriv d) => Double -> [Bool] -> [d] -> FisherInfo
+fisherInfoObserved theta resps params =
+    let is = zipWith go resps params
+        t  = sum is
+        s  = sqrt (1 / t)
+    in FisherInfo is t s
+    where go u x = negate $ l'' u x theta
+
+
+-- |The expected Fisher Information
+fisherInfoExpected :: ContDistr d => Double -> [d] -> FisherInfo
+fisherInfoExpected theta params =
+    let is = map go params
+        t  = sum is
+        s  = sqrt (1 / t)
+    in FisherInfo is t s
+    where go x = let (cdf, ccdf, pdf) = pqDers x theta
+                 in (square pdf) / (cdf * ccdf)
+
+
+-- |The first derivative of `l`, whatever `l` is... Once again, the catIrt documentation disappoints.
+-- According to catIrt, "u is the response, and x are the parameters."
+-- We only implement the MLE route
+l' :: ContDistr d => Bool -> d -> Double -> Double
+l' u x theta =
+    let (cdf, ccdf, pdf) = pqDers x theta
+        denom = cdf * ccdf
+        f k   = k * pdf / denom
+    in case u of
+         True  -> f ccdf
+         False -> f cdf
+
+
+-- |The second derivative of `l` (same one as in `l'`)
+l'' :: (ContDistr d, DensityDeriv d) => Bool -> d -> Double -> Double
+l'' u x theta =
+    let (cdf, ccdf, pdf) = pqDers x theta
+        pdf'             = densityDeriv x theta
+    in case u of
+         True  -> (((-1) / square cdf) * (square pdf))
+                  + (1 / cdf * pdf')
+         False -> negate $ ((1 / square ccdf) * square pdf)
+                           + (1 / ccdf * pdf')
+
+
+pqDers :: ContDistr d => d -> Double -> (Double, Double, Double)
+pqDers x theta = let pComp = cumulative x theta
+                     p'    = density x theta
+                 in ( pComp
+                    , (1 - pComp)
+                    , p')
+
+square :: Double -> Double
+square = (^^ (2 :: Int))
diff --git a/Math/IRT/Internal/Distribution.hs b/Math/IRT/Internal/Distribution.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Internal/Distribution.hs
@@ -0,0 +1,4 @@
+module Math.IRT.Internal.Distribution where
+
+class DensityDeriv a where
+    densityDeriv :: a -> Double -> Double
diff --git a/Math/IRT/Internal/LogLikelihood.hs b/Math/IRT/Internal/LogLikelihood.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Internal/LogLikelihood.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE GADTs #-}
+module Math.IRT.Internal.LogLikelihood where
+
+import Numeric.AD (Mode, Scalar)
+import Statistics.Distribution (Distribution)
+
+class (Distribution d) => LogLikelihood d where
+    logLikelihood :: (Mode a, Floating a, Scalar a ~ Double) => Bool -> d -> a -> a
+
+logLikeFunc :: (Distribution d, Mode a, Floating a, Scalar a ~ Double) => (d -> a -> a) -> Bool -> d -> a -> a
+logLikeFunc f True  = (log .) . f
+logLikeFunc f False = ((log . (1-)) .) . f
diff --git a/Math/IRT/MLE.hs b/Math/IRT/MLE.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/MLE.hs
@@ -0,0 +1,25 @@
+module Math.IRT.MLE
+  ( DF (..)
+  , MLEResult (..)
+  , mleEst
+  ) where
+
+import Data.Default.Class
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.MLE.Internal.Generic
+
+
+data DF = DF { steps         :: !Int
+             , thetaEstimate :: !Double }
+
+instance Default DF where
+  def = DF 10 0.0
+
+
+mleEst :: (Distribution d, ContDistr d, DensityDeriv d, LogLikelihood d) => DF -> [Bool] -> [d] -> MLEResult
+mleEst (DF n vTheta) rs params =
+    generic_mleEst rs params n vTheta
diff --git a/Math/IRT/MLE/Fenced.hs b/Math/IRT/MLE/Fenced.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/MLE/Fenced.hs
@@ -0,0 +1,31 @@
+module Math.IRT.MLE.Fenced
+  ( DF (..)
+  , MLEResult (..)
+  , mleEst
+  ) where
+
+import Data.Default.Class
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.MLE.Internal.Generic
+import Math.IRT.Model.Generic
+
+
+data DF = DF { steps                :: !Int
+             , thetaEstimate        :: !Double
+             , lower_fence          :: !Double
+             , upper_fence          :: !Double
+             , fence_discrimination :: !Double }
+
+instance Default DF where
+  def = DF 10 0.0 (-3.5) 3.5 3.0
+
+
+mleEst :: (ContDistr d, DensityDeriv d, LogLikelihood d, GenericModel d) => DF -> [Bool] -> [d] -> MLEResult
+mleEst (DF n vTheta lf uf fd) rs params =
+    let resp = True : False : rs
+        pars = fromThreePLM fd lf 0 : fromThreePLM fd uf 0 : params
+    in generic_mleEst resp pars n vTheta
diff --git a/Math/IRT/MLE/Internal/Generic.hs b/Math/IRT/MLE/Internal/Generic.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/MLE/Internal/Generic.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE GADTs #-}
+module Math.IRT.MLE.Internal.Generic
+  ( MLEResult (..)
+  , generic_mleEst
+  , logLike
+  ) where
+
+import Numeric.AD (Mode, Scalar)
+import Numeric.AD.Halley
+
+import Statistics.Distribution (Distribution, ContDistr)
+
+import Math.IRT.Fisher
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.MLE.Internal.MLEResult
+
+
+generic_mleEst :: (Distribution d, ContDistr d, DensityDeriv d, LogLikelihood d) => [Bool] -> [d] -> Int -> Double -> MLEResult
+generic_mleEst rs params steps vTheta =
+    let est    = (!! steps) $ extremumNoEq (logLike rs params) vTheta
+        fisher = fisherInfoObserved est rs params
+    in case fisher of
+         (FisherInfo _ t s) -> MLEResult est t s
+
+
+logLike :: (Mode a, Floating a, Scalar a ~ Double, Distribution d, LogLikelihood d) => [Bool] -> [d] -> a -> a
+logLike responses params vTheta =
+    let logLik   = zipWith (\a b -> logLikelihood a b vTheta) responses params
+        bmePrior = 1
+    in sum logLik + log bmePrior
diff --git a/Math/IRT/MLE/Internal/MLEResult.hs b/Math/IRT/MLE/Internal/MLEResult.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/MLE/Internal/MLEResult.hs
@@ -0,0 +1,6 @@
+module Math.IRT.MLE.Internal.MLEResult where
+
+data MLEResult = MLEResult { theta :: !Double
+                           , info  :: !Double
+                           , sem   :: !Double
+                           } deriving (Show)
diff --git a/Math/IRT/MLE/Truncated.hs b/Math/IRT/MLE/Truncated.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/MLE/Truncated.hs
@@ -0,0 +1,28 @@
+module Math.IRT.MLE.Truncated
+  ( DF (..)
+  , MLEResult (..)
+  , mleEst
+  ) where
+
+import Data.Default.Class
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.MLE.Internal.Generic
+
+
+data DF = DF { steps         :: !Int
+             , thetaEstimate :: !Double
+             , lower_bound   :: !Double
+             , upper_bound   :: !Double }
+
+instance Default DF where
+  def = DF 10 0.0 (-3.5) 3.5
+
+
+mleEst :: (Distribution d, ContDistr d, DensityDeriv d, LogLikelihood d) => DF -> [Bool] -> [d] -> MLEResult
+mleEst (DF n th lb ub) rs params =
+    let res = generic_mleEst rs params n th
+    in res { theta = min ub $ max lb $ theta res }
diff --git a/Math/IRT/Model/FourPLM.hs b/Math/IRT/Model/FourPLM.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Model/FourPLM.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE GADTs #-}
+module Math.IRT.Model.FourPLM
+  ( FourPLM (..)
+  ) where
+
+import Numeric.AD (Mode, Scalar, auto)
+import Numeric.AD.Mode.Forward.Double
+import qualified Numeric.AD.Mode.Tower as T
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.Model.Generic
+
+
+data FourPLM = FourPLM { discrimination :: !Double
+                       , difficulty     :: !Double
+                       , pseudoGuessing :: !Double
+                       , asymptote      :: !Double
+                       } deriving (Show)
+
+instance Distribution FourPLM where
+    cumulative = cumulative4PL
+
+instance ContDistr FourPLM where
+    density  x = diff (cumulative4PL x)
+    quantile _ = error "This shouldn't be needed"
+
+instance DensityDeriv FourPLM where
+    densityDeriv x = (!! 2) . T.diffs (cumulative4PL x)
+
+instance GenericModel FourPLM where
+    fromRasch         b = FourPLM 1.0 b 0.0 1.0
+    fromOnePLM        b = FourPLM 1.7 b 0.0 1.0
+    fromTwoPLM      a b = FourPLM   a b 0.0 1.0
+    fromThreePLM  a b c = FourPLM   a b   c 1.0
+    fromFourPLM         = FourPLM
+
+instance LogLikelihood FourPLM where
+    logLikelihood = logLikeFunc cumulative4PL
+
+
+cumulative4PL :: (Mode a, Floating a, Scalar a ~ Double) =>
+       FourPLM -- ^The IRT parameters
+    -> a       -- ^Theta
+    -> a
+cumulative4PL params theta =
+    let (a, b, c, d) = makeParamAutos params in
+    c + (              (d - c)
+         -----------------------------------
+         / (1 + exp ((-a) * (theta - b))))
+    where makeParamAutos (FourPLM sa sb sc sd) =
+              let a = auto sa
+                  b = auto sb
+                  c = auto sc
+                  d = auto sd
+              in (a, b, c, d)
diff --git a/Math/IRT/Model/Generic.hs b/Math/IRT/Model/Generic.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Model/Generic.hs
@@ -0,0 +1,8 @@
+module Math.IRT.Model.Generic where
+
+class GenericModel a where
+    fromRasch    :: Double -> a
+    fromOnePLM   :: Double -> a
+    fromTwoPLM   :: Double -> Double -> a
+    fromThreePLM :: Double -> Double -> Double -> a
+    fromFourPLM  :: Double -> Double -> Double -> Double -> a
diff --git a/Math/IRT/Model/OnePLM.hs b/Math/IRT/Model/OnePLM.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Model/OnePLM.hs
@@ -0,0 +1,37 @@
+module Math.IRT.Model.OnePLM
+  ( OnePLM (..)
+  ) where
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution 
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.Model.FourPLM ( FourPLM(..) )
+import Math.IRT.Model.Generic
+
+
+data OnePLM = OnePLM { difficulty :: !Double
+                     } deriving (Show)
+
+instance Distribution OnePLM where
+    cumulative = cumulative . toFourPLM
+
+instance ContDistr OnePLM where
+    density    = density . toFourPLM
+    quantile _ = error "This shouldn't be needed"
+
+instance DensityDeriv OnePLM where
+    densityDeriv = densityDeriv . toFourPLM
+
+instance GenericModel OnePLM where
+    fromRasch           = OnePLM
+    fromOnePLM          = OnePLM
+    fromTwoPLM      _ b = OnePLM b
+    fromThreePLM  _ b _ = OnePLM b
+    fromFourPLM _ b _ _ = OnePLM b
+
+instance LogLikelihood OnePLM where
+    logLikelihood b = logLikelihood b . toFourPLM
+
+toFourPLM :: OnePLM -> FourPLM
+toFourPLM (OnePLM sb) = FourPLM 1.7 sb 0.0 1.0
diff --git a/Math/IRT/Model/Rasch.hs b/Math/IRT/Model/Rasch.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Model/Rasch.hs
@@ -0,0 +1,37 @@
+module Math.IRT.Model.Rasch
+  ( RaschModel (..)
+  ) where
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.Model.FourPLM ( FourPLM(..) )
+import Math.IRT.Model.Generic
+
+
+data RaschModel = RaschModel { difficulty :: !Double
+                             } deriving (Show)
+
+instance Distribution RaschModel where
+    cumulative = cumulative . toFourPLM
+
+instance ContDistr RaschModel where
+    density    = density . toFourPLM
+    quantile _ = error "This shouldn't be needed"
+
+instance DensityDeriv RaschModel where
+    densityDeriv = densityDeriv . toFourPLM
+
+instance GenericModel RaschModel where
+    fromRasch           = RaschModel
+    fromOnePLM          = RaschModel
+    fromTwoPLM      _ b = RaschModel b
+    fromThreePLM  _ b _ = RaschModel b
+    fromFourPLM _ b _ _ = RaschModel b
+
+instance LogLikelihood RaschModel where
+    logLikelihood b = logLikelihood b . toFourPLM
+
+toFourPLM :: RaschModel -> FourPLM
+toFourPLM (RaschModel sb) = FourPLM 1.0 sb 0.0 1.0
diff --git a/Math/IRT/Model/ThreePLM.hs b/Math/IRT/Model/ThreePLM.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Model/ThreePLM.hs
@@ -0,0 +1,39 @@
+module Math.IRT.Model.ThreePLM
+  ( ThreePLM (..)
+  ) where
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.Model.FourPLM ( FourPLM(..) )
+import Math.IRT.Model.Generic
+
+
+data ThreePLM = ThreePLM { discrimination :: !Double
+                         , difficulty     :: !Double
+                         , pseudoGuessing :: !Double
+                         } deriving (Show)
+
+instance Distribution ThreePLM where
+    cumulative = cumulative . toFourPLM
+
+instance ContDistr ThreePLM where
+    density    = density . toFourPLM
+    quantile _ = error "This shouldn't be needed"
+
+instance DensityDeriv ThreePLM where
+    densityDeriv = densityDeriv . toFourPLM
+
+instance GenericModel ThreePLM where
+    fromRasch         b = ThreePLM 1.0 b 0.0
+    fromOnePLM        b = ThreePLM 1.7 b 0.0
+    fromTwoPLM      a b = ThreePLM   a b 0.0
+    fromThreePLM        = ThreePLM
+    fromFourPLM a b c _ = ThreePLM   a b   c
+
+instance LogLikelihood ThreePLM where
+    logLikelihood b = logLikelihood b . toFourPLM
+
+toFourPLM :: ThreePLM -> FourPLM
+toFourPLM (ThreePLM sa sb sc) = FourPLM sa sb sc 1.0
diff --git a/Math/IRT/Model/TwoPLM.hs b/Math/IRT/Model/TwoPLM.hs
new file mode 100644
--- /dev/null
+++ b/Math/IRT/Model/TwoPLM.hs
@@ -0,0 +1,38 @@
+module Math.IRT.Model.TwoPLM
+  ( TwoPLM (..)
+  ) where
+
+import Statistics.Distribution
+
+import Math.IRT.Internal.Distribution
+import Math.IRT.Internal.LogLikelihood
+import Math.IRT.Model.FourPLM ( FourPLM(..) )
+import Math.IRT.Model.Generic
+
+
+data TwoPLM = TwoPLM { discrimination :: !Double
+                     , difficulty     :: !Double
+                     } deriving (Show)
+
+instance Distribution TwoPLM where
+    cumulative = cumulative . toFourPLM
+
+instance ContDistr TwoPLM where
+    density    = density . toFourPLM
+    quantile _ = error "This shouldn't be needed"
+
+instance DensityDeriv TwoPLM where
+    densityDeriv = densityDeriv . toFourPLM
+
+instance GenericModel TwoPLM where
+    fromRasch         b = TwoPLM 1.0 b
+    fromOnePLM        b = TwoPLM 1.7 b
+    fromTwoPLM          = TwoPLM
+    fromThreePLM  a b _ = TwoPLM   a b
+    fromFourPLM a b _ _ = TwoPLM   a b
+
+instance LogLikelihood TwoPLM where
+    logLikelihood b = logLikelihood b . toFourPLM
+
+toFourPLM :: TwoPLM -> FourPLM
+toFourPLM (TwoPLM sa sb) = FourPLM sa sb 0.0 1.0
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+IRT
+===
+
+A Haskell library providing Item Response Theory functions for use in computerized adaptive testing. Provided functionality is similar to the CAT portions of the `catIrt` R library, though only the binary-response model is currently supported.
+
+IRT bucks the standard trend of starting Haskell libraries/executables with 'H', since `hirt` already exists.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/irt.cabal b/irt.cabal
new file mode 100644
--- /dev/null
+++ b/irt.cabal
@@ -0,0 +1,39 @@
+name: irt
+version: 0.2.0.0
+cabal-version: >=1.10
+build-type: Simple
+license: BSD3
+license-file: LICENSE
+maintainer: elliot.robinson@argiopetech.com
+homepage: https://github.com/argiopetech/irt
+synopsis: A Haskell library providing Item Response Theory functions for use in computerized adaptive testing
+category: Math
+author: Elliot Robinson
+extra-source-files:
+    README.md
+
+library
+    exposed-modules:
+        Math.IRT.Model.OnePLM
+        Math.IRT.Model.TwoPLM
+        Math.IRT.Model.ThreePLM
+        Math.IRT.Model.FourPLM
+        Math.IRT.Model.Rasch
+        Math.IRT.Model.Generic
+        Math.IRT.MLE
+        Math.IRT.MLE.Fenced
+        Math.IRT.MLE.Truncated
+        Math.IRT.MLE.Internal.Generic
+        Math.IRT.MLE.Internal.MLEResult
+        Math.IRT.Fisher
+        Math.IRT.Internal.Distribution
+        Math.IRT.Internal.LogLikelihood
+    build-depends:
+        base >=4.7 && <4.10,
+        ad >=4.2 && <4.4,
+        data-default-class ==0.1.*,
+        statistics ==0.13.*
+    default-language: Haskell2010
+    default-extensions: TemplateHaskell
+    other-extensions: GADTs
+
