{"id":50283890,"url":"https://github.com/dabrokarol/atmorad-py","last_synced_at":"2026-05-28T01:11:36.527Z","repository":{"id":357480044,"uuid":"1219466101","full_name":"dabrokarol/atmorad-py","owner":"dabrokarol","description":"A 3D Monte Carlo simulation for radiative transfer.","archived":false,"fork":false,"pushed_at":"2026-05-27T23:07:26.000Z","size":7555,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T23:12:10.580Z","etag":null,"topics":["atmospheric-science","monte-carlo","netcdf-files","numpy","physics-simulation","python","radiative-transfer"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dabrokarol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-23T22:53:33.000Z","updated_at":"2026-05-26T05:22:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dabrokarol/atmorad-py","commit_stats":null,"previous_names":["dabrokarol/atmorad-py"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/dabrokarol/atmorad-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabrokarol%2Fatmorad-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabrokarol%2Fatmorad-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabrokarol%2Fatmorad-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabrokarol%2Fatmorad-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dabrokarol","download_url":"https://codeload.github.com/dabrokarol/atmorad-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabrokarol%2Fatmorad-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33589844,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["atmospheric-science","monte-carlo","netcdf-files","numpy","physics-simulation","python","radiative-transfer"],"created_at":"2026-05-28T01:11:32.142Z","updated_at":"2026-05-28T01:11:36.521Z","avatar_url":"https://github.com/dabrokarol.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AtmoRad\n## A vectorized Monte Carlo simulation of atmospheric radiative transfer.\n\n[![PyPI version](https://img.shields.io/pypi/v/atmorad-py.svg?color=blue)](https://pypi.org/project/atmorad-py/)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Physics: Radiative Transfer](https://img.shields.io/badge/Physics-Radiative_Transfer-ff8c00)](#)\n[![NumPy](https://img.shields.io/badge/NumPy-013243?logo=numpy\u0026logoColor=white)](https://numpy.org/)\n[![xarray](https://img.shields.io/badge/xarray-000000?logo=xarray\u0026logoColor=white)](https://xarray.dev/)\n[![h5netcdf](https://img.shields.io/badge/h5netcdf-4B8BBE)](https://github.com/h5netcdf/h5netcdf)\n[![uv](https://img.shields.io/badge/uv-fast_python_packager-DE5FE9)](https://docs.astral.sh/uv/)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://docs.astral.sh/ruff/)\n[![pytest](https://img.shields.io/badge/pytest-0A9EDC?logo=pytest\u0026logoColor=white)](https://docs.pytest.org/)\n[![CI](https://github.com/dabrokarol/atmorad-py/actions/workflows/ci.yml/badge.svg)](https://github.com/dabrokarol/atmorad-py/actions)\n\n| **2D Surface absorption map** | **Sample photon paths** |\n| :--- | :--- |\n| ![map](https://raw.githubusercontent.com/dabrokarol/atmorad-py/main/docs/img/surface_absorption_map.png) | ![paths](https://raw.githubusercontent.com/dabrokarol/atmorad-py/main/docs/img/3d_photon_paths.png) |\n| **Vertical flux profile** | **Vertical absorption profile** |\n| ![profile](https://raw.githubusercontent.com/dabrokarol/atmorad-py/main/docs/img/vertical_flux_profile.png)| ![hist](https://raw.githubusercontent.com/dabrokarol/atmorad-py/main/docs/img/absorption_profile.png) |\n\n## Overview\nAtmoRad is a Python tool for simulating the radiative transfer of monochromatic light over a mixed 2D surface and a plane-parallel atmosphere. I started it as a hobby project during lectures of Radiative Processes in the Atmosphere at the Faculty of Physics, University of Warsaw to learn computational physics and software development. \n\n## Installation\n\nUsing [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (Recommended for project isolation):\n```bash\n\u003e uv tool install atmorad-py\n```\n\nUsing `pip`:\n```bash\n\u003e pip install atmorad-py\n```\n\n## Quickstart\n\nInitialize a default configuration file in your current directory:\n```bash\n\u003e atmorad --init\n```\n\nRun the simulation:\n```bash\n\u003e atmorad simulation.toml\ndemo001/baseline: 100%|███████████████████████████████| 400000/400000 [00:10\u003c00:00, 37632.74 photons/s]\n\n---- Simulation Summary: demo001/baseline ----\nTime: 10.63s (Total) | 35.49s (CPU)\nSimulated Photons: 400_000\n\nEnergy Distribution:\n  Outgoing (TOA)         :  64.35%\n  Surface Absorption     :  33.57%\n  Atmospheric Absorption :   2.08%\n  ------------------------------\n  Energy Conservation    : 100.00%\n\nOutputs saved to: results/demo001/\n  └─ atmorad_demo001_baseline.nc\n\n```\n*Check the `results/` directory for generated simulation artifacts and plots.*\n\n## Features \u0026 Physical Model\n- **Vectorized Monte Carlo Approach**: To fight Python's weak performance, uses **NumPy** and **multiprocessing** for fast and parallel processing of photons in large batches.\n- **3D Radiative Transfer in Plane-Parallel Approximation**: The atmosphere consists of horizontally uniform layers, but photon paths are tracked in fully 3D space over a 2D surface.\n- **Multi-Material Atmospheric Layers**: Layers can consist of multiple atmospheric materials simultaneously. Each material defines its own extinction coefficient, SSA, and phase function (built-in Rayleigh and Henyey-Greenstein, or custom).\n- **Two Scattering Mechanisms**: Supports photon scattering using analytical inverse phase functions as well as numerical inverse CDFs for custom distributions.\n- **Surface Reflections**: The surface consists of materials with specific albedos, predefined BRDF reflection models (`lambertian`, `specular`), and a `ProceduralMap` mapping material IDs to spatial coordinates.\n- **Photon Properties**: Light is treated as monochromatic, non-polarized, weighted particles that can be scattered, reflected, and partially absorbed.\n- **Checkpointing \u0026 Data Formats**: Supports resuming from checkpoints in case of interruption. Results are stored in the **NetCDF/HDF5** standard. Results are self-contained in a single `.nc` file.\n\n## Roadmap\nI'm planning to include more features in the future, such as:\n- Delta tracking for arbitrary 3D cloud geometries.\n- Wavelength-dependent optical properties of materials.\n- Roughness parameter in specular reflection and other BRDF models.\n- 3D surface topography.\n- Spherical geometry for high zenith angles and whole-Earth simulations.\n\n## Configuration\n\n\u003cdetails\u003e\n\u003csummary\u003eThe simulation is controlled via a TOML configuration file (click to expand).\u003c/summary\u003e\n\u003c!-- [[[cog\nimport cog\ncog.out(f'\\n```toml\\n{open(\"src/atmorad/config/simulation.toml\").read()}\\n```')\n]]] --\u003e\n\n```toml\n[metadata]\nexperiment_name = \"demo001\"\ndescription = \"A demo simulation of 3D radiative transfer over a heterogeneous surface.\"\n\n[engine]\nnum_photons = 400_000\nbatch_size = 100_000  # photons will be processed in arrays of batch_size in parallel       \nrandom_seed = 42\ncpu_cores = 4\nresume_from_checkpoint = false\n\n# Russian Roulette params\nphoton_weight_threshold = 1e-4 \nphoton_survival_chance = 0.1    # 10% chance to survive with 10x multiplied weight\n\n[source]\ntheta_sun_deg = 30           # solar zenith angle (0 = directly downwards)\nphi_sun_deg = 0              # solar azimuth angle\nwavelength_nm = 530          # only for reference, wavelength-dependent parameters are not implemented yet\n\n[geometry]\ndomain_size_x_km = 100\ndomain_size_y_km = 100\nboundary_condition = \"periodic\"\n\n[detectors]\nactive = [ # list of supported detectors\n    \"fate\", \n    \"path_tracking\", \n    \"vertical_flux\", \n    \"absorption_vertical\", \n    \"plane_flux\", \n    \"surface_absorption\"\n]\n# spatial resolution for bins in detectors\nvertical_profiles_resolution_km = 0.2\nhorizontal_maps_resolution_km = 1.0\nnum_full_paths = 200 # 200 photon paths will be saved to results\nflux_maps_z_levels_km = [0.0, 4.0, 10.0] # planes at which vertical flux will be counted\n\n[output]\nsave_plots = true\noverwrite = true\npath = 'results'\n\n# --- material names and properties (can be added or changed) ---\n\n[surface_materials.snow]\nalbedo = 0.85             \nreflection = {type = \"lambertian\"}\n\n[surface_materials.ocean]\nalbedo = 0.01                \nreflection = {type = \"specular\", roughness = 0.0}\n\n[atmosphere_materials.air]\nextinction_coeff_per_km = 0.01 # optical density\nssa = 0.9\nscattering = {type = \"rayleigh\"} \n\n[atmosphere_materials.light_clouds]\nextinction_coeff_per_km = 1  \nssa = 0.999999               # almost no absorption, scattering\nscattering = {type = \"hg\", g = 0.85} # g \u003e 0 means forward scattering\n\n[atmosphere_materials.dark_clouds]\nextinction_coeff_per_km = 5\nssa = 0.999999\nscattering = {type = \"hg\", g = 0.85}\n\n\n# ___ atmospheric layers (bottom to top) ___\n\n[[layer]] ## double square brackets are used for a list item\nthickness_km = 2\ncomponents = [{material = \"air\", concentration = 1.0}]\n\n[[layer]]\nthickness_km = 4\ncomponents = [\n    {material = \"air\", concentration = 1.0},\n    {material = \"dark_clouds\", concentration = 0.9}\n]\n\n[[layer]]\nthickness_km = 4\ncomponents = [{material = \"air\", concentration = 1.0}]\n\n# [[layer]] ... more layers can be added\n\n# ___ surface map configuration ___\n# choose one surface map by commenting out the others\n\n# [surface]\n# name = \"uniform\"\n# material = \"snow\"\n\n[surface]\nname = \"circle\"\nradius_km = 20\nmaterial_in = \"snow\"\nmaterial_out = \"ocean\"\n\n# [surface]\n# name = \"split_half_x\"\n# material_left = \"snow\"\n# material_right = \"ocean\"\n\n# [surface]\n# name = \"checkerboard\"\n# tile_size_km = 10\n# material_a = \"snow\"\n# material_b = \"ocean\"\n\n\n# batch experiments\n# append multiple [[scenario]] blocks (one per simulation) to run a series of experiments\n\n# overrides the solar angle to 30 degrees\n# [[scenario]]\n# name = \"sun_30\"\n# source.theta_sun_deg = 30\n\n# overrides both the solar angle and the photon count\n# [[scenario]]\n# name = \"sun_60\"\n# engine.num_photons = 500_000\n# source.theta_sun_deg = 60\n\n# overrides russian roulette treshold\n# [[scenario]]\n# name = \"no_roulette\"\n# engine.photon_weight_threshold = 0.0\n```\n\u003c!-- [[[end]]] --\u003e\n\n\u003c/details\u003e\n\n### Running Multiple Scenarios:\nYou can run multiple scenarios by appending [[scenario]] blocks to the end of your TOML file. You can override any base variable using dot-notation.\n```toml\n# Overrides the solar angle to 30 degrees\n[[scenario]]\nname = \"sun_30\"\nsource.theta_sun_deg = 30\n\n# Overrides both the solar angle and the photon count\n[[scenario]]\nname = \"sun_60\"\nengine.num_photons = 500_000\nsource.theta_sun_deg = 60\n```\n\n## Custom Physics \u0026 Geometries\n\u003cdetails\u003e\n\u003csummary\u003e\nAtmoRad allows you to easily inject custom surface maps, reflection models, scattering phase functions, and detectors using decorators (click to expand).\u003c/summary\u003e\n\n### Custom Materials and Geometries\n\u003c!-- [[[cog\nimport cog\ncog.out(f'\\n```python\\n{open(\"examples/custom_environment.py\").read()}\\n```')\n]]] --\u003e\n\n```python\nimport numpy as np\n\nimport atmorad\nfrom atmorad import (\n    Scattering,\n    register_reflection,\n    register_scattering,\n    register_surface_map,\n)\nfrom atmorad.constants import X\nfrom atmorad.physics import orientation\n\n\n# 1. Register a custom surface map\n@register_surface_map(\"custom-stripe-y\", [\"material_name_a\", \"material_name_b\"])\ndef stripe_y_map(pos: np.ndarray, stripe_width_km: float) -\u003e np.ndarray:\n    \"\"\"Returns 0 for material A, 1 for material B.\"\"\"\n    grid_x = np.mod(pos[X], stripe_width_km)\n    return np.where(grid_x \u003c (stripe_width_km / 2.0), 0, 1)\n\n\n# 2. Register a custom surface reflection\n@register_reflection(\"custom-reflection\")\ndef custom_reflection(\n    direction: np.ndarray, rand_1: np.ndarray, rand_2: np.ndarray, param_1: float, param_2: float\n) -\u003e np.ndarray:\n    \"\"\"\n    Cosine-weighted hemispherical sampling.\n    Note: param_1 and param_2 are injected directly from TOML.\n    \"\"\"\n    cos_theta = np.sqrt(rand_1)\n    sin_theta = np.sqrt(1.0 - rand_1)\n\n    phi = rand_2 * 2 * np.pi\n\n    return orientation(cos_theta, sin_theta, np.cos(phi), np.sin(phi))\n\n\n# 3.a. Register a custom numerical scattering phase function\n@register_scattering(\"custom-scattering\")\nclass CustomScattering(Scattering):\n    def __init__(self, g: float):\n        cos_grid = np.linspace(-1, 1, 1000)\n\n        # Calculate the probability density function\n        pdf = (1 - g**2) / (2 * (1 + g**2 - 2 * g * cos_grid) ** 1.5)\n\n        # Calling base class automatically normalizes and builds the numerical inverse\n        super().__init__(pdf_array=pdf)\n\n\n# 3.b. Register a custom analytical scattering phase function (usually better performance)\n@register_scattering(\"custom-scattering-b\")\ndef custom_scattering(rand_1, rand_2, g: float):\n    cos_theta = 2.0 * rand_1 - 1.0\n    sin_theta = np.sqrt(1.0 - cos_theta**2)\n\n    phi = 2.0 * np.pi * rand_2\n    return np.array((cos_theta, sin_theta, np.cos(phi), np.sin(phi)))\n\n\nif __name__ == \"__main__\":\n    # 4. Run the experiment\n    results = atmorad.run(\"simulation.toml\")\n\n```\n\u003c!-- [[[end]]] --\u003e\n\nTo use these in `simulation.toml`:\n```toml\n[atmosphere_materials.custom-atm-material]\nssa = 0.9\nscattering = {type = \"custom-scattering\", g = 0.8} \n\n[surface_materials.custom-surf-material-a]\nalbedo = 0.5\nreflection = {type = \"custom-reflection\", param_1 = 2, param_2 = 1.3}\n\n[surface]\nname = \"custom-stripe-y\"\nstripe_width_km = 5.0\nmaterial_name_a = \"custom-surf-material-a\"\nmaterial_name_b = \"ocean\"\n```\n\n### Custom Detectors\n\nYou can define custom detectors that record photon movement, interaction (scattering, reflection) and termination. It is also possible to create a class inheriting from 'BaseResult' which will allow for easy auto-saving to netcdf.   \n\u003c!-- [[[cog\nimport cog\ncog.out(f'\\n```python\\n{open(\"examples/custom_detector.py\").read()}\\n```')\n]]] --\u003e\n\n```python\nfrom dataclasses import dataclass\n\nimport numpy as np\n\nimport atmorad\nfrom atmorad import (\n    BaseDetector,\n    BaseResult,\n    Scene,\n    SimConfig,\n    nc_attr,\n    register_detector,\n)\n\n\n# 1. Define the result structure using AtmoRad's field wrappers\n@dataclass(slots=True)\nclass FateResult(BaseResult):\n    energy_absorbed_surface: float = nc_attr(normalize=True)\n    energy_absorbed_atmosphere: float = nc_attr(normalize=True)\n    energy_outgoing_toa: float = nc_attr(normalize=True)\n\n\n# 2. Implement the detector logic\n@register_detector(\"fate\", FateResult)\nclass FateDetector(BaseDetector):\n    def __init__(self, scene: Scene, config: SimConfig):\n        self.absorbed_surface = 0.0\n        self.absorbed_atmosphere = 0.0\n        self.escaped_toa = 0.0\n        self.scene = scene\n\n    def record_interaction(self, batch, scatter_mask, surface_mask):\n        # Calculate deposited energy by subtracting the photon's new weight from its old weight.\n        if np.any(scatter_mask):\n            deposited = batch.old_weight[scatter_mask] - batch.weight[scatter_mask]\n            self.absorbed_atmosphere += np.sum(deposited)\n\n        if np.any(surface_mask):\n            deposited = batch.old_weight[surface_mask] - batch.weight[surface_mask]\n            self.absorbed_surface += np.sum(deposited)\n\n    def record_termination(self, batch, terminated_mask):\n        if not np.any(terminated_mask):\n            return\n\n        term_pos = batch.pos[:, terminated_mask]\n        term_weight = batch.weight[terminated_mask]\n\n        escaped_toa_mask = self.scene.above_toa(term_pos)\n        if np.any(escaped_toa_mask):\n            self.escaped_toa += np.sum(term_weight[escaped_toa_mask])\n\n    def get_results(self) -\u003e FateResult:\n        return FateResult(\n            energy_absorbed_surface=self.absorbed_surface,\n            energy_absorbed_atmosphere=self.absorbed_atmosphere,\n            energy_outgoing_toa=self.escaped_toa,\n        )\n\n\nif __name__ == \"__main__\":\n    # 3. Run the simulation\n    results = atmorad.run(\"simulation.toml\")\n\n```\n\u003c!-- [[[end]]] --\u003e\n\u003c/details\u003e\n\n## Loading Results\nSimulation results and configurations can be loaded in Python (e.g., Jupyter Notebook) for further analysis in two ways:\n\n### 1. Using the built-in `atmorad.load()`\nThis method loads both the exact configuration used (a `SimConfig` instance) and results of the simulation (a `SimResults` instance).\n\n\u003c!-- [[[cog\nimport cog\ncog.out(f'\\n```python\\n{open(\"examples/load_results.py\").read()}\\n```')\n]]] --\u003e\n\n```python\nimport matplotlib.pyplot as plt\n\nimport atmorad\n\n# Load the completed simulation\nresults = atmorad.load(\"results/demo001/\")\n\n# Access physical data as NumPy arrays\nmap_2d = results.detector_results[\"surface_absorption\"].surface_absorption_map_2d\n\n# Analyze or plot\nplt.imshow(map_2d)\nplt.title(f\"Flux Map for {results.config.metadata.experiment_name}\")\nplt.show()\n\n```\n\u003c!-- [[[end]]] --\u003e\n\n### 2. Using standard NetCDF libraries\nBecause AtmoRad saves data in the standard NetCDF4/HDF5 format, you can read the `data.nc` file directly using libraries like `xarray` or `netCDF4`.\n\n\u003c!-- [[[cog\nimport cog\ncog.out(f'\\n```python\\n{open(\"examples/load_netcdf.py\").read()}\\n```')\n]]] --\u003e\n\n```python\nimport xarray as xr\n\n# Open the NetCDF file directly\nds = xr.open_dataset(\"results/demo001/atmorad_demo001_baseline.nc\", engine=\"h5netcdf\")\n\n# Access variables and attributes ({detector_name}_{attribute_name})\nmap_2d = ds[\"surface_absorption_surface_absorption_map_2d\"].values\ntotal_reflected_energy = ds.attrs[\"fate_energy_outgoing_toa\"]\n\n```\n\u003c!-- [[[end]]] --\u003e\n\n### 3. Extracting configuration file from results\nEach data `.nc`  file contains configuration data used to run the simulation. You can extract it by running:\n```bash\natmorad --extract-config \u003cpath-to-data.nc\u003e\n```\nThis method created an \u003cexp_name\u003e_\u003cscen_name\u003e_config.toml file in your working directory.\n\n## Project Structure\n- `engine/`: Handles photon batching and runs the main simulation loop.\n- `physics/`: Contains rotation functions, scattering phase functions, and reflection models.\n- `environment/`: Manages the environment (`Scene`, `Atmosphere`, `Surface`).\n- `detectors/`: Implements photon tracking and result generation.\n- `models/`: Defines base classes used throughout the program (and extensive 'results.py' for parsing netcdf).\n- `output/`: Handles data IO and figure generation.\n- `config/`: Parses `.toml` configuration files and constructs the simulation context.\n- `cli.py`: Command-line interface entry point.\n\n## References and Literature\n- (in Polish) Script for Lecture about [Radiative Processes in the Atmosphere](https://www.igf.fuw.edu.pl/~kmark/stacja/wyklady/ProcesyRadiacyjne/2013/WykladRadiacjaKlimat.pdf), Prof. K. Markowicz, Faculty of Physics, University of Warsaw, 2013.\n\n## Acknowledgments\n- I created this project inspired by the lectures on *Radiative Processes in the Atmosphere* by Prof. K. Markowicz, Faculty of Physics, University of Warsaw.\n- I used Large Language Models for code debugging (quite a lot) and architectural decisions (e.g., how to structure the repository, which packages to use, how to save and read data).\n\n## Contributing\nFeel free to open an [Issue](https://github.com/dabrokarol/atmorad-py/issues) or submit a Pull Request to report bugs, suggest new features and ask questions :))","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabrokarol%2Fatmorad-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdabrokarol%2Fatmorad-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabrokarol%2Fatmorad-py/lists"}