diff --git a/deliberate.scm b/deliberate.scm index 895e00d..88f754b 100644 --- a/deliberate.scm +++ b/deliberate.scm @@ -19,6 +19,8 @@ #:use-module (goblins actor-lib methods) #:use-module (deliberate utils) #:use-module (deliberate preference) + #:use-module (deliberate irv) + #:use-module (deliberate borda) #:export (^deliberation ^deliberation-info ^ranked-choice-vote ^ranked-ballot ^ranked-preference)) ;; Constructor for ^deliberation-info @@ -28,89 +30,130 @@ ;; title: Title of the deliberation ;; ;; Messages: -;; get-title: Return the title -;; set-title: new-title: Set the title to new-title +;; title: Return the title +;; set-title: new-title: Set the title to `new-title` ;; (define (^deliberation-info bcom title) (methods - ((get-title) title) - ((set-title) (bcom (^decision bcom title))) + ((title) title) + ((set-title new-title) + (bcom (^deliberation-info bcom new-title))) )) ;; Constructor for ^deliberation -;; A deliberation with multiple possible alternative outcomes +;; A deliberation including discussion and multiple votes. +;; +;; TODO: Add discussion functionality ;; ;; Parameters: ;; title: Title of the deliberation ;; ;; Messages: -;; get-title: Return the title -;; set-title: new-title: Set the title to new-title -;; add-alternative alt: Add a new alternative (possible outcome) -;; represented by the string alt. -;; get-alternatives: Return a list of all alteratives +;; title: Return the title +;; set-title: new-title: Set the title to `new-title` +;; votes: Return the list of votes +;; add-vote: Create a new vote and add it to this deliberation. The new +;; vote is returned ;; (define (^deliberation _bcom title) - (let ( - (info (spawn ^deliberation-info title)) - (alternatives (spawn ^alternatives title)) - ) - (methods - ((get-title) ($ info 'get-title)) - ((set-title new-title) ($ info 'set-title new-title)) - ((add-alternative alt) ($ alternatives 'add alt)) - ((get-alternatives) ($ alternatives 'alternatives)) - ))) + (define info (spawn ^deliberation-info title)) + (define votes (spawn ^cell '())) + (methods + ((title) ($ info 'title)) + ((set-title new-title) ($ info 'set-title new-title)) + ((votes) ($ votes 'get)) + ((add-vote question) + (define vote (spawn ^ranked-choice-vote info question)) + (define old-votes ($ votes 'get)) + ($ votes 'set (cons vote old-votes)) + vote) + )) ;; Constructor for ^ranked-ballot ;; Allow one constituent to update their rank-order preference ;; ;; Parameters: ;; deliberation-info: ^deliberation-info relevnt to this ballot -;; alternatives: ^alternatives object representing possible outcomes +;; preference: A ^ranked-preference object to associate with this ballot ;; ;; Messages: +;; alternatives: Returns a list of the alternatives being voted on +;; question: Return the question being voted on ;; update-vote f: Updates the constituent's vote on this ballot. ;; The procedure f is called with one parameter: the list of available ;; alternatives. f must return a list of rank preferences ;; (1 is most preferred) corresponding to the alternatives componentwise. -;; get-preference: Returns a ^ranked-preference associated with this ballot. -;; The object is attenuated to be read-only. +;; ranking: Returns a ranked list of alternatives corresponding to the +;; current state of this ballot's vote. ;; -(define (^ranked-ballot _bcom deliberation-info alternatives) - (define ranking (spawn ^ranked-preference '())) +(define (^ranked-ballot _bcom deliberation-info preference) (methods + ((alternatives) + (define alternatives ($ preference 'alternatives)) + ($ alternatives 'alternatives)) + ((question) + (define alternatives ($ preference 'alternatives)) + ($ alternatives 'question)) ((update-vote f) - (let* ((alt-list ($ alternatives 'get)) - (ranks (f alt-list)) - (new-ranking (reorder alt-list ranks))) - ($ ranking 'set new-ranking))) - ((get-preference) (spawn ^attenuated ranking '(get get-rank))) + ($ preference 'set f)) + ((ranking) + ($ preference 'ranked)) )) ;; Constructor for ^ranked-choice-vote -;; A updateable vote using ranked choice preferences +;; A updateable vote using ranked-choice ballots. ;; ;; Parameters: -;; deliberation-info: deliberation-info relevant to this vote -;; alternatives: cell containig list of altenatives +;; deliberation-info: ^deliberation-info object +;; question: String describing the question being voted on ;; ;; Messages: -;; get-ballots: Return the list of ballots -;; add-ballot: Add a new ballot to this vote and return it +;; ballots: Return the list of ^ranked-ballot objects +;; add-ballot: Add a new ^ranked-ballot to this vote and return it +;; add-alternative: Add a new alternative to this vote +;; +;; irv-winner: Return a list containing the winner (or winners in case of +;; a tie) according to the instant-runoff method. +;; irv-preference: Return a list of lists corresponding to the social +;; preference for alternatives according to the instant-runoff method. +;; Inner lists represent alternatives tied for the same position. ;; -(define (^ranked-choice-vote bcom deliberation-info alternatives) +;; borda-winner: Return a list containing the winner (or winners in case +;; of a tie) according to the Borda method. +;; borda-preference: Return a list of lists corresponding to the social +;; preference for alternatives according to the Borda method. Inner lists +;; represent alternatives tied for the same position. +;; +(define (^ranked-choice-vote bcom deliberation-info question) (define ballots (spawn ^cell '())) + (define alternatives (spawn ^alternatives question '())) + (define profile (spawn ^ranked-profile alternatives)) (methods - ((get-ballots) ($ ballots 'get)) + ((ballots) ($ ballots 'get)) ((add-ballot) - (define ballot (spawn ^ranked-ballot deliberation-info alternatives)) + ;; Create a new preference in the profile + (define preference ($ profile 'add-preference)) + ;; Create the ballot + (define ballot + (spawn ^ranked-ballot deliberation-info preference)) + ;; Add ballot to list (define old-ballots ($ ballots 'get)) ($ ballots 'set (cons ballot old-ballots)) + ;; Return the new ballot ballot) - ((get-profile) - ;; This is a very space-heavy representation - ;; Would be better to use a hashmap keyed on rankings with counts as values - (map (lambda (ballot) ($ ($ ballot 'get-preference) 'get)) - ballots)) + ((add-alternative alt) ($ alternatives 'add alt)) + + ((irv-winner) + (social-choice-irv ($ profile 'counts))) + ((irv-preference) + (social-preference-irv ($ profile 'counts))) + + ((borda-winner) + (social-choice-borda + ($ alternatives 'alternatives) + ($ profile 'counts))) + ((borda-preference) + (social-preference-borda + ($ alternatives 'alternatives) + ($ profile 'counts))) )) diff --git a/test-deliberate.scm b/test-deliberate.scm index 540c39a..91bb001 100644 --- a/test-deliberate.scm +++ b/test-deliberate.scm @@ -1,16 +1,94 @@ -(use-modules (deliberate) - (goblins) - (goblins vrun) - (goblins actor-lib methods)) +;;; Copyright 2022 Edward L. Platt +;;; +;;; Licensed under the Apache License, Version 2.0 (the "License"); +;;; you may not use this file except in compliance with the License. +;;; You may obtain a copy of the License at +;;; +;;; http://www.apache.org/licenses/LICENSE-2.0 +;;; +;;; Unless required by applicable law or agreed to in writing, software +;;; distributed under the License is distributed on an "AS IS" BASIS, +;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;;; See the License for the specific language governing permissions and +;;; limitations under the License. + +(define-module (tests test-preference) + #:use-module (srfi srfi-64) + #:use-module (goblins) + #:use-module (goblins vrun) + #:use-module (goblins actor-lib methods) + #:use-module (deliberate)) (define a-vat (spawn-vat)) (define-vat-run a-run a-vat) -(define delib (a-run (spawn ^deliberation "Bike Shed"))) -(a-run ($ delib 'add-alternative "red")) -(a-run ($ delib 'add-alternative "green")) -(a-run ($ delib 'add-alternative "blue")) -(define vote (a-run (spawn ^ranked-choice-vote delib ($ delib 'debug-alternatives)))) + +(test-begin "test-deliberate") + +;; Tests for ^deliberation-info +(define info (a-run (spawn ^deliberation-info "Power Generator"))) + +(test-equal "Power Generator" + (a-run ($ info 'title))) + +(a-run ($ info 'set-title "Energy")) +(test-equal "Energy" + (a-run ($ info 'title))) + +;; Tests for ^deliberation +(define delib (a-run (spawn ^deliberation "Power Generator"))) + +(test-equal "Power Generator" + (a-run ($ delib 'title))) + +;; Tests for ^ranked-choice-vote +(define vote (a-run ($ delib 'add-vote "What type of generator?"))) + +(a-run ($ delib 'votes)) +(a-run ($ vote 'ballots)) + +;; Add alternatives +(a-run ($ vote 'add-alternative "solar")) +(a-run ($ vote 'add-alternative "wind")) +(a-run ($ vote 'add-alternative "hydro")) + +;; Tests for ^ranked-ballot (define ballot (a-run ($ vote 'add-ballot))) -(define (rank31f alts) '(3 1 #f)) -(a-run ($ ballot 'update-vote rank31f)) +(a-run ($ vote 'ballots)) +(a-run ($ ballot 'alternatives)) +(a-run ($ ballot 'question)) + +;; Example ranking functions +(define (f-wind-solar-hydro alts) '(3 1 2)) +(define (f-hydro-solar-wind alts) '(1 3 2)) +(define (f-solar-wind-hydro alts) '(3 2 1)) + +;; Cast a single vote +(a-run ($ ballot 'update-vote f-wind-solar-hydro)) + +(test-equal '("wind" "solar" "hydro") + (a-run ($ ballot 'ranking))) + + +;; Add more votes to test voting methods + +(let ((ballot (a-run ($ vote 'add-ballot)))) + (a-run ($ ballot 'update-vote f-wind-solar-hydro))) + +(let ((ballot (a-run ($ vote 'add-ballot)))) + (a-run ($ ballot 'update-vote f-hydro-solar-wind))) +(let ((ballot (a-run ($ vote 'add-ballot)))) + (a-run ($ ballot 'update-vote f-hydro-solar-wind))) + +(let ((ballot (a-run ($ vote 'add-ballot)))) + (a-run ($ ballot 'update-vote f-solar-wind-hydro))) + +;; Test instant-runoff +(test-equal '("wind") + (a-run ($ vote 'irv-winner))) + +;; Test Borda method +(test-equal '("solar") + (a-run ($ vote 'borda-winner))) + +(test-end "test-deliberate")