{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}

#include "unboxery.h"

#ifdef USE_GLASGOW_HACKS
module Main(mainPrimIO) where
import PreludeGlaST
#else
module Main(main) where
#endif

import System.Environment (getArgs)
import Control.Monad (replicateM_)

import Types
import RA
import RC
import RG
import RU

#ifdef USE_UNBOXED_FLOATS
-- EASY WAY:
--atan2Float# :: Float# -> Float# -> Float#
--atan2Float# x y = case atan2 (F# x) (F# y) of { F# z -> z }

atan2Float# :: Float# -> Float# -> Float#
atan2Float# y x  =
        if x `geFloat#` 0.0# then
                if y `eqFloat#` 0.0# then
                        0.0#
                else
                        atanFloat# (y `divideFloat#` x)
        else if x `ltFloat#` 0.0# then
                if y `eqFloat#` 0.0# then
                        pi#
                else
                        atanFloat# (y `divideFloat#` x) `plusFloat#` pi#
        else if y `gtFloat#` 0.0# then
                pi# `divideFloat#` 2.0#

        else
                negpi# `divideFloat#` 2.0#

        where pi# =     3.1415926536#
              negpi# = -3.1415926536#
#endif

--  File: "nucleic2.m"
--
--  Author: Marc Feeley (feeley@iro.umontreal.ca)
--
--  Last modified: June 6, 1994
--
--  This program is a modified version of the program described in the paper:
--
--    M. Feeley, M. Turcotte, G. Lapalme, "Using Multilisp for Solving
--    Constraint Satisfaction Problems: an Application to Nucleic Acid 3D
--    Structure Determination" published in the journal "Lisp and Symbolic
--    Computation".
--
--  The differences between this program and the original are described in
--  the paper:
--
--    "???" published in the "Journal of Functional Programming".

-- -- POINTS ------------------------------------------------------------------

pt_sub (Pt x1 y1 z1) (Pt x2 y2 z2) = Pt (x1 _SUB_ x2) (y1 _SUB_ y2) (z1 _SUB_ z2)

pt_dist (Pt x1 y1 z1) (Pt x2 y2 z2) = _SQRT_ ((dx _MUL_ dx) _ADD_ (dy _MUL_ dy) _ADD_ (dz _MUL_ dz))
    where dx = x1 _SUB_ x2
          dy = y1 _SUB_ y2
          dz = z1 _SUB_ z2

pt_phi (Pt x y z) = _ATAN2_ ((_COS_ b _MUL_ z) _ADD_ (_SIN_ b _MUL_ x)) y
    where b = _ATAN2_ x z

pt_theta (Pt x y z) = _ATAN2_ x z

-- -- COORDINATE TRANSFORMATIONS ----------------------------------------------

-- The notation for the transformations follows "Paul, R.P. (1981) Robot
-- Manipulators.  MIT Press." with the exception that our transformation
-- matrices don't have the perspective terms and are the transpose of
-- Paul's one.  See also "M\"antyl\"a, M. (1985) An Introduction to
-- Solid Modeling, Computer Science Press" Appendix A.
--
-- The components of a transformation matrix are named like this:
--
--  a  b  c
--  d  e  f
--  g  h  i
-- tx ty tz
--
-- The components tx, ty, and tz are the translation vector.

tfo_id = Tfo FL_LIT(1.0) FL_LIT(0.0) FL_LIT(0.0) FL_LIT(0.0) FL_LIT(1.0) FL_LIT(0.0) FL_LIT(0.0) FL_LIT(0.0) FL_LIT(1.0) FL_LIT(0.0) FL_LIT(0.0) FL_LIT(0.0)

-- The function "tfo-apply" multiplies a transformation matrix, tfo, by a
-- point vector, p.  The result is a new point.

tfo_apply (Tfo a b c d e f g h i tx ty tz) (Pt x y z)
  = Pt ((x _MUL_ a) _ADD_ (y _MUL_ d) _ADD_ (z _MUL_ g) _ADD_ tx)
       ((x _MUL_ b) _ADD_ (y _MUL_ e) _ADD_ (z _MUL_ h) _ADD_ ty)
       ((x _MUL_ c) _ADD_ (y _MUL_ f) _ADD_ (z _MUL_ i) _ADD_ tz)

