Skip to content

Moma#66

Merged
pollockDeVis merged 56 commits intomainfrom
moma
Mar 20, 2026
Merged

Moma#66
pollockDeVis merged 56 commits intomainfrom
moma

Conversation

@pollockDeVis
Copy link
Copy Markdown
Owner

  • Major integration with Borg MOEA besides eNSGA-II from platypus
  • connectors for Serial, Manager Worker, and Multi Manager Borg written in C with EMA Workbench and Platypus
  • Different configurations for agents during optimization
  • New Visualizations

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a major integration of the C-based Borg MOEA into the existing EMA-Workbench/Platypus optimization workflow, adds MOMADPS (multi-agent) optimization utilities, and expands post-processing/visualization tooling.

Changes:

  • Add Borg C-wrapper (solvers/moea/borg.py) and a Platypus Algorithm adapter (solvers/moea/borg_platypus_adapter.py) with serial/MS/MM execution paths.
  • Add new optimization orchestration for EMODPS/MOMADPS (incl. single-agent reruns) and supporting region/macro-region mapping utilities.
  • Add postprocessing utilities (regret processing, Pareto–Nash extraction, feature importance) plus docs/assets/config updates.

Reviewed changes

Copilot reviewed 26 out of 31 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
solvers/moea/borg_platypus_adapter.py Platypus adapter around the Borg C wrapper + EMA context + runtime snapshot packaging helpers.
solvers/moea/borg.py ctypes-based Borg MOEA wrapper with serial and MPI entrypoints.
run_optimization.py New top-level orchestration for EMODPS + MOMADPS + single-agent runs using Borg or EpsNSGAII.
pyproject.toml Version/python requirement bumps and dependency updates.
justice/welfare/social_welfare_function.py Adds spatially-disaggregated welfare computation used by macro-agent objectives.
justice/util/regional_configuration.py Adds macro-region mapping + macro aggregation utilities.
justice/util/reevaluate_optimal_policy.py Adds a script to reevaluate selected optimal policies across scenarios and extract variables.
justice/util/postprocessing_for_regret_calculations.py Adds a script for parallel scenario postprocessing and mapping generation.
justice/util/pareto_nash_run.py Adds payoff-table generation and Pareto–Nash set extraction utilities.
justice/util/output_data_processor.py Enhances reevaluation/extraction utilities with output dir creation + richer file naming.
justice/util/feature_importance.py Adds CatBoost+SHAP feature-importance pipeline over regret/optimization outputs.
justice/util/enumerations.py Updates optimizer enum to distinguish MS/MM Borg variants.
justice/util/borg_archive_processor.py Adds CLI tool to package per-island intermediate snapshots into EMA-like tarballs.
justice/util/EMA_model_wrapper.py Extends wrappers to MOMADPS and single-agent MOMADPS + macro aggregation support.
justice/model.py Updates clustering file reference and formatting cleanup.
docs/rl_docs.md Adds RL usage documentation.
docs/diagrams/JUSTICE.svg Adds an SVG diagram for the README/docs.
data/input/region_to_macro_5.npy Adds precomputed region→macro mapping asset.
data/input/R5_regions.json Replaces/updates 5-region macro definitions used by mapping.
data/input/R10_regions.json Adds 10-region mapping definition.
analysis/normative_uncertainty_optimization.json Updates epsilons and reorders reference scenario key.
analysis/momadps_config.json Adds MOMADPS configuration file.
analysis/hpc_slurm_scripts/run_single_agent_array.sh Adds SLURM array script for single-agent runs.
analysis/hpc_slurm_scripts/mm_justice.sh Adds SLURM script for MM Borg runs.
analysis/hpc_slurm_scripts/mm_borg_exclusive.sh Adds an exclusive-node SLURM script for MM Borg runs.
analysis/hpc_run.py Updates HPC entrypoint to run single-agent MOMADPS via new runner.
analysis/analyzer.py Removes legacy analyzer module in favor of new runner(s).
README.md Updates diagram embedding and replaces install/run text with citation block.
CITATION.cff Adds citation metadata file.
.gitignore Ignores generated plots/figures and local Borg shared libraries.
Comments suppressed due to low confidence (2)

