From: hanwen Date: Tue, 21 Feb 2006 17:55:44 +0000 (+0000) Subject: * lily/include/constrained-breaking.hh (class X-Git-Tag: release/2.7.38^2~77 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=62c667b6d2a264b1db300317a639328fa4d6b424;p=lilypond.git * lily/include/constrained-breaking.hh (class Constrained_breaking): new file. * lily/constrained-breaking.cc (resize): new file. * lily/gourlay-breaking.cc (do_solve): ragged-last iso raggedlast --- diff --git a/ChangeLog b/ChangeLog index 5c9c839fc3..482d5e568a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ 2006-02-21 Han-Wen Nienhuys + * lily/include/constrained-breaking.hh (class + Constrained_breaking): new file. + + * lily/constrained-breaking.cc (resize): new file. + + * lily/gourlay-breaking.cc (do_solve): ragged-last iso raggedlast + * Documentation/user/advanced-notation.itely (Clusters): insert end ignore too. diff --git a/THANKS b/THANKS index d2c1f7bcf0..3a08e00d3f 100644 --- a/THANKS +++ b/THANKS @@ -23,8 +23,8 @@ Sven Axelsson Werner Lemberg Yoshinobu Ishizaki -SPONSORS +SPONSORS Aaron Mehl Basil Crow diff --git a/lily/constrained-breaking.cc b/lily/constrained-breaking.cc new file mode 100644 index 0000000000..8b5e11ca8b --- /dev/null +++ b/lily/constrained-breaking.cc @@ -0,0 +1,343 @@ +/* + constrained-breaking.cc -- implement a line breaker that + support limits on the number of systems + + source file of the GNU LilyPond music typesetter + + (c) 2006 Han-Wen Nienhuys +*/ + +#include "constrained-breaking.hh" + +#include "warn.hh" +#include "main.hh" +#include "paper-column.hh" +#include "paper-score.hh" +#include "output-def.hh" +#include "simple-spacer.hh" +#include "system.hh" + +#include "international.hh" + +void +print_constrained_break_nodes (vector const &arr) +{ + for (vsize i = 0; i < arr.size (); i++) + { + printf ("node %d: ", (int)i); + arr[i].print (); + } +} + +/** + We use the following optimal substructure. Let W(A) be our weight function. + + Let A_{k,n} = (a_{k,n,1}, ... a_{k,n,k}) be the optimal set of line breaks + for k systems and n potential breakpoints. a_{k,n,k} = n (it is the end of + the piece) + + Then A_{k+1, m} is contructed from + min_ {k < j < m} ( W(A_{k,j} :: m) ) + where by A::m we denote appending m to the list A +*/ + +/* start and sys here are indexed from 0. + * max_break is indexed from starting_breakpoints_[start] + * (for max_break, starting_breakpoints_[start] is the beginning + * of the piece; the smallest value we should ever see here is + * starting_breakpoints_[start] + 1) */ +bool +Constrained_breaking::calc_subproblem (int start, int sys, int max_break) +{ + assert (sys < systems_); + assert (start < (int)start_.size ()); + assert (max_break < (int)breaks_.size ()); + + bool found_something = false; + int start_col = starting_breakpoints_[start]; + vector &st = state_[start]; + int rank = breaks_.size () - start_col; + int max_index = max_break - start_col; + for (int j=sys; j < max_index; j++) + { + if (0 == sys && j > 0) + break; /* the first line cannot have its first break after the beginning */ + + Column_x_positions const &cur = cols_[(j + start_col)*cols_rank_ + max_break]; + Column_x_positions prev; + Real prev_dem = 0; + + if (sys > 0) + { + prev = st[(sys-1) * rank + j].line_config_; + prev_dem = st[(sys-1) * rank + j].demerits_; + } + if (isinf (prev_dem)) + break; + + Real dem, force, pen; + combine_demerits(prev, cur, &force, &pen, &dem); + dem += prev_dem; + if (isinf (dem)) + continue; + + if (isinf (st[sys*rank + max_index].demerits_) + || dem < st[sys*rank + max_index].demerits_) + { + found_something = true; + st[sys*rank + max_index].demerits_ = dem; + st[sys*rank + max_index].force_ = force; + st[sys*rank + max_index].penalty_ = pen; + st[sys*rank + max_index].prev_ = j; + st[sys*rank + max_index].line_config_ = cur; + } + } + return found_something; +} + +vector +Constrained_breaking::do_solve () +{ + if (!systems_) + { + programming_error (_f ("no system number set in constrained-breaking")); + systems_ = 4; + } + + resize (); + return get_solution(0, systems_, -1); +} + +void +Constrained_breaking::resize () +{ + if (!breaks_.size ()) + { + bool ragged_right = to_boolean (pscore_->layout ()->c_variable ("ragged-right")); + bool ragged_last = to_boolean (pscore_->layout ()->c_variable ("ragged-last")); + + /* do all the rod/spring problems */ + breaks_ = find_break_indices (); + cols_rank_ = breaks_.size (); + all_ = pscore_->root_system ()->columns (); + cols_.resize (breaks_.size () * breaks_.size ()); + for (vsize i = 0; i < breaks_.size () - 1; i++) + for (vsize j = i + 1; j < breaks_.size (); j++) + { + vector line (all_.begin () + breaks_[i], + all_.begin() + breaks_[j] + 1); + + line[0] = dynamic_cast (line[0])->find_prebroken_piece (RIGHT); + line.back () = dynamic_cast (line.back ())->find_prebroken_piece (LEFT); + + cols_[i*cols_rank_ + j].cols_ = line; + + /* we have no idea what line this will be -- only whether it is the first */ + Interval line_dims = line_dimensions_int (pscore_->layout (), i); + Simple_spacer_wrapper *sp = generate_spacing_problem (line, line_dims); + + /* TODO: support raggedness */ + bool last = j == breaks_.size () - 1; + bool ragged = ragged_right || (last && ragged_last); + sp->solve (&cols_[i*cols_rank_ + j], ragged); + + if (!cols_[i*cols_rank_ + j].satisfies_constraints_) + break; + delete sp; + } + + /* work out all the starting indices */ + for (vsize i = 0; i < start_.size (); i++) + { + vsize j; + for (j = 0; j < breaks_.size () - 1 && breaks_[j] < start_[i]; j++) + ; + starting_breakpoints_.push_back (j); + start_[i] = breaks_[j]; + } + state_.resize (start_.size ()); + } + + for (vsize i = 0; i < state_.size (); i++) + state_[i].resize((breaks_.size () - starting_breakpoints_[i]) * systems_); + + /* fill out the matrices */ + for (vsize i = 0; i < state_.size (); i++) + for (int j = valid_systems_; j < systems_; j++) + for (vsize k = starting_breakpoints_[i] + j + 1; k < breaks_.size (); k++) + if (!calc_subproblem (i, j, k)) + break; /* if we couldn't break this, it is too cramped already */ + valid_systems_ = systems_; +} + +vector +Constrained_breaking::get_solution (int start, int end, int sys_count) +{ + int rank, brk; + prepare_solution (start, end, sys_count, &rank, &brk); + + vector const &st = state_[start]; + vector ret; + + for (int sys = sys_count-1; sys >= 0; sys--) + { + assert (brk > 0); + ret.push_back( st[sys*rank + brk].line_config_ ); + brk = st[sys*rank + brk].prev_; + } + assert (brk == 0); + reverse (ret); + return ret; +} + +Real +Constrained_breaking::get_demerits (int start, int end, int sys_count) +{ + int rank, brk; + prepare_solution (start, end, sys_count, &rank, &brk); + + return state_[start][(sys_count-1)*rank + brk].demerits_; +} + +Real +Constrained_breaking::get_force (int start, int end, int sys_count) +{ + int rank, brk; + prepare_solution (start, end, sys_count, &rank, &brk); + vector const &st = state_[start]; + Real f = 0; + + for (int sys = sys_count-1; sys >= 0 && brk >= 0; sys--) + { + f += fabs (st[sys*rank + brk].force_); + brk = st[sys*rank + brk].prev_; + } + if (brk < 0) + f = infinity_f; + + return f; +} + +Real +Constrained_breaking::get_penalty (int start, int end, int sys_count) +{ + int rank, brk; + prepare_solution (start, end, sys_count, &rank, &brk); + + return state_[start][(sys_count-1)*rank + brk].penalty_; +} + +Real +Constrained_breaking::get_page_penalty (int start, int end, int sys_count, int sys_num) +{ + int rank, brk; + prepare_solution (start, end, sys_count, &rank, &brk); + + int sys; + for (sys = sys_count-1; sys > sys_num; sys--) + brk = state_[start][sys*rank + brk].prev_; + + if (brk < 0) /* we didn't satisfy constraints */ + return 0; + vector &cols = state_[start][sys*rank + brk].line_config_.cols_; + if (cols.empty ()) + return 0; + + Grob *pc = cols.back (); + if (pc->original ()) + { + SCM pen = pc->get_property ("page-penalty"); + if (scm_is_number (pen) && fabs (scm_to_double (pen)) < 10000) + return scm_to_double (pen); + } + return 0; +} + +int +Constrained_breaking::get_min_systems (int start, int end) +{ + int rank, brk; + prepare_solution (start, end, 1, &rank, &brk); + int sys_count; + vector const &st = state_[start]; + + /* sys_count < rank : rank is the # of breakpoints, we can't have more systems */ + for (sys_count = 0; sys_count < rank; sys_count++) + { + if (sys_count >= valid_systems_) + { + systems_ = sys_count + 3; + resize (); + } + if (!isinf (st[sys_count*rank + brk].force_)) + return sys_count + 1; + } + /* no possible breaks satisfy constraints */ + return 0; +} + +int +Constrained_breaking::get_max_systems (int start, int end) +{ + int brk = (end < 0 || end >= (int)start_.size ()) ? breaks_.size () - 1 : start_[end]; + return brk - starting_breakpoints_[start]; +} + +void +Constrained_breaking::prepare_solution (int start, int end, int sys_count, int *rank, int *brk) +{ + assert (start < (int)start_.size () && end <= (int)start_.size ()); + assert (end < 0 || start < end); + assert (sys_count > 0); + + if (sys_count >= valid_systems_) + { + systems_ = sys_count; + resize (); + } + if (end == (int)start_.size ()) + end = -1; + + *rank = breaks_.size () - starting_breakpoints_[start]; + *brk = end < 0 ? breaks_.size () - 1 : starting_breakpoints_[end]; + *brk -= starting_breakpoints_[start]; +} + +Constrained_breaking::Constrained_breaking () +{ + valid_systems_ = systems_ = 0; + start_.push_back (0); +} + +Constrained_breaking::Constrained_breaking (vector const &start): + start_ (start) +{ + valid_systems_ = systems_ = 0; +} + +void +Constrained_breaking::combine_demerits (Column_x_positions const &prev, + Column_x_positions const &col, + Real *force, + Real *penalty, + Real *demerits) const +{ + *penalty = 0; + if (col.cols_.empty () || !col.satisfies_constraints_) + *force = infinity_f; + else + { + *force = col.force_; + + Grob *pc = col.cols_.back (); + if (pc->original ()) + { + SCM pen = pc->get_property ("penalty"); + if (scm_is_number (pen) && fabs (scm_to_double (pen)) < 10000) + *penalty += scm_to_double (pen); + } + } + + *demerits = (*force) * (*force) + abs (prev.force_ - *force) + *penalty; +} + diff --git a/lily/gourlay-breaking.cc b/lily/gourlay-breaking.cc index dfbd0b0f5c..a02b4a5533 100644 --- a/lily/gourlay-breaking.cc +++ b/lily/gourlay-breaking.cc @@ -87,7 +87,7 @@ Gourlay_breaking::do_solve () const optimal_paths.push_back (first_node); bool ragged_right = to_boolean (pscore_->layout ()->c_variable ("ragged-right")); - bool ragged_last = to_boolean (pscore_->layout ()->c_variable ("raggedlast")); + bool ragged_last = to_boolean (pscore_->layout ()->c_variable ("ragged-last")); Real worst_force = 0.0; for (vsize break_idx = 1; break_idx < breaks.size (); break_idx++) diff --git a/lily/include/constrained-breaking.hh b/lily/include/constrained-breaking.hh new file mode 100644 index 0000000000..11965e8b64 --- /dev/null +++ b/lily/include/constrained-breaking.hh @@ -0,0 +1,94 @@ +/* + constrained-breaking.hh -- declare a line breaker that + supports limits on the number of systems + + source file of the GNU LilyPond music typesetter + + (c) 2006 Han-Wen Nienhuys +*/ + +#ifndef CONSTRAINED_BREAKING_HH +#define CONSTRAINED_BREAKING_HH + +#include "break-algorithm.hh" + +/** + Helper to trace back an optimal path +*/ +struct Constrained_break_node +{ + /** the number of bars in all the systems before this one + */ + int prev_; + + /** unlike the Gourlay breaker, this is the sum of all demerits up to, + * and including, this line */ + Real demerits_; + Real force_; + Real penalty_; + Column_x_positions line_config_; + + Constrained_break_node () + { + prev_ = -1; + demerits_ = infinity_f; + force_ = infinity_f; + penalty_ = 0; + line_config_.satisfies_constraints_ = false; + } + + void print () const + { + printf ("prev break %d, demerits %f\n", + prev_, demerits_); + } +}; + +/** + A dynamic programming solution to breaking scores into lines +*/ +class Constrained_breaking : public Break_algorithm +{ + public: + std::vector do_solve (); + Constrained_breaking (); + Constrained_breaking (std::vector const &start_col_posns); + + std::vector get_solution(int start, int end, int sys_count); + Real get_demerits (int start, int end, int sys_count); + Real get_force (int start, int end, int sys_count); + Real get_penalty (int start, int end, int sys_count); + int get_max_systems (int start, int end); + int get_min_systems (int start, int end); + + /* get the page penalty of system number sys with the given breaking */ + Real get_page_penalty (int start, int end, int sys_count, int sys); + + private: + int valid_systems_; + int systems_; + + /* the (i,j)th entry is the column configuration for breaking between + * columns i and j */ + std::vector cols_; + int cols_rank_; + + /* the [i](j,k)th entry is the score for fitting the first k bars onto the + * first j systems, starting at the i'th allowed starting column */ + std::vector > state_; + + vector start_; /* the columns at which we might be asked to start breaking */ + vector starting_breakpoints_; /* the corresponding index in breaks_ */ + + vector all_; + std::vector breaks_; + + void prepare_solution (int start, int end, int sys_count, int *rank, int *brk); + + void combine_demerits (Column_x_positions const &, Column_x_positions const &, + Real *force, Real *pen, Real *dem) const; + + bool calc_subproblem(int start, int systems, int max_break_index); + void resize (); +}; +#endif /* CONSTRAINED_BREAKING_HH */ diff --git a/lily/paper-score.cc b/lily/paper-score.cc index 5f7e1540df..330bf3ae49 100644 --- a/lily/paper-score.cc +++ b/lily/paper-score.cc @@ -63,8 +63,16 @@ Paper_score::calc_breaking () { Break_algorithm *algorithm = 0; vector sol; - - algorithm = new Gourlay_breaking; + + int system_count = robust_scm2int (layout ()->c_variable ("system-count"), 0); + if (system_count) + { + Constrained_breaking *b = new Constrained_breaking; + algorithm = b; + } + else + algorithm = new Gourlay_breaking; + algorithm->set_pscore (this); sol = algorithm->solve (); delete algorithm; diff --git a/python/convertrules.py b/python/convertrules.py index 9f3a492ea0..3d89f22f60 100644 --- a/python/convertrules.py +++ b/python/convertrules.py @@ -2705,6 +2705,7 @@ def conv (str): ('betweensystempadding', 'between-system-padding'), ('pagetopspace', 'page-top-space'), ('raggedright', 'ragged-right'), + ('raggedlast', 'ragged-last'), ('raggedbottom', 'ragged-bottom'), ('raggedlastbottom', 'ragged-last-bottom'), ('aftertitlespace', 'after-title-space'),