Added full_chain_stiffness() to handle stiffness with piston tension.
[sawsim.git] / src / sawsim.nw
index 9917130a961264dfc716da6aa1e0afc4847d6e75..87ed220959228790a7f1b41a20171fb1a358a64e 100644 (file)
@@ -264,6 +264,7 @@ following models have been implemented:
  \item  Hookean spring (Appendix \ref{sec.hooke}),
  \item  Worm-like chain (WLC, Appendix \ref{sec.wlc}), and
  \item  Freely-jointed chain (FJC, Appendix \ref{sec.fjc}).
+ \item  Piston (Appendix \ref{sec.piston_tension}),
 \end{packed_item}
 
 The tension model and parameters used for a particular domain depend
@@ -418,7 +419,7 @@ you accidentally assign incompatible domains to the same group you
 should get an appropriate error message.
 
 <<find tension definitions>>=
-#define NUM_TENSION_MODELS 5
+#define NUM_TENSION_MODELS 6
 
 @
 
@@ -806,7 +807,8 @@ tension_model_getopt_t tension_models[NUM_TENSION_MODELS] = {
   <<constant tension model getopt>>,
   <<hooke tension model getopt>>,
   <<worm-like chain tension model getopt>>,
-  <<freely-jointed chain tension model getopt>>
+  <<freely-jointed chain tension model getopt>>,
+  <<piston tension model getopt>>
 };
 @
 