-- The function "tfo-combine" multiplies two transformation matrices A and B.
-- The result is a new matrix which cumulates the transformations described
-- by A and B.

tfo_combine
  (Tfo a_a a_b a_c a_d a_e a_f a_g a_h a_i a_tx a_ty a_tz)
  (Tfo b_a b_b b_c b_d b_e b_f b_g b_h b_i b_tx b_ty b_tz)
  = Tfo
      ((a_a _MUL_ b_a) _ADD_ (a_b _MUL_ b_d) _ADD_ (a_c _MUL_ b_g))
      ((a_a _MUL_ b_b) _ADD_ (a_b _MUL_ b_e) _ADD_ (a_c _MUL_ b_h))
      ((a_a _MUL_ b_c) _ADD_ (a_b _MUL_ b_f) _ADD_ (a_c _MUL_ b_i))
      ((a_d _MUL_ b_a) _ADD_ (a_e _MUL_ b_d) _ADD_ (a_f _MUL_ b_g))
      ((a_d _MUL_ b_b) _ADD_ (a_e _MUL_ b_e) _ADD_ (a_f _MUL_ b_h))
      ((a_d _MUL_ b_c) _ADD_ (a_e _MUL_ b_f) _ADD_ (a_f _MUL_ b_i))
      ((a_g _MUL_ b_a) _ADD_ (a_h _MUL_ b_d) _ADD_ (a_i _MUL_ b_g))
      ((a_g _MUL_ b_b) _ADD_ (a_h _MUL_ b_e) _ADD_ (a_i _MUL_ b_h))
      ((a_g _MUL_ b_c) _ADD_ (a_h _MUL_ b_f) _ADD_ (a_i _MUL_ b_i))
      ((a_tx _MUL_ b_a) _ADD_ (a_ty _MUL_ b_d) _ADD_ (a_tz _MUL_ b_g) _ADD_ b_tx)
      ((a_tx _MUL_ b_b) _ADD_ (a_ty _MUL_ b_e) _ADD_ (a_tz _MUL_ b_h) _ADD_ b_ty)
      ((a_tx _MUL_ b_c) _ADD_ (a_ty _MUL_ b_f) _ADD_ (a_tz _MUL_ b_i) _ADD_ b_tz)

-- The function "tfo-inv-ortho" computes the inverse of a homogeneous
-- transformation matrix.

tfo_inv_ortho (Tfo a b c d e f g h i tx ty tz)
  = Tfo
     a d g
     b e h
     c f i
     (_NEG_ ((a _MUL_ tx) _ADD_ (b _MUL_ ty) _ADD_ (c _MUL_ tz)))
     (_NEG_ ((d _MUL_ tx) _ADD_ (e _MUL_ ty) _ADD_ (f _MUL_ tz)))
     (_NEG_ ((g _MUL_ tx) _ADD_ (h _MUL_ ty) _ADD_ (i _MUL_ tz)))

-- Given three points p1, p2, and p3, the function "tfo-align" computes
-- a transformation matrix such that point p1 gets mapped to (0,0,0), p2 gets
-- mapped to the Y axis and p3 gets mapped to the YZ plane.

