diff --git a/mrdna/model/nonbonded.py b/mrdna/model/nonbonded.py
index 3dc4a6cbbd62930619685c551bb0ccb154e84634..53ce19fe4c16558acd284d0ce5d70bc0dee70124 100644
--- a/mrdna/model/nonbonded.py
+++ b/mrdna/model/nonbonded.py
@@ -57,12 +57,11 @@ class TabulatedPotential(NonbondedScheme):
             copyfile(self.tableFile, filename)
 
 ## Bonded potentials
-class HarmonicPotential():
-    def __init__(self, k, r0, rRange=(0,50), resolution=0.1, maxForce=None, max_potential=None, prefix="potentials/"):
-        self.k = k
+class BasePotential():
+    def __init__(self, r0, rRange=(0,50), resolution=0.1, maxForce=None, max_potential=None, prefix="potentials/"):
         self.r0 = r0
         self.rRange = rRange
-        self.resolution = 0.1
+        self.resolution = resolution
         self.maxForce = maxForce
         self.prefix = prefix
         self.periodic = False
@@ -71,9 +70,10 @@ class HarmonicPotential():
         self.kscale_ = None     # only used for 
 
     def filename(self):
-        # raise NotImplementedError("Not implemented")
-        return "%s%s-%.3f-%.3f.dat" % (self.prefix, self.type_,
-                                       self.k*self.kscale_, self.r0)
+        raise NotImplementedError("Not implemented")
+
+    def potential(self,dr):
+        raise NotImplementedError("Not implemented")
 
     def __str__(self):
         return self.filename()
@@ -89,7 +89,7 @@ class HarmonicPotential():
             assert(rSpan > 0)
             dr = np.mod( dr+0.5*rSpan, rSpan) - 0.5*rSpan 
 
-        u = 0.5*self.k*dr**2
+        u = self.potential(dr)
 
         maxForce = self.maxForce
         if maxForce is not None:
@@ -115,6 +115,18 @@ class HarmonicPotential():
 
         np.savetxt( self.filename(), np.array([r, u]).T, fmt="%f" )
 
+class HarmonicPotential(BasePotential):
+    def __init__(self, k, r0, rRange, resolution, maxForce, max_potential, prefix):
+        self.k = k
+        self.kscale_ = None
+        BasePotential.__init__(self, r0, rRange, resolution, maxForce, max_potential, prefix)
+
+    def filename(self):
+        return "%s%s-%.3f-%.3f.dat" % (self.prefix, self.type_,
+                                       self.k*self.kscale_, self.r0)
+    def potential(self,dr):
+        return 0.5*self.k*dr**2
+
     def __hash__(self):
         assert(self.type_ != "None")
         return hash((self.type_, self.k, self.r0, self.rRange, self.resolution, self.maxForce, self.max_potential, self.prefix, self.periodic))
@@ -149,3 +161,69 @@ class HarmonicDihedral(HarmonicPotential):
         self.type_ = "dihedral"
         self.kscale_ = (180.0/np.pi)**2
 
