A programme laid out as far as it would go — what was placed, and what was left out.
Agenda.Arranger.arrange/3 fails the whole programme by default:
if one session cannot be held, there is no arrangement. That is the
right answer when the programme is a unit, and the wrong one when it
is a wish list. A conference of forty sessions where thirty-eight fit
is not a failure — it is thirty-eight sessions and two conversations
to have.
A layout is what arrange/3 returns under unplaced: :allow when it
could not place everything. It is never returned as {:ok, …}:
the tag is {:partial, layout}, so a caller who has not thought
about incompleteness cannot mistake one for a finished programme.
case Agenda.arrange(programme, pool, unplaced: :allow) do
{:ok, arrangements} -> publish(arrangements)
{:partial, layout} -> review(layout.placed, layout.unplaced)
{:error, reason} -> abandon(reason)
endEach entry in unplaced is a Agenda.Infeasible.t/0, so a
session that could not be held still says why.
Whether this is the best partial layout
minimal? separates two answers that would otherwise look
identical:
true— no layout leaves out fewer sessions. The search either finished, or matched the most any arrangement of these candidates could place.false— the best found before the search hit its:nodescap. A better layout may exist; raise:nodesand ask again.
A caller that treats the second as though it were the first will turn away work it could have taken, so the distinction is a field rather than a footnote.
The score
score is what the layout cost against the programme's
Agenda.Preferences, where 0 is ideal. score_proven? is its
counterpart to minimal? and answers a different question:
minimal?— is this the fewest sessions left out? Proven by the first pass, and never affected by the second.score_proven?— did the scoring pass finish, or stop at its:score_nodesbudget with the best it had found?
The two are independent. A layout can be provably minimal and merely well-scored, which is the common case, because the count is cheap to prove and the score is not.
Agenda.explain_score/3 breaks the number down per preference,
since a bare total says a layout is worse without saying how.
Summary
Functions
The layout as a sentence — how much was placed, and why the rest was not.
Build a layout.
The names of the sessions that could not be placed.
Types
@type t() :: %Agenda.Layout{ minimal?: boolean(), placed: [Agenda.Arrangement.t()], programme: String.t(), score: number(), score_proven?: boolean(), unplaced: [Agenda.Infeasible.t()] }
A programme placed as far as it would go.
Functions
The layout as a sentence — how much was placed, and why the rest was not.
Arguments
layoutis at/0.
Returns
- a sentence naming the programme, the count placed, and each session that was left out with its reasons. A layout that is not known to be the best says so, since the reader's next move differs.
Examples
iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.explain(Agenda.Layout.new("Conf", [], [reason]))
"Conf: 0 of 1 sessions placed. Workshop cannot be held: no room seats 8"
iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.explain(Agenda.Layout.new("Conf", [], [reason], false))
"Conf: 0 of 1 sessions placed, and the search stopped at its node limit before proving that is the fewest left out — raise :nodes to be sure. Workshop cannot be held: no room seats 8"
@spec new( String.t(), [Agenda.Arrangement.t()], [Agenda.Infeasible.t(), ...], boolean() ) :: t()
Build a layout.
Arguments
programmeis the programme's name.placedis the list ofAgenda.Arrangement.t/0that were successfully placed.unplacedis a non-empty list ofAgenda.Infeasible.t/0, one per session that could not be held.minimal?is whether no layout leaves out fewer. The default istrue.
Returns
- a
t/0.
Examples
iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> layout = Agenda.Layout.new("Conf", [], [reason])
iex> {Enum.map(layout.unplaced, & &1.session), layout.minimal?}
{["Workshop"], true}
iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.new("Conf", [], [reason], false).minimal?
false
The names of the sessions that could not be placed.
Arguments
layoutis at/0.
Returns
- the session names, in the order they were given up on.
Examples
iex> reason = Agenda.Infeasible.new("Workshop", ["no room seats 8"])
iex> Agenda.Layout.unplaced_sessions(Agenda.Layout.new("Conf", [], [reason]))
["Workshop"]