tfo_align (Pt x1 y1 z1) (Pt x2 y2 z2) (Pt x3 y3 z3)
  = Tfo
      ((cost _MUL_ cosr) _SUB_ (cospsint _MUL_ sinr))
      sinpsint
      ((cost _MUL_ sinr) _ADD_ (cospsint _MUL_ cosr))
      (sinp _MUL_ sinr)
      cosp
      (_NEG_ (sinp _MUL_ cosr))
      ((_NEG_ (sint _MUL_ cosr)) _SUB_ (cospcost _MUL_ sinr))
      sinpcost
      ((_NEG_ (sint _MUL_ sinr)) _ADD_ (cospcost _MUL_ cosr))
      ((x _MUL_ cosr) _SUB_ (z _MUL_ sinr))
      y
      ((x _MUL_ sinr) _ADD_ (z _MUL_ cosr))
    where
      x31 = x3 _SUB_ x1
      y31 = y3 _SUB_ y1
      z31 = z3 _SUB_ z1
      rotpy = pt_sub (Pt x2 y2 z2) (Pt x1 y1 z1)
      phi = pt_phi rotpy
      theta = pt_theta rotpy
      sinp = _SIN_ phi
      sint = _SIN_ theta
      cosp = _COS_ phi
      cost = _COS_ theta
      sinpsint = sinp _MUL_ sint
      sinpcost = sinp _MUL_ cost
      cospsint = cosp _MUL_ sint
      cospcost = cosp _MUL_ cost
      rotpz = Pt
                ((cost _MUL_ x31) _SUB_ (sint _MUL_ z31))
                ((sinpsint _MUL_ x31) _ADD_
                 (cosp _MUL_ y31) _ADD_
                 (sinpcost _MUL_ z31))
                ((cospsint _MUL_ x31) _ADD_
                 (_NEG_ (sinp _MUL_ y31)) _ADD_
                 (cospcost _MUL_ z31))
      rho = pt_theta rotpz
      cosr = _COS_ rho
      sinr = _SIN_ rho
      x = ((_NEG_ (x1 _MUL_ cost)) _ADD_ (z1 _MUL_ sint))
      y = (((_NEG_ (x1 _MUL_ sinpsint)) _SUB_ (y1 _MUL_ cosp)) _SUB_
           (z1 _MUL_ sinpcost))
      z = (((_NEG_ (x1 _MUL_ cospsint)) _ADD_ (y1 _MUL_ sinp)) _SUB_
           (z1 _MUL_ cospcost))

-- -- NUCLEIC ACID CONFORMATIONS DATA BASE ------------------------------------

-- Numbering of atoms follows the paper:
--
-- IUPAC-IUB Joint Commission on Biochemical Nomenclature (JCBN)
-- (1983) Abbreviations and Symbols for the Description of
-- Conformations of Polynucleotide Chains.  Eur. J. Biochem 131,
-- 9-15.

-- Define part common to all 4 nucleotide types.

--  dgf_base_tfo  ; defines the standard position for wc and wc_dumas
--  p_o3XXX_275_tfo ; defines the standard position for the connect function
--  p_o3XXX_180_tfo
--  p_o3XXX_60_tfo
--  p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX c3XXX
--  h3XXX o3XXX n1 n3 c2 c4 c5 c6

is_A (Nuc
        dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
        p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
        c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
        (A n6 n7 n9 c8 h2 h61 h62 h8))
  = True
is_A x
  = False

is_C (Nuc
        dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
        p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
        c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
        (C n4 o2 h41 h42 h5 h6))
  = True
is_C x
  = False

is_G (Nuc
        dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
        p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
        c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
        (G n2 n7 n9 c8 o6 h1 h21 h22 h8))
  = True
is_G x
  = False

nuc_C1XXX
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = c1XXX

nuc_C2
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = c2

nuc_C3XXX
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = c3XXX

nuc_C4
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = c4

nuc_C4XXX
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = c4XXX

nuc_N1
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = n1

nuc_O3XXX
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = o3XXX

nuc_P
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = p

nuc_dgf_base_tfo
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = dgf_base_tfo

nuc_p_o3XXX_180_tfo
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = p_o3XXX_180_tfo

nuc_p_o3XXX_275_tfo
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = p_o3XXX_275_tfo

nuc_p_o3XXX_60_tfo
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = p_o3XXX_60_tfo

rA_N9
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (A n6 n7 n9 c8 h2 h61 h62 h8))
  = n9

rG_N9
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (G n2 n7 n9 c8 o6 h1 h21 h22 h8))
  = n9

-- Define remaining atoms for each nucleotide type.

-- Database of nucleotide conformations:


-- -- PARTIAL INSTANTIATIONS --------------------------------------------------

data Var = Var INT_TY Tfo Nuc

-- If you want lazy computation, comment the next two definitions and
-- uncomment the alternative definitions that follow.

#ifdef USE_HARTEL_LAZINESS
mk_var i t n = Var i t (make_relative_nuc t n)
absolute_pos (Var i t n) p = p
#else
mk_var i t n = Var i t n
absolute_pos (Var i t n) p = tfo_apply t p
#endif

atom_pos atom v@(Var i t n) = absolute_pos v (atom n)

get_var id (v@(Var i t n):lst) = if i _EQ_INT_ id then v else get_var id lst

