2 constrained-breaking.cc -- implement a line breaker that
3 support limits on the number of systems
5 source file of the GNU LilyPond music typesetter
7 (c) 2006--2009 Joe Neeman <joeneeman@gmail.com>
10 #include "constrained-breaking.hh"
12 #include "international.hh"
14 #include "output-def.hh"
15 #include "paper-column.hh"
16 #include "paper-score.hh"
17 #include "simple-spacer.hh"
22 We use the following optimal substructure. Let W (A) be our weight function.
24 Let A_{k, n} = (a_{k, n,1}, ... a_{k, n, k}) be the optimal set of line breaks
25 for k systems and n potential breakpoints. a_{k, n, k} = n (it is the end of
28 Then A_{k+1, m} is contructed from
29 min_ {k < j < m} ( W (A_{k, j} :: m) )
30 where by A::m we denote appending m to the list A
34 The above algorithm makes it easy to end at a point before the end of the
35 score (just find A_{k, m} for some m < breaks_.size () - 1). However, we must
36 add information for starting at a point after the beginning. One constructor
37 allows the specification of a list of starting columns, start_. We then have
38 start_.size () different solution arrays. state_[i] is the array for the
39 solution starting at column number start_[i].
41 The indices "start" and "end" refer to the index in the start_ array of the
42 desired starting and ending columns.
44 each solution array looks like
45 a_{1,1,1} a_{2,1,2} a_{3,1,3} . . .
46 X a_{2,2,2} a_{3,2,3} . . .
50 where the X's mark invalid solutions (can't have more systems than
51 breakpoints). Note that each value is of the form a_{x, n, x}. This is because
52 a breakpoint of the form a_{x, n, x-1} will also be called a_{x-1, m, x-1} for
53 some m < n. Each cell in the array stores the value of its m (ie. the
54 ending breakpoint of the previous line) as "prev_".
56 For finding A_{sys, brk}, let "me" be the (sys_count, brk) cell in our
57 solution array (state_[start][sys * rank + brk]).
59 Then A_{sys, brk} = A_{sys - 1, me.prev_} :: me
63 start and sys here are indexed from 0.
64 brk is indexed from starting_breakpoints_[start]
65 (for brk, starting_breakpoints_[start] is the beginning
66 of the piece; the smallest value we should ever see here is
67 starting_breakpoints_[start] + 1) */
69 Constrained_breaking::calc_subproblem (vsize start
, vsize sys
, vsize brk
)
71 assert (sys
< systems_
);
72 assert (start
< start_
.size ());
73 assert (brk
< breaks_
.size ());
75 bool found_something
= false;
76 vsize start_col
= starting_breakpoints_
[start
];
77 Matrix
<Constrained_break_node
> &st
= state_
[start
];
78 vsize max_index
= brk
- start_col
;
79 for (vsize j
=max_index
; j
-- > sys
;)
81 if (0 == sys
&& j
> 0)
82 continue; /* the first line cannot have its first break after the beginning */
84 Line_details
const &cur
= lines_
.at (brk
, j
+ start_col
);
85 if (isinf (cur
.force_
))
93 prev_f
= st
.at (j
, sys
-1).details_
.force_
;
94 prev_dem
= st
.at (j
, sys
-1).demerits_
;
99 Real dem
= combine_demerits (cur
.force_
, prev_f
) + prev_dem
+ cur
.break_penalty_
;
100 Constrained_break_node
&n
= st
.at (max_index
, sys
);
101 if (dem
< n
.demerits_
)
103 found_something
= true;
109 return found_something
;
114 Constrained_breaking::space_line (vsize i
, vsize j
)
116 bool ragged_right
= to_boolean (pscore_
->layout ()->c_variable ("ragged-right"));
117 bool ragged_last
= to_boolean (pscore_
->layout ()->c_variable ("ragged-last"));
118 Column_x_positions col
;
120 vector
<Grob
*> line (all_
.begin () + breaks_
[i
],
121 all_
.begin () + breaks_
[j
] + 1);
122 Interval line_dims
= line_dimensions_int (pscore_
->layout (), i
);
123 bool last
= j
== breaks_
.size () - 1;
124 bool ragged
= ragged_right
|| (last
&& ragged_last
);
126 /* As a special case, if there is only one line in the score and ragged-right
127 hasn't been specifically forbidden and the line is stretched, use
130 && lines_
.at (i
, j
).force_
>= 0
131 && !scm_is_bool (pscore_
->layout ()->c_variable ("ragged-right"))
132 && !scm_is_bool (pscore_
->layout ()->c_variable ("ragged-last")))
135 return get_line_configuration (line
, line_dims
[RIGHT
] - line_dims
[LEFT
], line_dims
[LEFT
], ragged
);
139 Constrained_breaking::resize (vsize systems
)
143 if (pscore_
&& systems_
> valid_systems_
)
145 for (vsize i
= 0; i
< state_
.size (); i
++)
146 state_
[i
].resize (breaks_
.size () - starting_breakpoints_
[i
], systems_
, Constrained_break_node ());
148 /* fill out the matrices */
149 for (vsize i
= 0; i
< state_
.size (); i
++)
150 for (vsize j
= valid_systems_
; j
< systems_
; j
++)
151 for (vsize k
= starting_breakpoints_
[i
] + j
+ 1; k
< breaks_
.size (); k
++)
152 if (!calc_subproblem (i
, j
, k
))
153 break; /* if we couldn't break this, it is too cramped already */
154 valid_systems_
= systems_
;
158 vector
<Column_x_positions
>
159 Constrained_breaking::solve (vsize start
, vsize end
, vsize sys_count
)
161 vsize start_brk
= starting_breakpoints_
[start
];
162 vsize end_brk
= prepare_solution (start
, end
, sys_count
);
164 Matrix
<Constrained_break_node
> const &st
= state_
[start
];
165 vector
<Column_x_positions
> ret
;
167 /* find the first solution that satisfies constraints */
168 for (vsize sys
= sys_count
-1; sys
!= VPOS
; sys
--)
170 for (vsize brk
= end_brk
; brk
!= VPOS
; brk
--)
172 if (!isinf (st
.at (brk
, sys
).details_
.force_
))
176 warning (_ ("cannot find line breaking that satisfies constraints" ));
177 ret
.push_back (space_line (brk
, end_brk
));
179 /* build up the good solution */
180 for (vsize cur_sys
= sys
; cur_sys
!= VPOS
; cur_sys
--)
182 vsize prev_brk
= st
.at (brk
, cur_sys
).prev_
;
183 assert (brk
!= VPOS
);
184 ret
.push_back (space_line (prev_brk
+ start_brk
, brk
+ start_brk
));
192 /* if we get to here, just put everything on one line */
193 warning (_ ("cannot find line breaking that satisfies constraints"));
194 ret
.push_back (space_line (0, end_brk
));
198 vector
<Column_x_positions
>
199 Constrained_breaking::best_solution (vsize start
, vsize end
)
201 vsize min_systems
= min_system_count (start
, end
);
202 vsize max_systems
= max_system_count (start
, end
);
203 Real best_demerits
= infinity_f
;
204 vector
<Column_x_positions
> best_so_far
;
206 for (vsize i
= min_systems
; i
<= max_systems
; i
++)
208 vsize brk
= prepare_solution (start
, end
, i
);
209 Real dem
= state_
[start
].at (brk
, i
-1).demerits_
;
211 if (dem
< best_demerits
)
214 best_so_far
= solve (start
, end
, i
);
218 vector
<Column_x_positions
> cur
= solve (start
, end
, i
);
219 bool too_many_lines
= true;
221 for (vsize j
= 0; j
< cur
.size (); j
++)
222 if (cur
[j
].force_
< 0)
224 too_many_lines
= false;
231 if (best_so_far
.size ())
233 return solve (start
, end
, max_systems
);
236 std::vector
<Line_details
>
237 Constrained_breaking::line_details (vsize start
, vsize end
, vsize sys_count
)
239 vsize brk
= prepare_solution (start
, end
, sys_count
);
240 Matrix
<Constrained_break_node
> const &st
= state_
[start
];
241 vector
<Line_details
> ret
;
243 for (int sys
= sys_count
-1; sys
>= 0 && brk
!= VPOS
; sys
--)
245 ret
.push_back (st
.at (brk
, sys
).details_
);
246 brk
= st
.at (brk
, sys
).prev_
;
253 Constrained_breaking::min_system_count (vsize start
, vsize end
)
256 vsize brk
= prepare_solution (start
, end
, 1);
257 vsize rank
= breaks_
.size () - starting_breakpoints_
[start
];
258 Matrix
<Constrained_break_node
> const &st
= state_
[start
];
260 /* sys_count < rank : rank is the # of breakpoints, we can't have more systems */
261 for (sys_count
= 0; sys_count
< rank
; sys_count
++)
263 if (sys_count
>= valid_systems_
)
265 resize (sys_count
+ 3);
267 if (!isinf (st
.at (brk
, sys_count
).details_
.force_
))
268 return sys_count
+ 1;
270 /* no possible breaks satisfy constraints */
275 Constrained_breaking::max_system_count (vsize start
, vsize end
)
277 vsize brk
= (end
>= start_
.size ()) ? breaks_
.size () - 1 : starting_breakpoints_
[end
];
278 return brk
- starting_breakpoints_
[start
];
282 Constrained_breaking::prepare_solution (vsize start
, vsize end
, vsize sys_count
)
284 assert (start
< start_
.size () && (end
== VPOS
|| end
<= start_
.size ()));
285 assert (start
< end
);
288 if (end
== start_
.size ())
292 brk
= end
== VPOS
? breaks_
.size () - 1 : starting_breakpoints_
[end
];
293 brk
-= starting_breakpoints_
[start
];
297 Constrained_breaking::Constrained_breaking (Paper_score
*ps
)
299 valid_systems_
= systems_
= 0;
300 start_
.push_back (0);
305 Constrained_breaking::Constrained_breaking (Paper_score
*ps
, vector
<vsize
> const &start
)
308 valid_systems_
= systems_
= 0;
314 min_permission (SCM perm1
, SCM perm2
)
316 if (perm1
== ly_symbol2scm ("force"))
318 if (perm1
== ly_symbol2scm ("allow")
319 && perm2
!= ly_symbol2scm ("force"))
324 /* find the forces for all possible lines and cache ragged_ and ragged_right_ */
326 Constrained_breaking::initialize ()
331 ragged_right_
= to_boolean (pscore_
->layout ()->c_variable ("ragged-right"));
332 ragged_last_
= to_boolean (pscore_
->layout ()->c_variable ("ragged-last"));
334 Output_def
*l
= pscore_
->layout ();
335 System
*sys
= pscore_
->root_system ();
336 Real space
= robust_scm2double (l
->c_variable ("ideal-system-space"), 0);
337 SCM padding_scm
= l
->c_variable ("page-breaking-between-system-padding");
338 if (!scm_is_number (padding_scm
))
339 padding_scm
= l
->c_variable ("between-system-padding");
340 Real padding
= robust_scm2double (padding_scm
, 0.0);
342 Interval first_line
= line_dimensions_int (pscore_
->layout (), 0);
343 Interval other_lines
= line_dimensions_int (pscore_
->layout (), 1);
344 /* do all the rod/spring problems */
345 breaks_
= pscore_
->find_break_indices ();
346 all_
= pscore_
->root_system ()->used_columns ();
347 lines_
.resize (breaks_
.size (), breaks_
.size (), Line_details ());
348 vector
<Real
> forces
= get_line_forces (all_
,
349 other_lines
.length (),
350 other_lines
.length () - first_line
.length (),
352 for (vsize i
= 0; i
+ 1 < breaks_
.size (); i
++)
354 for (vsize j
= i
+ 1; j
< breaks_
.size (); j
++)
356 int start
= Paper_column::get_rank (all_
[breaks_
[i
]]);
357 int end
= Paper_column::get_rank (all_
[breaks_
[j
]]);
358 Interval extent
= sys
->pure_height (sys
, start
, end
);
359 bool last
= j
== breaks_
.size () - 1;
360 bool ragged
= ragged_right_
|| (last
&& ragged_last_
);
361 Line_details
&line
= lines_
.at (j
, i
);
363 line
.force_
= forces
[i
*breaks_
.size () + j
];
364 if (ragged
&& last
&& !isinf (line
.force_
))
365 line
.force_
= (line
.force_
< 0 && j
> i
+ 1) ? infinity_f
: 0;
366 if (isinf (line
.force_
))
369 Grob
*c
= all_
[breaks_
[j
]];
370 line
.break_penalty_
= robust_scm2double (c
->get_property ("line-break-penalty"), 0);
371 line
.page_penalty_
= robust_scm2double (c
->get_property ("page-break-penalty"), 0);
372 line
.turn_penalty_
= robust_scm2double (c
->get_property ("page-turn-penalty"), 0);
373 line
.break_permission_
= c
->get_property ("line-break-permission");
374 line
.page_permission_
= c
->get_property ("page-break-permission");
375 line
.turn_permission_
= c
->get_property ("page-turn-permission");
377 /* turn permission should always be stricter than page permission
378 and page permission should always be stricter than line permission */
379 line
.page_permission_
= min_permission (line
.break_permission_
,
380 line
.page_permission_
);
381 line
.turn_permission_
= min_permission (line
.page_permission_
,
382 line
.turn_permission_
);
384 line
.extent_
= (extent
.is_empty ()
385 || isnan (extent
[LEFT
])
386 || isnan (extent
[RIGHT
]))
387 ? Interval (0, 0) : extent
;
388 line
.padding_
= padding
;
390 line
.inverse_hooke_
= extent
.length () + space
;
394 /* work out all the starting indices */
395 for (vsize i
= 0; i
< start_
.size (); i
++)
398 for (j
= 0; j
+ 1 < breaks_
.size () && breaks_
[j
] < start_
[i
]; j
++)
400 starting_breakpoints_
.push_back (j
);
401 start_
[i
] = breaks_
[j
];
403 state_
.resize (start_
.size ());
407 Constrained_breaking::combine_demerits (Real force
, Real prev_force
)
410 return force
* force
;
412 return force
* force
+ (prev_force
- force
) * (prev_force
- force
);