diff --git a/.cruft.json b/.cruft.json index f0ee6048..5536d2e3 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,6 +1,6 @@ { "template": "https://github.com/arup-group/cookiecutter-pypackage.git", - "commit": "b7724521f898ab28f541d492aff8e2be2ed76a01", + "commit": "bb2bab415eded9682b2c1d465a281450ec922f1c", "checkout": null, "context": { "cookiecutter": { @@ -15,11 +15,13 @@ "project_short_description": "The Population activity Modeller (PAM) is a python API for activity sequence modelling.", "upload_pypi_package": "y", "upload_conda_package": "y", + "upload_aws_image": "n", "conda_channel": "city-modelling-lab", "command_line_interface": "y", "create_docker_file": "y", "create_author_file": "n", "create_jupyter_notebook_directory": "y", + "check_docs_accessibility_in_CI": "n", "open_source_license": "MIT license", "_template": "https://github.com/arup-group/cookiecutter-pypackage.git" } diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 40422ee5..91ee369f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,9 +3,16 @@ name: Build docs on: push: branches: - - "**" + - main + + pull_request: + branches: + - main paths-ignore: - tests/** + - ".github/**/*" + - "!.github/workflows/docs.yml" + jobs: docs-test: diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 82498968..3c809a42 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -11,9 +11,20 @@ on: - CONTRIBUTING.md - docs/** - mkdocs.yml + - ".github/**/*" + - "!.github/workflows/pr-ci.yml" jobs: + lint: + if: github.event.repository.private + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pre-commit/action@v3.0.1 + test: + needs: lint + if: always() && (needs.lint.result == 'success' || needs.lint.result == 'skipped') strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] @@ -29,6 +40,8 @@ jobs: upload_to_codecov: false test-coverage: + needs: lint + if: always() && (needs.lint.result == 'success' || needs.lint.result == 'skipped') uses: arup-group/actions-city-modelling-lab/.github/workflows/python-install-lint-test.yml@main with: os: ubuntu-latest @@ -36,7 +49,7 @@ jobs: notebook_kernel: pam lint: false pytest_args: 'tests/' # ignore example notebooks - upload_to_codecov: true + upload_to_codecov: ${{ github.event.repository.visibility == 'public' }} # only attempt to upload if the project is public memory-profile: uses: arup-group/actions-city-modelling-lab/.github/workflows/python-memory-profile.yml@main diff --git a/.pa11yci b/.pa11yci new file mode 100644 index 00000000..c817d59f --- /dev/null +++ b/.pa11yci @@ -0,0 +1,24 @@ +{ + "defaults": { + "reporters": [ + "cli", + [ + "pa11y-ci-reporter-html", + { "destination": "./reports/pa11y", "includeZeroIssues": false } + ] + ], + "timeout": 100000, + "wait": 2000, + "ignore": ["color-contrast"], + "runners": [ + "htmlcs", "axe" + ], + "hideElements": "[id^='__codelineno'], .md-search__form, #__toc, clipboard-copy" + }, + "standard": "WCAG2AA", + "comments": [ + "Ignoring color-contrast due to https://github.com/pa11y/pa11y/issues/697.", + "Hiding `clipboard-copy` until https://github.com/danielfrg/mkdocs-jupyter/pull/206 is merged.", + "Hiding `[id^='__codelineno'], .md-search__form, #__toc` due to known false positives in mkdocs-material: https://github.com/squidfunk/mkdocs-material/discussions/4102" + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 235a1582..b24560ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,19 +36,19 @@ Look at the [development guide in our documentation](https://arup-group.github.i To contribute changes: 1. Fork the project on GitHub. -2. Create a feature branch to work on in your fork (`git checkout -b new-fix-or-feature`). -3. Test your changes using `pytest`. -4. Commit your changes to the feature branch (you should have `pre-commit` installed to ensure your code is correctly formatted when you commit changes). -5. Push the branch to GitHub (`git push origin new-fix-or-feature`). -6. On GitHub, create a new [pull request](https://github.com/arup-group/pam/pull/new/main) from the feature branch. +1. Create a feature branch to work on in your fork (`git checkout -b new-fix-or-feature`). +1. Test your changes using `pytest`. +1. Commit your changes to the feature branch (you should have `pre-commit` installed to ensure your code is correctly formatted when you commit changes). +1. Push the branch to GitHub (`git push origin new-fix-or-feature`). +1. On GitHub, create a new [pull request](https://github.com/arup-group/pam/pull/new/main) from the feature branch. ### Pull requests Before submitting a pull request, check whether you have: -* Added your changes to `CHANGELOG.md`. -* Added or updated documentation for your changes. -* Added tests if you implemented new functionality. +- Added your changes to `CHANGELOG.md`. +- Added or updated documentation for your changes. +- Added tests if you implemented new functionality. When opening a pull request, please provide a clear summary of your changes! diff --git a/docs/activity_plans.md b/docs/activity_plans.md index fb7514ac..570aec1a 100644 --- a/docs/activity_plans.md +++ b/docs/activity_plans.md @@ -135,4 +135,5 @@ This is resulting some difficult to follow logic (e.g., [][pam.activity.Plan.fil ## Plan cropping The [`pam.operations.cropping`](reference/pam/operations/cropping.md) module allows to spatially subset populations, by simplifying plan components that take place outside the "core" area. Any activities or legs that do not affect that core area are removed from the agents' plans, and agents with fully-external plans are removed from the population. -Examples of using the module can be found in the [][plan-cropping] notebook. \ No newline at end of file +Examples of using the module can be found in the [][plan-cropping] notebook. +Plan cropping now features basic logging on the input and then output population, quantifying the changes in agent and households as a result of the cropping. \ No newline at end of file diff --git a/docs/static/extras.css b/docs/static/extras.css index c1cf3c36..d3ae7270 100644 --- a/docs/static/extras.css +++ b/docs/static/extras.css @@ -7,4 +7,25 @@ div.doc-contents:not(.first) { /* Allow tables to be horizontally scrollable. */ .md-typeset__scrollwrap { overflow-x: auto; -} \ No newline at end of file +} +/* Improve contrast of text in rendered Jupyter notebook codeblocks. */ +.jp-Notebook { + --jp-cell-prompt-not-active-opacity: 1; +} +.jp-InputArea-editor { + --jp-cell-editor-background: var(--md-code-bg-color); +} +.highlight-ipynb { + --jp-mirror-editor-number-color: var(--md-code-hl-number-color); + --jp-mirror-editor-string-color: var(--md-code-hl-string-color); + --jp-mirror-editor-operator-color: var(--md-code-hl-operator-color); + --jp-mirror-editor-meta-color: var(--md-code-hl-operator-color); + --jp-mirror-editor-comment-color: var(--md-code-hl-comment-color); + --jp-mirror-editor-keyword-color: var(--md-code-hl-keyword-color); + --jp-mirror-editor-builtin-color: var(--md-code-hl-keyword-color); + --jp-mirror-editor-variable-2-color: var(--md-code-hl-punctuation-color); + --jp-mirror-editor-punctuation-color: var(--md-code-hl-punctuation-color); + --jp-mirror-editor-property-color: var(--md-code-hl-punctuation-color); + --jp-mirror-editor-variable-color: var(--md-code-hl-variable-color); + --jp-mirror-editor-def-color: var(--md-code-hl-constant-color); +} diff --git a/docs/static/hooks.py b/docs/static/hooks.py index 2071473d..5be412a2 100644 --- a/docs/static/hooks.py +++ b/docs/static/hooks.py @@ -1,3 +1,5 @@ +"""Hooks to run when building documentation.""" + import tempfile from pathlib import Path @@ -10,8 +12,17 @@ # Bump priority to ensure files are moved before jupyter notebook conversion takes place @mkdocs.plugins.event_priority(50) -def on_files(files: list, config: dict, **kwargs): - """Link (1) top-level files to mkdocs files and (2) generate the python API documentation.""" +def on_files(files: list, config: dict, **kwargs) -> list: + """Link (1) top-level files to mkdocs files and (2) generate the python API documentation. + + Args: + files (list): mkdocs file list. + config (dict): mkdocs config dictionary. + **kwargs: Automatic MKDocs hook inputs. + + Returns: + list: Updated mkdocs file list. + """ for file in sorted(Path("./examples").glob("*.ipynb")): files.append(_new_file(file, config)) _get_nav_list(config["nav"], "Examples").append(file.as_posix()) @@ -48,7 +59,7 @@ def _new_file(path: Path, config: dict, src_dir: str = ".") -> File: def _api_gen(files: list, config: dict) -> dict: - """Project Python API generator + """Project Python API generator. Args: files (list): mkdocs file list. @@ -78,6 +89,7 @@ def _py_to_md(filepath: Path, api_nav: dict, config: dict) -> File: filepath (Path): Path to python file relative to the package source code directory. api_nav (dict): Nested dictionary to fill with mkdocs navigation entries. config (Config): mkdocs config dictionary. + Returns: File: mkdocs object that links the temp file to the docs directory, ready to be added to the mkdocs file list. """ @@ -112,7 +124,6 @@ def _update_nav(api_nav: dict, config: dict) -> None: api_nav (dict): Python API navigation tree. config (dict): mkdocs config dictionary (in which `nav` can be found). """ - api_reference_nav = { "Python API": [*api_nav.pop("top_level"), *[{k: v} for k, v in api_nav.items()]] } @@ -121,6 +132,7 @@ def _update_nav(api_nav: dict, config: dict) -> None: def _get_nav_list(nav: list[dict | str], ref: str) -> list: """Get navigation entry sub-page list. + Navigation list entries can be dictionaries or strings. Sub-list entries can then also be dictionaries or strings. E.g., @@ -144,6 +156,6 @@ def on_post_build(**kwargs): """After mkdocs has finished building the docs, remove the temporary directory of markdown files. Args: - config (Config): mkdocs config dictionary (unused). + **kwargs: Automatic MKDocs hook inputs. """ TEMPDIR.cleanup() diff --git a/examples/02a_tabular_read_write.ipynb b/examples/02a_tabular_read_write.ipynb index 117354ec..bee27aa6 100644 --- a/examples/02a_tabular_read_write.ipynb +++ b/examples/02a_tabular_read_write.ipynb @@ -50,6 +50,7 @@ "import os\n", "\n", "import pandas as pd\n", + "\n", "from pam import read" ] }, diff --git a/examples/03_read_modify_write.ipynb b/examples/03_read_modify_write.ipynb index 87904f06..6cd6344a 100644 --- a/examples/03_read_modify_write.ipynb +++ b/examples/03_read_modify_write.ipynb @@ -46,6 +46,7 @@ "import geopandas as gp\n", "import pandas as pd\n", "from matplotlib import pyplot as plt\n", + "\n", "from pam import policy, read\n", "from pam.policy import apply_policies\n", "\n", diff --git a/examples/04_point_sampling.ipynb b/examples/04_point_sampling.ipynb index dbdf72c3..c20d816f 100644 --- a/examples/04_point_sampling.ipynb +++ b/examples/04_point_sampling.ipynb @@ -34,6 +34,7 @@ "import geopandas as gp\n", "import pandas as pd\n", "from matplotlib import pyplot as plt\n", + "\n", "from pam import read\n", "from pam.samplers.spatial import RandomPointSampler" ] diff --git a/examples/05_activity_plots.ipynb b/examples/05_activity_plots.ipynb index cce4a3d6..95dd766e 100644 --- a/examples/05_activity_plots.ipynb +++ b/examples/05_activity_plots.ipynb @@ -45,6 +45,7 @@ "import os\n", "\n", "import pandas as pd\n", + "\n", "from pam import read\n", "from pam.plot.stats import plot_activity_times, plot_leg_times" ] diff --git a/examples/07_travel_survey_to_matsim.ipynb b/examples/07_travel_survey_to_matsim.ipynb index 364cfcb2..3b3e20cb 100644 --- a/examples/07_travel_survey_to_matsim.ipynb +++ b/examples/07_travel_survey_to_matsim.ipynb @@ -49,6 +49,7 @@ "\n", "import geopandas as gp\n", "import pandas as pd\n", + "\n", "from pam import read, write\n", "from pam.core import Population\n", "from pam.plot.stats import plot_activity_times, plot_leg_times\n", diff --git a/examples/08_toy_matsim_population.ipynb b/examples/08_toy_matsim_population.ipynb index fc25b736..ed49a55e 100644 --- a/examples/08_toy_matsim_population.ipynb +++ b/examples/08_toy_matsim_population.ipynb @@ -64,6 +64,7 @@ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", + "\n", "from pam.activity import Activity, Leg\n", "from pam.core import Household, Person, Population\n", "from pam.plot.stats import plot_activity_times\n", diff --git a/examples/10_advanced_spatial_sampling.ipynb b/examples/10_advanced_spatial_sampling.ipynb index c98ff6b4..2c4f2103 100644 --- a/examples/10_advanced_spatial_sampling.ipynb +++ b/examples/10_advanced_spatial_sampling.ipynb @@ -75,13 +75,14 @@ "import geopandas as gp\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "import pam\n", "import pandas as pd\n", + "from shapely.geometry import Point, Polygon\n", + "\n", + "import pam\n", "from pam.activity import Activity, Leg\n", "from pam.core import Household, Person, Population\n", "from pam.samplers import facility\n", - "from pam.utils import minutes_to_datetime\n", - "from shapely.geometry import Point, Polygon" + "from pam.utils import minutes_to_datetime" ] }, { diff --git a/examples/13_Advanced_Freight_Synthesis.ipynb b/examples/13_Advanced_Freight_Synthesis.ipynb index d2cf4e1e..e22d8365 100644 --- a/examples/13_Advanced_Freight_Synthesis.ipynb +++ b/examples/13_Advanced_Freight_Synthesis.ipynb @@ -40,6 +40,7 @@ "import numpy as np\n", "import pandas as pd\n", "from matplotlib import pyplot as plt\n", + "\n", "from pam.core import Household, Person, Population\n", "from pam.samplers import tour\n", "from pam.samplers.facility import FacilitySampler\n", diff --git a/examples/14_Advanced_Plan_Cropping.ipynb b/examples/14_Advanced_Plan_Cropping.ipynb index 48be5357..c8aac236 100644 --- a/examples/14_Advanced_Plan_Cropping.ipynb +++ b/examples/14_Advanced_Plan_Cropping.ipynb @@ -38,12 +38,13 @@ "\n", "import geopandas as gp\n", "import matplotlib.pyplot as plt\n", + "from shapely.geometry import Point, Polygon\n", + "\n", "from pam.activity import Activity, Leg, Plan\n", "from pam.core import Household, Person, Population\n", "from pam.operations import cropping\n", "from pam.utils import minutes_to_datetime as mtdt\n", - "from pam.variables import END_OF_DAY\n", - "from shapely.geometry import Point, Polygon" + "from pam.variables import END_OF_DAY" ] }, { diff --git a/examples/15_advanced_choice_modelling.ipynb b/examples/15_advanced_choice_modelling.ipynb index 44d0dcbc..a769776c 100644 --- a/examples/15_advanced_choice_modelling.ipynb +++ b/examples/15_advanced_choice_modelling.ipynb @@ -57,11 +57,12 @@ "\n", "import numpy as np\n", "import pandas as pd\n", + "from prettytable import PrettyTable\n", + "\n", "from pam.operations.cropping import link_population\n", "from pam.planner import choice_location as choice\n", "from pam.planner.od import ODFactory, ODMatrix\n", "from pam.read import read_matsim\n", - "from prettytable import PrettyTable\n", "\n", "logging.basicConfig(level=logging.DEBUG)\n", "random.seed(0)" diff --git a/examples/17_advanced_discretionary_locations.ipynb b/examples/17_advanced_discretionary_locations.ipynb index bb0648de..375376d5 100644 --- a/examples/17_advanced_discretionary_locations.ipynb +++ b/examples/17_advanced_discretionary_locations.ipynb @@ -55,6 +55,9 @@ "\n", "import numpy as np\n", "import pandas as pd\n", + "from prettytable import PrettyTable\n", + "from shapely.geometry import Point\n", + "\n", "from pam.activity import Activity, Leg, Plan\n", "from pam.location import Location\n", "from pam.planner.choice_location import DiscretionaryTripOD, DiscretionaryTrips\n", @@ -62,8 +65,6 @@ "from pam.planner.utils_planner import get_trip_chains_either_anchor\n", "from pam.utils import minutes_to_datetime as mtdt\n", "from pam.variables import END_OF_DAY\n", - "from prettytable import PrettyTable\n", - "from shapely.geometry import Point\n", "\n", "logging.getLogger(\"pam\").setLevel(logging.DEBUG)\n", "random.seed(0)" diff --git a/examples/18_advanced_ipf.ipynb b/examples/18_advanced_ipf.ipynb index 7a2550a3..f9e27b51 100644 --- a/examples/18_advanced_ipf.ipynb +++ b/examples/18_advanced_ipf.ipynb @@ -50,6 +50,7 @@ "import itertools\n", "\n", "import pandas as pd\n", + "\n", "from pam.core import Person, Population\n", "from pam.planner import ipf" ] diff --git a/mkdocs.yml b/mkdocs.yml index 15d171e0..7b5b1242 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -36,13 +36,12 @@ markdown_extensions: anchor_linenums: true line_spans: __span pygments_lang_class: true - - pymdownx.inlinehilite + - pymdownx.inlinehilite: + style_plain_text: shell - pymdownx.superfences - pymdownx.snippets - pymdownx.tabbed: alternate_style: true - - pymdownx.tasklist: - clickable_checkbox: true - toc: permalink: "#" toc_depth: 3 diff --git a/requirements/dev.txt b/requirements/dev.txt index 57383799..0d463b0f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,10 +1,10 @@ cruft >= 2, < 3 jupyter < 2 mike >= 2, < 3 -mkdocs < 1.6 +mkdocs >= 1.6, < 2 mkdocs-material >= 9.4, < 10 mkdocs-click < 0.7 -mkdocs-jupyter < 0.24.7 +mkdocs-jupyter >= 0.24.8, < 0.25 mkdocstrings-python < 2 nbmake >= 1.5.1, < 2 pre-commit < 4 diff --git a/src/pam/operations/cropping.py b/src/pam/operations/cropping.py index b0e82c70..7fd8a8ca 100644 --- a/src/pam/operations/cropping.py +++ b/src/pam/operations/cropping.py @@ -23,18 +23,25 @@ def simplify_population( # remove empty person-plans and households remove_persons = [] + track_those_removed = [] for hid, pid, person in population.people(): if len(person.plan) == 1 and person.plan.day[0].act == "external": remove_persons.append((hid, pid)) + track_those_removed.append({"hid": hid, "pid": pid}) for hid, pid in remove_persons: del population[hid].people[pid] + print(len(remove_persons), "persons to be removed") + remove_hhs = [ hid for hid in population.households if len(population.households[hid].people) == 0 ] for hid in remove_hhs: del population.households[hid] + print(len(remove_hhs), "households to be removed") + print("After simplification", population.stats) + def simplify_external_plans( plan: Plan,