.. _library_copeland_ranker:

``copeland_ranker``
===================

Copeland pairwise preference ranker. Ranks each item by its number of
matchup wins minus losses after aggregating weighted pairwise
preferences per observed opponent pair.

The library implements the ``ranker_protocol`` defined in the
``ranking_protocols`` library. It provides predicates for learning a
ranker from pairwise preference judgments, using it to order candidate
items, and exporting it as a list of predicate clauses or to a file.

Datasets are represented as objects implementing the
``pairwise_ranking_dataset_protocol`` protocol from the
``ranking_protocols`` library. See the ``test_datasets`` directory for
examples. The training dataset must declare each ranked item once, use
only declared items in preferences, assign positive weights to
preferences between distinct items, and induce a connected undirected
comparison graph.

API documentation
-----------------

Open the
`../../apis/library_index.html#copeland_ranker <../../apis/library_index.html#copeland_ranker>`__
link in a web browser.

Loading
-------

To load this library, load the ``loader.lgt`` file:

::

   | ?- logtalk_load(copeland_ranker(loader)).

Testing
-------

To test this library predicates, load the ``tester.lgt`` file:

::

   | ?- logtalk_load(copeland_ranker(tester)).

Features
--------

- **Pairwise Preference Learning**: Learns one deterministic score per
  item from pairwise preference datasets.
- **Portable Copeland Scoring**: Computes Copeland scores from
  aggregated head-to-head outcomes using only standard Logtalk library
  predicates.
- **Integer Score Semantics**: Learned scores are restricted to
  integers, because each observed aggregated matchup contributes exactly
  ``+1``, ``-1``, or ``0`` to an item's total score.
- **Deterministic Ranking**: Orders candidate items by learned score
  with deterministic tie-breaking using the standard term order of the
  item identifiers after sorting by descending score.
- **Strict Dataset Validation**: Rejects duplicate items, undeclared
  items in preferences, self-preferences, non-positive weights, and
  disconnected comparison graphs.
- **Training Diagnostics**: Learned rankers include dataset summary
  metadata that can be accessed using the ``diagnostics/2`` predicate.
- **Ranker Export**: Learned rankers can be exported as self-contained
  terms.
- **Shared Ranking Infrastructure**: Uses the common
  ``ranking_protocols`` helper predicates for option processing, dataset
  validation, diagnostics, export, and candidate ranking.

Scoring semantics
-----------------

This implementation uses a Copeland score defined over aggregated
observed matchups. For each unordered pair of observed opponents, the
preference data is aggregated into total wins for the left and right
item. The item with the higher aggregated total receives ``+1`` for that
matchup, the item with the lower total receives ``-1``, and both receive
``0`` when the aggregated totals tie.

Only observed opponent pairs contribute to the learned scores.
Unobserved pairs are ignored rather than treated as implicit ties. This
makes the implementation a sparse-data Copeland variant suitable for
incomplete pairwise datasets.

Because each observed matchup contributes only ``+1``, ``-1``, or ``0``,
every learned Copeland score is an integer. The ranker validation logic
enforces this invariant when consuming serialized or exported ranker
terms so that malformed non-integer score payloads are rejected instead
of silently accepted.

Usage
-----

Learning a ranker
~~~~~~~~~~~~~~~~~

::

       % Learn from a pairwise ranking dataset object
       | ?- copeland_ranker::learn(my_dataset, Ranker).
       ...

       % Learn with an explicit empty options list
       | ?- copeland_ranker::learn(my_dataset, Ranker, []).
       ...

The current implementation accepts only the empty options list ``[]``.
Any non-empty options list is rejected.

Inspecting diagnostics
~~~~~~~~~~~~~~~~~~~~~~

::

       % Inspect model and dataset summary metadata
       | ?- copeland_ranker::learn(my_dataset, Ranker),
            copeland_ranker::diagnostics(Ranker, Diagnostics).
       Diagnostics = [...]
       ...

Ranking candidate items
~~~~~~~~~~~~~~~~~~~~~~~

::

       % Rank a candidate set from most preferred to least preferred
       | ?- copeland_ranker::learn(my_dataset, Ranker),
            copeland_ranker::rank(Ranker, [item_a, item_b, item_c], Ranking).
       Ranking = [...]
       ...

Candidate lists must be proper lists of unique, ground items declared by
the training dataset. Invalid ranker terms, duplicate candidates, and
candidates containing variables are rejected with errors instead of
being silently accepted.

Exporting the ranker
~~~~~~~~~~~~~~~~~~~~

Learned rankers can be exported as a list of clauses or to a file for
later use.

::

       % Export as predicate clauses
       | ?- copeland_ranker::learn(my_dataset, Ranker),
            copeland_ranker::export_to_clauses(my_dataset, Ranker, my_ranker, Clauses).
       Clauses = [my_ranker(copeland_ranker(...))]
       ...

       % Export to a file
       | ?- copeland_ranker::learn(my_dataset, Ranker),
            copeland_ranker::export_to_file(my_dataset, Ranker, my_ranker, 'ranker.pl').
       ...

Diagnostics syntax
------------------

The ``diagnostics/2`` predicate returns a list of metadata terms with
the form:

::

       [
           model(copeland_ranker),
           options(Options),
           dataset_summary(DatasetSummary)
       ]

Where:

- ``model(copeland_ranker)`` identifies the learning algorithm that
  produced the ranker.
- ``options(Options)`` stores the effective learning options after
  merging the user options with the library defaults.
- ``dataset_summary(DatasetSummary)`` stores a summary list describing
  the validated training dataset.

The current ``dataset_summary/1`` payload has the form:

::

       [
           items(NumberOfItems),
           preferences(NumberOfPreferences),
           connected_components(NumberOfComponents),
           isolated_items(IsolatedItems)
       ]

Use the ``ranking_protocols`` ``diagnostic/2`` and ``ranker_options/2``
helper predicates when you only need a single metadata term or the
effective options.

Options
-------

The current ``learn/3`` implementation does not define any user options
beyond the default empty list. Non-empty options lists are rejected.

Ranker representation
---------------------

The learned ranker is represented by a compound term of the form:

::

       copeland_ranker(Items, Scores, Diagnostics)

Where:

- ``Items``: List of ranked items.
- ``Scores``: List of ``Item-Score`` pairs.
- ``Diagnostics``: List of metadata terms, including the effective
  options and dataset summary.

The ``Scores`` payload is expected to contain integer Copeland scores
only. This restriction is not just an implementation preference but a
direct consequence of the scoring semantics: each observed aggregated
matchup changes an item's score by one of the three integer values
``+1``, ``-1``, or ``0``.

When exported using ``export_to_clauses/4`` or ``export_to_file/4``,
this ranker term is serialized directly as the single argument of the
generated predicate clause so that the exported model can be loaded and
reused as-is.

References
----------

1. Copeland, A. H. (1951). A reasonable social welfare function.
   *Seminar on Mathematics in the Social Sciences, University of
   Michigan*.
