diff --git a/flake.nix b/flake.nix index ced12c3eac79a6691425414f0e92e8d70a91f102..3d9407d352a327bf35f4f1cce936455cd5736763 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=23.05"; + nixpkgs.url = "github:nixos/nixpkgs?ref=branch-off-24.11"; flake-utils.url = "github:numtide/flake-utils"; typst = { url = "github:typst/typst/main"; diff --git a/stud_proj_alloc.py b/stud_proj_alloc.py new file mode 100755 index 0000000000000000000000000000000000000000..9a0a5d3baa3a7589042f9553763410fe6ae5c746 --- /dev/null +++ b/stud_proj_alloc.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +import collections +import copy +import random + +import numpy as np +import pandas as pd + +def moodle_poll_csv_filename_to_df(filename, vote_field='Q01_preference'): + df = pd.read_csv(filename) + df.rename(columns={ + 'Nom complet': 'voter_name', + vote_field: 'vote', + 'Soumis le :': 'date', + }, inplace=True) + df = df[['date', 'voter_name', 'vote']] + return df + +def check_vote(vote): + if not isinstance(vote, str): + return False + if len(vote) != 4: + return False + if ''.join(sorted(vote)) != '1234': + return False + return True + +def serial_dictatorship(vote_df): + empty_slots = {str(x+1):3 for x in range(4)} + alloc = [] + for _, row in vote_df.iterrows(): + allocated = False + for topic in row['vote']: + if empty_slots[topic] > 0: + alloc.append((row['voter_name'], topic)) + empty_slots[topic] = empty_slots[topic] - 1 + allocated = True + break + if not allocated: + raise Exception('Could not find any available slot for student {}, whose vote is {}'.format(row['voter_name'], row['vote'])) + return sorted(alloc) + +def alloc_rank(vote, got): + # returns the rank of the choice that a voter (that voted 'vote') obtained in a given allocation + # 0 is the best, 3 the worst + for rank, value in enumerate(vote): + if value == got: + return rank + raise Exception(f'no rank found from vote={vote} when got={got}') + +def compare_alloc(allocA, allocB, vote_df): + # return how many voters prefer A to B. can be negative or 0. + prefA = 0 + prefB = 0 + for _, row in vote_df.iterrows(): + rankA = alloc_rank(row['vote'], allocA[row['voter_name']]) + rankB = alloc_rank(row['vote'], allocA[row['voter_name']]) + if rankA < rankB: + prefA += 1 + elif rankB < rankA: + prefB += 1 + return prefA, prefB + +candidates = ['1', '2', '3', '4'] + +# example from CSV +#df = moodle_poll_csv_filename_to_df('./proj-preferences.csv') +#df_valid = df[df['vote'].apply(check_vote)] +#df_latest = df_valid.sort_values(by='date').groupby('voter_name', as_index=False).last() + +# random data +random_data = [] +random.seed(2) +for stud_id in range(12): + stud_name = f"stud{stud_id:02d}" + stud_vote = copy.deepcopy(candidates) + random.shuffle(stud_vote) + stud_vote = ''.join(stud_vote) + random_data.append({'voter_name': stud_name, 'vote': stud_vote}) +random_df = pd.DataFrame(random_data) + +# generate many allocations from various random order traversal of votes, only keep unique solutions +df = random_df +pareto_optimal_allocs = dict() +alloc_values_seen = set() +for seed in range(100): + alloc_name = f'seed{seed:03d}' + reordered_df = df.sample(frac=1, random_state=seed) + alloc = serial_dictatorship(reordered_df) + alloc_values = ''.join([topic for stud,topic in alloc]) + if alloc_values not in alloc_values_seen: + pareto_optimal_allocs[alloc_name] = alloc + alloc_values_seen.add(alloc_values) +print(len(alloc_values_seen)) \ No newline at end of file