summaryrefslogtreecommitdiff
path: root/branching_models.cc
blob: 701c9d87f2eb1decee53c94f4890d0cb0ba56afe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
/*
  © Copyright 2008 Randal A. Koene <randalk@netmorph.org>
  
  With design assistance from J. van Pelt & A. van Ooyen, and support
  from the Netherlands Organization for Scientific Research (NWO)
  Program Computational Life Sciences grant CLS2003 (635.100.005) and
  from the EC Marie Curie Research and Training Network (RTN)
  NEURoVERS-it 019247.

  This file is part of NETMORPH.

  NETMORPH is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  NETMORPH is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with NETMORPH.  If not, see <http://www.gnu.org/licenses/>.
*/
// branching_models.cc
// Randal A. Koene, 20070509

/* Documentation:
   This implements base and derived classes for models of branching during
   network development that are based on bifurcation statistics (rather than
   causal hypotheses about environmental interactions).
 */

#include <math.h>
#include "branching_models.hh"
#include "neuron.hh"
#include "fibre_structure.hh"
#include "dendritic_growth_model.hh"

/* [***INCOMPLETE] Here I can include functions that parse the command line for
   general parameters to be used in specific branching models if no neuron or
   terminal segment specific parameters are given.
 */

// global variables
// See http://rak.minduploading.org:8080/caspan/Members/randalk/model-specification-implementation/.
branching_model default_branching_model = van_Pelt_bm; // identifier, changed when a different universal model is set
String universal_branching_model_root; // Used only to report if chaining at the universal set level.

TSBM default_TSBM = van_Pelt_tsbm; // identifier, changed when a different universal model is set
String universal_TSBM_root; // Used only to report if chaining at the universal set level.

branch_angle_model default_branch_angle_model = balanced_forces_bam; // identifier, changed when a different universal model is set
String universal_branch_angle_model_root; // Used only to report if chaining at the universal set level.

// Region and natural subset specific schemas (initialized in neuron.cc:general_neuron_parameters_interface::parse_CLP(Command_Line_Parameters & clp, network & net))
region_branching_model_base_ptr * branching_model_region_subset_schemas = NULL;

// Region and natural subset specific schemas (initialized in neuron.cc:general_neuron_parameters_interface::parse_CLP(Command_Line_Parameters & clp, network & net))
region_TSBM_base_ptr * TSBM_region_subset_schemas = NULL;
 
// Region and natural subset specific schemas (initialized in neuron.cc:general_neuron_parameters_interface::parse_CLP(Command_Line_Parameters & clp, network & net))
region_branch_angle_model_base_ptr * branch_angle_model_region_subset_schemas = NULL;

branching_model_base::branching_model_base(branching_model_base * bmcontrib, double & bmweight, branching_model_base & schema):
  contributing(NULL), contributingweight(1.0), base_parameters(schema.base_parameters), cache1(0.0) {
  // [***INCOMPLETE] Can I suggest inlining this constructor somehow?
  if (bmcontrib) { // Clone chained models
    contributingweight = bmweight;
    contributing = bmcontrib->clone();
  }
}

branching_model_base::branching_model_base(String & thislabel, String & label, Command_Line_Parameters & clp):
  contributing(NULL), contributingweight(1.0), base_parameters(NULL,0.0), cache1(0.0) {
  // [***INCOMPLETE] Can I suggest inlining this constructor somehow?
  // [***NOTE] This is not a CLP_Modifiable, because I do not wish to have to
  // store the label in order to be able to parse_CLP at any time.
  int n;
  if ((n=clp.Specifies_Parameter(thislabel+"min_node_interval"))>=0) {
    base_parameters.min_node_interval = atof(clp.ParValue(n));
    if (base_parameters.min_node_interval<0.0) {
      warning("Warning: "+thislabel+"min_node_interval="+clp.ParValue(n)+" interpreted as 0.0\n");
      base_parameters.min_node_interval = 0.0;
    }
  }
  if (label.empty()) return;
  if ((n=clp.Specifies_Parameter(label+"bm_weight"))>=0) contributingweight = atof(clp.ParValue(n));
  if (!branching_model_selection(label,clp,contributing,contributing)) error("Error in branching_model_base(): No contributing branching model found for "+label+".\n");
}

double branching_model_base::predict(double weight, double & probability) {
  // Called in a chain of branching models to apply, where the first model
  // in the chain is automatically assigned the weight 1.0 and the weighting
  // of other direction models is proportional to that (i.e. it can be > 1.0).
  // The actual weighting of contributions is done in the function predict_branching().
  // The sum of all weights is returned, so that division by that sum can produce
  // a properly scaled combined branching probability.
  probability += predict_branching(weight);
  if (contributing) weight += contributing->predict(contributingweight,probability);
  return weight;
}

double branching_model_base::branching_probability() {
  // PROTOCOL: This is the function that is called during iterative growth.
  //double predicted1=0.0, predicted2=0.0;
  double probability = predict_branching(1.0);
  if (contributing) {
    double summedcontributingweights = 1.0 + contributing->predict(contributingweight,probability);
    probability /= summedcontributingweights;
  }
  return probability;
}

String branching_model_base::report_parameters() {
  // This calls report_parameters_specific() in any derived class and
  // propagates parameter reporting to contributing models.
  String res(" min_node_interval="+String(base_parameters.min_node_interval,"%.3f")+report_parameters_specific());
  /*res += String(base_parameters.veeranglemin,"\n  [veeranglemin=%.2f] ");
    res += String(base_parameters.veeranglemax,"\n  [veeranglemax=%.2f] "); */
  if (contributing) {
    res += String(contributingweight,"\n  [contrib.w=%.2f] ");
    res += contributing->report_parameters();
  }
  return res;
}