+class WLCSKBond(BasePotential):
+    """ ## https://aip.scitation.org/doi/full/10.1063/1.4968020 """
+    def __init__(self, d, lp, kT, rRange=(0,50), resolution=0.02, maxForce=100, max_potential=None, prefix="potentials/"):
+        BasePotential.__init__(self, 0, rRange, resolution, maxForce, max_potential, prefix)
+        self.type_ = "wlcbond"
+        self.d = d          # separation
+        self.lp = lp            # persistence length
+        self.kT = kT
+
+    def filename(self):
+        return "%s%s-%.3f-%.3f.dat" % (self.prefix, self.type_,
+                                       self.d, self.lp)
+    def potential(self, dr):
+        nk = self.d / (2*self.lp)
+        q2 = (dr / self.d)**2
+        a1,a2 = 1, -7.0/(2*nk)
+        a3 = 3.0/32 - 3.0/(8*nk) - 6.0/(4*nk**2)
+        p0,p1,p2,p3,p4 = 13.0/32, 3.4719,2.5064,-1.2906,0.6482
+        a4 = (p0 + p1/(2*nk) + p2*(2*nk)**-2) / (1+p3/(2*nk)+p4*(2*nk)**-2)
+        with np.errstate(divide='ignore',invalid='ignore'):
+            u = self.kT * nk * ( a1/(1-q2) - a2*np.log(1-q2) + a3*q2 - 0.5*a4*q2*(q2-2) )
+        max_force = np.diff(u[q2<1][-2:]) / np.diff(dr).mean()
+        max_u = u[q2<1][-1]
+        max_dr = dr[q2<1][-2]
+        assert( max_force >= 0 )
+        u[q2>=1] = (dr[q2>=1]-max_dr)*max_force + max_u
+        return u
+
+    def __hash__(self):
+        assert(self.type_ != "None")
+        return hash((self.type_, self.d, self.lp, self.kT, self.rRange, self.resolution, self.maxForce, self.max_potential, self.prefix, self.periodic))
+
+    def __eq__(self, other):
+        for a in ("type_", "d", "lp", "kT" "rRange", "resolution", "maxForce", "max_potential", "prefix", "periodic"):
+            if self.__dict__[a] != other.__dict__[a]:
+                return False
+        return True
+
+class WLCSKAngle(BasePotential):
+    ## https://aip.scitation.org/doi/full/10.1063/1.4968020
+    def __init__(self, d, lp, kT, rRange=(0,181), resolution=0.5, maxForce=None, max_potential=None, prefix="potentials/"):
+        BasePotential.__init__(self, 180, rRange, resolution, maxForce, max_potential, prefix)
+        self.type_ = "wlcangle"
+        self.d = d          # separation
+        self.lp = lp            # persistence length
+        self.kT = kT
+
+    def filename(self):
+        return "%s%s-%.3f-%.3f.dat" % (self.prefix, self.type_,
+                                       self.d, self.lp)
+    def potential(self,dr):
+        nk = self.d / (2*self.lp)
+        p1,p2,p3,p4 = -1.237, 0.8105, -1.0243, 0.4595
+        C = (1 + p1*(2*nk) + p2*(2*nk)**2) / (2*nk+p3*(2*nk)**2+p4*(2*nk)**3)
+        u = self.kT * C * (1-np.cos(dr * np.pi / 180))
+        return u
+
+    def __hash__(self):
+        assert(self.type_ != "None")
+        return hash((self.type_, self.d, self.lp, self.kT, self.rRange, self.resolution, self.maxForce, self.max_potential, self.prefix, self.periodic))
+
+    def __eq__(self, other):
+        for a in ("type_", "d", "lp", "kT" "rRange", "resolution", "maxForce", "max_potential", "prefix", "periodic"):
+            if self.__dict__[a] != other.__dict__[a]:
+                return False
+        return True
diff --git a/mrdna/segmentmodel.py b/mrdna/segmentmodel.py
index 8c8029337c843c85ded2f49c3467ed36a0c66de1..be15a4602c22ac178d2f15de953aa4c237a94f6d 100644
--- a/mrdna/segmentmodel.py
+++ b/mrdna/segmentmodel.py
@@ -1637,6 +1637,34 @@ class SegmentModel(ArbdModel):
         while d < -180: d+=360
         return self._get_potential("dihedral", kSpring, d, max_potential)
 
+    def _get_wlc_sk_bond_potential(self, d):
+        ## https://aip.scitation.org/doi/full/10.1063/1.4968020
+        type_='wclsk-bond'
+        # lp = 2*5                # TODO place these parameters in a logical spot
+        lp = 15                 # https://journals.aps.org/pre/abstract/10.1103/PhysRevE.86.021901
+        # https://www.pnas.org/content/pnas/109/3/799.full.pdf
+        lp = 12                 # selected semi-empirically to make ssDNA force extension match
+        key = (type_, d, lp)
+        assert( d > 0.2 )
+        if key not in self._bonded_potential:
+            kT = self.temperature * 0.0019872065 # kcal/mol
+            self._bonded_potential[key] = WLCSKBond( d, lp, kT )
+        return self._bonded_potential[key]
+
+    def _get_wlc_sk_angle_potential(self, d):
+        ## https://aip.scitation.org/doi/full/10.1063/1.4968020
+        type_='wclsk-angle'
+        ## TODO move
+        lp = 15                 # https://journals.aps.org/pre/abstract/10.1103/PhysRevE.86.021901
+        # https://www.pnas.org/content/pnas/109/3/799.full.pdf
+        lp = 12                 # selected semi-empirically to make ssDNA force extension match
+        key = (type_, d, lp)
+        assert( d > 0.2 )
+        if key not in self._bonded_potential:
+            kT = self.temperature * 0.0019872065 # kcal/mol
+            self._bonded_potential[key] = WLCSKAngle( d, lp, kT )
+        return self._bonded_potential[key]
+
 
     def _getParent(self, *beads ):
         if np.all( [b1.parent == b2.parent 
@@ -1647,7 +1675,7 @@ class SegmentModel(ArbdModel):
 
     def _get_twist_spring_constant(self, sep):
         """ sep in nt """
-        kT = 0.58622522         # kcal/mol
+        kT = self.temperature * 0.0019872065 # kcal/mol
         twist_persistence_length = 90  # set semi-arbitrarily as there is a large spread in literature
         ## <cos(q)> = exp(-s/Lp) = integrate( cos[x] exp(-A x^2), {x, 0, pi} ) / integrate( exp(-A x^2), {x, 0, pi} )
         ##   Assume A is small
@@ -2217,18 +2245,16 @@ class SegmentModel(ArbdModel):
             return conversion*elastic_modulus_times_area/d
 
         def k_ssdna_bond(d):
-            conversion = 0.014393265 # units "pN/AA" kcal_mol/AA^2
-            ## TODO: get better numbers our ssDNA model
-            elastic_modulus_times_area = 800 # pN http://markolab.bmbcb.northwestern.edu/marko/Cocco.CRP.02.pdf
+            # conversion = 0.014393265 # units "pN/AA" kcal_mol/AA^2
+            # ## TODO: get better numbers our ssDNA model
+            # elastic_modulus_times_area = 800 # pN http://markolab.bmbcb.northwestern.edu/marko/Cocco.CRP.02.pdf
+            
             return conversion*elastic_modulus_times_area/d
 
         def k_dsdna_angle(sep):
             Lp = get_effective_dsDNA_Lp(sep)
             return angle_spring_from_lp(sep,Lp)
 
-        def k_ssdna_angle(sep):
-            return angle_spring_from_lp(sep,2)
-
         def k_xover_angle(sep):
             return 0.25 * angle_spring_from_lp(sep,147)
 
@@ -2249,15 +2275,15 @@ class SegmentModel(ArbdModel):
             ## TODO: could be sligtly smarter about sep
             sep = 0.5*(b1.num_nt+b2.num_nt)
 
-            if b1.type_.name[0] == "D" and b2.type_.name[0] == "D":
+            is_dsdna = b1.type_.name[0] == "D" and b2.type_.name[0] == "D"
+            if is_dsdna:
                 d = 3.4*sep
-                k = k_dsdna_bond(d)
             else:
-                d = 5*sep
+                # d = 6.5*sep     # https://journals.aps.org/pre/abstract/10.1103/PhysRevE.86.021901
+                d = 6.4*sep     # https://journals.aps.org/pre/abstract/10.1103/PhysRevE.86.021901
                 if b1.type_.name[0] != b2.type_.name[0]:
                     """ Add a small extra distance to junction """
                     d += 3
-                k = k_ssdna_bond(d)
 
             if b1 not in dists:
                 dists[b1] = dict()
@@ -2271,7 +2297,11 @@ class SegmentModel(ArbdModel):
             if b1 is b2: continue
 
             # dists[[b1,b2]] = dists[[b2,b1]] = sep
-            bond = self.get_bond_potential(k,d)
+            if is_dsdna:
+                k = k_dsdna_bond(d)
+                bond = self.get_bond_potential(k,d)
+            else:
+                bond = self._get_wlc_sk_bond_potential(d)
             parent.add_bond( b1, b2, bond, exclude=True )
 
         # for s in self.segments:
@@ -2309,7 +2339,6 @@ class SegmentModel(ArbdModel):
             sep = 0.5*(0.5*b1.num_nt+b2.num_nt+0.5*b3.num_nt)
             parent = self._getParent(b1,b2,b3)
 
-            kT = 0.58622522         # kcal/mol
             if b1.type_.name[0] == "D" and b2.type_.name[0] == "D" and b3.type_.name[0] == "D":
                 
                 k = k_dsdna_angle(sep)
@@ -2318,16 +2347,16 @@ class SegmentModel(ArbdModel):
                     k *= 0.75    # reduce because orientation beads impose similar springs
                     dihed = self.get_dihedral_potential(k_dihed,180)
                     parent.add_dihedral(b1,b2,b2.orientation_bead,b3, dihed)
+                angle = self.get_angle_potential(k,180)
 
 
             elif b1.type_.name[0] == "S" and b2.type_.name[0] == "S" and b3.type_.name[0] == "S":
-                ## TODO: get correct number from ssDNA model
-                k = k_ssdna_angle(sep)
+                # angle = self._get_wlc_sk_angle_potential(sep*6.5) # TODO move 6.5 somewhere sensible
+                angle = self._get_wlc_sk_angle_potential(sep*6.4) # TODO move 6.4 somewhere sensible
             else:
                 ## Considered as a sscrossover below
                 continue
 
-            angle = self.get_angle_potential(k,180)
             parent.add_angle( b1, b2, b3, angle )
 
         """ Add intrahelical exclusions """
@@ -2643,6 +2672,7 @@ class SegmentModel(ArbdModel):
                 Lp = s1.twist_persistence_length/0.34  # set semi-arbitrarily as there is a large spread in literature
 
                 def get_spring(sep):
+                    kT = self.temperature * 0.0019872065 # kcal/mol
                     fitFun = lambda x: np.real(erf( (4*np.pi*x + 1j)/(2*np.sqrt(x)) )) * np.exp(-1/(4*x)) / erf(2*np.sqrt(x)*np.pi) - np.exp(-sep/Lp)
                     k = opt.leastsq( fitFun, x0=np.exp(-sep/Lp) )
                     return k[0][0] * 2*kT*0.00030461742