make_relative_nuc
  t
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (A n6 n7 n9 c8 h2 h61 h62 h8))
  = Nuc
      dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
      (tfo_apply t p)
      (tfo_apply t o1p)
      (tfo_apply t o2p)
      (tfo_apply t o5XXX)
      (tfo_apply t c5XXX)
      (tfo_apply t h5XXX)
      (tfo_apply t h5XXXXXX)
      (tfo_apply t c4XXX)
      (tfo_apply t h4XXX)
      (tfo_apply t o4XXX)
      (tfo_apply t c1XXX)
      (tfo_apply t h1XXX)
      (tfo_apply t c2XXX)
      (tfo_apply t h2XXXXXX)
      (tfo_apply t o2XXX)
      (tfo_apply t h2XXX)
      (tfo_apply t c3XXX)
      (tfo_apply t h3XXX)
      (tfo_apply t o3XXX)
      (tfo_apply t n1)
      (tfo_apply t n3)
      (tfo_apply t c2)
      (tfo_apply t c4)
      (tfo_apply t c5)
      (tfo_apply t c6)
      (A
      (tfo_apply t n6)
      (tfo_apply t n7)
      (tfo_apply t n9)
      (tfo_apply t c8)
      (tfo_apply t h2)
      (tfo_apply t h61)
      (tfo_apply t h62)
      (tfo_apply t h8)
      )

make_relative_nuc
  t
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (C n4 o2 h41 h42 h5 h6))
  = Nuc
      dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
      (tfo_apply t p)
      (tfo_apply t o1p)
      (tfo_apply t o2p)
      (tfo_apply t o5XXX)
      (tfo_apply t c5XXX)
      (tfo_apply t h5XXX)
      (tfo_apply t h5XXXXXX)
      (tfo_apply t c4XXX)
      (tfo_apply t h4XXX)
      (tfo_apply t o4XXX)
      (tfo_apply t c1XXX)
      (tfo_apply t h1XXX)
      (tfo_apply t c2XXX)
      (tfo_apply t h2XXXXXX)
      (tfo_apply t o2XXX)
      (tfo_apply t h2XXX)
      (tfo_apply t c3XXX)
      (tfo_apply t h3XXX)
      (tfo_apply t o3XXX)
      (tfo_apply t n1)
      (tfo_apply t n3)
      (tfo_apply t c2)
      (tfo_apply t c4)
      (tfo_apply t c5)
      (tfo_apply t c6)
      (C
      (tfo_apply t n4)
      (tfo_apply t o2)
      (tfo_apply t h41)
      (tfo_apply t h42)
      (tfo_apply t h5)
      (tfo_apply t h6)
      )

make_relative_nuc
  t
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (G n2 n7 n9 c8 o6 h1 h21 h22 h8))
  = Nuc
      dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
      (tfo_apply t p)
      (tfo_apply t o1p)
      (tfo_apply t o2p)
      (tfo_apply t o5XXX)
      (tfo_apply t c5XXX)
      (tfo_apply t h5XXX)
      (tfo_apply t h5XXXXXX)
      (tfo_apply t c4XXX)
      (tfo_apply t h4XXX)
      (tfo_apply t o4XXX)
      (tfo_apply t c1XXX)
      (tfo_apply t h1XXX)
      (tfo_apply t c2XXX)
      (tfo_apply t h2XXXXXX)
      (tfo_apply t o2XXX)
      (tfo_apply t h2XXX)
      (tfo_apply t c3XXX)
      (tfo_apply t h3XXX)
      (tfo_apply t o3XXX)
      (tfo_apply t n1)
      (tfo_apply t n3)
      (tfo_apply t c2)
      (tfo_apply t c4)
      (tfo_apply t c5)
      (tfo_apply t c6)
      (G
      (tfo_apply t n2)
      (tfo_apply t n7)
      (tfo_apply t n9)
      (tfo_apply t c8)
      (tfo_apply t o6)
      (tfo_apply t h1)
      (tfo_apply t h21)
      (tfo_apply t h22)
      (tfo_apply t h8)
      )

