From ef92c5a43258a4202601b789716287b4646596c5 Mon Sep 17 00:00:00 2001 From: GondekNP Date: Thu, 9 Jan 2025 18:06:00 +0000 Subject: [PATCH] also add black while we're at it - setting pixi interpreter working nicely --- pixi.lock | 79 ++++++++++++++++++++++++++++++++++++++ pixi.toml | 1 + vegetation/app.py | 16 ++++---- vegetation/batch.py | 14 +++---- vegetation/config/paths.py | 4 +- vegetation/patch/model.py | 79 ++++++++++++++++++++++++-------------- vegetation/patch/space.py | 12 ++++-- vegetation/patch/utils.py | 12 +++--- 8 files changed, 163 insertions(+), 54 deletions(-) diff --git a/pixi.lock b/pixi.lock index 4192d6d..107c486 100644 --- a/pixi.lock +++ b/pixi.lock @@ -14,6 +14,7 @@ environments: - conda: https://conda.anaconda.org/main/linux-64/annotated-types-0.6.0-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/noarch/asttokens-2.0.5-pyhd3eb1b0_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/attrs-24.3.0-py311h06a4308_0.tar.bz2 + - conda: https://conda.anaconda.org/main/linux-64/black-24.8.0-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/blas-1.0-mkl.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/blosc-1.21.3-h6a678d5_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/boost-cpp-1.82.0-hdb19cb5_2.tar.bz2 @@ -128,6 +129,7 @@ environments: - conda: https://conda.anaconda.org/main/linux-64/mkl-service-2.4.0-py311h5eee18b_1.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/mkl_fft-1.3.11-py311h5eee18b_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/mkl_random-1.2.8-py311ha02d727_0.tar.bz2 + - conda: https://conda.anaconda.org/main/linux-64/mypy_extensions-1.0.0-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/mysql-8.4.0-h29a9f33_1.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/ncurses-6.4-h6a678d5_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/networkx-3.3-py311h06a4308_0.tar.bz2 @@ -143,12 +145,14 @@ environments: - conda: https://conda.anaconda.org/main/linux-64/pandas-2.2.3-py311h6a678d5_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/parso-0.8.4-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/partd-1.4.1-py311h06a4308_0.tar.bz2 + - conda: https://conda.anaconda.org/main/linux-64/pathspec-0.10.3-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/pcre2-10.42-hebb0a14_1.tar.bz2 - conda: https://conda.anaconda.org/main/noarch/pexpect-4.8.0-pyhd3eb1b0_3.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/pillow-11.0.0-py311hcea889d_1.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/pip-24.2-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/pixman-0.40.0-h7f8727e_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/planetary-computer-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/main/linux-64/platformdirs-3.10.0-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/pluggy-1.5.0-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/ply-3.11-py311h06a4308_0.tar.bz2 - conda: https://conda.anaconda.org/main/linux-64/poppler-24.09.0-hcf11d46_1.tar.bz2 @@ -526,6 +530,33 @@ packages: - html5lib ; extra == 'html5lib' - lxml ; extra == 'lxml' requires_python: '>=3.6.0' +- kind: conda + name: black + version: 24.8.0 + build: py311h06a4308_0 + subdir: linux-64 + url: https://conda.anaconda.org/main/linux-64/black-24.8.0-py311h06a4308_0.tar.bz2 + sha256: bde757189727c873b32fe255f661c9635acb84aabc8980bd9cabe8c19c38d19f + md5: a6e7c11f2509ce9c6df4b51c1734c25a + depends: + - click >=8.0.0 + - mypy_extensions >=0.4.3 + - packaging >=22.0 + - pathspec >=0.9.0 + - platformdirs >=2 + - python >=3.11,<3.12.0a0 + constrains: + - tokenize-rt >=3.2.0 + - aiohttp >=3.7.4 + - ipython >=7.8.0 + - colorama >=0.4.3 + - uvloop >=0.15.2 + arch: x86_64 + platform: linux + license: MIT + license_family: MIT + size: 375127 + timestamp: 1725573982362 - kind: conda name: blas version: '1.0' @@ -3528,6 +3559,22 @@ packages: license_family: BSD size: 411216 timestamp: 1730824036145 +- kind: conda + name: mypy_extensions + version: 1.0.0 + build: py311h06a4308_0 + subdir: linux-64 + url: https://conda.anaconda.org/main/linux-64/mypy_extensions-1.0.0-py311h06a4308_0.tar.bz2 + sha256: 957cdcab5e926f5fa17472c62058f4f46c597d3383480470d8fe84a877469f05 + md5: 61500d19a6803d0a4bd5d11a5395a9fd + depends: + - python >=3.11,<3.12.0a0 + arch: x86_64 + platform: linux + license: MIT + license_family: MIT + size: 12931 + timestamp: 1695130958822 - kind: conda name: mysql version: 8.4.0 @@ -4043,6 +4090,22 @@ packages: license_family: BSD size: 47447 timestamp: 1698702703255 +- kind: conda + name: pathspec + version: 0.10.3 + build: py311h06a4308_0 + subdir: linux-64 + url: https://conda.anaconda.org/main/linux-64/pathspec-0.10.3-py311h06a4308_0.tar.bz2 + sha256: 2dc037d04fb7cdbde8797764375f21b0cdd577db04112446258768fdd4c2d7a3 + md5: 5170fffdbb712419b735673c4ac7a317 + depends: + - python >=3.11,<3.12.0a0 + arch: x86_64 + platform: linux + license: MPL-2.0 + license_family: Other + size: 55491 + timestamp: 1679337308390 - kind: conda name: pcre2 version: '10.42' @@ -4216,6 +4279,22 @@ packages: - pytest >=8.3.2 ; extra == 'test' - mypy >=1.11.2 ; extra == 'type' requires_python: '>=3.8' +- kind: conda + name: platformdirs + version: 3.10.0 + build: py311h06a4308_0 + subdir: linux-64 + url: https://conda.anaconda.org/main/linux-64/platformdirs-3.10.0-py311h06a4308_0.tar.bz2 + sha256: d2bbab8bfc57c7f46ca8994bc87df7e744d3f036b867bc4be1145ba6d8d7e091 + md5: de41707272a8e3ccab764f0b7010a27d + depends: + - python >=3.11,<3.12.0a0 + arch: x86_64 + platform: linux + license: MIT + license_family: MIT + size: 37394 + timestamp: 1692205565974 - kind: conda name: pluggy version: 1.5.0 diff --git a/pixi.toml b/pixi.toml index b5ab342..f2722a0 100644 --- a/pixi.toml +++ b/pixi.toml @@ -10,6 +10,7 @@ platforms = ["linux-64"] [dependencies] pytest = ">=7.4.4,<7.5" +black = ">=24.8.0,<24.9" [target.linux-64.dependencies] python = "3.11.*" diff --git a/vegetation/app.py b/vegetation/app.py index 547f4b5..321fcdf 100644 --- a/vegetation/app.py +++ b/vegetation/app.py @@ -7,6 +7,7 @@ from mesa_geo.visualization import make_geospace_component from patch.model import Vegetation, JoshuaTreeAgent from patch.space import VegCell + # from patch.management import init_tree_management_control from config.stages import LIFE_STAGE_RGB_VIZ_MAP @@ -32,7 +33,9 @@ model_params = { "num_steps": Slider("total number of steps", 20, 1, 100, 1), - "management_planting_density": Slider("management planting density", 0.1, 0.01, 1.0, 0.01), + "management_planting_density": Slider( + "management planting density", 0.1, 0.01, 1.0, 0.01 + ), "export_data": False, "bounds": TST_JOTR_BOUNDS, } @@ -53,7 +56,6 @@ def cell_portrayal(agent): max_stage = max(patch_life_stages) rgba = LIFE_STAGE_RGB_VIZ_MAP[max_stage] - else: if not agent.refugia_status: debug_normalized_elevation = int((agent.elevation / 5000) * 255) @@ -61,7 +63,7 @@ def cell_portrayal(agent): debug_normalized_elevation, debug_normalized_elevation, debug_normalized_elevation, - .25, + 0.25, ) else: rgba = (0, 255, 0, 1) @@ -85,11 +87,7 @@ def cell_portrayal(agent): model = Vegetation(bounds=TST_JOTR_BOUNDS) tree_management = GeomanDrawControl( - drag=False, - cut=False, - rotate=False, - polyline={}, - circlemarker={} + drag=False, cut=False, rotate=False, polyline={}, circlemarker={} ) tree_management.on_draw(model.add_agents_from_management_draw) @@ -97,7 +95,7 @@ def cell_portrayal(agent): model, name="Veg Model", components=[ - make_geospace_component(cell_portrayal, zoom=14, controls = [tree_management]), + make_geospace_component(cell_portrayal, zoom=14, controls=[tree_management]), make_plot_component( [ "Mean Age", diff --git a/vegetation/batch.py b/vegetation/batch.py index c8cfd6f..cc7e57f 100644 --- a/vegetation/batch.py +++ b/vegetation/batch.py @@ -3,22 +3,22 @@ from numpy import arange from config.paths import LOCAL_STAC_CACHE_FSTRING, SAVE_LOCAL_STAC_CACHE, DEM_STAC_PATH -#TODO: Batch run can't see local cache -#Issue URL: https://github.com/SchmidtDSE/mesa_abm_poc/issues/16 +# TODO: Batch run can't see local cache +# Issue URL: https://github.com/SchmidtDSE/mesa_abm_poc/issues/16 -#TODO: Implement early stopping when all the JOTR die off -#Issue URL: https://github.com/SchmidtDSE/mesa_abm_poc/issues/18 +# TODO: Implement early stopping when all the JOTR die off +# Issue URL: https://github.com/SchmidtDSE/mesa_abm_poc/issues/18 TST_JOTR_BOUNDS = [-116.326332, 33.975823, -116.289768, 34.004147] model_params = { "num_steps": [100], - "management_planting_density": arange(0, 1, .05), + "management_planting_density": arange(0, 1, 0.05), "export_data": [False], "bounds": [TST_JOTR_BOUNDS], } -if __name__ == '__main__': +if __name__ == "__main__": results = batch_run( Vegetation, parameters=model_params, @@ -27,4 +27,4 @@ number_processes=1, data_collection_period=1, display_progress=True, - ) \ No newline at end of file + ) diff --git a/vegetation/config/paths.py b/vegetation/config/paths.py index 74892b0..2fd4708 100644 --- a/vegetation/config/paths.py +++ b/vegetation/config/paths.py @@ -1,4 +1,6 @@ INITIAL_AGENTS_PATH = "/workspaces/mesa_abm_poc/vegetation/data/initial_agents.json" DEM_STAC_PATH = "https://planetarycomputer.microsoft.com/api/stac/v1/" -LOCAL_STAC_CACHE_FSTRING = "/workspaces/mesa_abm_poc/.local_dev_data/{band_name}_{bounds_md5}.tif" +LOCAL_STAC_CACHE_FSTRING = ( + "/workspaces/mesa_abm_poc/.local_dev_data/{band_name}_{bounds_md5}.tif" +) SAVE_LOCAL_STAC_CACHE = True diff --git a/vegetation/patch/model.py b/vegetation/patch/model.py index c80db31..84547a7 100644 --- a/vegetation/patch/model.py +++ b/vegetation/patch/model.py @@ -26,6 +26,7 @@ JOTR_UTM_PROJ = "+proj=utm +zone=11 +ellps=WGS84 +datum=WGS84 +units=m +no_defs +north" STD_INDENT = " " + class JoshuaTreeAgent(mg.GeoAgent): def __init__(self, model, geometry, crs, age=None, parent_id=None): super().__init__( @@ -115,7 +116,6 @@ def step(self): ) self.life_stage = LifeStage.DEAD - # Increment age self.age += 1 life_stage_promotion = self._update_life_stage() @@ -162,17 +162,27 @@ def _update_life_stage(self): else: return False - def disperse_seeds(self, n_seeds, max_dispersal_distance=JOTR_SEED_DISPERSAL_DISTANCE): + def disperse_seeds( + self, n_seeds, max_dispersal_distance=JOTR_SEED_DISPERSAL_DISTANCE + ): if self.life_stage != LifeStage.BREEDING: - raise ValueError(f"Agent {self.unique_id} is not breeding and cannot disperse seeds") + raise ValueError( + f"Agent {self.unique_id} is not breeding and cannot disperse seeds" + ) - print(f"{STD_INDENT*2}🌰 Agent {self.unique_id} ({self.life_stage.name}) is dispersing {n_seeds} seeds...") + print( + f"{STD_INDENT*2}🌰 Agent {self.unique_id} ({self.life_stage.name}) is dispersing {n_seeds} seeds..." + ) - wgs84_to_utm, utm_to_wgs84 = transform_point_wgs84_utm(self.geometry.x, self.geometry.y) + wgs84_to_utm, utm_to_wgs84 = transform_point_wgs84_utm( + self.geometry.x, self.geometry.y + ) x_utm, y_utm = wgs84_to_utm.transform(self.geometry.x, self.geometry.y) for __seed_idx in np.arange(0, n_seeds): - seed_x_utm, seed_y_utm = generate_point_in_utm(x_utm, y_utm, max_dispersal_distance) + seed_x_utm, seed_y_utm = generate_point_in_utm( + x_utm, y_utm, max_dispersal_distance + ) seed_x_wgs84, seed_y_wgs84 = utm_to_wgs84.transform(seed_x_utm, seed_y_utm) seed_agent = JoshuaTreeAgent( @@ -183,17 +193,25 @@ def disperse_seeds(self, n_seeds, max_dispersal_distance=JOTR_SEED_DISPERSAL_DIS parent_id=self.unique_id, ) seed_agent._update_life_stage() - + self.model.space.add_agents(seed_agent) delta_x_index = self.indices[0] - seed_agent.indices[0] delta_y_index = self.indices[1] - seed_agent.indices[1] - print(f"{STD_INDENT*3}➕ Seed ({seed_agent.unique_id}, lifestage {seed_agent.life_stage}) to {seed_agent._pos} (🔺index: {delta_x_index}, {delta_y_index})") - + print( + f"{STD_INDENT*3}➕ Seed ({seed_agent.unique_id}, lifestage {seed_agent.life_stage}) to {seed_agent._pos} (🔺index: {delta_x_index}, {delta_y_index})" + ) class Vegetation(mesa.Model): - def __init__(self, bounds, export_data=False, num_steps=20, management_planting_density=.01, epsg=4326): + def __init__( + self, + bounds, + export_data=False, + num_steps=20, + management_planting_density=0.01, + epsg=4326, + ): super().__init__() self.bounds = bounds self.export_data = export_data @@ -240,9 +258,9 @@ def _add_agents_from_geojson(self, agents_geojson): # def add_agents_from_management_draw(event, geo_json, action): def add_agents_from_management_draw(self, *args, **kwargs): - + assert kwargs.get("action") == "create" - management_area = kwargs.get('geo_json') + management_area = kwargs.get("geo_json") outplanting_point_locations = self._generate_planting_points(management_area) @@ -257,38 +275,41 @@ def add_agents_from_management_draw(self, *args, **kwargs): parent_id=None, ) management_agent._update_life_stage() - - self.space.add_agents(management_agent) - print(f"{STD_INDENT*3}✨ Outplanted ({management_agent.unique_id}, lifestage {management_agent.life_stage.name}) to {management_agent._pos}") + self.space.add_agents(management_agent) + print( + f"{STD_INDENT*3}✨ Outplanted ({management_agent.unique_id}, lifestage {management_agent.life_stage.name}) to {management_agent._pos}" + ) def _generate_planting_points(self, geo_json): # Convert GeoJSON to Shapely polygon - coords = geo_json[0]['geometry']['coordinates'][0] + coords = geo_json[0]["geometry"]["coordinates"][0] polygon = sg.Polygon(coords) - + # Get UTM zone from polygon centroid lon, lat = polygon.centroid.x, polygon.centroid.y wgs84_to_utm, utm_to_wgs84 = transform_point_wgs84_utm(lon, lat) - + # Project polygon to UTM utm_polygon = transform(wgs84_to_utm.transform, polygon) area = utm_polygon.area num_points = int(area * self.management_planting_density) - + points = [] minx, miny, maxx, maxy = utm_polygon.bounds - + while len(points) < num_points: x_utm = np.random.uniform(minx, maxx) y_utm = np.random.uniform(miny, maxy) point_utm = sg.Point(x_utm, y_utm) - + if utm_polygon.contains(point_utm): - management_x_wgs84, management_y_wgs84 = utm_to_wgs84.transform(x_utm, y_utm) + management_x_wgs84, management_y_wgs84 = utm_to_wgs84.transform( + x_utm, y_utm + ) points.append((management_x_wgs84, management_y_wgs84)) - + return points def update_metrics(self): @@ -313,12 +334,14 @@ def update_metrics(self): # Number of refugia cells occupied by JoshuaTreeAgents count_dict = ( - self.agents.select(agent_type=VegCell) \ - .select(filter_func = lambda agent: agent.refugia_status) \ - .groupby("occupied_by_jotr_agents") \ - .count() + self.agents.select(agent_type=VegCell) + .select(filter_func=lambda agent: agent.refugia_status) + .groupby("occupied_by_jotr_agents") + .count() + ) + self.pct_refugia_cells_occupied = count_dict.get(True, 0) / ( + count_dict.get(True, 0) + count_dict.get(False, 0) ) - self.pct_refugia_cells_occupied = count_dict.get(True, 0) / (count_dict.get(True, 0) + count_dict.get(False, 0)) def step(self): # Print timestep header diff --git a/vegetation/patch/space.py b/vegetation/patch/space.py index 6c2fcca..e6c4888 100644 --- a/vegetation/patch/space.py +++ b/vegetation/patch/space.py @@ -18,6 +18,7 @@ # from patch.model import JoshuaTreeAgent # import rioxarray as rxr + class VegCell(mg.Cell): elevation: int | None aridity: int | None @@ -46,16 +47,19 @@ def __init__( def step(self): self.update_occupancy() pass - + def update_occupancy(self): - # Very clunky way to exclude dead agents - alive_jotr_agents = [agent for agent in self.jotr_agents if agent.life_stage != LifeStage.DEAD] - self.occupied_by_jotr_agents = True if len(alive_jotr_agents) > 0 else False + # Very clunky way to exclude dead agents + alive_jotr_agents = [ + agent for agent in self.jotr_agents if agent.life_stage != LifeStage.DEAD + ] + self.occupied_by_jotr_agents = True if len(alive_jotr_agents) > 0 else False def add_agent_link(self, jotr_agent): if jotr_agent.life_stage and jotr_agent not in self.jotr_agents: self.jotr_agents.append(jotr_agent) + class StudyArea(mg.GeoSpace): def __init__(self, bounds, epsg, model): super().__init__(crs=f"epsg:{epsg}") diff --git a/vegetation/patch/utils.py b/vegetation/patch/utils.py index 3ba17b8..d2bd092 100644 --- a/vegetation/patch/utils.py +++ b/vegetation/patch/utils.py @@ -3,23 +3,25 @@ import numpy as np import random + def transform_point_wgs84_utm(lon: float, lat: float, utm_zone: int = None) -> tuple: """Transform single point between WGS84 and UTM""" if utm_zone is None: utm_zone = int((lon + 180) / 6) + 1 utm_crs = f"+proj=utm +zone={utm_zone} +datum=WGS84 +units=m +no_defs" - + wgs84_to_utm = Transformer.from_crs("EPSG:4326", utm_crs, always_xy=True) utm_to_wgs84 = Transformer.from_crs(utm_crs, "EPSG:4326", always_xy=True) - + return wgs84_to_utm, utm_to_wgs84 + def generate_point_in_utm(x_utm: float, y_utm: float, max_distance: float) -> tuple: """Generate random point within distance of UTM coordinates""" angle = random.uniform(0, 2 * np.pi) distance = random.uniform(0, max_distance) - + new_x = x_utm + distance * np.cos(angle) new_y = y_utm + distance * np.sin(angle) - - return new_x, new_y \ No newline at end of file + + return new_x, new_y