van_Pelt_branching_model::van_Pelt_branching_model(branching_model_base * bmbcontrib, double & bmbweight, van_Pelt_branching_model & schema): branching_model_base(bmbcontrib,bmbweight,schema), probability(0.0), parameters(schema.parameters) {
  // Chaining of models is taken care of in the base constructor.
  creation_t = eq->T();
  last_t = creation_t;
}

van_Pelt_branching_model::van_Pelt_branching_model(String & thislabel, String & label, Command_Line_Parameters & clp): branching_model_base(thislabel,label,clp), probability(0.0), parameters(4.75,319680,0.5,gcc_whole_neuron) {
  // Chaining of models is taken care of in the base constructor.
  creation_t = eq->T();
  last_t = creation_t;
  int n;
  if ((n=clp.Specifies_Parameter(thislabel+"B_inf"))>=0) {
    double b = atof(clp.ParValue(n));
    if (b<0.0) {
      warning("Warning: Negative "+thislabel+"B_inf="+clp.ParValue(n)+" ignored\n");
    } else parameters.B_inf = b;
  }
  if ((n=clp.Specifies_Parameter(thislabel+"tau"))>=0) {
    double _tau = atof(clp.ParValue(n));
    if (_tau==0.0) {
      warning("Warning: Zero "+thislabel+"tau="+clp.ParValue(n)+" ignored\n");
    } else parameters.tau = _tau;
  }
  if ((n=clp.Specifies_Parameter(thislabel+"E"))>=0) parameters.E = atof(clp.ParValue(n));
  if ((n=clp.Specifies_Parameter(thislabel+"E_competes_with"))>=0) {
    if (downcase(clp.ParValue(n))==String("whole_neuron")) parameters.competition_with_all_trees = gcc_whole_neuron;
    else if (downcase(clp.ParValue(n))==String("all_dendrites")) parameters.competition_with_all_trees = gcc_within_all_AorD;
    else if (downcase(clp.ParValue(n))==String("all_axons")) parameters.competition_with_all_trees = gcc_within_all_AorD;
    else if (downcase(clp.ParValue(n))==String("same_arbor")) parameters.competition_with_all_trees = gcc_within_tree;
    else warning("Unknown E competition value:"+clp.ParValue(n)+'\n');
  }
}

branching_model_base * van_Pelt_branching_model::clone(fibre_structure * _arbor) {
  branching_model_base * bmb = new van_Pelt_branching_model(contributing,contributingweight,*this);
  if (_arbor!=NULL) bmb->set_arbor(_arbor);
  return bmb;
}

//void van_Pelt_branching_model::handle_branching(terminal_segment & ts1, terminal_segment & ts2) {
// [***NOTE] Could set a new temp.variable in here that depends on N, so that its calculation is only done when needed.
//}

//void van_Pelt_branching_model::handle_turning(terminal_segment & ts) {
//}

double Btestarray[30] = {0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
// 0 = t
// 1 = probability (Bt)
// 2 = TSBM probability
// 3 = TSBM probability * Bt
// 6 = number of probability calculations
// 11 = weight
// 12 = B_inf
// 13 = -t
// 14 = tau
// 15 = dt
// 16 = E
// 21 = sum for Cn
// 22 = divisor for Cn
// 
// 24 = binSgamma during init
// 25 = binSgamma before prob. comp.

double van_Pelt_branching_model::predict_branching(double weight) {
  // Computes the arbor-specific part of the van Pelt model branching probability,
  // That is the part of Eq.6 in [KOE:NETMORPH] before the parts involving S and C.
  // When S is not zero, those latter parts can be included by using the appropriate
  // TSBM.
  // Note that this model only calculates a value once per time step and uses a cache
  // of the last time for which a calculation was done to detect this. Also, since
  // only one calculation is needed for the entire arbor, we use the difference between
  // the last and current time for dt, instead of caching a fixed dt time step size.
  // That makes this model more versatile, and usable with variable time steps.
  // Parameters are:
  //   B_inf, tau, E
  if (last_t >= eq->T()) return probability;
  double dt = eq->T() - last_t;
  last_t = eq->T();
  double t = last_t - creation_t;
#ifdef USE_BTESTARRAY
  Btestarray[0] = t;
  Btestarray[11] = weight;
  Btestarray[12] = parameters.B_inf;
  Btestarray[13] = -t;
  Btestarray[14] = parameters.tau;
  Btestarray[15] = dt;
  Btestarray[16] = parameters.E;
#endif
  probability = weight * parameters.B_inf*exp(-t/parameters.tau)*(exp(dt/parameters.tau)-1.0);
#ifdef USE_BTESTARRAY
  Btestarray[10] = probability;
#endif
  switch (parameters.competition_with_all_trees) {
  case gcc_within_all_AorD:
    probability *= exp(-parameters.E*log((double) base_parameters.arbor->count_terminal_segments_in_PLL()));;
#ifdef USE_BTESTARRAY
    Btestarray[1] = probability;
#endif
    return probability;
    break;;
  case gcc_whole_neuron:
    probability *= exp(-parameters.E*log((double) (base_parameters.arbor->N()->total_input_terminal_segments()+base_parameters.arbor->N()->total_output_terminal_segments())));
#ifdef USE_BTESTARRAY
    Btestarray[1] = probability;
#endif
    return probability;
    break;;
  default: // gcc_within_tree
    probability *= exp(-parameters.E*log((double) base_parameters.arbor->count_terminal_segments()));
  }
#ifdef USE_BTESTARRAY
  Btestarray[6] += 1.0;
  Btestarray[1] = probability;
#endif
  return probability;
}

String van_Pelt_branching_model::report_parameters_specific() {
  String res(" van_Pelt (B_inf="+String(parameters.B_inf,"%.2f, tau=")+String(parameters.tau,"%.2f, E=")+String(parameters.E,"%.2f)"));
  return res;
}

TSBM_base::TSBM_base(TSBM_base * tsbmcontrib, double & tsbmweight, TSBM_base & schema):
  cache_ts(NULL), contributing(NULL), contributingweight(1.0) { //, base_parameters(schema.base_parameters) {
  if (tsbmcontrib) { // Clone chained models
    contributingweight = tsbmweight;
    contributing = tsbmcontrib->clone();
  }
}

TSBM_base::TSBM_base(String & thislabel, String & label, Command_Line_Parameters & clp):
  cache_ts(NULL), contributing(NULL), contributingweight(1.0) { //, base_parameters(0.0) {
  int n;
  // local parameters here
  // ...
  if (label.empty()) return;
  if ((n=clp.Specifies_Parameter(label+"tsbm_weight"))>=0) contributingweight = atof(clp.ParValue(n));
  if (!TSBM_selection(label,clp,contributing,contributing)) error("Error in TSBM_base(): No contributing TSBM found for "+label+".\n");
}

void TSBM_base::handle_branching(terminal_segment & ts1, terminal_segment & ts2) {
  ts2.set_TSBM_model(clone());
  ts1.set_TSBM_model(this);
}

void TSBM_base::handle_turning(terminal_segment & ts) {
  ts.set_TSBM_model(this);
}

double TSBM_base::predict(double weight, double & probability) {
  // Called in a chain of TSBMs to apply, where the first model
  // in the chain is automatically assigned the weight 1.0 and the weighting
  // of other direction models is proportional to that (i.e. it can be > 1.0).
  // The actual weighting of contributions is done in the function predict_branching().
  // The sum of all weights is returned, so that division by that sum can produce
  // a properly scaled combined branching probability.
  probability += weight*predict_branching(); //weight);
  if (contributing) weight += contributing->predict(contributingweight,probability);
  return weight;
}

double TSBM_base::branching_probability(terminal_segment & ts) {
  // PROTOCOL: This is the function that is called during iterative growth.
  cache_ts = &ts;
  // 1. Local multiplicative adjustment (e.g. for S and C)
  double probability = predict_branching();
#ifdef USE_BTESTARRAY
  Btestarray[23] = probability;
#endif
  if (contributing) {
    double summedcontributingweights = 1.0 + contributing->predict(contributingweight,probability);
    probability /= summedcontributingweights;
  }
  // 2. Multiply with the arbor probability
#ifdef USE_BTESTARRAY
  Btestarray[2] = probability;
#endif
  probability *= cache_ts->Arbor()->BranchingModel()->branching_probability();
#ifdef USE_BTESTARRAY
  Btestarray[3] = probability;
#endif
  return probability;
}

String TSBM_base::report_parameters() {
  // This calls report_parameters_specific() in any derived class and
  // propagates parameter reporting to contributing models.
  String res(report_parameters_specific());
  /*res += String(base_parameters.veeranglemin,"\n  [veeranglemin=%.2f] ");
    res += String(base_parameters.veeranglemax,"\n  [veeranglemax=%.2f] "); */
  if (contributing) {
    res += String(contributingweight,"\n  [contrib.w=%.2f] ");
    res += contributing->report_parameters();
  }
  return res;
}

van_Pelt_TSBM::van_Pelt_TSBM(TSBM_base * tsbmbcontrib, double & tsbmbweight, van_Pelt_TSBM & schema): TSBM_base(tsbmbcontrib,tsbmbweight,schema), binSgamma(1.0), parameters(schema.parameters) {
  // Chaining of models is taken care of in the base constructor.
}

van_Pelt_TSBM::van_Pelt_TSBM(String & thislabel, String & label, Command_Line_Parameters & clp): TSBM_base(thislabel,label,clp), binSgamma(1.0), parameters(0.0) {
  // Chaining of models is taken care of in the base constructor.
  int n;
  if ((n=clp.Specifies_Parameter(thislabel+"S"))>=0) parameters.S = atof(clp.ParValue(n));
}

//***TEMP:
#define BINSGAMMA(_ts) exp(-(parameters.S*_ts->CentrifugalOrder())*LOG2)

#define LOG2 0.693147
void van_Pelt_TSBM::init_cache(terminal_segment * _ts) {
  binSgamma = exp(-(parameters.S*_ts->CentrifugalOrder())*LOG2);
#ifdef USE_BTESTARRAY
  Btestarray[24] = binSgamma;
#endif
  _ts->Arbor()->BranchingModel()->cache1 += binSgamma;
}

TSBM_base * van_Pelt_TSBM::clone(terminal_segment * _ts) {
  van_Pelt_TSBM * tsbmb = new van_Pelt_TSBM(contributing,contributingweight,*this);
  if (_ts!=NULL) tsbmb->init_cache(_ts);
  return tsbmb;
}

void van_Pelt_TSBM::handle_branching(terminal_segment & ts1, terminal_segment & ts2) {
  ts2.set_TSBM_model(clone());
  ts1.set_TSBM_model(this);
  ts1.Arbor()->BranchingModel()->cache1 -= binSgamma; // update the sum for Cn
  init_cache(&ts1);
}

double van_Pelt_TSBM::predict_branching() { //double weight) {
  // Computes the S and C parts of Eq.6 in [KOE:NETMORPH] for the van Pelt model.
  // Note that binSgamma is computed and cached in the init_cache() function above.
#ifdef USE_BTESTARRAY
  Btestarray[21] = (double) cache_ts->Arbor()->count_terminal_segments();
  Btestarray[22] = cache_ts->Arbor()->BranchingModel()->cache1;
#endif
  //***double Cn = ((double) cache_ts->Arbor()->count_terminal_segments()) / cache_ts->Arbor()->BranchingModel()->cache1;
  double Cnsum = 0.0;
  PLL_LOOP_FORWARD(terminal_segment,cache_ts->Arbor()->TerminalSegments()->head(),1) Cnsum += BINSGAMMA(e);
  double Cn = ((double) cache_ts->Arbor()->count_terminal_segments()) / Cnsum;
  // [***NOTE] There may be cause to consider Cn over the whole neuron or over all axons or all dendrites (with or without an apical dendrite),
  // in which case I can add a switch here, as in the arbor model above. For now, we will assume that the order dependence plays a role only
  // within the same arbor.
#ifdef USE_BTESTARRAY
  Btestarray[25] = binSgamma;
#endif
  return BINSGAMMA(cache_ts)*Cn;
  //***return binSgamma*Cn;
}

String van_Pelt_TSBM::report_parameters_specific() {
  String res(" van_Pelt (S="+String(parameters.S,"%.2f)"));
  return res;
}

van_Pelt_specBM_TSBM::van_Pelt_specBM_TSBM(TSBM_base * tsbmbcontrib, double & tsbmbweight, van_Pelt_specBM_TSBM & schema): van_Pelt_TSBM(tsbmbcontrib,tsbmbweight,schema), specparameters(schema.specparameters) {
  // Chaining of models is taken care of in the base constructor.
}

van_Pelt_specBM_TSBM::van_Pelt_specBM_TSBM(String & thislabel, String & label, Command_Line_Parameters & clp, terminal_segment * ts): van_Pelt_TSBM(thislabel,label,clp), specparameters(NULL) {
  // Chaining of models is taken care of in the base constructor.
  // A specific branching model is identified here, which will then be shared with daughter branches.
  // [***NOTE] Should we expand thislabel with a tag such as "TSBM." to insure differentiation from other branching_model specifications?
  branching_model_selection(thislabel,clp,NULL,specparameters.bm);
  if (specparameters.bm) if (ts) specparameters.bm->set_arbor(ts->Arbor()); // The arbor has to be set either here or during cloning!
  //int n;
  //if ((n=clp.Specifies_Parameter(thislabel+"S"))>=0) parameters.S = atof(clp.ParValue(n));
}

TSBM_base * van_Pelt_specBM_TSBM::clone(terminal_segment * _ts) {
  //cout << "_CLONING_TSBM_"; cout.flush();
  van_Pelt_specBM_TSBM * tsbmb = new van_Pelt_specBM_TSBM(contributing,contributingweight,*this);
  if (_ts!=NULL) {
    tsbmb->init_cache(_ts);
    if (tsbmb->specparameters.bm) if (!(tsbmb->specparameters.bm->Arbor())) tsbmb->specparameters.bm->set_arbor(_ts->Arbor());
  }
  return tsbmb;
}

/* double van_Pelt_specBM_TSBM::predict_branching(double weight) {
  // Computes the S and C parts of Eq.6 in [KOE:NETMORPH] for the van Pelt model.
  // Uses a specific branching model, see TL#200807210612.5.
  // Note that binSgamma is computed and cached in the van_Pelt_TSBM::init_cache() function above.
  // In this function, the Arbor() is referenced to compute S and C.
  //double Cn = ((double) cache_ts->Arbor()->count_terminal_segments()) / cache_ts->Arbor()->BranchingModel()->cache1;
  double Cnsum = 0.0;
  PLL_LOOP_FORWARD(terminal_segment,cache_ts->Arbor()->TerminalSegments()->head(),1) Cnsum += BINSGAMMA(e);
  double Cn = ((double) cache_ts->Arbor()->count_terminal_segments()) / Cnsum;
  // [***NOTE] There may be cause to consider Cn over the whole neuron or over all axons or all dendrites (with or without an apical dendrite),
  // in which case I can add a switch here, as in the arbor model above. For now, we will assume that the order dependence plays a role only
  // within the same arbor.
  return BINSGAMMA(cache_ts)*Cn;
  //return binSgamma*Cn;
  } */

double van_Pelt_specBM_TSBM::branching_probability(terminal_segment & ts) {
  // PROTOCOL: This is the function that is called during iterative growth.
  // Uses a specific branching model, see TL#200807210612.5.
  cache_ts = &ts;
  // 1. Local multiplicative adjustment (e.g. for S and C)
  double probability = predict_branching();
  if (contributing) {
    double summedcontributingweights = 1.0 + contributing->predict(contributingweight,probability);
    probability /= summedcontributingweights;
  }
  // 2. Multiply with the arbor probability
  if (!specparameters.bm) probability *= cache_ts->Arbor()->BranchingModel()->branching_probability(); // by default exactly as in van_Pelt_TSBM
  else probability *= specparameters.bm->branching_probability();
  return probability;
}

String van_Pelt_specBM_TSBM::report_parameters_specific() {
  String res(" specific BM "+van_Pelt_TSBM::report_parameters_specific());
  return res;
}

/* Use this function information to create polynomial O1 and O3 branching TSBMs:

Polynomial_O1_dendritic_growth_model::Polynomial_O1_dendritic_growth_model(double E, double c, double tf): van_Pelt_dendritic_growth_plus_turns_model(E,1.0,1.0,E,1.0,1.0,E,1.0,1.0,tm_each_dt,tf,0.05) {
  _c = c;
  _L0_min = 0.0;
  _L0_max = 0.0;
  set_fixed_time_step_size(van_Pelt_SUGGESTED_dt);
  e_1 = exp(-1.0);
}

Polynomial_O1_dendritic_growth_model::Polynomial_O1_dendritic_growth_model(Command_Line_Parameters & clp) {
  // default values are obtained by polynomial approximation of mirrored data
  _E = 1.0; _c = 0.156204/DAYSECONDS; _L0_min = 0.0; _L0_max = 0.0; _turnfactor = 9.25;
  _dt = van_Pelt_SUGGESTED_dt;
  e_1 = exp(-1.0);
  parse_CLP(clp);
}

Polynomial_O3_dendritic_growth_model::Polynomial_O3_dendritic_growth_model(double E, double c, double c1, double c2, double tf): Polynomial_O1_dendritic_growth_model(E,c,tf), _c1(c1), _c2(c2) {
  _L0_min = 0.0; // set by L(0) cubic approximation of dendrite length for mirrored data
  _L0_max = 0.0;
}

Polynomial_O3_dendritic_growth_model::Polynomial_O3_dendritic_growth_model(Command_Line_Parameters & clp): Polynomial_O1_dendritic_growth_model(clp), _c1(0.0/DAYSECONDSSQR), _c2(0.000039/DAYSECONDSCUB) {
  _c = 0.145511/DAYSECONDS;
  local_parse_CLP(clp);
}

double Polynomial_O1_dendritic_growth_model::expected_number_of_branches(neuron * n, double t1, double t2) {
  // The result will be multiplied by nt_E to obtain a probability per
  // terminal segment.
  // This function does not use the parameter n.
  //double nt_E = exp(-_E*log((double) n->total_input_terminal_segments()));
  return _c*(t2-t1);//vnt_E; // An O1 polynomial fit to bifurcation in Ger Ramakers' data (_c =  0.145156 / (24.0*60.0*60.0))
}

double Polynomial_O3_dendritic_growth_model::expected_number_of_branches(neuron * n, double t1, double t2) {
  // The result will be multiplied by nt_E to obtain a probability per
  // terminal segment.
  // This function does not use the parameter n.
  double t1sq = t1*t1, t2sq = t2*t2;
  return (_c*(t2-t1)) + (_c1*(t2sq-t1sq)) + (_c2*((t2sq*t2)-(t1sq*t1))); // set the c parameters by dividing daily fits to Ger Ramaker's data by (24.0*60.0*60.0)
}

void Polynomial_O1_dendritic_growth_model::set_fixed_time_step_size(double dt) {
  _dt=dt;
  _c_dt = _c*dt;
}

double Polynomial_O1_dendritic_growth_model::expected_number_of_branches(neuron * n, double t1) {
  // [*** NOTE] The O3 polynomial fit to bifurcation in Ger Ramakers' data
  // does not use the t1 parameter.
  //double nt_E = exp(-_E*log((double) n->total_input_terminal_segments()));
  return _c_dt;// nt_E; // An O1 polynomial fit to bifurcation in Ger Ramakers' data (_c =  0.145156 / (24.0*60.0*60.0))
}

double Polynomial_O3_dendritic_growth_model::expected_number_of_branches(neuron * n, double t1) {
  // The result will be multiplied by nt_E to obtain a probability per
  // terminal segment.
  // This function does not use the parameter n.
  return expected_number_of_branches(n,t1,t1+_dt);
}

*/

branch_angle_model_base::branch_angle_model_base(branch_angle_model_base * bmcontrib, double & bmweight, branch_angle_model_base & schema):
  contributing(NULL), contributingweight(1.0) { //, base_parameters(schema.base_parameters) {
  // [***INCOMPLETE] Can I suggest inlining this constructor somehow?
  if (bmcontrib) { // Clone chained models
    contributingweight = bmweight;
    contributing = bmcontrib->clone();
  }
  pdf = schema.pdf->clone();
}

branch_angle_model_base::branch_angle_model_base(String & thislabel, String & label, Command_Line_Parameters & clp):
  contributing(NULL), contributingweight(1.0), pdf(NULL) { //, base_parameters(turnanglemin,turnanglemax) {
  // [***INCOMPLETE] Can I suggest inlining this constructor somehow?
  // [***NOTE] This is not a CLP_Modifiable, because I do not wish to have to
  // store the label in order to be able to parse_CLP at any time.
  int n;
  pdf = pdfselection(thislabel+"bam.",clp,X_branch);
  if (!pdf) pdf = new normal_pdf(X_branch,0.0,0.3,1.0); // a normal PDF is the default for BAMs
  else if (pdf->amplitude()>1.0) warning("Warning: Branch Angle Model has perturbation with amplitude greater than 1.0, unpredictable angles!\n");
  /* double vmin = base_parameters.veeranglemin;
  double vmax = base_parameters.veeranglemax;
  if ((n=clp.Specifies_Parameter(thislabel+"veeranglemin"))>=0) {
    vmin = atof(clp.ParValue(n));
    if (vmin<0.0) {
      warning("Warning: "+thislabel+"veeranglemin="+clp.ParValue(n)+" not permitted, value must be >=0.0, not modified\n");
      vmin = base_parameters.veeranglemin;
    }
  }
  if ((n=clp.Specifies_Parameter(thislabel+"veeranglemax"))>=0) {
    vmax = atof(clp.ParValue(n));
    if (vmax<=0.0) {
      warning("Warning: "+thislabel+"veeranglemax="+clp.ParValue(n)+" not prmitted, value must be >0.0, not modified\n");
      vmax = base_parameters.veeranglemax;
    }
  }
  if (vmax<=vmin) warning("Warning: "+thislabel+"veeranglemax must be > "+thislabel+"veeranglemin, not modified\n");
  else {
    base_parameters.veeranglemin = vmin;
    base_parameters.veeranglemax = vmax;
    } */
  if (label.empty()) return;
  if ((n=clp.Specifies_Parameter(label+"bm_weight"))>=0) contributingweight = atof(clp.ParValue(n));
  if (!branch_angle_model_selection(label,clp,contributing,contributing)) error("Error in branch_angle_model_base(): No contributing branch angle model found for "+label+".\n");
}

String branch_angle_model_base::report_parameters() {
  // This calls report_parameters_specific() in any derived class and
  // propagates parameter reporting to contributing models.
  String res(report_parameters_specific());
  res += " p_BAM=" + pdf->report_parameters();
  /*res += String(base_parameters.veeranglemin,"\n  [veeranglemin=%.2f] ");
    res += String(base_parameters.veeranglemax,"\n  [veeranglemax=%.2f] "); */
  if (contributing) {
    res += String(contributingweight,"\n  [contrib.w=%.2f] ");
    res += contributing->report_parameters();
  }
  return res;
}

void branch_angle_model_base::bifurcate(terminal_segment * ts1, double branchangle1, terminal_segment * ts2, double branchangle2) {
  // This function receives expected branch angles, computed with branch_angle() functions.
  // Those branch angles may be modified to proportionally reduce or increase the two branch angles.
  // Such modification can be done in the form of a probabilistic perturbation or by scaling to the
  // sum of initial lengths (or elongation rates). Presently, perturbation takes the form of
  // normal distributed perturbation, as described in the gfneuroinformatics paper.
  // This function (and the Branch Angle Models as a whole) are only concerned with the PITCH
  // angle relative to the parent fiber direction. As such, they do not consider the ROLL in
  // 3D, or the transformation to absolute angular vectors for ts1 and ts2.
  // For more about the ensuing computations, please see the terminal_segment::branch() function.
  branchangle1 *= (1.0+pdf->random_selection());
  branchangle2 *= (1.0+pdf->random_selection());
  ts1->AngularCoords().set_Y(branchangle1);
  ts2->AngularCoords().set_Y(branchangle2);
}

balanced_forces_branch_angle_model::balanced_forces_branch_angle_model(branch_angle_model_base * bambcontrib, double & bambweight, balanced_forces_branch_angle_model & schema): branch_angle_model_base(bambcontrib,bambweight,schema) {
  parallelogramanglepdf = schema.parallelogramanglepdf->clone();
}

balanced_forces_branch_angle_model::balanced_forces_branch_angle_model(String & thislabel, String & label, Command_Line_Parameters & clp): branch_angle_model_base(thislabel,label,clp), parallelogramanglepdf(NULL) {
  parallelogramanglepdf = pdfselection(thislabel+"bam.bfbam.",clp,X_branch);
  if (!parallelogramanglepdf) parallelogramanglepdf = new normal_pdf(X_branch,0.5*M_PI,0.5,M_PI-0.1);
  else if (parallelogramanglepdf->amplitude()>=M_PI) warning("Warning: The parallelogram angle PDF of the Balanced Forces Branch Angle Model has an amplitude of 180 degrees or more!\n");
}

String balanced_forces_branch_angle_model::report_parameters_specific() {
  String res(" balanced forces (parallelogram angle PDF: ");
  res += parallelogramanglepdf->report_parameters() + ')';
  return res;
}

branch_angle_model_base * balanced_forces_branch_angle_model::clone() {
  return new balanced_forces_branch_angle_model(contributing,contributingweight,*this);
}

void balanced_forces_branch_angle_model::handle_branching(terminal_segment & ts1, terminal_segment & ts2) {
  ts2.set_branch_angle_model(clone());
  ts1.set_branch_angle_model(this);
}

void balanced_forces_branch_angle_model::handle_turning(terminal_segment & ts) {
  ts.set_branch_angle_model(this);
}

void balanced_forces_branch_angle_model::predict_branch_angles(double & predicted1, double & predicted2, terminal_segment * ts1, terminal_segment * ts2, double weight) {
  // The results are added to the values in predicted1 and predicted2 weighted by weight.
  // Here forces are derived from the initial elongation rates of the new terminal segments.
  // Those forces create a parallelogram with a combined force vector along the parent
  // direction. The resulting angles of the force vectors with the projected parent direction
  // are the predicted branch angles.
  // In addition to using the ratio of the forces to determine relative angles, the angles
  // can be narrowed or widened in proportion, by lengthening or shortening the vector along
  // the parent direction, with which the parallelogram is made. An equivalent alternative
  // is to use a right angled parallelogram (which is easy) to find the relative angles
  // first, and to then proportionally reduce or enlarge both angles.
  // The reduction or enlargement could be (a) probabilistic or (b) determined by the
  // absolute elongation rate of the sum of the two elongation rates, since we can imagine
  // that more rapid forward progression of a growth cone could lead to greater continuing
  // forward motion along both branches.
  // The probabilistic option fits in well with the "veering" approach usually done in the
  // base function, such as the bifurcation() function.
  // Either (a) or (b), or both (a) and (b) could be applied.
  // Note that using the distributed elongations or the random values here does not matter,
  // since the total distributed cancels out.
  double parallelogramangle = parallelogramanglepdf->random_positive();
  double force1 = ts1->Length(), force2 = ts2->Length();
  if ((force1==0.0) && (force2==0.0)) { // The distributed elongation was zero, 
    force1 = X_branch.get_rand_real1();
    force2 = X_branch.get_rand_real1();
  }
  if (force1==0.0) {
    /*if (force2==0.0) {
      warning("Warning: Both forces in balanced_forces_branch_angle_model::predict_branch_angles() are zero, branches do not diverge!\n");
      return;
      }*/
    // angle1 is parallelogramangle, angle2 is zero
    predicted1 += (weight*parallelogramangle);
    return;
  } else if (force2==0.0) {
    // angle1 is zero, angle2 is parallelogramangle
    predicted2 += (weight*parallelogramangle);
    return;
  }
  double angle1 = atan(force2/force1);
  double angle2 = parallelogramangle - angle1;
  predicted1 += weight*angle1; predicted2 += weight*angle2;
}

double balanced_forces_branch_angle_model::predict(double weight, double & predicted1, double & predicted2, terminal_segment * ts1, terminal_segment * ts2) {
  // Called in a chain of branch angle models to apply, where the first model
  // in the chain is automatically assigned the weight 1.0 and the weighting
  // of other direction models is proportional to that (i.e. it can be > 1.0).
  // The actual weighting of contributions is done in the function predict_branch_angles().
  // The sum of all weights is returned, so that division by that sum can produce
  // properly scaled combined branch angles.
  predict_branch_angles(predicted1,predicted2,ts1,ts2,weight);
  if (contributing) weight += contributing->predict(contributingweight,predicted1,predicted2,ts1,ts2);
  return weight;
}

void balanced_forces_branch_angle_model::branch_angle(terminal_segment * ts1, terminal_segment * ts2) { // fibre_segment * parent
  // PROTOCOL: This is the function that is called during iterative growth.
  // The following are the preparations of this branch angle model:
  // [...]
  // The following are the functions of this branch angle model:
  // 1. predict the branch angles based on a balance of forces analogy
  double predicted1=0.0, predicted2=0.0;
  predict_branch_angles(predicted1,predicted2,ts1,ts2);
  if (contributing) {
    double summedcontributingweights = 1.0 + contributing->predict(contributingweight,predicted1,predicted2,ts1,ts2);
    predicted1 /= summedcontributingweights; predicted2 /= summedcontributingweights;
  }
  // [***BEWARE] Is it possible for both angles to be zero? 
  // 2. bifurcate with a roll around the parent direction, with possibly perturbed predicted branch angles, and spherical coordinates for the new terminal segments
  bifurcate(ts1,predicted1,ts2,predicted2);
}

String branching_model_idstr[NUM_bm] = {
  "van_pelt",
  "polynomial_o1",
  "polynomial_o3"
};

branching_model_base * branching_model_selection(String & label, Command_Line_Parameters & clp, branching_model_base * superior_set, branching_model_base_ptr & bmbptr) {
  // Note: Providing the result pointer in bmbptr enables this function to
  // delete a previously defined schema before linking to a new one.
  // PROTOCOL: [See 200709130802.] Sets for which a branching model is not defined explicitly inherit the branching model of
  //           their specific direct superior by cloning it, thereby also inheriting parameters specified therein.
  branching_model bm = NUM_bm; // implies undefined
  String nextlabel;
  int n;
  // Determine if a specific model is selected for the set with the given label
  if ((n=clp.Specifies_Parameter(label+"branching_model"))>=0) {
    String bmstr(downcase(clp.ParValue(n)));
    for (branching_model i = branching_model(0); i < NUM_bm; i = branching_model(i+1)) if (bmstr==branching_model_idstr[i]) { bm = i; break; }
    if (bm==NUM_bm) return NULL; // catch branching_model syntax errors
    if (label.empty()) default_branching_model = bm; // modify default if setting universal
  } else if (label.empty()) bm = default_branching_model; // the universal set must have a model, since it cannot inherit
  // Determine if a following model selection should include chaining detection
  if ((n=clp.Specifies_Parameter(label+"branching_model_label"))>=0) {
    nextlabel = label+clp.URI_unescape_ParValue(n)+'.';
    if (label.empty()) universal_branching_model_root = nextlabel; // to report at universal level
  } else if ((n=clp.Specifies_Parameter(label+"bm_label"))>=0) { // alternate syntax
    nextlabel = label+clp.URI_unescape_ParValue(n)+'.';
    if (label.empty()) universal_branching_model_root = nextlabel; // to report at universal level
  }
  // Clean up any previously defined schema
  if (bmbptr) bmbptr->delete_shared();
  // If specified, set and return specified model schema or universal model schema if at universal set level
  switch (bm) {
  case van_Pelt_bm:
    bmbptr = new van_Pelt_branching_model(label,nextlabel,clp);
    break;;
  case polynomial_O1_bm:
    //[***INCOMPLETE]    bmbptr = new polynomial_O1_branching_model(label,nextlabel,clp);
    break;;
  case polynomial_O3_bm:
    //[***INCOMPLETE]    bmbptr = new polynomial_O3_branching_model(label,nextlabel,clp);
    break;;
  default: // If not specified, inherit from immediate superior set
    //cout << "Branching model... defaulting\n";
    if (!superior_set) return NULL;
    bmbptr = superior_set->clone();
    break;;
  }
  // In case a secondary schema reference is provided through bmbptr for the universal set level, create a clone for the universal set
  if ((label.empty()) && (branching_model_region_subset_schemas[0][universal_sps]!=bmbptr)) {
    if (branching_model_region_subset_schemas[0][universal_sps]) branching_model_region_subset_schemas[0][universal_sps]->delete_shared();
    branching_model_region_subset_schemas[0][universal_sps] = bmbptr->clone();
  }
  return bmbptr;
}

String TSBM_idstr[NUM_tsbm] = {
  "van_pelt",
  "van_pelt_specbm"
};

TSBM_base * TSBM_selection(String & label, Command_Line_Parameters & clp, TSBM_base * superior_set, TSBM_base_ptr & tsbmbptr, terminal_segment * ts) {
  // Note: Providing the result pointer in tsbmbptr enables this function to
  // delete a previously defined schema before linking to a new one.
  // PROTOCOL: [See 200709130802.] Sets for which a branching model is not defined explicitly inherit the branching model of
  //           their specific direct superior by cloning it, thereby also inheriting parameters specified therein.
  TSBM tsbm = NUM_tsbm; // implies undefined
  String nextlabel;
  int n;
  // Determine if a specific model is selected for the set with the given label
  if ((n=clp.Specifies_Parameter(label+"TSBM"))>=0) {
    String tsbmstr(downcase(clp.ParValue(n)));
    for (TSBM i = TSBM(0); i < NUM_tsbm; i = TSBM(i+1)) if (tsbmstr==TSBM_idstr[i]) { tsbm = i; break; }
    if (tsbm==NUM_tsbm) return NULL; // catch TSBM syntax errors
    if (label.empty()) default_TSBM = tsbm; // modify default if setting universal
  } else if (label.empty()) tsbm = default_TSBM; // the universal set must have a model, since it cannot inherit
  // Determine if a following model selection should include chaining detection
  if ((n=clp.Specifies_Parameter(label+"TSBM_label"))>=0) {
    nextlabel = label+clp.URI_unescape_ParValue(n)+'.';
    if (label.empty()) universal_TSBM_root = nextlabel; // to report at universal level
  } else if ((n=clp.Specifies_Parameter(label+"tsbm_label"))>=0) { // alternate syntax
    nextlabel = label+clp.URI_unescape_ParValue(n)+'.';
    if (label.empty()) universal_TSBM_root = nextlabel; // to report at universal level
  }
  // Clean up any previously defined schema
  if (tsbmbptr) tsbmbptr->delete_shared();
  // If specified, set and return specified model schema or universal model schema if at universal set level
  switch (tsbm) {
  case van_Pelt_tsbm:
    tsbmbptr = new van_Pelt_TSBM(label,nextlabel,clp);
    break;;
  case van_Pelt_specBM_tsbm:
    tsbmbptr = new van_Pelt_specBM_TSBM(label,nextlabel,clp,ts);
    break;;
  default: // If not specified, inherit from immediate superior set
    if (!superior_set) return NULL;
    tsbmbptr = superior_set->clone();
    break;;
  }
  // In case a secondary schema reference is provided through tsbmbptr for the universal set level, create a clone for the universal set
  if ((label.empty()) && (TSBM_region_subset_schemas[0][universal_sps]!=tsbmbptr)) {
    if (TSBM_region_subset_schemas[0][universal_sps]) TSBM_region_subset_schemas[0][universal_sps]->delete_shared();
    TSBM_region_subset_schemas[0][universal_sps] = tsbmbptr->clone();
  }
  return tsbmbptr;
}

String branch_angle_model_idstr[NUM_bam] = {
  "balanced_forces",
  "fork_main_daughter" // downcased!
};

branch_angle_model_base * branch_angle_model_selection(String & label, Command_Line_Parameters & clp, branch_angle_model_base * superior_set, branch_angle_model_base_ptr & bambptr) {
  // Note: Providing the result pointer in bambptr enables this function to
  // delete a previously defined schema before linking to a new one.
  // PROTOCOL: [See 200709130802.] Sets for which a branch angle model is not defined explicitly inherit the branch angle model of
  //           their specific direct superior by cloning it, thereby also inheriting parameters specified therein.
  branch_angle_model bam = NUM_bam; // implies undefined
  String nextlabel;
  int n;
  // Determine if a specific model is selected for the set with the given label
  if ((n=clp.Specifies_Parameter(label+"branch_angle_model"))>=0) {
    String bamstr(downcase(clp.ParValue(n)));
    for (branch_angle_model i = branch_angle_model(0); i < NUM_bam; i = branch_angle_model(i+1)) if (bamstr==branch_angle_model_idstr[i]) { bam = i; break; }
    if (bam==NUM_bam) return NULL; // catch branch_angle_model syntax errors
    if (label.empty()) default_branch_angle_model = bam; // modify default if setting universal
  } else if (label.empty()) bam = default_branch_angle_model; // the universal set must have a model, since it cannot inherit
  // Determine if a following model selection should include chaining detection
  if ((n=clp.Specifies_Parameter(label+"branch_angle_model_label"))>=0) {
    nextlabel = label+clp.URI_unescape_ParValue(n)+'.';
    if (label.empty()) universal_branch_angle_model_root = nextlabel; // to report at universal level
  } else if ((n=clp.Specifies_Parameter(label+"bam_label"))>=0) { // alternate syntax
    nextlabel = label+clp.URI_unescape_ParValue(n)+'.';
    if (label.empty()) universal_branch_angle_model_root = nextlabel; // to report at universal level
  }
  // Clean up any previously defined schema
  if (bambptr) bambptr->delete_shared();
  // If specified, set and return specified model schema or universal model schema if at universal set level
  switch (bam) {
  case balanced_forces_bam: 
    bambptr = new balanced_forces_branch_angle_model(label,nextlabel,clp);
    break;;
  case fork_main_daughter_bam:
    //[***INCOMPLETE]    bambptr = new fork_main_daughter_branch_angle_model(label,nextlabel,clp);
    break;;
  default: // If not specified, inherit from immediate superior set
    if (!superior_set) return NULL;
    bambptr = superior_set->clone();
    break;;
  }
  // In case a secondary schema reference is provided through bambptr for the universal set level, create a clone for the universal set
  if ((label.empty()) && (branch_angle_model_region_subset_schemas[0][universal_sps]!=bambptr)) {
    if (branch_angle_model_region_subset_schemas[0][universal_sps]) branch_angle_model_region_subset_schemas[0][universal_sps]->delete_shared();
    branch_angle_model_region_subset_schemas[0][universal_sps] = bambptr->clone();
  }
  return bambptr;
}