make_relative_nuc
  t
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (U o2 o4 h3 h5 h6))
  = Nuc
      dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
      (tfo_apply t p)
      (tfo_apply t o1p)
      (tfo_apply t o2p)
      (tfo_apply t o5XXX)
      (tfo_apply t c5XXX)
      (tfo_apply t h5XXX)
      (tfo_apply t h5XXXXXX)
      (tfo_apply t c4XXX)
      (tfo_apply t h4XXX)
      (tfo_apply t o4XXX)
      (tfo_apply t c1XXX)
      (tfo_apply t h1XXX)
      (tfo_apply t c2XXX)
      (tfo_apply t h2XXXXXX)
      (tfo_apply t o2XXX)
      (tfo_apply t h2XXX)
      (tfo_apply t c3XXX)
      (tfo_apply t h3XXX)
      (tfo_apply t o3XXX)
      (tfo_apply t n1)
      (tfo_apply t n3)
      (tfo_apply t c2)
      (tfo_apply t c4)
      (tfo_apply t c5)
      (tfo_apply t c6)
      (U
      (tfo_apply t o2)
      (tfo_apply t o4)
      (tfo_apply t h3)
      (tfo_apply t h5)
      (tfo_apply t h6)
      )

-- -- SEARCH ------------------------------------------------------------------

-- Sequential backtracking algorithm

search partial_inst [] constraint = [partial_inst]
search partial_inst (h:t) constraint = try_assignments (h partial_inst)
    where try_assignments [] = []
          try_assignments (v:vs) | constraint v partial_inst = (search (v:partial_inst) t constraint) ++ (try_assignments vs)
                                 | otherwise                 = try_assignments vs

-- -- DOMAINS -----------------------------------------------------------------

-- Primary structure:   strand A CUGCCACGUCUG, strand B CAGACGUGGCAG
--
-- Secondary structure: strand A CUGCCACGUCUG
--                               ------------
--                               GACGGUGCAGAC strand B
--
-- Tertiary structure:
--
--    5XXX end of strand A C1----G12 3XXX end of strand B
--                     U2-------A11
--                    G3-------C10
--                    C4-----G9
--                     C5---G8
--                        A6
--                      G6-C7
--                     C5----G8
--                    A4-------U9
--                    G3--------C10
--                     A2-------U11
--   5' end of strand B C1----G12 3' end of strand A
--
-- "helix", "stacked" and "connected" describe the spatial relationship
-- between two consecutive nucleotides. E.g. the nucleotides C1 and U2
-- from the strand A.
--
-- "wc" (stands for Watson-Crick and is a type of base-pairing),
-- and "wc-dumas" describe the spatial relationship between
-- nucleotides from two chains that are growing in opposite directions.
-- E.g. the nucleotides C1 from strand A and G12 from strand B.

-- Dynamic Domains

-- Given,
--   "ref" a nucleotide which is already positioned,
--   "nuc" the nucleotide to be placed,
--   and "tfo" a transformation matrix which expresses the desired
--   relationship between "ref" and "nuc",
-- the function "dgf-base" computes the transformation matrix that
-- places the nucleotide "nuc" in the given relationship to "ref".

dgf_base tfo v@(Var i t n) nuc
  = tfo_combine (nuc_dgf_base_tfo nuc)
                (tfo_combine tfo (tfo_inv_ortho x))
    where
       x | is_A n
         = tfo_align (atom_pos nuc_C1XXX v)
                     (atom_pos rA_N9   v)
                     (atom_pos nuc_C4  v)
         | is_C n
         = tfo_align (atom_pos nuc_C1XXX v)
                     (atom_pos nuc_N1  v)
                     (atom_pos nuc_C2  v)
         | is_G n
         = tfo_align (atom_pos nuc_C1XXX v)
                     (atom_pos rG_N9   v)
                     (atom_pos nuc_C4  v)
         | otherwise
         = tfo_align (atom_pos nuc_C1XXX v)
                     (atom_pos nuc_N1  v)
                     (atom_pos nuc_C2  v)

-- Placement of first nucleotide.

reference nuc i partial_inst = [ mk_var i tfo_id nuc ]

-- The transformation matrix for wc is from:
--
-- Chandrasekaran R. et al (1989) A Re-Examination of the Crystal
-- Structure of A-DNA Using Fiber Diffraction Data. J. Biomol.
-- Struct. & Dynamics 6(6):1189-1202.

wc_tfo
  = Tfo
      FL_LIT(-1.0000)  FL_LIT(0.0028) FL_LIT(-0.0019)
       FL_LIT(0.0028)  FL_LIT(0.3468) FL_LIT(-0.9379)
      FL_LIT(-0.0019) FL_LIT(-0.9379) FL_LIT(-0.3468)
      FL_LIT(-0.0080)  FL_LIT(6.0730)  FL_LIT(8.7208)

wc nuc i j partial_inst
  = [ mk_var i (dgf_base wc_tfo (get_var j partial_inst) nuc) nuc ]

wc_dumas_tfo
  = Tfo
      FL_LIT(-0.9737) FL_LIT(-0.1834)  FL_LIT(0.1352)
      FL_LIT(-0.1779)  FL_LIT(0.2417) FL_LIT(-0.9539)
       FL_LIT(0.1422) FL_LIT(-0.9529) FL_LIT(-0.2679)
       FL_LIT(0.4837)  FL_LIT(6.2649)  FL_LIT(8.0285)

wc_dumas nuc i j partial_inst
  = [ mk_var i (dgf_base wc_dumas_tfo (get_var j partial_inst) nuc) nuc ]

helix5XXX_tfo
  = Tfo
       FL_LIT(0.9886) FL_LIT(-0.0961)  FL_LIT(0.1156)
       FL_LIT(0.1424)  FL_LIT(0.8452) FL_LIT(-0.5152)
      FL_LIT(-0.0482)  FL_LIT(0.5258)  FL_LIT(0.8492)
      FL_LIT(-3.8737)  FL_LIT(0.5480)  FL_LIT(3.8024)

helix5XXX nuc i j partial_inst
  = [ mk_var i (dgf_base helix5XXX_tfo (get_var j partial_inst) nuc) nuc ]

helix3XXX_tfo
  = Tfo
       FL_LIT(0.9886)  FL_LIT(0.1424) FL_LIT(-0.0482)
      FL_LIT(-0.0961)  FL_LIT(0.8452)  FL_LIT(0.5258)
       FL_LIT(0.1156) FL_LIT(-0.5152)  FL_LIT(0.8492)
       FL_LIT(3.4426)  FL_LIT(2.0474) FL_LIT(-3.7042)

helix3XXX nuc i j partial_inst
  = [ mk_var i (dgf_base helix3XXX_tfo (get_var j partial_inst) nuc) nuc ]

g37_a38_tfo
  = Tfo
       FL_LIT(0.9991)  FL_LIT(0.0164) FL_LIT(-0.0387)
      FL_LIT(-0.0375)  FL_LIT(0.7616) FL_LIT(-0.6470)
       FL_LIT(0.0189)  FL_LIT(0.6478)  FL_LIT(0.7615)
      FL_LIT(-3.3018)  FL_LIT(0.9975)  FL_LIT(2.5585)

g37_a38 nuc i j partial_inst
  = mk_var i (dgf_base g37_a38_tfo (get_var j partial_inst) nuc) nuc

stacked5XXX nuc i j partial_inst
  = (g37_a38 nuc i j partial_inst) : (helix5XXX nuc i j partial_inst)

a38_g37_tfo
  = Tfo
       FL_LIT(0.9991) FL_LIT(-0.0375)  FL_LIT(0.0189)
       FL_LIT(0.0164)  FL_LIT(0.7616)  FL_LIT(0.6478)
      FL_LIT(-0.0387) FL_LIT(-0.6470)  FL_LIT(0.7615)
       FL_LIT(3.3819)  FL_LIT(0.7718) FL_LIT(-2.5321)

a38_g37 nuc i j partial_inst
  = mk_var i (dgf_base a38_g37_tfo (get_var j partial_inst) nuc) nuc

stacked3XXX nuc i j partial_inst
  = a38_g37 nuc i j partial_inst : helix3XXX nuc i j partial_inst

p_o3XXX nucs i j partial_inst
  = generate [] nucs
    where
      ref = get_var j partial_inst
      align = tfo_inv_ortho
                (tfo_align (atom_pos nuc_O3XXX ref)
                           (atom_pos nuc_C3XXX ref)
                           (atom_pos nuc_C4XXX ref))
      generate domains []
        = domains
      generate domains (n:ns)
        = generate
           ((mk_var i (tfo_combine (nuc_p_o3XXX_60_tfo n) align) n):
            (mk_var i (tfo_combine (nuc_p_o3XXX_180_tfo n) align) n):
            (mk_var i (tfo_combine (nuc_p_o3XXX_275_tfo n) align) n):domains)
           ns

-- -- PROBLEM STATEMENT -------------------------------------------------------

-- Define anticodon problem -- Science 253:1255 Figure 3a, 3b and 3c