pyproject.toml:24

  • dependencies declares numpy twice (numpy (>=1.23) and numpy (==1.24.3)), which is an unsatisfiable/confusing constraint for resolvers and can lead to non-deterministic installs. Keep a single constraint (pin or range) and remove the duplicate entry.
    "numpy (>=1.23)",
    "scipy (>=1.10.0)",
    "deap (>=1.4.0)",
    "ipyparallel (>=8.6.1)",
    "plotly (>=5.19.0)",
    "pycountry (>=24.6.1)",
    "seaborn (>=0.13.0)",
    "tyro (>=0.9.24)",
    "wandb (==0.20.1)",
    "numpy (==1.24.3)",
    "torch (==2.4.1)",

solvers/moea/borg.py:1078

  • Configuration.initialize() runs at import time, but initialize() calls seed(), which calls Configuration.check() and will raise if setBorgLibrary() failed to auto-detect the shared library. This makes importing solvers.moea.borg fail on machines without the Borg .so/.dylib present, preventing even serial users from handling the error gracefully. Consider removing the import-time initialization and requiring explicit initialization (or make initialize()/seed() conditional on libborg being successfully loaded).
Configuration.initialize()


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +453 to +456
from borg import Borg, Configuration

if self.borg_library_path:
Configuration.setBorgLibrary(self.borg_library_path)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

This adapter imports Borg/Configuration from the top-level module name borg, but the wrapper added in this PR lives at solvers/moea/borg.py and other call sites import it via from solvers.moea.borg import .... Using from borg import ... will fail unless a separate borg package is installed / on PYTHONPATH. Update the import to use the in-repo module path (or a relative import) to avoid ModuleNotFoundError and ambiguity.

Copilot uses AI. Check for mistakes.
if __name__ == "__main__":

# Get swf, ssp, base_dir from sys.argv or set default values
base_dir = sys.argv[1] if len(sys.argv) > 4 else "data/temporary/NU_DATA/mmBorg/"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

base_dir = sys.argv[1] if len(sys.argv) > 4 else ... uses the wrong length check. When 2–4 CLI args are provided, this falls back to the default but still tries to read sys.argv[2]/sys.argv[3] below, and when exactly 2 args are provided it will IndexError on sys.argv[1] if the condition is incorrect. The check for sys.argv[1] should be len(sys.argv) > 1 (and similarly for other indices).

Suggested change
base_dir = sys.argv[1] if len(sys.argv) > 4 else "data/temporary/NU_DATA/mmBorg/"
base_dir = sys.argv[1] if len(sys.argv) > 1 else "data/temporary/NU_DATA/mmBorg/"

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +71
def extract_if_needed(archive_path: Path) -> Path:
"""If archive_path is a zip file, extract it to a temp dir and return the mm_intermediate folder.
Otherwise, assume it already points to the mm_intermediate directory."""
if archive_path.is_file() and archive_path.suffix == ".zip":
temp_dir = Path(tempfile.mkdtemp(prefix="mm_intermediate_"))
with zipfile.ZipFile(archive_path, "r") as zf:
zf.extractall(temp_dir)
subdirs = [p for p in temp_dir.glob("*") if p.is_dir()]
if len(subdirs) == 1:
extracted_root = subdirs[0]
else:
extracted_root = temp_dir / "mm_intermediate"
return extracted_root
else:
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

extract_if_needed() extracts a zip archive to a temp directory created via tempfile.mkdtemp(...) but never cleans it up, leaving temporary folders behind on every run. Prefer using tempfile.TemporaryDirectory() and cleaning up after packaging, or document that callers must remove the extracted directory.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +62
_ArchiveView,
_AlgorithmStub,
_create_intermediate_archives,
)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

run_optimization.py imports underscored names (_ArchiveView, _AlgorithmStub, _create_intermediate_archives) from borg_platypus_adapter. Since underscore-prefixed symbols are intended as private implementation details, this creates a fragile dependency between modules. Consider either (a) making these helpers part of the adapter's public API (drop underscores / add __all__) or (b) moving this runtime/archive logic into run_optimization.py (or a dedicated public helper module).

Suggested change
_ArchiveView,
_AlgorithmStub,
_create_intermediate_archives,
)
)
import solvers.moea.borg_platypus_adapter as _borg_platypus_adapter
_ArchiveView = _borg_platypus_adapter._ArchiveView
_AlgorithmStub = _borg_platypus_adapter._AlgorithmStub
_create_intermediate_archives = _borg_platypus_adapter._create_intermediate_archives

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +9
import matplotlib.pyplot as plt

from catboost import CatBoostRegressor, Pool
from sklearn.model_selection import KFold

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

This module imports catboost, sklearn, and matplotlib at import time, but these packages are not listed in pyproject.toml dependencies. As a result, installing the project will succeed but importing justice.util.feature_importance will raise ImportError in many environments. Either add these to dependencies (or an optional extra) or move the imports inside the functions/CLI entrypoints that require them.

Suggested change
import matplotlib.pyplot as plt
from catboost import CatBoostRegressor, Pool
from sklearn.model_selection import KFold
class _MissingDependency:
"""
Lightweight proxy used when optional dependencies are not installed.
Any attribute access will raise an informative ImportError.
"""
def __init__(self, package: str, feature: str = "this functionality") -> None:
self._package = package
self._feature = feature
def __getattr__(self, name):
raise ImportError(
f"{self._package} is required to use {self._feature} "
f"in justice.util.feature_importance but is not installed."
)
def _missing_dependency_callable(package: str, feature: str):
"""
Return a callable that raises ImportError when invoked.
Used for missing classes/functions such as CatBoostRegressor, Pool, or KFold.
"""
def _raiser(*args, **kwargs):
raise ImportError(
f"{package} is required to use {feature} "
f"in justice.util.feature_importance but is not installed."
)
return _raiser
try:
import matplotlib.pyplot as plt
except ImportError: # pragma: no cover
plt = _MissingDependency("matplotlib", "plotting utilities") # type: ignore
try:
from catboost import CatBoostRegressor, Pool
except ImportError: # pragma: no cover
CatBoostRegressor = _missing_dependency_callable("catboost", "CatBoostRegressor") # type: ignore
Pool = _missing_dependency_callable("catboost", "Pool") # type: ignore
try:
from sklearn.model_selection import KFold
except ImportError: # pragma: no cover
KFold = _missing_dependency_callable("scikit-learn", "KFold") # type: ignore

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +34
start_year = 2015
end_year = 2300
data_timestep = 5
timestep = 1

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The function parameters start_year, end_year, data_timestep, and timestep are immediately overwritten with hard-coded values, so caller-provided values are ignored. This makes the function signature misleading and can cause unexpected behavior when used outside the default 2015–2300 configuration. Either remove these parameters or stop overwriting them and rely on the passed-in values (or the config file) consistently.

Suggested change
start_year = 2015
end_year = 2300
data_timestep = 5
timestep = 1

Copilot uses AI. Check for mistakes.
- deep uncertainty
- normative uncertainty
license: BSD-3-Clause
version: 0.2.0
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

CITATION.cff still lists version: 0.2.0 while pyproject.toml bumps the project to 1.0.0. This inconsistency can confuse downstream citation tooling and users. Update the citation metadata version (and release date/DOI if applicable) to match the package version.

Suggested change
version: 0.2.0
version: 1.0.0

Copilot uses AI. Check for mistakes.
@pollockDeVis pollockDeVis merged commit 2a20f19 into main Mar 20, 2026
2 checks passed
@pollockDeVis pollockDeVis deleted the moma branch March 20, 2026 15:14
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.

2 participants