@@ -947,7 +949,7 @@ void get_options(int argc, char **argv,
   extern char *optarg;
   extern int optind, optopt, opterr;
 
-  assert (argc > 0);
+  assert(argc > 0);
 
 #ifdef DEBUG
   for (i=0; i<n_tension_models; i++) {
@@ -2314,6 +2316,7 @@ the implementation in [[tension_balance.c]], and the unit testing in
 <<x of x0>>
 <<group stiffness function>>
 <<chain stiffness function>>
+<<full chain stiffness function>>
 <<tension balance function>>
 @
 
@@ -2429,9 +2432,14 @@ double tension_balance(int num_tension_groups,
 
   F = (*tension_handlers[0])(xo, params[0]);
 
-  if (stiffness != NULL)
+  if (stiffness != NULL) {
     *stiffness = chain_stiffness(&x_xo_data, min_relx);
-
+    if (*stiffness == 0.0) { /* re-calculate based on full chain */
+      *stiffness = full_chain_stiffness(num_tension_groups, tension_handlers,
+                                        inverse_tension_handlers, params,
+                                        last_x, x, min_relx, F);
+    }
+  }
   return F;
 }
 
@@ -2497,6 +2505,10 @@ double x_of_xo(double xo, void *pdata)
 }
 @
 
+Determine the stiffness of a single tension group by taking a small
+step [[dx]] back from the position [[x]] for which we want the
+stiffness.  The touchy part is determining a good [[dx]] and ensuring
+the whole interval is on [[x>=0]].
 <<group stiffness function>>=
 double group_stiffness(one_dim_fn_t *tension_handler, void *handler_data, double x, double relx)
 {
@@ -2517,15 +2529,24 @@ double group_stiffness(one_dim_fn_t *tension_handler, void *handler_data, double
   F = (*tension_handler)(x, handler_data);
   Fl = (*tension_handler)(xl, handler_data);
   stiffness = (F-Fl)/dx;
-  assert(stiffness > 0);
+  assert(stiffness >= 0);
   return stiffness;
 }
 @
 
+Attempt to calculate the chain stiffness by adding group stiffnesses
+as springs in series.  In the case where a group has undefined
+stiffness (e.g. piston tension, Section \ref{sec.piston_tension}),
+this algorithm breaks down.  In that case, [[tension_balance()]]
+(\ref{sec.tension_balance}) should use [[full_chain_stiffness()]]
+which uses the full chain's $F(x)$ rather than that of the individual
+domains'.  We attempt the series approach first because, lacking the
+numerical inversion inside [[tension_balance()]], it is both faster
+and more accurate than the full-chain derivative.
 <<chain stiffness function>>=
 double chain_stiffness(x_of_xo_data_t *x_xo_data, double relx)
 {
-  double stiffness;
+  double stiffness, gstiff;
   int i;
   /* add group stiffnesses in series */
   for (i=0; i < x_xo_data->n_groups; i++) {
@@ -2533,7 +2554,10 @@ double chain_stiffness(x_of_xo_data_t *x_xo_data, double relx)
     fprintf(stderr, "%s:\tgetting stiffness of active state %d of %d for x[%d]=%g, relx=%g\n", __FUNCTION__, i, x_xo_data->n_groups, i, x_xo_data->xi[i], relx);
     fflush(stderr);
 #endif
-    stiffness += 1.0/group_stiffness(x_xo_data->tension_handler[i], x_xo_data->handler_data[i], x_xo_data->xi[i], relx);
+    gstiff = group_stiffness(x_xo_data->tension_handler[i], x_xo_data->handler_data[i], x_xo_data->xi[i], relx);
+    if (gstiff == 0.0);
+      return 0.0;
+    stiffness += 1.0/gstiff;
   }
   stiffness = 1.0 / stiffness;
   return stiffness;
@@ -2541,6 +2565,63 @@ double chain_stiffness(x_of_xo_data_t *x_xo_data, double relx)
 
 @
 
+Determine the chain stiffness using only [[tension_balance()]].  This
+works around difficulties with tension models that have undefined
+stiffness (see discussion for [[chain_stiffness()]] above).
+<<full chain stiffness function>>=
+double full_chain_stiffness(int num_tension_groups,
+                       one_dim_fn_t **tension_handlers,
+                      one_dim_fn_t **inverse_tension_handlers,
+                       void **params, /* array of pointers to tension_handler_data_t */
+                      double last_x,
+                      double x,
+                      double relx,
+                      double F /* already evaluated F(x) */)
+{
+  double dx, xl, Fl, stiffness;
+
+  assert(x >= 0);
+  assert(relx > 0 && relx < 1);
+
+  /* Other option for dx that we ignore because of our poor tension_balance()
+   * resolution (i.e. bad slopes if you zoom in too close):
+   *     if (last_x != -1.0 && last_x != x)  // last_x limited
+   *       dx fabs(x-last_x);
+   *     else
+   *       dx = HUGE_VAL;
+   *     if (1==1) {                 // constant max-value limited
+   *       dx_p = 1e-12;
+   *       dx = MIN(dx, dx_p);
+   *     }
+   *     if (x != 0 && relx != 0) {  // relx limited
+   *       dx_p = x*relx;
+   *       dx = MIN(dx, dx_p);
+   *     }
+   * TODO, determine which of (min_relx, min_rely, max_steps) is actually
+   * limiting tension_balance.
+   */
+  dx = 1e-12; /* HACK, how to get length scale? */
+
+  xl = x-dx;
+  if (xl >= 0) {
+    Fl = tension_balance(num_tension_groups, tension_handlers,
+                         inverse_tension_handlers, params,
+                         last_x, xl, NULL);
+  } else {
+    xl = x;
+    Fl = F;
+    x += dx;
+    F = tension_balance(num_tension_groups, tension_handlers,
+                       inverse_tension_handlers, params,
+                       last_x, x, NULL);
+  }
+  stiffness = (F-Fl)/dx;
+  assert(stiffness >= 0);
+  return stiffness;
+}
+
+@
+
 Solve $f(x) = y$ to a certain precision in $x$ or $y$ in a limited
 number of steps for monotonically increasing $f(x)$.  Simple
 bisection, so it's robust and converges fairly quickly.  You can set
@@ -3695,6 +3776,7 @@ The interface is defined in [[tension_model.h]], the implementation in [[tension
 <<hooke tension model declarations>>
 <<worm-like chain tension model declarations>>
 <<freely-jointed chain tension model declarations>>
+<<piston tension model declarations>>
 <<find tension definitions>>
 <<tension model global declarations>>
 #endif /* TENSION_MODEL_H */
@@ -3764,7 +3846,7 @@ double const_tension_handler(double x, void *pdata)
   list_t *list = data->group_tension_params;
   double F;
 
-  assert (x >= 0.0);
+  assert(x >= 0.0);
   list = head(list);
   assert(list != NULL); /* empty active group?! */
   F = ((const_tension_param_t *)list->d)->F;
@@ -3829,7 +3911,7 @@ char const_tension_param_string[] = "0";
 @
 
 <<constant tension model getopt>>=
-{&const_tension_handler, NULL, "const", "an infinitely stretchable domain with constant tension", 1, const_tension_param_descriptions, const_tension_param_string, &string_create_const_tension_param_t, &destroy_const_tension_param_t}
+{&const_tension_handler, NULL, "const", "an infinitely stretchable domain with constant tension", num_const_tension_params, const_tension_param_descriptions, const_tension_param_string, &string_create_const_tension_param_t, &destroy_const_tension_param_t}
 @
 
 \subsection{Hooke}
@@ -3896,7 +3978,7 @@ double hooke_handler(double x, void *pdata)
 {
   hooke_param_t param = {0};
 
-  assert (x >= 0.0);
+  assert(x >= 0.0);
   hooke_param_grouper((tension_handler_data_t *)pdata, &param);
   return param.k*x;
 }
@@ -3909,7 +3991,7 @@ double inverse_hooke_handler(double F, void *pdata)
 {
   hooke_param_t param = {0};
 
-  assert (F >= 0.0);
+  assert(F >= 0.0);
   hooke_param_grouper((tension_handler_data_t *)pdata, &param);
   return F/param.k;
 }
@@ -4132,6 +4214,7 @@ double wlc_handler(double x, void *pdata)
   tension_handler_data_t *data = (tension_handler_data_t *)pdata;
   wlc_param_t param = {0};
 
+  assert(x >= 0.0);
   wlc_param_grouper(data, &param);
   return wlc(x, data->env->T, param.p, param.L);
 }
@@ -4214,7 +4297,7 @@ char wlc_param_string[]="0.39e-9,28.4e-9";
 @
 
 <<worm-like chain tension model getopt>>=
-{wlc_handler, inverse_wlc_handler, "wlc", "a worm-like chain (F=kbT/p{.25[(1-x/L)^-2 -1] + x/L})", num_wlc_params, wlc_param_descriptions, (char *)wlc_param_string, &string_create_wlc_param_t, &destroy_wlc_param_t}
+{&wlc_handler, &inverse_wlc_handler, "wlc", "a worm-like chain (F=kbT/p{.25[(1-x/L)^-2 -1] + x/L})", num_wlc_params, wlc_param_descriptions, (char *)wlc_param_string, &string_create_wlc_param_t, &destroy_wlc_param_t}
 @
 Titin parameters from Carrion-Vazquez 1999, PNAS USA 96, 3696.
 
@@ -4348,6 +4431,151 @@ char fjc_param_string[]="0.5e-9,200";
 {fjc_handler, NULL, "fjc", "a freely-jointed chain [F=kbT/l L^-1(x/L)]", num_fjc_params, fjc_param_descriptions, (char *)fjc_param_string, &string_create_fjc_param_t, &destroy_fjc_param_t}
 @
 
+\subsection{Piston}
+\label{sec.piston_tension}
+
+An infinitely stretchable domain with no tension for extensions less
+than a particular threshold $L$, and infinite tension for $x>L$.  The
+tension at $x=L$ is undefined, but may be any positive value.  The
+model is called the ``piston'' model because it resembles a
+frictionless piston sliding in a rigid cylinder of length $L$.
+
+Note that because of it's infinte stiffness at $x=L$, fully extended
+domains with this tension model will be identical to completely rigid
+domains.  However, there is a distinction between this model and rigid
+domains for $x<L$, because any reactions that occur at $F=0$
+(e.g. refolding) will have more time to occur.
+
+Because the tension at $x=L$ is undefined, the user must make sure
+domains with this tension model are never the initial active group,
+because it would break [[tension_balance()]] in [[find_tension()]]
+(see Section \ref{sec.tension_balance}).
+
+<<piston tension functions>>=
+<<piston tension parameter grouper>>
+<<piston tension handler>>
+<<inverse piston tension handler>>
+<<piston tension parameter create/destroy functions>>
+@
+
+<<piston tension model declarations>>=
+<<piston tension handler declaration>>
+<<inverse piston tension handler declaration>>
+<<piston tension parameter create/destroy function declarations>>
+<<piston tension model global declarations>>
+
+@
+
+First we define a function that computes the effective piston parameters
+(stored in a single [[piston_tension_param_t]]) for the entire group.
+<<piston tension parameter grouper>>=
+static void piston_tension_param_grouper(tension_handler_data_t *data,
+                                         piston_tension_param_t *param) {
+  list_t *list = data->group_tension_params;
+  double L=0;
+
+  list = head(list);
+  assert(list != NULL); /* empty active group?! */
+  while (list != NULL) {
+    L += ((piston_tension_param_t *)list->d)->L;
+    list = list->next;
+  }
+  param->L = L;
+}
+
+<<piston tension handler declaration>>=
+extern double piston_tension_handler(double x, void *pdata);
+@
+<<piston tension handler>>=
+double piston_tension_handler(double x, void *pdata)
+{
+  tension_handler_data_t *data = (tension_handler_data_t *)pdata;
+  piston_tension_param_t param = {0};
+  double F;
+
+  piston_tension_param_grouper(data, &param);
+  if (x <= param.L) F = 0;
+  else F = HUGE_VAL;
+#ifdef DEBUG
+  fprintf(stderr, "%s: x %g, L %g, tension %g N\n", __FUNCTION__, x, param.L, F);
+#endif
+  return F;
+}
+
+@
+
+<<inverse piston tension handler declaration>>=
+extern double inverse_piston_tension_handler(double x, void *pdata);
+@
+<<inverse piston tension handler>>=
+double inverse_piston_tension_handler(double F, void *pdata)
+{
+  tension_handler_data_t *data = (tension_handler_data_t *)pdata;
+  piston_tension_param_t param = {0};
+
+  assert(F >= 0.0);
+  piston_tension_param_grouper(data, &param);
+  if (F == 0.0) return 0;
+  return param.L;
+}
+
+@
+
+<<piston tension structure>>=
+typedef struct piston_tension_param_struct {
+  double L; /* length of piston in m */
+} piston_tension_param_t;
+
+@
+
+<<piston tension parameter create/destroy function declarations>>=
+extern void *string_create_piston_tension_param_t(char **param_strings);
+extern void destroy_piston_tension_param_t(void *p);
+
+@
+
+<<piston tension parameter create/destroy functions>>=
+piston_tension_param_t *create_piston_tension_param_t(double L)
+{
+  piston_tension_param_t *ret
+    = (piston_tension_param_t *) malloc(sizeof(piston_tension_param_t));
+  assert(ret != NULL);
+  ret->L = L;
+  return ret;
+}
+
+void *string_create_piston_tension_param_t(char **param_strings)
+{
+  return create_piston_tension_param_t(
+      safe_strtod(param_strings[0],__FUNCTION__));
+}
+
+void destroy_piston_tension_param_t(void *p)
+{
+  if (p)
+    free(p);
+}
+
+@
+
+<<piston tension model global declarations>>=
+extern int num_piston_tension_params;
+extern char *piston_tension_param_descriptions[];
+extern char piston_tension_param_string[];
+
+@
+
+<<piston tension model globals>>=
+int num_piston_tension_params = 1;
+char *piston_tension_param_descriptions[] = {"length L, m"};
+char piston_tension_param_string[] = "0";
+
+@
+
+<<piston tension model getopt>>=
+{&piston_tension_handler, &inverse_piston_tension_handler, "piston", "a domain that slides without tension for x<L, but has infinite tension for x>L.", num_piston_tension_params, piston_tension_param_descriptions, piston_tension_param_string, &string_create_piston_tension_param_t, &destroy_piston_tension_param_t}
+@
+
 \subsection{Tension model implementation}
 
 <<tension-model.c>>=
@@ -4376,6 +4604,7 @@ char fjc_param_string[]="0.5e-9,200";
 <<hooke structure>>
 <<worm-like chain structure>>
 <<freely-jointed chain structure>>
+<<piston tension structure>>
 @
 
 <<tension model globals>>=
@@ -4384,6 +4613,7 @@ char fjc_param_string[]="0.5e-9,200";
 <<hooke tension model globals>>
 <<worm-like chain tension model globals>>
 <<freely-jointed chain tension model globals>>
+<<piston tension model globals>>
 @
 
 <<tension model functions>>=
@@ -4392,9 +4622,9 @@ char fjc_param_string[]="0.5e-9,200";
 <<hooke functions>>
 <<worm-like chain functions>>
 <<freely-jointed chain functions>>
+<<piston tension functions>>
 @
 
-
 \subsection{Utilities}
 
 The tension models can get complicated, and users may want to reassure
@@ -4545,7 +4775,7 @@ void get_options(int argc, char **argv, environment_t *env,
   extern char *optarg;
   extern int optind, optopt, opterr;
 
-  assert (argc > 0);
+  assert(argc > 0);
 
   /* setup defaults */
 
@@ -5894,7 +6124,7 @@ void get_options(int argc, char **argv, environment_t *env,
   extern char *optarg;
   extern int optind, optopt, opterr;
 
-  assert (argc > 0);
+  assert(argc > 0);
 
   /* setup defaults */