anticodon_domains
  = [
     reference rC  INT_LIT(27),
     helix5XXX   rC  INT_LIT(28) INT_LIT(27),
     helix5XXX   rA  INT_LIT(29) INT_LIT(28),
     helix5XXX   rG  INT_LIT(30) INT_LIT(29),
     helix5XXX   rA  INT_LIT(31) INT_LIT(30),
     wc        rU  INT_LIT(39) INT_LIT(31),
     helix5XXX   rC  INT_LIT(40) INT_LIT(39),
     helix5XXX   rU  INT_LIT(41) INT_LIT(40),
     helix5XXX   rG  INT_LIT(42) INT_LIT(41),
     helix5XXX   rG  INT_LIT(43) INT_LIT(42),
     stacked3XXX rA  INT_LIT(38) INT_LIT(39),
     stacked3XXX rG  INT_LIT(37) INT_LIT(38),
     stacked3XXX rA  INT_LIT(36) INT_LIT(37),
     stacked3XXX rA  INT_LIT(35) INT_LIT(36),
     stacked3XXX rG  INT_LIT(34) INT_LIT(35), -- <-. Distance
     p_o3XXX     rCs INT_LIT(32) INT_LIT(31), --  | Constraint
     p_o3XXX     rUs INT_LIT(33) INT_LIT(32)  -- <-XXX 3.0 Angstroms
    ]

-- Anticodon constraint

anticodon_constraint v@(Var INT_LIT(33) t n) partial_inst = dist INT_LIT(34) _LE_FLT_ FL_LIT(3.0)
    where dist j = pt_dist p o3XXX
                   where p = atom_pos nuc_P (get_var j partial_inst)
                         o3XXX = atom_pos nuc_O3XXX v
anticodon_constraint _ _ = True

anticodon = search [] anticodon_domains anticodon_constraint

-- Define pseudoknot problem -- Science 253:1255 Figure 4a and 4b

pseudoknot_domains
  = [
     reference rA  INT_LIT(23),
     wc_dumas  rU   INT_LIT(8) INT_LIT(23),
     helix3XXX   rG  INT_LIT(22) INT_LIT(23),
     wc_dumas  rC   INT_LIT(9) INT_LIT(22),
     helix3XXX   rG  INT_LIT(21) INT_LIT(22),
     wc_dumas  rC  INT_LIT(10) INT_LIT(21),
     helix3XXX   rC  INT_LIT(20) INT_LIT(21),
     wc_dumas  rG  INT_LIT(11) INT_LIT(20),
     helix3XXX   rU'{-'-} INT_LIT(19) INT_LIT(20), -- <-.
     wc_dumas  rA  INT_LIT(12) INT_LIT(19), --  | Distance
--                        --  | Constraint
-- Helix 1                --  | 4.0 Angstroms
     helix3XXX   rC   INT_LIT(3) INT_LIT(19), --  |
     wc_dumas  rG  INT_LIT(13)  INT_LIT(3), --  |
     helix3XXX   rC   INT_LIT(2)  INT_LIT(3), --  |
     wc_dumas  rG  INT_LIT(14)  INT_LIT(2), --  |
     helix3XXX   rC   INT_LIT(1)  INT_LIT(2), --  |
     wc_dumas  rG'{-'-} INT_LIT(15)  INT_LIT(1), --  |
--                        --  |
-- L2 LOOP                --  |
     p_o3XXX     rUs INT_LIT(16) INT_LIT(15), --  |
     p_o3XXX     rCs INT_LIT(17) INT_LIT(16), --  |
     p_o3XXX     rAs INT_LIT(18) INT_LIT(17), -- <-XXX
--
-- L1 LOOP
     helix3XXX   rU   INT_LIT(7)  INT_LIT(8), -- <-.
     p_o3XXX     rCs  INT_LIT(4)  INT_LIT(3), --  | Constraint
     stacked5XXX rU   INT_LIT(5)  INT_LIT(4), --  | 4.5 Angstroms
     stacked5XXX rC   INT_LIT(6)  INT_LIT(5)  -- <-XXX
    ]

-- Pseudoknot constraint

