Skip to content

Conversation

@vivianu2001
Copy link
Contributor

Implementation of the PB-EAR algorithm as described in:

"Proportionally Representative Participatory Budgeting with Ordinal Preferences"
Haris Aziz & Barton E. Lee, 2020
https://arxiv.org/abs/1911.00864v2

Summary of contributions:

  • pb_ear.py: Implementation of the PB-EAR algorithm with detailed logging.
  • test_pb_ear.py: Unit tests including motivating examples.
  • utils_formatting.py: Generates clean tabular output for logs..
  • logging_config.py: Centralized logging setup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should not be in the PR. You should delete it and then ignore it with .gitigonre

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why you changed this file. It should not be part of the PR.

@@ -0,0 +1,143 @@
import pytest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the pytest import creates an error in the automatic tests. Do you really need this import?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migrate these to unittest2.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this change?

from pabutools.utils_formatting import format_table
from datetime import datetime
import os
from pabutools.logging_config import logger
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put the single line defining the logger to this file, and then remove the import

@Simon-Rey
Copy link
Member

The test should be implemented with unittest2. Take inspiration from the other tests in the package.

@Simon-Rey
Copy link
Member

Please don't include the logging mechanism. This has not been implemented at the package level, it should be unified. So far, some rules have a "verbose" argument (see MES) that turn on/off print statements. That should be used for logging purposes (or, update the whole package to be using the logging package).

@Simon-Rey
Copy link
Member

For typing, do not use from typing import List, Tuple, Set but use directly the builtin types: list, tuple, set. Using the typing package is now old-fashioned and should be avoided.

@Simon-Rey
Copy link
Member

The implementation of the rule does not follow the standard of the package. A rule should take an instance and a profile as argument, you need to check that the profile is of the correct type and compute the results. In general, try to mimic other implementations to understand how the package work. Right now, your code is more of a standalone thing than a piece of code integrated in the package.

f"c_cost={added_cost:.2f}, rel_budget={rel_budget:.2f})"
)

if __name__ == "__main__":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the main

pyproject.toml Outdated
[project.urls]
"Homepage" = "https://github.com/comsoc-community/pabutools"
"Bug Tracker" = "https://github.com/comsoc-community/pabutools/issues"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the pytest part, we are working with unittest2

@Simon-Rey
Copy link
Member

Please remove all the DS_Store that have been added.

@erelsgl
Copy link
Contributor

erelsgl commented Jun 12, 2025

(or, update the whole package to be using the logging package).

What does it involve -- only changing the functions using the "verbose" argument to use a logger? Or something else?

@erelsgl
Copy link
Contributor

erelsgl commented Jun 12, 2025

One advantage of the logging system is that it allows to easily use the logs in other contexts than printing, e.g. in a demo website such as this one.

@Simon-Rey
Copy link
Member

I fully agree that using logging is much beter. I just want unity within the package. If we use logging then we use it for the whole package and we offer the options to adjust the setting for the whole package and not just for this rule.

I don't think there is much work to change the package to logging. As far as I can tell the parts to update are: priceability, cstv, mes_rule, visualisation.

@Simon-Rey
Copy link
Member

@vivianu2001 please enable the github actions on your fork so that you can test on your side before sending to the PR. For that, go to the settings of the repo, and then under "Actions"

@vivianu2001
Copy link
Contributor Author

@Simon-Rey
I’d be happy to work on this. I’m planning to replace all print statements with proper logging, using
logger = logging.getLogger in each file, and set up centralized initialization via setup_logging() (with PABUTOOLS_LOGLEVEL).

Before I proceed, when would you like logs to be active?
And should they be printed to the terminal only?
Happy to adjust based on what works best for you.

@Simon-Rey
Copy link
Member

Ok if you want to implement logging for the whole package, please follow this:
https://stackoverflow.com/questions/27016870/how-should-logging-be-used-in-a-python-package
https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library

In particular, there should be no configuration, that should be left to the user. So without a module, create a logger like this:

import logging

logger = logging.getLogger(__name__)

This ensures loggers are hierarchical and namespaced which gives users the freedom to do whatever you want.

@Simon-Rey
Copy link
Member

Additionally, with regards to the PB-EAR. Couple of comments:

  • For fractions use the function pabutools.fractions.frac() and not the / to ensure working of the exact fractions
  • If your implementation works for MultiProfile, you should probably make use of profile.multiplicity() to account for the correct number of each ballot.

all_projects = set(project_cost)

# Convert the profile to a list of (weight, preference list) pairs
voters = [(profile[ballot], list(ballot)) for ballot in profile]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this can work if you don't have a MultiProfile. Use profile.multiplicity(ballot) instead.

# Identify projects whose support justifies their cost (IPSC condition)
C_star = {
c for c in available_projects
if round(candidate_support[c], 6) >= round((initial_n * project_cost[c]) / budget, 6)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frac not /

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, for the round do not use a fixed value but a optional argument for the function such as rounding_precision = 6. That allows users to change this.


if N_prime:
sum_supporters = sum(voter_weights[i] for i in N_prime)
weight_fraction = total_weight_to_reduce / sum_supporters if sum_supporters > 0 else 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frac not /

# Construct the final BudgetAllocation object
allocation = BudgetAllocation()
for name in sorted(selected_projects):
allocation.append(instance.get_project(name))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you not store the Project instance in selected_projects? Rather than having to call get_project for all of them (which iterates through all the projects each time).

@Simon-Rey
Copy link
Member

I just saw your last commit. A lot has changed since we started discussing, don't forget to always synchronise yourself with the latest state of the repository.

@vivianu2001
Copy link
Contributor Author

I just saw your last commit. A lot has changed since we started discussing, don't forget to always synchronise yourself with the latest state of the repository.

Hey! Thanks for the heads up. I’m still working on it and planning to run all the tests and sync everything properly once I finish. I’ll make sure to update you when it’s ready.

@vivianu2001 vivianu2001 marked this pull request as draft June 17, 2025 15:38
@vivianu2001 vivianu2001 marked this pull request as draft June 17, 2025 15:38
@vivianu2001 vivianu2001 marked this pull request as ready for review June 17, 2025 16:32
@vivianu2001
Copy link
Contributor Author

@Simon-Rey Hi,
I’ve finished making the changes.I added the logging and updated the algorithm based on the comments. I also ran the workflow on my branch, and everything seems to be working.

@Simon-Rey
Copy link
Member

That looks perfect, thanks for the work you put in here :)

@Simon-Rey Simon-Rey merged commit 6d6dac7 into COMSOC-Community:main Jun 19, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants