]> git.donarmstrong.com Git - lilypond.git/blob - lily/spring-spacer.cc
release: 1.1.44
[lilypond.git] / lily / spring-spacer.cc
1 /*
2   spring-spacer.cc -- implement Spring_spacer
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--1999 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9
10 #include <math.h>
11 #include <limits.h>
12 #include "killing-cons.tcc"
13 #include "spring-spacer.hh"
14 #include "p-col.hh"
15 #include "debug.hh"
16 #include "dimensions.hh"
17 #include "qlp.hh"
18 #include "unionfind.hh"
19 #include "idealspacing.hh"
20 #include "pointer.tcc"
21 #include "score-column.hh"
22 #include "paper-def.hh"
23 #include "colhpos.hh"
24 #include "main.hh"
25
26 Vector
27 Spring_spacer::default_solution() const
28 {
29   return try_initial_solution();
30 }
31
32 Score_column*
33 Spring_spacer::scol_l (int i)
34 {
35   return dynamic_cast<Score_column*>(cols_[i].pcol_l_);
36 }
37
38 const Real COLFUDGE=1e-3;
39 template class P<Real>;         // ugh.
40
41 bool
42 Spring_spacer::contains_b (Paper_column const *w)
43 {
44   for (int i=0; i< cols_.size(); i++)
45     if (cols_[i].pcol_l_ == w)
46       return true;
47   return false;
48 }
49
50
51 void
52 Spring_spacer::OK() const
53 {
54 #ifndef NDEBUG
55   for (int i = 1; i < cols_.size(); i++)
56     assert (cols_[i].rank_i_ > cols_[i-1].rank_i_);
57   for (int i = 1; i < loose_col_arr_.size(); i++)
58     assert (loose_col_arr_[i].rank_i_ > loose_col_arr_[i-1].rank_i_);
59 #endif
60 }
61
62 /**
63   Make sure no unconnected columns happen.
64  */
65 void
66 Spring_spacer::handle_loose_cols()
67 {
68   Union_find connected (cols_.size());
69   Array<int> fixed;
70   
71   for (Cons<Idealspacing> *i  = ideal_p_list_; i; i = i->next_)
72     {
73       connected.connect (i->car_->cols_drul_[LEFT],i->car_->cols_drul_[RIGHT]);
74     }
75   for (int i = 0; i < cols_.size(); i++)
76     if (cols_[i].fixed_b())
77       fixed.push (i);
78   for (int i=1; i < fixed.size(); i++)
79     connected.connect (fixed[i-1], fixed[i]);
80
81   for (int i = cols_.size(); i--;)
82     {
83       if (! connected.equiv (fixed[0], i))
84         {
85           warning (_f ("unconnected column: %d", i));
86           loosen_column (i);
87         }
88     }
89   OK();
90 }
91
92
93 /**
94   Guess a stupid position for loose columns.  Put loose columns at
95   regular distances from enclosing calced columns
96   */
97 void
98 Spring_spacer::position_loose_cols (Vector &sol_vec) const
99 {
100   if (!loose_col_arr_.size())
101     return ;
102   assert (sol_vec.dim());
103   Array<bool> fix_b_arr;
104   fix_b_arr.set_size (cols_.size() + loose_col_arr_.size ());
105   Real utter_right_f=-infinity_f;
106   Real utter_left_f =infinity_f;
107   for (int i=0; i < loose_col_arr_.size(); i++)
108     {
109       fix_b_arr[loose_col_arr_[i].rank_i_] = false;
110     }
111   for (int i=0; i < cols_.size(); i++)
112     {
113       int r= cols_[i].rank_i_;
114       fix_b_arr[r] = true;
115       utter_right_f = utter_right_f >? sol_vec (i);
116       utter_left_f = utter_left_f <? sol_vec (i);
117     }
118   Vector v (fix_b_arr.size());
119   int j =0;
120   int k =0;
121   for (int i=0; i < v.dim(); i++)
122     {
123       if (fix_b_arr[i])
124         {
125           assert (cols_[j].rank_i_ == i);
126           v (i) = sol_vec (j++);
127         }
128       else
129         {
130           Real left_pos_f =
131             (j>0) ?sol_vec (j-1) : utter_left_f;
132           Real right_pos_f =
133             (j < sol_vec.dim()) ? sol_vec (j) : utter_right_f;
134           int left_rank = (j>0) ? cols_[j-1].rank_i_ : 0;
135           int right_rank = (j<sol_vec.dim()) ? cols_[j].rank_i_ : sol_vec.dim ();
136
137           int d_r = right_rank - left_rank;
138           Column_info loose=loose_col_arr_[k++];
139           int r = loose.rank_i_ ;
140           assert (r > left_rank && r < right_rank);
141
142           v (i) =  (r - left_rank)*left_pos_f/ d_r +
143             (right_rank - r) *right_pos_f /d_r;
144         }
145     }
146   sol_vec = v;
147 }
148
149 bool
150 Spring_spacer::check_constraints (Vector v) const
151 {
152   int dim=v.dim();
153   assert (dim == cols_.size());
154   DOUT << "checking " << v;
155   for (int i=0; i < dim; i++)
156     {
157       if (cols_[i].fixed_b() &&
158           abs (cols_[i].fixed_position() - v (i)) > COLFUDGE)
159         {
160           DOUT << "Fixpos broken\n";
161           return false;
162         }
163       Array<Spacer_rod> const &rods (cols_[i].rods_[RIGHT]);
164       for (int j =0; j < rods.size (); j++)
165         {
166           int other =rods[j].other_idx_;
167           Real diff =v (other) - v (i) ;
168           if (COLFUDGE +diff <  rods[j].distance_f_)
169             {
170               DOUT << "i, other_i: " << i << "  " << other << '\n';
171               DOUT << "dist, minimal = " << diff << " "
172                    << rods[j].distance_f_ << '\n';
173               return false;
174             }
175         }
176
177     }
178   return true;
179 }
180
181 /** try to generate a solution which obeys the min
182     distances and fixed positions
183  */
184 Vector
185 Spring_spacer::try_initial_solution() const
186 {
187   Vector v;
188   if (!try_initial_solution_and_tell (v))
189     {
190       warning (_ ("I'm too fat; call Oprah"));
191     }
192   return v;
193
194 }
195
196 bool
197 Spring_spacer::try_initial_solution_and_tell (Vector &v) const
198 {
199   int dim=cols_.size();
200   bool succeeded = true;
201   Vector initsol (dim);
202
203   assert (cols_[0].fixed_b ());
204   DOUT << "fixpos 0 " << cols_[0].fixed_position ();
205   for (int i=0; i < dim; i++)
206     {
207       Real min_x = i ?  initsol (i-1) : cols_[0].fixed_position ();
208       Array<Spacer_rod> const &sr_arr(cols_[i].rods_[LEFT]);
209       for (int j=0; j < sr_arr.size (); j++)
210         {
211           min_x = min_x >? (initsol (sr_arr[j].other_idx_) + sr_arr[j].distance_f_);
212         }
213       initsol (i) = min_x;
214       
215       if (cols_[i].fixed_b())
216         {
217           initsol (i)=cols_[i].fixed_position();
218           if (initsol (i) < min_x )
219             {
220               DOUT << "failing: init, min : " << initsol (i) << " " << min_x << '\n';
221               initsol (i) = min_x;
222               succeeded = false;
223             }
224         }
225     }
226   v = initsol;
227   
228   DOUT << "tried and told solution: " << v;
229   if (!succeeded)
230     DOUT << "(failed)\n";
231   return succeeded;
232 }
233
234
235
236 // generate the matrices
237 void
238 Spring_spacer::make_matrices (Matrix &quad, Vector &lin, Real &c) const
239 {
240   quad.fill (0);
241   lin.fill (0);
242   c = 0;
243
244   for (Cons<Idealspacing> *p =ideal_p_list_; p; p = p->next_)
245     {
246       Idealspacing *i = p->car_;
247       int l = i->cols_drul_[LEFT];
248       int r = i->cols_drul_[RIGHT];
249
250       quad (r,r) += i->hooke_f_;
251       quad (r,l) -= i->hooke_f_;
252       quad (l,r) -= i->hooke_f_;
253       quad (l,l) += i->hooke_f_;
254
255       lin (r) -= i->space_f_*i->hooke_f_;
256       lin (l) += i->space_f_*i->hooke_f_;
257
258       c += sqr (i->space_f_);
259     }
260
261   if (quad.dim() > 10)
262     quad.set_band();
263   
264
265 }
266
267 void
268 Spring_spacer::set_fixed_cols (Mixed_qp &qp) const
269 {
270   for (int j=0; j < cols_.size(); j++)
271     if (cols_[j].fixed_b())
272       qp.add_fixed_var (j,cols_[j].fixed_position());
273
274
275 // put the constraints into the LP problem
276 void
277 Spring_spacer::make_constraints (Mixed_qp& lp) const
278 {
279   int dim=cols_.size();
280   
281   for (int j=0; j < dim -1; j++)
282     {
283       Array<Spacer_rod> const&rod_arr (cols_[j].rods_[RIGHT]);
284       for (int i = 0; i < rod_arr.size (); i++)
285         {
286           Vector c1(dim);
287           c1(rod_arr[i].other_idx_)=1.0 ;
288           c1(j)=-1.0 ;
289
290           lp.add_inequality_cons (c1, rod_arr[i].distance_f_);
291         }
292     }
293 }
294
295
296 Real
297 Spring_spacer::calculate_energy_f (Vector solution) const
298 {
299   Real e = 0.0;
300   for (Cons<Idealspacing>*p =ideal_p_list_; p; p = p->next_)
301     {
302       Idealspacing * i = p->car_;
303       e += i->energy_f(solution(i->cols_drul_[RIGHT]) - solution(i->cols_drul_[LEFT]));
304     }
305
306   return e;
307 }
308 void
309 Spring_spacer::lower_bound_solution (Column_x_positions*positions) const
310 {
311   Mixed_qp lp (cols_.size());
312   make_matrices (lp.quad_,lp.lin_, lp.const_term_);
313   set_fixed_cols (lp);
314
315   Vector start (cols_.size());
316   start.fill (0.0);
317   Vector solution_vec (lp.solve (start));
318
319   DOUT << "Lower bound sol: " << solution_vec;
320   positions->energy_f_ = calculate_energy_f (solution_vec);
321   positions->config = solution_vec;
322   positions->satisfies_constraints_b_ = check_constraints (solution_vec);
323 }
324
325 Spring_spacer::Spring_spacer ()
326 {
327   ideal_p_list_ =0;
328   energy_normalisation_f_ = 1.0;
329 }
330
331 void
332 Spring_spacer::solve (Column_x_positions*positions) const
333 {
334   DOUT << "Spring_spacer::solve ()...";
335
336   Vector solution_try;
337     
338   bool constraint_satisfaction = try_initial_solution_and_tell (solution_try); 
339   if  (constraint_satisfaction)
340     {
341       Mixed_qp lp (cols_.size());
342       make_matrices (lp.quad_,lp.lin_, lp.const_term_);
343       make_constraints (lp);
344       set_fixed_cols (lp);
345         
346       Vector solution_vec (lp.solve (solution_try));
347         
348       positions->satisfies_constraints_b_ = check_constraints (solution_vec);
349       if (!positions->satisfies_constraints_b_)
350         {
351           WARN << _ ("solution doesn't satisfy constraints") << '\n' ;
352         }
353       position_loose_cols (solution_vec);
354       positions->energy_f_ = calculate_energy_f (solution_vec);
355       positions->config = solution_vec;
356       positions->error_col_l_arr_ = error_pcol_l_arr();
357     }
358   else
359     {
360       positions->set_stupid_solution (solution_try);
361     }
362
363   DOUT << "Finished Spring_spacer::solve ()...";
364 }
365
366 /**
367   add one column to the problem.
368 */
369 void
370 Spring_spacer::add_column (Paper_column  *col, bool fixed, Real fixpos)
371 {
372   Column_info c (col,(fixed)? &fixpos :  0);
373   int this_rank =  cols_.size();
374   c.rank_i_ = this_rank;
375   
376   for (int i=0; i < col->minimal_dists_arr_drul_[LEFT].size (); i++)
377     {
378       Column_rod &cr = col->minimal_dists_arr_drul_[LEFT][i];
379       int left_idx = cr.other_l_->rank_i () - cols_[0].pcol_l_->rank_i ();
380       if (left_idx < 0)
381         continue;
382
383       if (cols_[left_idx].pcol_l_ != cr.other_l_)
384         continue;
385
386       Spacer_rod l_rod;
387       l_rod.distance_f_ = cr.distance_f_;
388       l_rod.other_idx_ = left_idx;
389       c.rods_[LEFT].push (l_rod);
390
391       Spacer_rod r_rod;
392       r_rod.distance_f_ = cr.distance_f_;
393       r_rod.other_idx_ = this_rank;
394       cols_[left_idx].rods_[RIGHT].push (r_rod);
395     }
396 #if 1 
397   if (experimental_features_global_b)
398     {
399       for (int i=0; i < col->spring_arr_drul_[LEFT].size (); i++)
400         {
401           Column_spring &cr = col->spring_arr_drul_[LEFT][i];
402           int idx = cr.other_l_->rank_i () - cols_[0].pcol_l_->rank_i ();
403           if (idx < 0)
404             continue;
405
406           if (cols_[idx].pcol_l_ != cr.other_l_)
407             continue;
408
409
410           connect (idx, this_rank, cr.distance_f_,
411                    cr.strength_f_ / cr.distance_f_);
412         }
413     }
414 #endif  
415   
416   cols_.push (c);
417 }
418
419 Line_of_cols
420 Spring_spacer::error_pcol_l_arr() const
421 {
422   Link_array<Paper_column> retval;
423   for (int i=0; i< cols_.size(); i++)
424     if (cols_[i].ugh_b_)
425       retval.push (cols_[i].pcol_l_);
426   for (int i=0;  i < loose_col_arr_.size(); i++)
427     {
428       retval.push (loose_col_arr_[i].pcol_l_);
429     }
430   return retval;
431 }
432 /*
433   Ugh. Should junk this.
434  */
435 void
436 Spring_spacer::loosen_column (int idx)
437 {
438   Column_info c=cols_.get (idx);
439
440   Cons<Idealspacing> **pp = &ideal_p_list_;
441
442   while (*pp)
443     {
444       Idealspacing *j = (*pp)->car_;
445       if (j->cols_drul_[LEFT] == idx|| j->cols_drul_[RIGHT] == idx)
446         {
447           delete remove_cons (pp);
448         }
449       else
450         {
451           pp = &(*pp)->next_;
452         }
453     }
454   c.ugh_b_ = true;
455
456   int j=0;
457   for (; j < loose_col_arr_.size(); j++)
458     {
459       if (loose_col_arr_[j].rank_i_ > c.rank_i_)
460         break;
461     }
462   loose_col_arr_.insert (c,j);
463 }
464
465
466 void
467 Spring_spacer::print() const
468 {
469 #ifndef NPRINT
470   for (int i=0; i < cols_.size(); i++)
471     {
472       DOUT << "col " << i << " ";
473       cols_[i].print();
474     }
475
476   for (Cons<Idealspacing> *p =ideal_p_list_; p; p = p->next_)
477     {
478       p->car_->print();
479     }
480 #endif
481 }
482
483
484 void
485 Spring_spacer::connect (int i, int j, Real d, Real h)
486 {
487   assert(d >= 0 && d <= 100 CM);
488   assert(h >=0);
489
490   Idealspacing * s = new Idealspacing;
491
492   s->cols_drul_[LEFT] = i ;
493   s->cols_drul_[RIGHT] = j;
494   s->space_f_ = d;
495   s->hooke_f_ = h;
496
497   ideal_p_list_ = new Killing_cons<Idealspacing> (s, ideal_p_list_);
498 }
499
500 void
501 Spring_spacer::prepare()
502 {
503   if (!experimental_features_global_b)
504     calc_idealspacing();
505   handle_loose_cols();
506   print();
507 }
508
509 Line_spacer*
510 Spring_spacer::constructor()
511 {
512   return new Spring_spacer;
513 }
514
515
516
517 /**
518   get the shortest_playing running note at a time. */
519 void
520 Spring_spacer::get_ruling_durations(Array<Moment> &context_shortest_arr)
521 {
522   for (int i=0; i < cols_.size(); i++)
523     {
524       scol_l (i)->preprocess();
525       scol_l (i)->print ();
526     }
527   int start_context_i=0;
528   Moment context_shortest;
529   context_shortest.set_infinite (1);
530   context_shortest_arr.set_size(cols_.size());
531
532   for (int i=0; i < cols_.size(); i++)
533     {
534       Score_column * sc = scol_l(i);
535
536       if (sc->breakable_b () || sc->break_status_dir ())
537         {
538           for (int ji=i; ji >= start_context_i; ji--)
539             context_shortest_arr[ji] = context_shortest;
540           start_context_i = i;
541           context_shortest.set_infinite (1);
542         }
543       else if (sc->musical_b ())
544         context_shortest = context_shortest <? sc->shortest_starter_mom_;
545     }
546
547 #ifndef NPRINT
548   DOUT << "context shortest :[ ";
549   for (int i=0; i < context_shortest_arr.size(); i++)
550     {
551       DOUT << context_shortest_arr[i] << ", ";
552     }
553   DOUT << "]\n";
554 #endif
555 }
556
557 /*
558   TODO: take out the refs to width
559
560  */
561 /**
562   generate springs between columns.
563
564   TODO: This needs rethinking....... 
565
566   * Spacing should take optical effects into account
567
568   * Should be decentralised
569   
570   The algorithm is taken from :
571
572   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
573   OSU-CISRC-10/87-TR35, Department of Computer and Information
574   Science, The Ohio State University, 1987.
575
576   */
577 void
578 Spring_spacer::calc_idealspacing()
579 {
580   Array<Moment> context_shortest_arr;
581   get_ruling_durations(context_shortest_arr);
582
583   Real interline_f = paper_l ()->get_realvar (interline_scm_sym);
584
585   Array<Real> ideal_arr;
586   Array<Real> hooke_arr;
587   for (int i=0; i < cols_.size() - 1; i++){
588     ideal_arr.push (-1.0);
589     hooke_arr.push (1.0);
590   }
591
592   /* 
593      First do all non-musical columns
594   */
595   for (int i=0; i < cols_.size(); i++)
596     {
597       if (!scol_l (i)->musical_b() && i+1 < cols_.size())
598         {
599           Real symbol_distance =cols_[i].width_[RIGHT] + 2 PT;
600           Real durational_distance = 0;
601           Moment delta_t =  scol_l (i+1)->when_mom () - scol_l (i)->when_mom () ;
602
603           /*
604             ugh should use shortest_playing distance
605           */
606           if (delta_t)
607             {
608               Real k=  paper_l()->arithmetic_constant (context_shortest_arr[i]);
609               durational_distance =  paper_l()->length_mom_to_dist (delta_t,k);
610             }
611           symbol_distance += -cols_[i+1].width_[LEFT];
612  
613
614           ideal_arr[i] = symbol_distance >? durational_distance;
615           hooke_arr[i] = 1; //2.0;
616         }
617     }
618
619   /* 
620      Then musicals
621   */
622   for (int i=0; i < cols_.size(); i++)
623     {
624       if (scol_l (i)->musical_b())
625         {
626           Moment shortest_playing_len = scol_l(i)->shortest_playing_mom_;
627           Moment context_shortest = context_shortest_arr[i];
628           if (! shortest_playing_len)
629             {
630               warning (_f ("can't find a ruling note at %s", 
631                 scol_l (i)->when_mom ().str ()));
632               shortest_playing_len = 1;
633             }
634           if (! context_shortest)
635             {
636               warning (_f ("no minimum in measure at %s", 
637                       scol_l (i)->when_mom ().str ()));
638               context_shortest = 1;
639             }
640           Moment delta_t = scol_l (i+1)->when_mom () - scol_l (i)->when_mom ();
641           Real k=  paper_l()->arithmetic_constant(context_shortest);
642           Real dist = paper_l()->length_mom_to_dist (shortest_playing_len, k);
643           dist *= (double)(delta_t / shortest_playing_len);
644
645           /*
646             According to [Ross] and [Wanske], and from what i've seen:
647              
648             * whitespace at the begin of the bar should be fixed at 
649             (about) one interline.
650
651             [Ross]:
652             when spacing gets real tight, a smaller fixed value may be 
653             used, so that there are two discrete amounts of whitespace 
654             possible at the begin of a bar; but this is not implemented 
655             right now.
656              
657             * whitespace at the end of the bar is the normal amount of 
658             "hinterfleish" that would have been used, had there been
659             yet another note in the bar.  
660
661             [Ross]:
662             some editors argue that the bar line should not take any 
663             space, not to hinder the flow of music spaced around a bar 
664             line.  
665
666             [Ross] and [Wanske] do not suggest this, however.  Further, 
667             it introduces some spacing problems and I think that it is ugly 
668             too.
669             
670             [jcn]
671           */
672
673           /* 
674              first musical column of bar
675           */
676           if (i && !scol_l (i - 1)->musical_b ())
677             {
678               // one interline minimum at start of bar
679
680               // cols_[i].width_[RIGHT] += interline_f;
681               cols_[i].width_[RIGHT] = cols_[i].width_[RIGHT] >? interline_f;
682
683               // should adjust dist too?
684               ideal_arr[i-1] = ideal_arr[i-1] >? (2 * interline_f);
685             }
686
687           /* 
688              last musical column of bar
689           */
690           if (i + 1 < cols_.size () && !scol_l(i+1)->musical_b ())
691             {
692               // two interline minimum ok for last column?
693               dist = dist >? 2 * interline_f;
694
695               // set minimum rod 
696               /*
697                 urg: simply *adding* an interline leaves big gaps at
698                 end of measure in star-spangled-banner (after lyrics
699                 at eom).
700
701                    cols_[i].width_[RIGHT] += interline_f; // before
702
703                 having a minimum of one interline solves this problem
704                 in most (but not all??) cases.
705
706                 for music without lyrics (esp. when set very tightly),
707                 adding an interline looks good: probably because this
708                 hides a bug that allows the last note's "hinterfleish"
709                 to be removed (e.g., see wtk1-fugue2: that's ugly now).
710                 -- jcn
711                */
712
713               cols_[i].width_[RIGHT] = cols_[i].width_[RIGHT] >? 2 * interline_f;
714             }
715
716           // ugh, do we need this?
717           if (i < cols_.size () - 1 && !scol_l (i + 1)->musical_b ())
718             {
719               Real minimum = -cols_[i + 1].width_[LEFT] + cols_[i].width_[RIGHT]
720                 + interline_f / 2;
721               dist = dist >? minimum;
722             }
723           ideal_arr[i] = dist;
724         }
725     }
726
727   /*
728     shorter distances should stretch less.
729
730     (and how bout
731
732       hooke[i] = 2 * max_ideal_space - ideal[i]
733
734     ?)
735   */
736   for (int i=0; i < ideal_arr.size(); i++)
737     hooke_arr[i] = 1/ideal_arr[i];
738
739   for (int i=0; i < ideal_arr.size(); i++)
740     {
741       assert (ideal_arr[i] >=0 && hooke_arr[i] >=0);
742       connect (i, i+1, ideal_arr[i], hooke_arr[i]);
743     }
744 }