pseudoknot_constraint v@(Var i t n) partial_inst
  | i _EQ_INT_ INT_LIT(18)
  = dist INT_LIT(19) _LE_FLT_ FL_LIT(4.0)
  | i _EQ_INT_ INT_LIT(6)
  = dist INT_LIT(7) _LE_FLT_ FL_LIT(4.5)
  | otherwise
  = True
    where dist j = pt_dist p o3XXX
                   where p = atom_pos nuc_P (get_var j partial_inst)
                         o3XXX = atom_pos nuc_O3XXX v

pseudoknot s = search xs pseudoknot_domains pseudoknot_constraint
  where
    xs | s == "nope" = error "This will never be the result of getArgs"
       | otherwise   = []

-- -- TESTING -----------------------------------------------------------------

list_of_atoms n = list_of_common_atoms n ++ list_of_specific_atoms n

list_of_common_atoms
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    x)
  = [p,o1p,o2p,o5XXX,c5XXX,h5XXX,h5XXXXXX,c4XXX,h4XXX,o4XXX,c1XXX,h1XXX,c2XXX,h2XXXXXX,o2XXX,h2XXX,c3XXX,
     h3XXX,o3XXX,n1,n3,c2,c4,c5,c6]

list_of_specific_atoms
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (A n6 n7 n9 c8 h2 h61 h62 h8))
  = [n6,n7,n9,c8,h2,h61,h62,h8]

list_of_specific_atoms
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (C n4 o2 h41 h42 h5 h6))
  = [n4,o2,h41,h42,h5,h6]

list_of_specific_atoms
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX n1 n3 c2 c4 c5 c6
    (G n2 n7 n9 c8 o6 h1 h21 h22 h8))
  = [n2,n7,n9,c8,o6,h1,h21,h22,h8]

list_of_specific_atoms
  (Nuc
    dgf_base_tfo p_o3XXX_275_tfo p_o3XXX_180_tfo p_o3XXX_60_tfo
    p o1p o2p o5XXX c5XXX h5XXX h5XXXXXX c4XXX h4XXX o4XXX c1XXX h1XXX c2XXX h2XXXXXX o2XXX h2XXX
    c3XXX h3XXX o3XXX  n1 n3 c2 c4 c5 c6
    (U o2 o4 h3 h5 h6))
  = [o2,o4,h3,h5,h6]

var_most_distant_atom v@(Var i t n)
#ifdef USE_GLASGOW_HACKS
  = maximum_map distance (list_of_atoms n)
#else
  = maximum (map distance (list_of_atoms n))
#endif
#ifdef USE_UNBOXED_FLOATS
    -- partain: can't do lazy pattern-match if x y z are unboxed...
    where distance p = case (absolute_pos v p) of { Pt x y z ->
                       _SQRT_ ((x _MUL_ x) _ADD_ (y _MUL_ y) _ADD_ (z _MUL_ z))
                       }
#else
    -- original (partain)
    where distance p = BOX_FLOAT(_SQRT_ ((x _MUL_ x) _ADD_ (y _MUL_ y) _ADD_ (z _MUL_ z)))
                       where Pt x y z = absolute_pos v p
#endif

#ifdef USE_GLASGOW_HACKS
sol_most_distant_atom s = maximum_map var_most_distant_atom s

most_distant_atom sols = maximum_map sol_most_distant_atom sols
#else
sol_most_distant_atom s = maximum (map var_most_distant_atom s)

most_distant_atom sols = maximum (map sol_most_distant_atom sols)
#endif

#ifdef USE_GLASGOW_HACKS
maximum_map :: (a->Float#) -> [a]->Float#
maximum_map f (h:t) =
        max f t (f h)
        where max f (x:xs) m = max f xs (let fx = f x in if fx `gtFloat#` m then fx else m)
              max f [] m = m
              max :: (a->Float#) -> [a] -> Float# -> Float#
#endif

check = length (pseudoknot "")

-- To run program, evaluate: run

-- Printing is slow because of the way the Prelude's structured.
-- We use direct C Calls and monadic IO instead

#ifdef USE_GLASGOW_HACKS
mainPrimIO =
        let most_distant = most_distant_atom pseudoknot in
        _ccall_ printf ``"%f\n"'' (F# most_distant) `seqPrimIO`
        returnPrimIO ()
#else
main = do
        [n] <- getArgs
        replicateM_ (read n) $ do
                [n] <- getArgs
                most_distant_atom (pseudoknot n) `seq` return ()
#endif
