From a2b75fca6066038babe1399c94454179c90abc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Nguy=C3=AAn=20Hoang?= Date: Sun, 21 Jan 2024 00:50:30 +0100 Subject: [PATCH] Reorganized the imports to make it cleaner --- solidago/src/solidago/__init__.py | 4 + solidago/src/solidago/aggregation/__init__.py | 48 ++------- solidago/src/solidago/aggregation/base.py | 40 ++++++++ .../aggregation/standardized_qrmed.py | 2 +- .../solidago/generative_model/test_data.py | 3 +- solidago/src/solidago/pipeline/__init__.py | 24 ++--- .../src/solidago/post_process/__init__.py | 31 ++---- solidago/src/solidago/post_process/base.py | 25 +++++ solidago/src/solidago/post_process/squash.py | 2 +- .../solidago/preference_learning/__init__.py | 64 ++---------- .../src/solidago/preference_learning/base.py | 55 +++++++++++ .../generalized_bradley_terry.py | 2 +- solidago/src/solidago/scaling/__init__.py | 65 +++---------- solidago/src/solidago/scaling/base.py | 53 ++++++++++ solidago/src/solidago/scaling/mehestan.py | 2 +- .../solidago/scaling/quantile_zero_shift.py | 2 +- solidago/src/solidago/scoring_model.py | 2 - .../solidago/trust_propagation/__init__.py | 43 ++------ .../src/solidago/trust_propagation/base.py | 34 +++++++ .../trust_propagation/lipschitrust.py | 2 +- .../solidago/trust_propagation/trust_all.py | 2 +- .../src/solidago/voting_rights/__init__.py | 97 ++----------------- .../voting_rights/affine_overtrust.py | 4 +- solidago/src/solidago/voting_rights/base.py | 93 ++++++++++++++++++ 24 files changed, 372 insertions(+), 327 deletions(-) create mode 100644 solidago/src/solidago/aggregation/base.py create mode 100644 solidago/src/solidago/post_process/base.py create mode 100644 solidago/src/solidago/preference_learning/base.py create mode 100644 solidago/src/solidago/scaling/base.py create mode 100644 solidago/src/solidago/trust_propagation/base.py create mode 100644 solidago/src/solidago/voting_rights/base.py diff --git a/solidago/src/solidago/__init__.py b/solidago/src/solidago/__init__.py index 5c76fa0957..4427c85897 100644 --- a/solidago/src/solidago/__init__.py +++ b/solidago/src/solidago/__init__.py @@ -1,3 +1,7 @@ """Solidago library, robust and secure algorithms for the Tournesol platform""" from .__version__ import __version__ + +from .privacy_settings import PrivacySettings +from .judgments import Judgments +from .scoring_model import ScoringModel diff --git a/solidago/src/solidago/aggregation/__init__.py b/solidago/src/solidago/aggregation/__init__.py index 6dcc4d116d..bc04bb51a7 100644 --- a/solidago/src/solidago/aggregation/__init__.py +++ b/solidago/src/solidago/aggregation/__init__.py @@ -1,40 +1,8 @@ -from abc import ABC, abstractmethod - -import pandas as pd - -from solidago.voting_rights import VotingRights -from solidago.scoring_model import ScoringModel - - -class Aggregation: - @abstractmethod - def __call__( - self, - voting_rights: VotingRights, - user_models: dict[int, ScoringModel], - users: pd.DataFrame, - entities: pd.DataFrame - ) -> tuple[dict[int, ScoringModel], ScoringModel]: - """ Returns scaled user models - - Parameters - ---------- - voting_rights: VotingRights - voting_rights[user, entity]: float - user_models: dict[int, ScoringModel] - user_models[user] is user's scoring model - users: DataFrame with columns - * user_id (int, index) - * trust_score (float) - entities: DataFrame with columns - * entity_id (int, ind) - - Returns - ------- - updated_user_models[user]: ScoringModel - Returns a scaled user model - global_model: ScoringModel - Returns a global scoring model - """ - raise NotImplementedError - +""" Step 5 of the pipeline. + +Aggregation combines the different user models to construct a global model. +The aggregation may also adjust the user models to the learned global model. +""" + +from .base import Aggregation +from .standardized_qrmed import QuantileStandardizedQrMedian diff --git a/solidago/src/solidago/aggregation/base.py b/solidago/src/solidago/aggregation/base.py new file mode 100644 index 0000000000..6dcc4d116d --- /dev/null +++ b/solidago/src/solidago/aggregation/base.py @@ -0,0 +1,40 @@ +from abc import ABC, abstractmethod + +import pandas as pd + +from solidago.voting_rights import VotingRights +from solidago.scoring_model import ScoringModel + + +class Aggregation: + @abstractmethod + def __call__( + self, + voting_rights: VotingRights, + user_models: dict[int, ScoringModel], + users: pd.DataFrame, + entities: pd.DataFrame + ) -> tuple[dict[int, ScoringModel], ScoringModel]: + """ Returns scaled user models + + Parameters + ---------- + voting_rights: VotingRights + voting_rights[user, entity]: float + user_models: dict[int, ScoringModel] + user_models[user] is user's scoring model + users: DataFrame with columns + * user_id (int, index) + * trust_score (float) + entities: DataFrame with columns + * entity_id (int, ind) + + Returns + ------- + updated_user_models[user]: ScoringModel + Returns a scaled user model + global_model: ScoringModel + Returns a global scoring model + """ + raise NotImplementedError + diff --git a/solidago/src/solidago/aggregation/standardized_qrmed.py b/solidago/src/solidago/aggregation/standardized_qrmed.py index 784daebf68..4483f0a8f5 100644 --- a/solidago/src/solidago/aggregation/standardized_qrmed.py +++ b/solidago/src/solidago/aggregation/standardized_qrmed.py @@ -1,6 +1,6 @@ import pandas as pd -from . import Aggregation +from .base import Aggregation from solidago.voting_rights import VotingRights from solidago.scoring_model import ScoringModel diff --git a/solidago/src/solidago/generative_model/test_data.py b/solidago/src/solidago/generative_model/test_data.py index 2eb60577f4..94ccd85e23 100644 --- a/solidago/src/solidago/generative_model/test_data.py +++ b/solidago/src/solidago/generative_model/test_data.py @@ -1,6 +1,7 @@ +from solidago import PrivacySettings + from solidago.voting_rights import VotingRights from solidago.scoring_model import DirectScoringModel -from solidago.privacy_settings import PrivacySettings from solidago.judgments import DataFrameJudgments from solidago.pipeline import Pipeline diff --git a/solidago/src/solidago/pipeline/__init__.py b/solidago/src/solidago/pipeline/__init__.py index 127c1080f1..0485dfab77 100644 --- a/solidago/src/solidago/pipeline/__init__.py +++ b/solidago/src/solidago/pipeline/__init__.py @@ -7,24 +7,14 @@ import pandas as pd import logging -from solidago.privacy_settings import PrivacySettings -from solidago.judgments import Judgments -from solidago.voting_rights import VotingRights -from solidago.scoring_model import ScoringModel +from solidago import PrivacySettings, Judgments, ScoringModel -from solidago.trust_propagation import TrustPropagation -from solidago.trust_propagation.lipschitrust import LipschiTrust -from solidago.voting_rights import VotingRightsAssignment -from solidago.voting_rights.affine_overtrust import AffineOvertrust -from solidago.preference_learning import PreferenceLearning -from solidago.preference_learning.generalized_bradley_terry import UniformGBT -from solidago.scaling import Scaling, ScalingCompose -from solidago.scaling.mehestan import Mehestan -from solidago.scaling.quantile_zero_shift import QuantileZeroShift -from solidago.aggregation import Aggregation -from solidago.aggregation.standardized_qrmed import QuantileStandardizedQrMedian -from solidago.post_process import PostProcess -from solidago.post_process.squash import Squash +from solidago.trust_propagation import TrustPropagation, LipschiTrust +from solidago.voting_rights import VotingRights, VotingRightsAssignment, AffineOvertrust +from solidago.preference_learning import PreferenceLearning, UniformGBT +from solidago.scaling import Scaling, ScalingCompose, Mehestan, QuantileZeroShift +from solidago.aggregation import Aggregation, QuantileStandardizedQrMedian +from solidago.post_process import PostProcess, Squash logger = logging.getLogger(__name__) diff --git a/solidago/src/solidago/post_process/__init__.py b/solidago/src/solidago/post_process/__init__.py index 21ad8ae65d..bd2d2524dd 100644 --- a/solidago/src/solidago/post_process/__init__.py +++ b/solidago/src/solidago/post_process/__init__.py @@ -1,25 +1,8 @@ -from abc import ABC, abstractmethod +""" Step 6 of the pipeline. + +Post-process modifies the user and global models, typically with the goal +of yielding more human-readible scores. +""" -from solidago.scoring_model import ScoringModel - -class PostProcess(ABC): - @abstractmethod - def __call__( - self, - user_models: dict[int, ScoringModel], - global_model: ScoringModel - ) -> tuple[dict[int, ScoringModel], ScoringModel]: - """ Post-processes user models and global models, - typically to yield human-readible scores - - Parameters - ---------- - user_models: user_model[user] should be a ScoringModel to post-process - global_model: ScoringModel to post-process - - Returns - ------- - user_models: post-processed user models - global_model: post-processed global model - """ - raise NotImplementedError +from .base import PostProcess +from .squash import Squash diff --git a/solidago/src/solidago/post_process/base.py b/solidago/src/solidago/post_process/base.py new file mode 100644 index 0000000000..21ad8ae65d --- /dev/null +++ b/solidago/src/solidago/post_process/base.py @@ -0,0 +1,25 @@ +from abc import ABC, abstractmethod + +from solidago.scoring_model import ScoringModel + +class PostProcess(ABC): + @abstractmethod + def __call__( + self, + user_models: dict[int, ScoringModel], + global_model: ScoringModel + ) -> tuple[dict[int, ScoringModel], ScoringModel]: + """ Post-processes user models and global models, + typically to yield human-readible scores + + Parameters + ---------- + user_models: user_model[user] should be a ScoringModel to post-process + global_model: ScoringModel to post-process + + Returns + ------- + user_models: post-processed user models + global_model: post-processed global model + """ + raise NotImplementedError diff --git a/solidago/src/solidago/post_process/squash.py b/solidago/src/solidago/post_process/squash.py index d07ced6da1..39a7f1d2fd 100644 --- a/solidago/src/solidago/post_process/squash.py +++ b/solidago/src/solidago/post_process/squash.py @@ -2,7 +2,7 @@ import numpy as np -from . import PostProcess +from .base import PostProcess from solidago.scoring_model import ScoringModel, PostProcessedScoringModel diff --git a/solidago/src/solidago/preference_learning/__init__.py b/solidago/src/solidago/preference_learning/__init__.py index aca35a7e18..3e2de74928 100644 --- a/solidago/src/solidago/preference_learning/__init__.py +++ b/solidago/src/solidago/preference_learning/__init__.py @@ -1,55 +1,11 @@ -from abc import ABC, abstractmethod -from typing import Optional - -import pandas as pd -import numpy as np - -from solidago.scoring_model import ScoringModel - - -class PreferenceLearning(ABC): - @abstractmethod - def __call__( - self, - user_judgments: dict[str, pd.DataFrame], - entities: pd.DataFrame - ) -> ScoringModel: - """ Learns a scoring model, given user judgments of entities - - Parameters - ---------- - user_judgments: dict[str, pd.DataFrame] - May contain different forms of judgments, - but most likely will contain "comparisons" and/or "assessments" - entities: DataFrame with columns - * entity_id: int, index - * May contain others, such as vector representation - """ - raise NotImplementedError - - -class ComparisonBasedPreferenceLearning(PreferenceLearning): - @abstractmethod - def comparison_learning(self, comparisons, entities) -> ScoringModel: - """ Learns only based on comparisons - - Parameters - ---------- - comparisons: DataFrame with columns - * entity_a: int - * entity_b: int - * score: float - entities: DataFrame with columns - * entity_id: int, index - * May contain others, such as vector representation - """ - raise NotImplementedError - - def __call__( - self, - user_judgments: dict[str, pd.DataFrame], - entities: pd.DataFrame - ) -> ScoringModel: - return self.comparison_learning(user_judgments["comparisons"], entities) - +""" Step 3 of the pipeline. +Preference learning infers, for each user and based on their data, +a model of the user's preferences. +This corresponds to the idea of "algorithmic representatives", +which will participate in the digital democracy +to remedy users' lack of activity and reactivity. +""" + +from .base import PreferenceLearning +from .generalized_bradley_terry import UniformGBT diff --git a/solidago/src/solidago/preference_learning/base.py b/solidago/src/solidago/preference_learning/base.py new file mode 100644 index 0000000000..aca35a7e18 --- /dev/null +++ b/solidago/src/solidago/preference_learning/base.py @@ -0,0 +1,55 @@ +from abc import ABC, abstractmethod +from typing import Optional + +import pandas as pd +import numpy as np + +from solidago.scoring_model import ScoringModel + + +class PreferenceLearning(ABC): + @abstractmethod + def __call__( + self, + user_judgments: dict[str, pd.DataFrame], + entities: pd.DataFrame + ) -> ScoringModel: + """ Learns a scoring model, given user judgments of entities + + Parameters + ---------- + user_judgments: dict[str, pd.DataFrame] + May contain different forms of judgments, + but most likely will contain "comparisons" and/or "assessments" + entities: DataFrame with columns + * entity_id: int, index + * May contain others, such as vector representation + """ + raise NotImplementedError + + +class ComparisonBasedPreferenceLearning(PreferenceLearning): + @abstractmethod + def comparison_learning(self, comparisons, entities) -> ScoringModel: + """ Learns only based on comparisons + + Parameters + ---------- + comparisons: DataFrame with columns + * entity_a: int + * entity_b: int + * score: float + entities: DataFrame with columns + * entity_id: int, index + * May contain others, such as vector representation + """ + raise NotImplementedError + + def __call__( + self, + user_judgments: dict[str, pd.DataFrame], + entities: pd.DataFrame + ) -> ScoringModel: + return self.comparison_learning(user_judgments["comparisons"], entities) + + diff --git a/solidago/src/solidago/preference_learning/generalized_bradley_terry.py b/solidago/src/solidago/preference_learning/generalized_bradley_terry.py index 7c71077905..199084ffb8 100644 --- a/solidago/src/solidago/preference_learning/generalized_bradley_terry.py +++ b/solidago/src/solidago/preference_learning/generalized_bradley_terry.py @@ -6,7 +6,7 @@ from solidago.scoring_model import ScoringModel, DirectScoringModel from solidago.solvers.optimize import coordinate_descent -from . import ComparisonBasedPreferenceLearning +from .base import ComparisonBasedPreferenceLearning class GeneralizedBradleyTerry(ComparisonBasedPreferenceLearning): diff --git a/solidago/src/solidago/scaling/__init__.py b/solidago/src/solidago/scaling/__init__.py index b69c4de589..a14dd817b1 100644 --- a/solidago/src/solidago/scaling/__init__.py +++ b/solidago/src/solidago/scaling/__init__.py @@ -1,53 +1,12 @@ -from abc import ABC, abstractmethod - -import pandas as pd - -from solidago.privacy_settings import PrivacySettings -from solidago.scoring_model import ScoringModel -from solidago.voting_rights import VotingRights - - -class Scaling: - @abstractmethod - def __call__( - self, - user_models: dict[int, ScoringModel], - users: pd.DataFrame, - entities: pd.DataFrame, - voting_rights: VotingRights, - privacy: PrivacySettings - ) -> dict[int, ScoringModel]: - """ Returns scaled user models - - Parameters - ---------- - user_models: dict[int, ScoringModel] - user_models[user] is user's scoring model - users: DataFrame with columns - * user_id (int, index) - * trust_score (float) - entities: DataFrame with columns - * entity_id (int, ind) - voting_rights: VotingRights - voting_rights[user, entity]: float - privacy: PrivacySettings - privacy[user, entity] in { True, False, None } - - Returns - ------- - out[user]: ScoringModel - Will be scaled by the Scaling method - """ - raise NotImplementedError - - -class ScalingCompose: - """ Class used to compose any number of scaling solutions """ - def __init__(self, *scalings): - self.scalings = scalings - - def __call__(self, user_models, users, entities, voting_rights, privacy): - scaled_models = user_models - for scaling in self.scalings: - scaled_models = scaling(scaled_models, users, entities, voting_rights, privacy) - return scaled_models +""" Step 4 of the pipeline. + +Scaling addresses the "Parisian" and the "Marseillais" problems, +i.e. users with too extreme scores, +or whose negative scores correspond to entities that others rate as positive. +This latter effect is particularly an issue in comparison-based preference learning, +assuming each user has a very specific selection bias of rated entities. +""" + +from .base import Scaling, ScalingCompose +from .mehestan import Mehestan +from .quantile_zero_shift import QuantileZeroShift diff --git a/solidago/src/solidago/scaling/base.py b/solidago/src/solidago/scaling/base.py new file mode 100644 index 0000000000..b69c4de589 --- /dev/null +++ b/solidago/src/solidago/scaling/base.py @@ -0,0 +1,53 @@ +from abc import ABC, abstractmethod + +import pandas as pd + +from solidago.privacy_settings import PrivacySettings +from solidago.scoring_model import ScoringModel +from solidago.voting_rights import VotingRights + + +class Scaling: + @abstractmethod + def __call__( + self, + user_models: dict[int, ScoringModel], + users: pd.DataFrame, + entities: pd.DataFrame, + voting_rights: VotingRights, + privacy: PrivacySettings + ) -> dict[int, ScoringModel]: + """ Returns scaled user models + + Parameters + ---------- + user_models: dict[int, ScoringModel] + user_models[user] is user's scoring model + users: DataFrame with columns + * user_id (int, index) + * trust_score (float) + entities: DataFrame with columns + * entity_id (int, ind) + voting_rights: VotingRights + voting_rights[user, entity]: float + privacy: PrivacySettings + privacy[user, entity] in { True, False, None } + + Returns + ------- + out[user]: ScoringModel + Will be scaled by the Scaling method + """ + raise NotImplementedError + + +class ScalingCompose: + """ Class used to compose any number of scaling solutions """ + def __init__(self, *scalings): + self.scalings = scalings + + def __call__(self, user_models, users, entities, voting_rights, privacy): + scaled_models = user_models + for scaling in self.scalings: + scaled_models = scaling(scaled_models, users, entities, voting_rights, privacy) + return scaled_models diff --git a/solidago/src/solidago/scaling/mehestan.py b/solidago/src/solidago/scaling/mehestan.py index ce07d129c9..be6dcf77e7 100644 --- a/solidago/src/solidago/scaling/mehestan.py +++ b/solidago/src/solidago/scaling/mehestan.py @@ -3,7 +3,7 @@ import logging -from . import Scaling +from .base import Scaling from solidago.privacy_settings import PrivacySettings from solidago.scoring_model import ScoringModel, ScaledScoringModel diff --git a/solidago/src/solidago/scaling/quantile_zero_shift.py b/solidago/src/solidago/scaling/quantile_zero_shift.py index a5f6810509..188a8467c2 100644 --- a/solidago/src/solidago/scaling/quantile_zero_shift.py +++ b/solidago/src/solidago/scaling/quantile_zero_shift.py @@ -1,7 +1,7 @@ import pandas as pd import numpy as np -from . import Scaling +from .base import Scaling from solidago.privacy_settings import PrivacySettings from solidago.scoring_model import ScoringModel, ScaledScoringModel diff --git a/solidago/src/solidago/scoring_model.py b/solidago/src/solidago/scoring_model.py index 309ac28a8b..0ff3bdf246 100644 --- a/solidago/src/solidago/scoring_model.py +++ b/solidago/src/solidago/scoring_model.py @@ -30,7 +30,6 @@ def scored_entities(self, entities) -> set[int]: """ If not None, then the scoring model only scores a subset of entities. """ return set(range(len(entities))) - class DirectScoringModel(ScoringModel): def __init__( self, @@ -106,7 +105,6 @@ def __call__(self, entity_id, entity_features): def scored_entities(self, entities=None) -> set[int]: return self.base_model.scored_entities(entities) - class PostProcessedScoringModel(ScoringModel): def __init__(self, base_model: ScoringModel, post_process: callable): """ Defines a derived scoring model, based on a base model and a post process diff --git a/solidago/src/solidago/trust_propagation/__init__.py b/solidago/src/solidago/trust_propagation/__init__.py index f4cca7c4a5..9fb975cf35 100644 --- a/solidago/src/solidago/trust_propagation/__init__.py +++ b/solidago/src/solidago/trust_propagation/__init__.py @@ -1,38 +1,9 @@ -""" Trust propagation is tasked to combine pretrusts and vouches - to derive trust scores for the different users +""" Step 1 of the pipeline. + +Trust propagation is tasked to combine pretrusts and vouches +to derive trust scores for the different users. """ -from abc import ABC, abstractmethod - -import pandas as pd - -class TrustPropagation(ABC): - @abstractmethod - def __call__(self, - users: pd.DataFrame, - vouches: pd.DataFrame - ) -> pd.DataFrame: - """ Propagates trust through vouch network - - Parameters - ---------- - users: DataFrame with columns - * user_id (int, index) - * is_pretrusted (bool) - vouches: DataFrame with columns - * voucher (str) - * vouchee (str) - * vouch (float) - - Returns - ------- - users: DataFrame with columns - * user_id (int, index) - * is_pretrusted (bool) - * trust_score (float) - """ - raise NotImplementedError - - def __str__(self): - return type(self).__name__ - +from .base import TrustPropagation +from .lipschitrust import LipschiTrust +from .trust_all import TrustAll diff --git a/solidago/src/solidago/trust_propagation/base.py b/solidago/src/solidago/trust_propagation/base.py new file mode 100644 index 0000000000..f82ffb5fe3 --- /dev/null +++ b/solidago/src/solidago/trust_propagation/base.py @@ -0,0 +1,34 @@ +from abc import ABC, abstractmethod + +import pandas as pd + +class TrustPropagation(ABC): + @abstractmethod + def __call__(self, + users: pd.DataFrame, + vouches: pd.DataFrame + ) -> pd.DataFrame: + """ Propagates trust through vouch network + + Parameters + ---------- + users: DataFrame with columns + * user_id (int, index) + * is_pretrusted (bool) + vouches: DataFrame with columns + * voucher (str) + * vouchee (str) + * vouch (float) + + Returns + ------- + users: DataFrame with columns + * user_id (int, index) + * is_pretrusted (bool) + * trust_score (float) + """ + raise NotImplementedError + + def __str__(self): + return type(self).__name__ + diff --git a/solidago/src/solidago/trust_propagation/lipschitrust.py b/solidago/src/solidago/trust_propagation/lipschitrust.py index 9228cf38fd..996993cec7 100644 --- a/solidago/src/solidago/trust_propagation/lipschitrust.py +++ b/solidago/src/solidago/trust_propagation/lipschitrust.py @@ -3,7 +3,7 @@ Governance with Security Guarantees", available on ArXiV. """ -from . import TrustPropagation +from .base import TrustPropagation import pandas as pd import numpy as np diff --git a/solidago/src/solidago/trust_propagation/trust_all.py b/solidago/src/solidago/trust_propagation/trust_all.py index ca827eda9c..b74a53a67d 100644 --- a/solidago/src/solidago/trust_propagation/trust_all.py +++ b/solidago/src/solidago/trust_propagation/trust_all.py @@ -1,7 +1,7 @@ """ TrustAll is a naive solution that assignes an equal amount of trust to all users """ -from . import TrustPropagation +from .base import TrustPropagation import pandas as pd import numpy as np diff --git a/solidago/src/solidago/voting_rights/__init__.py b/solidago/src/solidago/voting_rights/__init__.py index 15b117a4bd..4d5b11327d 100644 --- a/solidago/src/solidago/voting_rights/__init__.py +++ b/solidago/src/solidago/voting_rights/__init__.py @@ -1,93 +1,8 @@ -from abc import ABC, abstractmethod -from typing import Optional - -import pandas as pd -import numpy as np - -from .compute_voting_rights import compute_voting_rights - -class VotingRights: - def __init__(self, dct: Optional[dict[int, dict[int, float]]]=None): - """ Initialize voting rights - - Parameters - ---------- - dct: dict[int, dict[int, float]] - dct[entity][user] is the voting right of user for entity - """ - self._dict = dict() if dct is None else dct - - def __getitem__(self, user_entity_tuple:tuple[int, int]) -> float: - """ self[user, entity] must returns the voting right of a user for an entity - - Parameters - ---------- - user_entity_tuple: (user: int, entity: int) - - Returns - ------- - out: float - """ - user, entity = user_entity_tuple - if entity not in self._dict: - return 0 - if user not in self._dict[entity]: - return 0 - return self._dict[entity][user] +""" Step 2 of the pipeline. - def __setitem__(self, user_entity_tuple:tuple[int, int], value: float): - """ sets the voting right of a user for an entity - - Parameters - ---------- - user_entity_tuple: (user: int, entity: int) - value: float - """ - user, entity = user_entity_tuple - if entity not in self._dict: - self._dict[entity] = dict() - self._dict[entity][user] = value +Voting rights are assigned per user and per entity, +based on users' trust scores and privacy settings. +""" - def entities(self, user: Optional[int] = None) -> set[int]: - if user is None: - return set(self._dict.keys()) - return { e for e in self._dict if user in self._dict[e] } - - def on_entity(self, entity: int) -> dict[int, float]: - return self._dict[entity] - - -class VotingRightsAssignment(ABC): - @abstractmethod - def __call__( - self, - users: pd.DataFrame, - entities: pd.DataFrame, - vouches: pd.DataFrame, - privacy: pd.DataFrame - ) -> tuple[VotingRights, pd.DataFrame]: - """ Compute voting rights - - Parameters - ---------- - users: DataFrame with columns - * user_id (int, index) - * trust_score (float) - entities: DataFrame with columns - * entity_id (int, index) - vouches: DataFrame with columns - * voucher (int) - * vouchee (int) - * vouch (float) - privacy: PrivacySettings - privacy[user, entity] in { True, False, None } - - Returns - ------- - voting_rights[user, entity] is the voting right - of a user on entity for criterion - entities: DataFrame with columns - * entity_id (int, index) - """ - raise NotImplementedError - +from .base import VotingRights, VotingRightsAssignment +from .affine_overtrust import AffineOvertrust diff --git a/solidago/src/solidago/voting_rights/affine_overtrust.py b/solidago/src/solidago/voting_rights/affine_overtrust.py index 5f688404b0..1c315ad240 100644 --- a/solidago/src/solidago/voting_rights/affine_overtrust.py +++ b/solidago/src/solidago/voting_rights/affine_overtrust.py @@ -1,9 +1,9 @@ import pandas as pd import numpy as np -from . import VotingRights, VotingRightsAssignment +from .base import VotingRights, VotingRightsAssignment -from solidago.privacy_settings import PrivacySettings +from solidago import PrivacySettings from solidago.solvers.dichotomy import solve class AffineOvertrust(VotingRightsAssignment): diff --git a/solidago/src/solidago/voting_rights/base.py b/solidago/src/solidago/voting_rights/base.py new file mode 100644 index 0000000000..15b117a4bd --- /dev/null +++ b/solidago/src/solidago/voting_rights/base.py @@ -0,0 +1,93 @@ +from abc import ABC, abstractmethod +from typing import Optional + +import pandas as pd +import numpy as np + +from .compute_voting_rights import compute_voting_rights + +class VotingRights: + def __init__(self, dct: Optional[dict[int, dict[int, float]]]=None): + """ Initialize voting rights + + Parameters + ---------- + dct: dict[int, dict[int, float]] + dct[entity][user] is the voting right of user for entity + """ + self._dict = dict() if dct is None else dct + + def __getitem__(self, user_entity_tuple:tuple[int, int]) -> float: + """ self[user, entity] must returns the voting right of a user for an entity + + Parameters + ---------- + user_entity_tuple: (user: int, entity: int) + + Returns + ------- + out: float + """ + user, entity = user_entity_tuple + if entity not in self._dict: + return 0 + if user not in self._dict[entity]: + return 0 + return self._dict[entity][user] + + def __setitem__(self, user_entity_tuple:tuple[int, int], value: float): + """ sets the voting right of a user for an entity + + Parameters + ---------- + user_entity_tuple: (user: int, entity: int) + value: float + """ + user, entity = user_entity_tuple + if entity not in self._dict: + self._dict[entity] = dict() + self._dict[entity][user] = value + + def entities(self, user: Optional[int] = None) -> set[int]: + if user is None: + return set(self._dict.keys()) + return { e for e in self._dict if user in self._dict[e] } + + def on_entity(self, entity: int) -> dict[int, float]: + return self._dict[entity] + + +class VotingRightsAssignment(ABC): + @abstractmethod + def __call__( + self, + users: pd.DataFrame, + entities: pd.DataFrame, + vouches: pd.DataFrame, + privacy: pd.DataFrame + ) -> tuple[VotingRights, pd.DataFrame]: + """ Compute voting rights + + Parameters + ---------- + users: DataFrame with columns + * user_id (int, index) + * trust_score (float) + entities: DataFrame with columns + * entity_id (int, index) + vouches: DataFrame with columns + * voucher (int) + * vouchee (int) + * vouch (float) + privacy: PrivacySettings + privacy[user, entity] in { True, False, None } + + Returns + ------- + voting_rights[user, entity] is the voting right + of a user on entity for criterion + entities: DataFrame with columns + * entity_id (int, index) + """ + raise NotImplementedError +