├── scripts ├── __init__.py ├── commit-changes.py ├── upload.py ├── configure-git.py ├── common.py ├── build-all.py └── remove-incompatible-packages.py ├── recipes ├── mojmelo │ ├── tests │ │ ├── mojmelo │ │ │ ├── __init__.mojo │ │ │ └── utils │ │ │ │ ├── __init__.mojo │ │ │ │ ├── mojmelo_matmul │ │ │ │ ├── __init__.mojo │ │ │ │ ├── params.mojo │ │ │ │ └── matmul.mojo │ │ │ │ └── Matrix.mojo │ │ ├── setup.sh │ │ └── setup.mojo │ ├── image.jpeg │ └── recipe.yaml ├── mimage │ ├── tests │ │ ├── __init__.mojo │ │ ├── images │ │ │ └── hopper.png │ │ ├── test_open_png.mojo │ │ └── testing_utils.mojo │ ├── image.jpeg │ ├── recipe.yaml │ └── README.md ├── ish │ ├── image.jpeg │ └── recipe.yaml ├── mist │ ├── image.jpeg │ └── recipe.yaml ├── MojoINI │ ├── image.jpeg │ └── recipe.yaml ├── bridge │ ├── avatar.jpeg │ ├── image.jpeg │ ├── recipe.yaml │ ├── README.md │ └── test_numpy.🔥 ├── kelvin │ ├── image.jpeg │ └── recipe.yaml ├── mosaic │ ├── image.jpeg │ └── recipe.yaml ├── nabla │ ├── image.jpeg │ ├── recipe.yaml │ └── test.mojo ├── nanoid │ ├── image.jpeg │ └── recipe.yaml ├── numojo │ ├── image.jpeg │ ├── recipe.yaml │ ├── tests.mojo │ └── README.md ├── ExtraMojo │ ├── image.jpeg │ └── recipe.yaml ├── decimojo │ ├── image.jpeg │ ├── recipe.yaml │ ├── README.md │ └── tests.mojo ├── emberjson │ ├── image.jpeg │ └── recipe.yaml ├── infrared │ ├── avatar.jpeg │ ├── image.jpeg │ ├── recipe.yaml │ ├── README.md │ └── test.mojo ├── mojo-libc │ ├── image.jpeg │ ├── recipe.yaml │ └── README.md ├── mojo-regex │ ├── image.jpeg │ ├── recipe.yaml │ └── README.md ├── small_time │ ├── image.jpeg │ └── recipe.yaml ├── vulkan-mojo │ ├── image.jpg │ └── recipe.yaml ├── lightbug_http │ ├── image.jpeg │ └── recipe.yaml ├── mojo-websockets │ ├── image.jpeg │ ├── recipe.yaml │ └── README.md └── mojo_csv │ ├── mojo_csv_logo.png │ ├── recipe.yaml │ └── README.md ├── data ├── failed-compatibility-macos-latest.json ├── failed-compatibility-ubuntu-latest.json └── failed-compatibility-magic_arm64_8core.json ├── mypy.ini ├── variants └── variants.yaml ├── mkdocs.yml ├── ruff.toml ├── .gitattributes ├── .github ├── actionlint.yaml ├── dependabot.yml ├── workflows │ ├── docs.yml │ ├── pre-commit.yml │ ├── remove-incompatible-packages.yml │ ├── build-pr.yml │ ├── update-lockfiles.yml │ └── build-upload.yml └── pull_request_template.md ├── DEVELOPERS.md ├── .gitignore ├── pixi.toml ├── docs └── index.md ├── README.md └── .pre-commit-config.yaml /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/failed-compatibility-macos-latest.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /data/failed-compatibility-ubuntu-latest.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /data/failed-compatibility-magic_arm64_8core.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | strict = True 3 | files = scripts 4 | -------------------------------------------------------------------------------- /variants/variants.yaml: -------------------------------------------------------------------------------- 1 | max: 2 | - "24.5.0" 3 | - "24.6.0" 4 | -------------------------------------------------------------------------------- /recipes/mimage/tests/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .testing_utils import * 2 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/mojmelo_matmul/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: "Modular Community" 2 | theme: 3 | name: material 4 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | [lint] 2 | # See https://docs.astral.sh/ruff/rules/ 3 | fixable = ["I"] 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub syntax highlighting 2 | pixi.lock linguist-language=YAML linguist-generated=true 3 | -------------------------------------------------------------------------------- /recipes/ish/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/ish/image.jpeg -------------------------------------------------------------------------------- /recipes/mist/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mist/image.jpeg -------------------------------------------------------------------------------- /recipes/MojoINI/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/MojoINI/image.jpeg -------------------------------------------------------------------------------- /recipes/bridge/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/bridge/avatar.jpeg -------------------------------------------------------------------------------- /recipes/bridge/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/bridge/image.jpeg -------------------------------------------------------------------------------- /recipes/kelvin/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/kelvin/image.jpeg -------------------------------------------------------------------------------- /recipes/mimage/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mimage/image.jpeg -------------------------------------------------------------------------------- /recipes/mojmelo/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mojmelo/image.jpeg -------------------------------------------------------------------------------- /recipes/mosaic/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mosaic/image.jpeg -------------------------------------------------------------------------------- /recipes/nabla/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/nabla/image.jpeg -------------------------------------------------------------------------------- /recipes/nanoid/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/nanoid/image.jpeg -------------------------------------------------------------------------------- /recipes/numojo/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/numojo/image.jpeg -------------------------------------------------------------------------------- /recipes/ExtraMojo/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/ExtraMojo/image.jpeg -------------------------------------------------------------------------------- /recipes/decimojo/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/decimojo/image.jpeg -------------------------------------------------------------------------------- /recipes/emberjson/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/emberjson/image.jpeg -------------------------------------------------------------------------------- /recipes/infrared/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/infrared/avatar.jpeg -------------------------------------------------------------------------------- /recipes/infrared/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/infrared/image.jpeg -------------------------------------------------------------------------------- /recipes/mojo-libc/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mojo-libc/image.jpeg -------------------------------------------------------------------------------- /recipes/mojo-regex/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mojo-regex/image.jpeg -------------------------------------------------------------------------------- /recipes/small_time/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/small_time/image.jpeg -------------------------------------------------------------------------------- /recipes/vulkan-mojo/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/vulkan-mojo/image.jpg -------------------------------------------------------------------------------- /recipes/lightbug_http/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/lightbug_http/image.jpeg -------------------------------------------------------------------------------- /recipes/mojo-websockets/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mojo-websockets/image.jpeg -------------------------------------------------------------------------------- /recipes/mojo_csv/mojo_csv_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mojo_csv/mojo_csv_logo.png -------------------------------------------------------------------------------- /recipes/mimage/tests/images/hopper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/HEAD/recipes/mimage/tests/images/hopper.png -------------------------------------------------------------------------------- /.github/actionlint.yaml: -------------------------------------------------------------------------------- 1 | # Configuration related to self-hosted runner. 2 | self-hosted-runner: 3 | # Labels of self-hosted runner in array of string. 4 | labels: 5 | - magic_arm64_8core 6 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/mojmelo_matmul/params.mojo: -------------------------------------------------------------------------------- 1 | comptime L1_CACHE_SIZE = 32768 2 | comptime L1_ASSOCIATIVITY = 8 3 | comptime L2_CACHE_SIZE = 262144 4 | comptime L2_ASSOCIATIVITY = 4 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | groups: 8 | gh-actions: 9 | patterns: 10 | - "*" 11 | -------------------------------------------------------------------------------- /DEVELOPERS.md: -------------------------------------------------------------------------------- 1 | # Run tasks locally 2 | 3 | In order to run the pixi tasks locally, it is convenient to set the environment variables via [direnv](https://direnv.net/). 4 | 5 | 1. Create `.envrc` and set environment variables in it 6 | 2. Run `direnv allow` 7 | 3. Run pixi task 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # pixi environments 3 | .pixi 4 | *.egg-info 5 | .magic 6 | 7 | # rattler-build 8 | output/ 9 | log.txt 10 | 11 | # data 12 | data.json 13 | 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # direnv 20 | .envrc 21 | 22 | # mkdocs 23 | site 24 | -------------------------------------------------------------------------------- /recipes/mimage/tests/test_open_png.mojo: -------------------------------------------------------------------------------- 1 | import mimage as mi 2 | from pathlib import Path 3 | from tests import compare_to_numpy 4 | 5 | 6 | def main(): 7 | hop_tensor = mi.imread("tests/images/hopper.png") 8 | hop_tensor = mi.imread(String("tests/images/hopper.png")) 9 | hop_tensor = mi.imread(Path("tests/images/hopper.png")) 10 | 11 | compare_to_numpy(hop_tensor, "tests/images/hopper.png") 12 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | path="./.pixi/envs/default/etc/conda/test-files/mojmelo/0/tests" 4 | curr=$(pwd) 5 | cd $path 6 | 7 | mojo ./setup.mojo 8 | mojo ./setup.mojo 1 9 | mojo ./setup.mojo 2 10 | mojo ./setup.mojo 3 11 | mojo ./setup.mojo 4 12 | mojo ./setup.mojo 5 13 | mojo ./setup.mojo 6 14 | mojo ./setup.mojo 7 15 | mojo ./setup.mojo 8 16 | mojo ./setup.mojo 9 17 | 18 | cd $curr 19 | 20 | mojo package $path/mojmelo/utils/mojmelo_matmul -o ./.pixi/envs/default/lib/mojo/mojmelo_matmul.mojopkg 21 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 14 | - uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1 15 | with: 16 | pixi-version: v0.37.0 17 | 18 | - name: Configure Git Credentials 19 | run: pixi run configure-git 20 | 21 | - run: pixi run deploy-docs 22 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-commit 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | pre-commit: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 17 | with: 18 | fetch-depth: 2 19 | - uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1 20 | with: 21 | pixi-version: v0.37.0 22 | 23 | - name: Run pre-commit 24 | run: pixi run lint 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Checklist** 2 | - [ ] My `recipe.yaml` file specifies which version(s) of MAX is compatible with my project (see [here](https://github.com/modular/modular-community/blob/main/recipes/endia/recipe.yaml) for an example). If not, my package is compatible with both 24.5 and 24.6. 3 | - [ ] License file is packaged (see [here](https://github.com/modular/modular-community/blob/dbe0200598733fea411ee2246507705e8ea07a32/recipes/hue/recipe.yaml#L33-L40) for an example). 4 | - [ ] Set the build number to `0` (for new packages, or if the version changed). 5 | - [ ] Bumped the build number (if the version is unchanged). 6 | -------------------------------------------------------------------------------- /scripts/commit-changes.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pathlib import Path 3 | import argparse 4 | 5 | from scripts.common import commit_push_changes, eprint, run_command 6 | 7 | 8 | def main() -> None: 9 | parser = argparse.ArgumentParser(description="Commit changes to a specified file.") 10 | parser.add_argument("file", type=Path, help="The file to commit.") 11 | args = parser.parse_args() 12 | 13 | if not args.file.exists(): 14 | eprint(f"{args.file} does not exist.") 15 | sys.exit(1) 16 | 17 | # Commit and push changes 18 | run_command(["git", "add", str(args.file)]) 19 | commit_push_changes(f"Update {args.file.name}", "main") 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /recipes/mimage/tests/testing_utils.mojo: -------------------------------------------------------------------------------- 1 | from tensor import Tensor 2 | from python import Python 3 | from testing import assert_true 4 | 5 | 6 | fn compare_to_numpy(mojo_tensor: Tensor, image_path: StringLiteral) raises: 7 | var PillowImage = Python.import_module("PIL.Image") 8 | var np = Python.import_module("numpy") 9 | var py_array = np.array(PillowImage.open(image_path)) 10 | 11 | for rows in range(mojo_tensor.shape()[0]): 12 | for columns in range(mojo_tensor.shape()[1]): 13 | for channels in range(mojo_tensor.shape()[2]): 14 | assert_true( 15 | mojo_tensor[rows, columns, channels] == py_array[rows][columns][channels].__int__(), 16 | "Pixel values do not match", 17 | ) 18 | -------------------------------------------------------------------------------- /recipes/mojo-libc/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.1.9" 3 | 4 | package: 5 | name: "libc" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/msaelices/mojo-libc.git 10 | rev: 674d0fd35af4e716eb30df18cb1fe25cdddb08cf 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src/libc -o ${PREFIX}/lib/mojo/libc.mojopkg 16 | 17 | requirements: 18 | host: 19 | - max >= 25.2 20 | run: 21 | - ${{ pin_compatible('max') }} 22 | 23 | about: 24 | homepage: https://github.com/msaelices/mojo-libc 25 | license: MIT 26 | license_file: LICENSE 27 | summary: Mojo's libc support 28 | repository: https://github.com/msaelices/mojo-libc 29 | 30 | extra: 31 | maintainers: 32 | - msaelices 33 | - crisadamo 34 | project_name: mojo-libc 35 | -------------------------------------------------------------------------------- /recipes/vulkan-mojo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.1.0" 3 | 4 | package: 5 | name: "vulkan-mojo" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/Ryul0rd/vulkan-mojo.git 10 | rev: 06906c5c563c2f74b3bd5ea53532afb2ae2bc3b3 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package vk -o ${{ PREFIX }}/lib/mojo/vulkan.mojopkg 16 | requirements: 17 | host: 18 | - max=25.6 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | about: 23 | homepage: https://github.com/Ryul0rd/vulkan-mojo 24 | license: MIT 25 | license_file: LICENSE 26 | summary: Generated Mojo bindings for the Vulkan graphics API 27 | repository: https://github.com/Ryul0rd/vulkan-mojo 28 | 29 | extra: 30 | project_name: vulkan-mojo 31 | maintainers: 32 | - Ryul0rd 33 | -------------------------------------------------------------------------------- /recipes/nanoid/recipe.yaml: -------------------------------------------------------------------------------- 1 | 2 | context: 3 | version: "0.2.0" 4 | 5 | package: 6 | name: "nanoid" 7 | version: ${{ version }} 8 | 9 | source: 10 | - git: https://github.com/cirello-py/nanoid.git 11 | rev: 637a02ef7fcb627b43c0de2f2fe0fca8d0e5199b 12 | 13 | build: 14 | number: 0 15 | script: 16 | - mojo package nanoid -o ${{ PREFIX }}/lib/mojo/nanoid.mojopkg 17 | 18 | requirements: 19 | host: 20 | - max=25.3 21 | run: 22 | - ${{ pin_compatible('max') }} 23 | 24 | tests: 25 | - script: 26 | - if: unix 27 | then: 28 | - mojo test 29 | 30 | about: 31 | homepage: https://github.com/cirello-py/nanoid 32 | license: Apache-2.0 33 | license_file: LICENSE 34 | summary: A Mojo port of NanoID, a tiny, secure, URL-friendly, unique string ID generator. 35 | repository: https://github.com/cirello-py/nanoid 36 | 37 | extra: 38 | maintainers: 39 | - ucirello 40 | -------------------------------------------------------------------------------- /.github/workflows/remove-incompatible-packages.yml: -------------------------------------------------------------------------------- 1 | name: Remove Incompatible Packages 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 17 * * *" # 9 AM PST 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | check-incompatible-packages: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 18 | with: 19 | fetch-depth: 2 20 | - uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1 21 | with: 22 | pixi-version: v0.37.0 23 | 24 | - name: Configure git 25 | run: pixi run configure-git 26 | 27 | - name: Run compatibility check script 28 | run: pixi run remove-incompatible-packages 29 | env: 30 | GITHUB_REPOSITORY: "${{ github.repository }}" 31 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 32 | -------------------------------------------------------------------------------- /recipes/nabla/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "25.3.0" 3 | 4 | package: 5 | name: "nabla" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/nabla-ml/nabla.git 10 | rev: f677a02a82caada7d233e11bf4e189fdb702ef7f 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package nabla -o ${{ PREFIX }}/lib/mojo/nabla.mojopkg 16 | requirements: 17 | host: 18 | - max =25.3 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo test.mojo 27 | requirements: 28 | run: 29 | - max =25.3 30 | files: 31 | recipe: 32 | - test.mojo 33 | 34 | about: 35 | homepage: https://github.com/nabla-ml/nabla 36 | license: Apache-2.0 37 | license_file: LICENSE 38 | summary: Differtiable Programming in Mojo. 39 | repository: https://github.com/nabla-ml/nabla 40 | 41 | extra: 42 | maintainers: 43 | - TilliFe -------------------------------------------------------------------------------- /recipes/mojo_csv/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: 1.5.0 3 | 4 | package: 5 | name: "mojo_csv" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/Phelsong/mojo_csv.git 10 | rev: b3a9dc4422efbea7a94939e3a48ff4a3b03e3505 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src -o ${{ PREFIX }}/lib/mojo/mojo_csv.mojopkg 16 | 17 | requirements: 18 | host: 19 | - max >=25.4.0,<26 20 | run: 21 | - ${{ pin_compatible('max') }} 22 | 23 | tests: 24 | - script: 25 | - if: unix 26 | then: 27 | - mojo test tests 28 | files: 29 | recipe: 30 | - mojo_csv 31 | 32 | about: 33 | homepage: https://github.com/Phelsong/mojo_csv 34 | license: MIT 35 | license_file: LICENSE 36 | summary: Csv parsing library written in pure Mojo 37 | repository: https://github.com/Phelsong/mojo_csv 38 | 39 | extra: 40 | maintainers: 41 | - phelsong 42 | project_name: 43 | - mojo_csv 44 | -------------------------------------------------------------------------------- /.github/workflows/build-pr.yml: -------------------------------------------------------------------------------- 1 | name: Build pull request 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | types: 7 | - labeled 8 | - synchronize 9 | 10 | jobs: 11 | build: 12 | if: ${{ contains(github.event.pull_request.labels.*.name, 'OK to test') }} 13 | strategy: 14 | matrix: 15 | label: 16 | - ubuntu-latest 17 | - macos-latest 18 | - magic_arm64_8core 19 | runs-on: ${{ matrix.label }} 20 | steps: 21 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 22 | with: 23 | fetch-depth: 2 24 | - uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1 25 | with: 26 | pixi-version: v0.37.0 27 | 28 | - name: Build packages 29 | shell: bash 30 | run: pixi run build-all 31 | env: 32 | RATTLER_BUILD_ENABLE_GITHUB_INTEGRATION: true 33 | RATTLER_BUILD_COLOR: always 34 | -------------------------------------------------------------------------------- /recipes/infrared/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.1.0" 3 | 4 | package: 5 | name: "infrared" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/helehex/infrared.git 10 | rev: 1776092af17ce25af4d3c1273bee9974a4bc66c4 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src -o ${{ PREFIX }}/lib/mojo/infrared.mojopkg 16 | requirements: 17 | host: 18 | - max=24.5 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo run test.mojo 27 | requirements: 28 | run: 29 | - max=24.5 30 | files: 31 | recipe: 32 | - test.mojo 33 | 34 | about: 35 | homepage: https://github.com/helehex/infrared 36 | license: MIT 37 | license_file: LICENSE 38 | summary: A geometric algebra library for mojo. 39 | repository: https://github.com/helehex/infrared 40 | 41 | extra: 42 | maintainers: 43 | - helehex 44 | project_name: Infrared 45 | -------------------------------------------------------------------------------- /scripts/upload.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pathlib import Path 3 | import argparse 4 | 5 | from scripts.common import eprint, run_command_unchecked 6 | 7 | 8 | def main() -> None: 9 | parser = argparse.ArgumentParser( 10 | description="Upload .conda files to a specified channel." 11 | ) 12 | parser.add_argument( 13 | "--channel", required=True, help="The channel to upload the .conda files to." 14 | ) 15 | args = parser.parse_args() 16 | 17 | exit_code = 0 18 | 19 | for conda_file in Path("output").glob("**/*.conda"): 20 | command = [ 21 | "rattler-build", 22 | "upload", 23 | "prefix", 24 | "--channel", 25 | args.channel, 26 | str(conda_file), 27 | ] 28 | result = run_command_unchecked(command) 29 | if result.returncode != 0: 30 | eprint(f"Error uploading {conda_file}: {result.stderr}") 31 | exit_code = 1 32 | 33 | sys.exit(exit_code) 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /recipes/decimojo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.4.1" 3 | 4 | package: 5 | name: "decimojo" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/forFudan/decimojo.git 10 | rev: 51c983229749df1caf490ccb9ddea19e7f220441 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src/decimojo -o ${{ PREFIX }}/lib/mojo/decimojo.mojopkg 16 | requirements: 17 | host: 18 | - max=25.4 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo run tests.mojo 27 | requirements: 28 | run: 29 | - max=25.4 30 | files: 31 | recipe: 32 | - tests.mojo 33 | 34 | about: 35 | homepage: https://github.com/forFudan/decimojo.git 36 | license: Apache-2.0 37 | license_file: LICENSE 38 | summary: An arbitrary-precision decimal and integer mathematics library for Mojo 39 | repository: https://github.com/forFudan/decimojo.git 40 | 41 | extra: 42 | project_name: decimojo 43 | maintainers: 44 | - forfudan 45 | -------------------------------------------------------------------------------- /recipes/mojo-websockets/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.2.0" 3 | 4 | package: 5 | name: "websockets" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/msaelices/mojo-websockets.git 10 | rev: 751c48ed576fe5a6113afd8ae83f6e48dedf832b 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src/websockets -o ${PREFIX}/lib/mojo/websockets.mojopkg 16 | 17 | requirements: 18 | host: 19 | - max == 25.2 20 | run: 21 | - ${{ pin_compatible('max') }} 22 | 23 | tests: 24 | - script: 25 | - if: unix 26 | then: 27 | - mojo test tests/test_bytes.mojo 28 | requirements: 29 | run: 30 | - max=25.2 31 | files: 32 | recipe: 33 | - tests/test_bytes.mojo 34 | 35 | about: 36 | homepage: https://github.com/msaelices/mojo-websockets 37 | license: MIT 38 | license_file: LICENSE 39 | summary: Mojo's websockets library 40 | repository: https://github.com/msaelices/mojo-websockets 41 | 42 | extra: 43 | maintainers: 44 | - msaelices 45 | project_name: mojo-websockets 46 | -------------------------------------------------------------------------------- /scripts/configure-git.py: -------------------------------------------------------------------------------- 1 | from scripts.common import run_command 2 | 3 | 4 | def main() -> None: 5 | """ 6 | Configures the global Git user name and email for the current environment. 7 | 8 | This function sets the global Git configuration for the user name and email 9 | to be used by Git commands. It is typically used in automated environments 10 | such as CI/CD pipelines to ensure that Git operations are performed with a 11 | consistent identity. 12 | 13 | The user name is set to "github-actions[bot]" and the email is set to 14 | "github-actions[bot]@users.noreply.github.com". 15 | 16 | Raises: 17 | subprocess.CalledProcessError: If any of the subprocess calls fail. 18 | """ 19 | run_command(["git", "config", "--global", "user.name", "github-actions[bot]"]) 20 | run_command( 21 | [ 22 | "git", 23 | "config", 24 | "--global", 25 | "user.email", 26 | "github-actions[bot]@users.noreply.github.com", 27 | ] 28 | ) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /recipes/mojo-libc/README.md: -------------------------------------------------------------------------------- 1 | # Mojo's libc 2 | 3 | `mojo-libc` is a library that provides access to the C standard library functions in Mojo. 4 | 5 | ## Getting Started 6 | 7 | The only dependency for `libc` is Mojo. 8 | 9 | You can install Mojo following the instructions from the [Modular website](https://www.modular.com/max/mojo). 10 | 11 | Once you have created a Mojo project using the `magic` tool, 12 | 13 | 1. Add `libc` as a dependency: 14 | ```toml 15 | [dependencies] 16 | libc = ">=0.1.9" 17 | ``` 18 | 2. Run `magic install` at the root of your project, where `mojoproject.toml` is located 19 | 20 | 3. `libc` should now be installed as a dependency. You can import libc functions from the library, e.g: 21 | ```mojo 22 | from libc import socket 23 | ``` 24 | 25 | ## Supported Functionality 26 | 27 | ### Basic socket connections 28 | 29 | See the examples in [examples/sockets/](https://github.com/msaelices/mojo-libc/tree/main/examples/sockets) directory. 30 | 31 | ### Basic file system operations 32 | 33 | See the examples in [examples/files/](https://github.com/msaelices/mojo-libc/tree/main/examples/files) directory. 34 | -------------------------------------------------------------------------------- /.github/workflows/update-lockfiles.yml: -------------------------------------------------------------------------------- 1 | name: Update lockfiles 2 | 3 | permissions: 4 | contents: write 5 | pull-requests: write 6 | 7 | on: 8 | workflow_dispatch: 9 | schedule: 10 | - cron: 0 5 1 * * 11 | 12 | jobs: 13 | pixi-update: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 17 | - name: Set up pixi 18 | uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1 19 | with: 20 | run-install: false 21 | - name: Update lockfiles 22 | run: | 23 | set -euo pipefail 24 | pixi update --json --no-install | pixi exec pixi-diff-to-markdown >> diff.md 25 | - name: Create pull request 26 | uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | commit-message: Update pixi lockfile 30 | title: Update pixi lockfile 31 | body-path: diff.md 32 | branch: update-pixi 33 | base: main 34 | labels: pixi 35 | delete-branch: true 36 | add-paths: pixi.lock 37 | -------------------------------------------------------------------------------- /recipes/mimage/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.2.3" 3 | 4 | package: 5 | name: "mimage" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/fnands/mimage.git 10 | tag: "0.2.3" 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package mimage -o ${{ PREFIX }}/lib/mojo/mimage.mojopkg 16 | requirements: 17 | host: 18 | - max=25.2 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo run -I ${{ PREFIX }}/lib/mojo/mimage.mojopkg tests/test_open_png.mojo 27 | 28 | requirements: 29 | run: 30 | - max=25.2 31 | - pillow 32 | - numpy 33 | 34 | files: 35 | recipe: 36 | - tests/test_open_png.mojo 37 | - tests/testing_utils.mojo 38 | - tests/__init__.mojo 39 | - tests/images/hopper.png 40 | 41 | about: 42 | homepage: https://github.com/fnands/mimage 43 | license: Apache-2.0 44 | license_file: LICENSE 45 | summary: mimage is a library for image processing in Mojo 🔥. 46 | repository: https://github.com/fnands/mimage 47 | 48 | extra: 49 | project_name: mimage 50 | maintainers: 51 | - fnands 52 | -------------------------------------------------------------------------------- /recipes/numojo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.7.0" 3 | 4 | package: 5 | name: "numojo" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo.git 10 | rev: 2b72ef420f68329a5d99120e0cdb642f027ae837 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package numojo -o ${{ PREFIX }}/lib/mojo/numojo.mojopkg 16 | requirements: 17 | host: 18 | - max=25.3 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo run tests.mojo 27 | requirements: 28 | run: 29 | - max=25.3 30 | - numpy 31 | files: 32 | recipe: 33 | - tests.mojo 34 | 35 | about: 36 | homepage: https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo 37 | license: Apache-2.0 38 | license_file: LICENSE 39 | summary: NuMojo is a library for numerical computing in Mojo 🔥 similar to NumPy, SciPy in Python. 40 | repository: https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo 41 | 42 | extra: 43 | project_name: NuMojo 44 | maintainers: 45 | - shivasankarka 46 | - MadAlex1997 47 | - forFudan 48 | - sandstromviktor 49 | -------------------------------------------------------------------------------- /recipes/emberjson/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: 0.2.1 3 | 4 | package: 5 | name: "emberjson" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/bgreni/EmberJson.git 10 | rev: 6c6ed09fba6c426546e099af886deb53824bb4ba 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package emberjson -o ${{ PREFIX }}/lib/mojo/emberjson.mojopkg 16 | 17 | requirements: 18 | host: 19 | - mojo-compiler =0.25.7 20 | build: 21 | - mojo-compiler =0.25.7 22 | run: 23 | - ${{ pin_compatible('mojo-compiler') }} 24 | 25 | tests: 26 | - script: 27 | - if: unix 28 | then: 29 | - python run_tests.py 30 | files: 31 | source: 32 | - run_tests.py 33 | - test/ 34 | - emberjson/ 35 | - bench_data/ 36 | requirements: 37 | build: 38 | - mojo =0.25.7 39 | run: 40 | - mojo =0.25.7 41 | 42 | about: 43 | homepage: https://github.com/bgreni/EmberJson 44 | license: Apache-2.0 45 | license_file: LICENSE 46 | summary: A lightweight JSON parsing library written in pure Mojo 47 | repository: https://github.com/bgreni/EmberJson 48 | 49 | extra: 50 | maintainers: 51 | - bgreni 52 | project_name: 53 | - EmberJson -------------------------------------------------------------------------------- /recipes/mojo-regex/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: 0.5.0 3 | mojo_version: "=0.25.6" 4 | about: 5 | description: "# Mojo Regex\nRegular Expressions Library for Mojo\n\n`mojo-regex` is a\ 6 | \ regex library featuring a hybrid DFA/NFA engine architecture that automatically\ 7 | \ optimizes pattern matching based on complexity.\n\nIt aims to provide a similar\ 8 | \ interface as the [re](https://docs.python.org/3/library/re.html) stdlib package\ 9 | \ while leveraging Mojo's performance capabilities." 10 | homepage: https://github.com/msaelices/mojo-regex 11 | license: MIT 12 | license_file: LICENSE 13 | repository: https://github.com/msaelices/mojo-regex 14 | summary: Library for dealing with regular expressions in Mojo 15 | build: 16 | number: 0 17 | script: 18 | - mkdir -p ${PREFIX}/lib/mojo 19 | - mojo package src/regex -o ${PREFIX}/lib/mojo/regex.mojopkg 20 | package: 21 | name: mojo-regex 22 | version: ${{ version }} 23 | requirements: 24 | host: 25 | - mojo-compiler ${{ mojo_version }} 26 | build: 27 | - mojo-compiler ${{ mojo_version }} 28 | run: 29 | - ${{ pin_compatible('mojo-compiler') }} 30 | source: 31 | - git: https://github.com/msaelices/mojo-regex.git 32 | rev: 7913f1c7f0f95e1befa5730e934ca39f14ed44ca 33 | -------------------------------------------------------------------------------- /recipes/mojmelo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.1.0" 3 | 4 | package: 5 | name: "mojmelo" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/yetalit/mojmelo.git 10 | rev: 01692d2078e55f4dbeac981240edc620d0dc96af 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package pixi/mojmelo/utils/mojmelo_matmul -o ${{ PREFIX }}/lib/mojo/mojmelo_matmul.mojopkg 16 | - mojo package pixi/mojmelo -o ${{ PREFIX }}/lib/mojo/mojmelo.mojopkg 17 | requirements: 18 | host: 19 | - mojo-compiler =0.25.7 20 | run: 21 | - ${{ pin_compatible('mojo-compiler') }} 22 | 23 | tests: 24 | - script: 25 | - if: unix 26 | then: 27 | - mojo tests/setup.mojo 28 | requirements: 29 | run: 30 | - mojo-compiler =0.25.7 31 | files: 32 | recipe: 33 | - tests/setup.mojo 34 | - tests/setup.sh 35 | - tests/mojmelo/__init__.mojo 36 | - tests/mojmelo/utils/*.mojo 37 | - tests/mojmelo/utils/mojmelo_matmul/*.mojo 38 | 39 | about: 40 | homepage: https://github.com/yetalit/Mojmelo 41 | license: BSD-3-Clause 42 | license_file: LICENSE 43 | summary: Machine Learning algorithms in pure Mojo. 44 | repository: https://github.com/yetalit/Mojmelo 45 | 46 | extra: 47 | project_name: Mojmelo 48 | maintainers: 49 | - yetalit 50 | -------------------------------------------------------------------------------- /pixi.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "modular-community" 3 | channels = ["conda-forge"] 4 | platforms = ["linux-64", "linux-aarch64", "osx-arm64"] 5 | 6 | [feature.build.tasks] 7 | build-all = "python -m scripts.build-all" 8 | upload = "python -m scripts.upload" 9 | commit-changes = "python -m scripts.commit-changes" 10 | remove-incompatible-packages = "python -m scripts.remove-incompatible-packages" 11 | configure-git = "python -m scripts.configure-git" 12 | 13 | [feature.build.dependencies] 14 | rattler-build = ">=0.30.0,<0.31" 15 | python = ">=3.13.0,<3.14" 16 | pygithub = ">=2.5.0,<3" 17 | pyyaml = ">=6.0.2,<7" 18 | types-pyyaml = ">=6.0.12.20240917,<7" 19 | conda = ">=25.5.1,<26" 20 | 21 | [feature.lint.dependencies] 22 | actionlint = ">=1.7.4,<2" 23 | ruff = ">=0.7.3,<0.8" 24 | check-jsonschema = ">=0.29.4,<0.30" 25 | shellcheck = ">=0.10.0,<0.11" 26 | mypy = ">=1.13.0,<2" 27 | 28 | [feature.pre-commit.dependencies] 29 | pre-commit = ">=4.0.1,<5" 30 | 31 | [feature.pre-commit.tasks] 32 | pre-commit-install = "pre-commit install" 33 | lint = "pre-commit run -a" 34 | 35 | [feature.docs.dependencies] 36 | mkdocs = ">=1.6.1,<2" 37 | mkdocs-material = ">=9.5.20,<10" 38 | 39 | [feature.docs.tasks] 40 | deploy-docs = "mkdocs gh-deploy --force" 41 | docs = "mkdocs serve" 42 | 43 | [environments] 44 | default = ["build", "lint", "pre-commit"] 45 | docs = ["docs"] 46 | -------------------------------------------------------------------------------- /recipes/kelvin/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: 0.1.4 3 | 4 | package: 5 | name: "kelvin" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/bgreni/Kelvin.git 10 | rev: 3018ba56fa762a3090bbf4ca26438b6c506278ea 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package kelvin -o ${{ PREFIX }}/lib/mojo/kelvin.mojopkg 16 | 17 | requirements: 18 | host: 19 | - mojo-compiler =0.25.7 20 | build: 21 | - mojo-compiler =0.25.7 22 | run: 23 | - ${{ pin_compatible('mojo-compiler') }} 24 | 25 | tests: 26 | - script: 27 | - if: unix 28 | then: 29 | - python run_test.py 30 | - python scripts/generate_tests.py && python scripts/run_reject_tests.py 31 | 32 | files: 33 | source: 34 | - kelvin/ 35 | - test/ 36 | - run_test.py 37 | - scripts/run_reject_tests.py 38 | - scripts/generate_tests.py 39 | requirements: 40 | build: 41 | - mojo =0.25.7 42 | run: 43 | - mojo =0.25.7 44 | 45 | about: 46 | homepage: https://github.com/bgreni/Kelvin 47 | license: Apache-2.0 48 | license_file: LICENSE 49 | summary: A powerful dimensional analysis library written in Mojo 50 | repository: https://github.com/bgreni/Kelvin 51 | 52 | extra: 53 | maintainers: 54 | - bgreni 55 | project_name: 56 | - Kelvin -------------------------------------------------------------------------------- /recipes/mist/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "25.5.0" 3 | 4 | package: 5 | name: "mist" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/thatstoasty/mist.git 10 | rev: 1c89c766f2827607c2fe64cfdc13b8680d55d6c0 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package mist -o ${{ PREFIX }}/lib/mojo/mist.mojopkg 16 | requirements: 17 | host: 18 | - max =25.5 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo test test 27 | 28 | about: 29 | homepage: https://github.com/thatstoasty/mist 30 | # Remember to specify the license variants for BSD, Apache, GPL, and LGPL. 31 | # Use the SPDX identifier, e.g: GPL-2.0-only instead of GNU General Public License version 2.0 32 | # See https://spdx.org/licenses/ 33 | license: MIT 34 | # It is strongly encouraged to include a license file in the package, 35 | # (even if the license doesn't require it) using the license_file entry. 36 | # See https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html#license-file 37 | license_file: LICENSE 38 | summary: Mist lets you safely use advanced styling options on the terminal. 39 | repository: https://github.com/thatstoasty/mist 40 | 41 | extra: 42 | maintainers: 43 | - thatstoasty 44 | -------------------------------------------------------------------------------- /.github/workflows/build-upload.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload packages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | label: 18 | - ubuntu-latest 19 | - macos-latest 20 | - magic_arm64_8core 21 | runs-on: ${{ matrix.label }} 22 | env: 23 | DATA_FILE: data/failed-compatibility-${{ matrix.label }}.json 24 | steps: 25 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 26 | with: 27 | fetch-depth: 2 28 | - uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1 29 | with: 30 | pixi-version: v0.37.0 31 | 32 | - name: Configure git 33 | run: pixi run configure-git 34 | 35 | - name: Build packages 36 | shell: bash 37 | run: pixi run build-all 38 | env: 39 | RATTLER_BUILD_ENABLE_GITHUB_INTEGRATION: true 40 | RATTLER_BUILD_COLOR: always 41 | 42 | - name: Commit changes to failed-compatibility.json 43 | run: pixi run commit-changes ${{ env.DATA_FILE }} 44 | if: always() 45 | 46 | - name: Upload all packages 47 | shell: bash 48 | run: pixi run upload --channel modular-community 49 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | part: modular-community 3 | title: Getting Started 4 | description: The modular community conda channel 5 | --- 6 | 7 | # Modular Community 8 | 9 | Welcome to the modular community conda channel. 10 | This is a place to contribute or consume packages within the Mojo or MAX ecosystem. 11 | 12 | ## Contributing 13 | 14 | All packages are built from [rattler-build] recipes 15 | and are managed on the [modular-community] GitHub repository. 16 | Contributions are done via opening pull requests against the aforementioned repository. 17 | 18 | ### Adding a New Package 19 | 20 | In order to create a new package, follow the following steps: 21 | 22 | - Fork [modular-community] 23 | - Add a folder for your new packages under `recipe` 24 | - Create a new `recipe.yaml` in that folder, feel free to take inspiration from the other recipes 25 | - Make sure that the build number is set to `0` and that the license file is packaged 26 | - Open a pull request 27 | 28 | ### Update an Existing Package 29 | 30 | - Fork [modular-community] 31 | - Adapt the `recipe.yaml` of the package you want to update 32 | - If you updated the version, reset the build number to 0 33 | - If the version didn't change, increase the build number by 1 34 | - Open a pull request 35 | 36 | 37 | [modular-community]: https://github.com/modular/modular-community 38 | [rattler-build]: https://prefix-dev.github.io/rattler-build/latest/ 39 | -------------------------------------------------------------------------------- /recipes/mosaic/recipe.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # recipe.yaml 3 | # mosaic 4 | # 5 | # Created by Christian Bator on 03/11/2025 6 | # 7 | # Usage: rattler-build build --recipe recipe.yaml -c https://conda.modular.com/max -c conda-forge 8 | # 9 | 10 | context: 11 | version: "0.0.1" 12 | 13 | package: 14 | name: mosaic 15 | version: ${{ version }} 16 | 17 | source: 18 | - git: https://github.com/christianbator/mosaic.git 19 | rev: 6c22e6dad13636e390eeec6e40be99e0687e9536 20 | 21 | build: 22 | number: 4 23 | script: build/build.sh 24 | dynamic_linking: 25 | missing_dso_allowlist: 26 | - if: osx 27 | then: 28 | - /usr/lib/swift/libswift*.dylib 29 | - /usr/lib/libc++*.dylib 30 | 31 | requirements: 32 | build: 33 | - clang==20.1.4 34 | - lld==20.1.4 35 | host: 36 | - max==25.4.0 37 | run: 38 | - ${{ pin_compatible('max') }} 39 | 40 | tests: 41 | - script: | 42 | cd test 43 | mojo test 44 | files: 45 | source: 46 | - test/**.mojo 47 | - test/data/ 48 | 49 | about: 50 | homepage: https://mosaiclib.org 51 | license: Apache-2.0 52 | license_file: LICENSE 53 | summary: An open source computer vision library in Mojo 54 | repository: https://github.com/christianbator/mosaic 55 | documentation: https://mosaiclib.org/docs 56 | 57 | extra: 58 | project_name: Mosaic 59 | maintainers: 60 | - christianbator 61 | -------------------------------------------------------------------------------- /recipes/ExtraMojo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.19.0" 3 | mojo_version: "=0.25.7" 4 | 5 | package: 6 | name: "extramojo" 7 | version: ${{ version }} 8 | 9 | source: 10 | - git: https://github.com/ExtraMojo/ExtraMojo.git 11 | rev: 6afbfc66b7d9840c82a20ea1090443934e31b673 12 | # path: . 13 | # use_gitignore: true 14 | 15 | build: 16 | number: 0 17 | script: 18 | - mojo package extramojo -o ${{ PREFIX }}/lib/mojo/extramojo.mojopkg 19 | requirements: 20 | build: 21 | - mojo-compiler ${{ mojo_version }} 22 | host: 23 | - mojo-compiler ${{ mojo_version }} 24 | run: 25 | - mojo-compiler ${{ mojo_version }} 26 | 27 | tests: 28 | - script: 29 | - if: unix 30 | then: 31 | - sh -c 'find ./tests -name test_*.mojo | xargs -I % pixi run mojo run -I . -D ASSERT=all %' 32 | requirements: 33 | build: 34 | - mojo ${{ mojo_version }} 35 | run: 36 | - mojo ${{ mojo_version }} 37 | files: 38 | source: 39 | - extramojo/ 40 | - tests/ 41 | 42 | about: 43 | homepage: https://github.com/ExtraMojo/ExtraMojo 44 | license: "Unlicense OR MIT" 45 | license_file: 46 | - LICENSE-MIT 47 | - UNLICENSE 48 | summary: Extra functionality not yet in the standard library, mostly focused on strings and file IO. 49 | repository: https://github.com/ExtraMojo/ExtraMojo 50 | 51 | extra: 52 | maintainers: 53 | - sstadick 54 | project_name: extramojo 55 | -------------------------------------------------------------------------------- /recipes/MojoINI/recipe.yaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | context: 4 | version: "0.1.0" 5 | 6 | package: 7 | name: "mojo-ini" 8 | version: ${{ version }} 9 | 10 | source: 11 | - git: https://github.com/Hammad-hab/MojoINI.git 12 | rev: 6ebe2f95e092582c254fd3bfcd59006371fe3d85 13 | 14 | build: 15 | number: 0 16 | script: 17 | - mojo package src/mojoini -o ${{ PREFIX }}/lib/mojo/mojoini.mojopkg 18 | 19 | requirements: 20 | host: 21 | - max =25.4 22 | run: 23 | - ${{ pin_compatible('max') }} 24 | 25 | tests: 26 | - script: 27 | - if: unix 28 | then: 29 | - mojo test 30 | files: 31 | source: 32 | - test_parser.mojo 33 | - test_perf.mojo 34 | 35 | about: 36 | homepage: https://github.com/Hammad-hab/MojoINI.git 37 | # Remember to specify the license variants for BSD, Apache, GPL, and LGPL. 38 | # Use the SPDX identifier, e.g: GPL-2.0-only instead of GNU General Public License version 2.0 39 | # See https://spdx.org/licenses/ 40 | license: MIT 41 | # It is strongly encouraged to include a license file in the package, 42 | # (even if the license doesn't require it) using the license_file entry. 43 | # See https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html#license-file 44 | license_file: LICENSE 45 | summary: Simple INI file parser for Mojo 46 | repository: https://github.com/Hammad-hab/MojoINI.git 47 | 48 | extra: 49 | maintainers: 50 | - Hammad-hab 51 | -------------------------------------------------------------------------------- /recipes/bridge/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "1.0.0" 3 | 4 | package: 5 | name: "bridge" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://gitlab.com/hylkedonker/bridge.git 10 | rev: 7b5d8bec7b8ae9b547afdec33cfce05d1c01abd2 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src/bridge -o ${{ PREFIX }}/lib/mojo/bridge.mojopkg 16 | requirements: 17 | host: 18 | - max=25.4 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo test test_numpy.🔥 27 | files: 28 | recipe: 29 | - test_numpy.🔥 30 | about: 31 | homepage: https://gitlab.com/hylkedonker/bridge 32 | # Remember to specify the license variants for BSD, Apache, GPL, and LGPL. 33 | # Use the SPDX identifier, e.g: GPL-2.0-only instead of GNU General Public License version 2.0 34 | # See https://spdx.org/licenses/ 35 | license: MIT 36 | # It is strongly encouraged to include a license file in the package, 37 | # (even if the license doesn't require it) using the license_file entry. 38 | # See https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html#license-file 39 | license_file: LICENSE.txt 40 | summary: Convert (bridge) Python objects to Mojo and vice versa. 41 | repository: https://gitlab.com/hylkedonker/bridge 42 | 43 | extra: 44 | maintainers: 45 | - hylkedonker 46 | project_name: Bridge 47 | -------------------------------------------------------------------------------- /recipes/bridge/README.md: -------------------------------------------------------------------------------- 1 | # Bridge 2 | [![SAST](https://gitlab.com/hylkedonker/bridge/badges/main/pipeline.svg?job=sast)](https://gitlab.com/hylkedonker/bridge/-/security/dashboard) 3 |

4 | Bridge 5 |

6 | 7 | This package acts as a bridge between Mojo native types and those from the Python world. 8 | 9 | Example usage, to convert a NumPy array to a Mojo `LayoutTensor`: 10 | ```mojo 11 | from python import Python 12 | from bridge.numpy import ndarray_to_tensor 13 | 14 | var np = Python.import_module("numpy") 15 | np_array = np.arange(6.0).reshape(2,3) 16 | 17 | # Convert to new-style `LayoutTensor`. 18 | mojo_tensor = ndarray_to_tensor[order=2](np_array) 19 | ``` 20 | Or to achieve the reverse: 21 | ```mojo 22 | from bridge.numpy import tensor_to_ndarray 23 | 24 | # Convert `LayoutTensor` to numpy array. 25 | np_array = tensor_to_ndarray(mojo_tensor) 26 | ``` 27 | 28 | # Installation 29 | Add the `modular-community` channel to your `mojoproject.toml` file and `bridge` to 30 | your dependencies, by running: 31 | ```bash 32 | pixi project channel add "https://repo.prefix.dev/modular-community" 33 | pixi add bridge 34 | ``` 35 | That's it, success! 🎉 36 | 37 | # Dependencies 38 | Requires numpy and mojo. 39 | 40 | # Contributing 41 | Please refer to the [contribution guidelines](https://gitlab.com/hylkedonker/bridge/-/blob/main/CONTRIBUTING.md) before contributing. 42 | 43 | # License 44 | This code is licensed under the terms of the [MIT License](LICENSE.txt). -------------------------------------------------------------------------------- /recipes/small_time/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "25.4.1" 3 | 4 | package: 5 | name: "small_time" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/thatstoasty/small-time.git 10 | rev: 5eb887d8efcf2d401d7ebef82f332550bcffbdb9 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src/small_time -o ${{ PREFIX }}/lib/mojo/small_time.mojopkg 16 | requirements: 17 | host: 18 | - max =25.4 19 | run: 20 | - ${{ pin_compatible('max') }} 21 | 22 | tests: 23 | - script: 24 | - if: unix 25 | then: 26 | - mojo test test 27 | files: 28 | recipe: 29 | - test_small_time.mojo 30 | - test_time_delta.mojo 31 | - test_time_zone.mojo 32 | 33 | about: 34 | homepage: https://github.com/thatstoasty/small-time 35 | # Remember to specify the license variants for BSD, Apache, GPL, and LGPL. 36 | # Use the SPDX identifier, e.g: GPL-2.0-only instead of GNU General Public License version 2.0 37 | # See https://spdx.org/licenses/ 38 | license: Apache-2.0 39 | # It is strongly encouraged to include a license file in the package, 40 | # (even if the license doesn't require it) using the license_file entry. 41 | # See https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html#license-file 42 | license_file: LICENSE 43 | summary: Datetime library forked from morrow.mojo 44 | repository: https://github.com/thatstoasty/small-time 45 | 46 | extra: 47 | maintainers: 48 | - thatstoasty 49 | -------------------------------------------------------------------------------- /recipes/nabla/test.mojo: -------------------------------------------------------------------------------- 1 | import nabla 2 | 3 | 4 | fn main() raises: 5 | 6 | # Simple test to check if nabla is callable, extensive testing is done elsewhere! 7 | 8 | fn foo(args: List[nabla.Array]) raises -> List[nabla.Array]: 9 | return List( 10 | nabla.sum(nabla.sin(args[0] * args[1])), 11 | nabla.sum(nabla.cos(args[0] * args[1])), 12 | ) 13 | 14 | var foo_vmapped = nabla.vmap(foo) 15 | var foo_d1 = nabla.jacrev(foo_vmapped) 16 | var foo_d2 = nabla.jacfwd(foo_d1) 17 | var foo_d3 = nabla.jacfwd(foo_d2) 18 | 19 | var args = List(nabla.arange((2, 3)), nabla.arange((2, 3))) 20 | 21 | var res = foo(args) 22 | print("foo checksum: ", nabla.sum(res[0])) # , nabla.sum(res[1])) 23 | 24 | var d1 = foo_d1(args) 25 | print( 26 | "foo_d1 checksum: ", nabla.sum(d1[0]) 27 | ) # , nabla.sum(d1[1]), nabla.sum(d1[2]), nabla.sum(d1[3])) 28 | 29 | var d2 = foo_d2(args) 30 | print( 31 | "foo_d2 checksum: ", nabla.sum(d2[0]) 32 | ) # , nabla.sum(d2[1]), nabla.sum(d2[2]), nabla.sum(d2[3]), nabla.sum(d2[4]), nabla.sum(d2[5]), nabla.sum(d2[6]), nabla.sum(d2[7])) 33 | 34 | var d3 = foo_d3(args) 35 | print( 36 | "foo_d3 checksum: ", nabla.sum(d3[0]) 37 | ) # , nabla.sum(d3[1]), nabla.sum(d3[2]), nabla.sum(d3[3]), nabla.sum(d3[4]), nabla.sum(d3[5]), nabla.sum(d3[6]), nabla.sum(d3[7]), nabla.sum(d3[8]), nabla.sum(d3[9]), nabla.sum(d3[10]), nabla.sum(d3[11]), nabla.sum(d3[12]), nabla.sum(d3[13]), nabla.sum(d3[14]), nabla.sum(d3[15])) -------------------------------------------------------------------------------- /recipes/numojo/tests.mojo: -------------------------------------------------------------------------------- 1 | import numojo as nm 2 | from numojo.prelude import * 3 | from python import Python, PythonObject 4 | from testing.testing import ( 5 | assert_raises, 6 | assert_equal, 7 | assert_true, 8 | assert_almost_equal, 9 | ) 10 | 11 | 12 | fn check[ 13 | dtype: DType 14 | ](array: nm.NDArray[dtype], np_sol: PythonObject, st: String) raises: 15 | var np = Python.import_module("numpy") 16 | assert_true(np.all(np.equal(array.to_numpy(), np_sol)), st) 17 | 18 | 19 | fn check_is_close[ 20 | dtype: DType 21 | ](array: nm.NDArray[dtype], np_sol: PythonObject, st: String) raises: 22 | var np = Python.import_module("numpy") 23 | assert_true(np.all(np.isclose(array.to_numpy(), np_sol, atol=0.1)), st) 24 | 25 | 26 | fn check_values_close[ 27 | dtype: DType 28 | ](value: Scalar[dtype], np_sol: PythonObject, st: String) raises: 29 | var np = Python.import_module("numpy") 30 | assert_true(np.isclose(value, np_sol, atol=0.01), st) 31 | 32 | 33 | def test_arange(): 34 | var np = Python.import_module("numpy") 35 | check( 36 | nm.arange[nm.i64](0, 100), 37 | np.arange(0, 100, dtype=np.int64), 38 | "Arange is broken", 39 | ) 40 | check( 41 | nm.arange[nm.f64](0, 100), 42 | np.arange(0, 100, dtype=np.float64), 43 | "Arange is broken", 44 | ) 45 | 46 | 47 | def test_linspace(): 48 | var np = Python.import_module("numpy") 49 | check( 50 | nm.linspace[nm.f64](0, 100), 51 | np.linspace(0, 100, dtype=np.float64), 52 | "Linspace is broken", 53 | ) 54 | 55 | 56 | def main(): 57 | test_arange() 58 | test_linspace() 59 | -------------------------------------------------------------------------------- /recipes/infrared/README.md: -------------------------------------------------------------------------------- 1 | # Infrared 2 | Infrared is a geometric algebra library for Mojo. 3 | 4 | ## Geometric Algebra 5 | [Geometric algebras](https://en.wikipedia.org/wiki/Geometric_algebra) are [Clifford algebras](https://en.wikipedia.org/wiki/Clifford_algebra) in the context of geometry. 6 | 7 | It's an alternative paradigm to linear algebra that uses 'multivectors' instead of matrices. 8 | 9 | Multivectors can represent geometric objects and transformations such as lines, planes, rotations, etc. 10 | 11 | Multivectors can be multiplied together using the 'geometric product'. 12 | 13 | ## Using Infrared 14 | With infrared, you can generate the geometric product table for an arbitrary signature. 15 | 16 | Multivectors are also parameterized on a basis masks, to avoid unnecessary overhead. 17 | 18 | Example: 19 | ```mojo 20 | alias sig = Signature(2, 0, 1) 21 | var m = Multivector[sig, sig.vector_mask()](0.0, 1.0, 2.0) 22 | print(m * m) 23 | ``` 24 | 25 | Infrared has no dependecies other than max. 26 | 27 | Developers can use infrared as a mathematical abstraction over geometry (projective, conformal, spacetime, dimension-agnostic, etc.) 28 | 29 | ## Contributing 30 | I'm accepting contributions, but haven't made a contributors guide yet. 31 | 32 | Some issues and areas of work include: 33 | - performance improvements, signature generation could use bitwise operations 34 | - constructors for geometric objects and a better model for initializing multivectors 35 | - better basis masking, currently uses a list of bools 36 | - examples and benchmarks 37 | 38 | If you want to reach out, you can email me at helehex@gmail.com, or message me on discord (my alias in the modular server is ghostfire) -------------------------------------------------------------------------------- /recipes/lightbug_http/recipe.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/prefix-dev/recipe-format/main/schema.json 2 | context: 3 | version: "0.25.6" 4 | mojo_version: "=0.25.6" 5 | package: 6 | name: "lightbug_http" 7 | version: ${{ version }} 8 | source: 9 | - git: https://github.com/saviorand/lightbug_http.git 10 | rev: b7b81a8332259fa14e1057dc86a8249c2e6df927 11 | build: 12 | number: 0 13 | script: 14 | - mkdir -p ${PREFIX}/lib/mojo 15 | - mojo package lightbug_http -o ${{ PREFIX }}/lib/mojo/lightbug_http.mojopkg 16 | requirements: 17 | build: 18 | - mojo-compiler ${{ mojo_version }} 19 | host: 20 | - mojo-compiler ${{ mojo_version }} 21 | run: 22 | - mojo-compiler ${{ mojo_version }} 23 | tests: 24 | - script: 25 | - if: unix 26 | then: 27 | - mojo test 28 | requirements: 29 | build: 30 | - mojo ${{ mojo_version }} 31 | run: 32 | - mojo ${{ mojo_version }} 33 | files: 34 | recipe: 35 | - tests/lightbug_http/io/test_bytes.mojo 36 | - tests/lightbug_http/test_client.mojo 37 | - tests/lightbug_http/test_cookie.mojo 38 | - tests/lightbug_http/test_header.mojo 39 | - tests/lightbug_http/test_http.mojo 40 | - tests/lightbug_http/test_net.mojo 41 | - tests/lightbug_http/test_uri.mojo 42 | about: 43 | homepage: https://lightbug.site 44 | license: MIT 45 | license_file: LICENSE 46 | summary: Lightbug is a simple and sweet HTTP framework for Mojo 47 | repository: https://github.com/saviorand/lightbug_http 48 | extra: 49 | maintainers: 50 | - saviorand 51 | - bgreni 52 | - thatstoasty 53 | project_name: lightbug_http 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modular Community Channel ✨ 2 | 3 | Welcome to the repository for the Modular community channel! This conda-based [Prefix.dev](http://Prefix.dev) channel allows Modular community members to distribute their packages built with MAX and Mojo via Pixi 🪄 4 | 5 | ## Installing a package 6 | 7 | ### Add the Modular community channel to your `pixi.toml` file 8 | 9 | Before you can install a community package, you’ll need to add the Modular community channel to your `pixi.toml` file. 10 | 11 | Add the Modular community channel (https://repo.prefix.dev/modular-community) to your `pixi.toml file` in the channels section: 12 | 13 | ``` 14 | # pixi.toml 15 | 16 | [project] 17 | channels = ["conda-forge", "https://conda.modular.com/max", "https://repo.prefix.dev/modular-community"] 18 | description = "Add a short description here" 19 | name = "my-mojo-project" 20 | platforms = ["osx-arm64"] 21 | version = "0.1.0" 22 | 23 | [tasks] 24 | 25 | [dependencies] 26 | max = ">=25.4.0" 27 | ``` 28 | 29 | ### **Install a package** 30 | 31 | To install a package from the Modular community channel, simply enter the following in the command line: 32 | ``` 33 | pixi add =1.3.1,<2 33 | 34 | run: 35 | - mojo-compiler ${{ mojo_version }} 36 | - zlib >=1.3.1,<2 37 | 38 | tests: 39 | - script: 40 | - if: unix 41 | then: 42 | - mojo test -I ${{ PREFIX }}/share/mojo/ishlib.mojopkg tests 43 | requirements: 44 | run: 45 | - mojo-compiler ${{ mojo_version }} 46 | - extramojo ${{ extramojo_version }} 47 | - zlib >=1.3.1,<2 48 | 49 | files: 50 | source: 51 | - tests 52 | 53 | about: 54 | homepage: https://github.com/BioRadOpenSource/ish 55 | license: Apache-2.0 56 | license_file: LICENSE 57 | summary: Alignment-based filtering CLI tool 58 | repository: https://github.com/BioRadOpenSource/ish 59 | 60 | extra: 61 | maintainers: 62 | - sstadick 63 | project_name: ish -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-toml 6 | - id: check-yaml 7 | - id: trailing-whitespace 8 | exclude_types: ['markdown'] 9 | - id: check-merge-conflict 10 | - id: mixed-line-ending 11 | - repo: local 12 | hooks: 13 | # ensure pixi environments are up to date 14 | # workaround for https://github.com/prefix-dev/pixi/issues/1482 15 | - id: pixi-install 16 | name: Make sure pixi envs are up to date 17 | entry: pixi install 18 | language: system 19 | always_run: true 20 | require_serial: true 21 | pass_filenames: false 22 | - id: actionlint 23 | name: Lint GitHub Actions workflow files 24 | language: system 25 | entry: pixi run actionlint 26 | types: [yaml] 27 | files: ^\.github/workflows/ 28 | - id: check-jsonschema 29 | name: Check rattler recipes 30 | language: system 31 | entry: pixi run check-jsonschema --schemafile https://raw.githubusercontent.com/prefix-dev/recipe-format/main/schema.json 32 | files: ^[^/]+/recipe.yaml$ 33 | types: [yaml] 34 | - id: typecheck-python 35 | name: typecheck-python 36 | entry: pixi run mypy 37 | language: system 38 | types_or: [python, pyi] 39 | pass_filenames: false 40 | # Use ruff for python examples 41 | - id: ruff 42 | name: ruff 43 | entry: pixi run ruff check --fix --exit-non-zero-on-fix --force-exclude 44 | language: system 45 | types_or: [python, pyi] 46 | require_serial: true 47 | - id: ruff-format 48 | name: ruff-format 49 | entry: pixi run ruff format --force-exclude 50 | language: system 51 | types_or: [python, pyi] 52 | require_serial: true 53 | -------------------------------------------------------------------------------- /recipes/mimage/README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Logo 5 | 6 | 7 |

Mimage

8 | 9 |

10 | A library for reading images in pure* Mojo 🔥 11 | 12 | ![Language Badge](https://img.shields.io/badge/language-mojo-orange) 13 | ![CodeQL](https://github.com/fnands/mimage/workflows/CodeQL/badge.svg) 14 |

15 |
16 | 17 | *Not pure Mojo yet, but hopefully soon. 18 | ## About The Project 19 | 20 | Mimage is a image manipulation library loosely based on Python's [Pillow](https://github.com/python-pillow/Pillow). The goal is to be able to read and write the most popular image formats directly from Mojo. 21 | 22 | ## Quick Start 23 | 24 | Basic usage: 25 | 26 | ```mojo 27 | import mimage as mi 28 | 29 | def main(): 30 | 31 | tensor = mi.imread("my/png/image.png") 32 | 33 | ``` 34 | 35 | Try out the tests yourself: 36 | 37 | ```sh 38 | mojo -I . tests/test_open_png.mojo 39 | ``` 40 | 41 | ## Roadmap 42 | 43 | ### v0.1.0 ✅ 44 | - [x] Read simple 8-bit PNGs 45 | 46 | ### Near term 47 | - [ ] Read jpegs 48 | 49 | ### Medium term 50 | - [ ] Read more complex PNGs 51 | - [ ] Write PNGs 52 | - [ ] Write jpegs 53 | 54 | ### Long term 55 | - [ ] v1.0.0 will be achieved when Mimage can open all the same images as Pillow. 56 | 57 | 58 | ## Contributing 59 | 60 | Before creating a new issue, please: 61 | * Check if the issue already exists. If an issue is already reported, you can contribute by commenting on the existing issue. 62 | * If not, create a new issue and include all the necessary details to understand/recreate the problem or feature request. 63 | 64 | ### Creating A Pull Request 65 | 66 | 1. Fork the Project 67 | 2. Create your Feature Branch 68 | 3. Commit your Changes 69 | 4. Push to the Branch 70 | 5. Open a Pull Request 71 | > Once your changes are pushed, navigate to your fork on GitHub. And create a pull request against the original fnands/mimage repository. 72 | > - Before creating a PR make sure it doesn't break any of the unit-tests. (e.g. `mojo -I . tests/test_open_png.mojo`) 73 | > - Introducing new big features requires a new test! 74 | > - In the pull request, provide a detailed description of the changes and why they're needed. Link any relevant issues. 75 | > - If there are any specific instructions for testing or validating your changes, include those as well. 76 | 77 | ## License 78 | 79 | Distributed under the Apache 2.0 License with LLVM Exceptions. See [LICENSE](https://github.com/fnands/mimage/blob/main/LICENSE) and the LLVM [License](https://llvm.org/LICENSE.txt) for more information. 80 | 81 | ## Acknowledgements 82 | 83 | * Built with [Mojo](https://github.com/modularml/mojo) created by [Modular](https://github.com/modularml) -------------------------------------------------------------------------------- /scripts/common.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, TypedDict 3 | 4 | import json 5 | import subprocess 6 | import sys 7 | import yaml 8 | 9 | 10 | class RecipeFailure(TypedDict): 11 | failed_at: str 12 | 13 | 14 | def load_failed_compatibility(file_path: Path) -> dict[str, RecipeFailure]: 15 | if file_path.exists(): 16 | with file_path.open("r") as file: 17 | return dict(json.load(file)) 18 | return {} 19 | 20 | 21 | def save_failed_compatibility(file_path: Path, data: dict[str, RecipeFailure]) -> None: 22 | if not file_path.parent.exists(): 23 | file_path.parent.mkdir(parents=True, exist_ok=True) 24 | with file_path.open("w") as file: 25 | json.dump(data, file, indent=4) 26 | 27 | 28 | def eprint(*args: Any, **kwargs: Any) -> None: 29 | print(*args, file=sys.stderr, **kwargs) 30 | 31 | 32 | def commit_push_changes(message: str, branch_name: str) -> None: 33 | """ 34 | Commit and push changes to the specified branch with a given commit message. 35 | If there are no changes, do nothing. 36 | """ 37 | 38 | # Switch to branch 39 | run_command(["git", "switch", branch_name]) 40 | 41 | # Check if there are changes to commit 42 | result = run_command_unchecked(["git", "diff-index", "--quiet", "HEAD"]) 43 | if result.returncode == 0: 44 | eprint("No changes to commit.") 45 | return 46 | 47 | # Commit, pull and push the changes 48 | run_command(["git", "pull", "origin", branch_name]) 49 | run_command(["git", "commit", "--message", message, "--no-verify"]) 50 | run_command(["git", "push", "--set-upstream", "origin", branch_name]) 51 | 52 | 53 | def recipe_name_collisions( 54 | recipe_file: Path, *, channels: list[str] = ["conda-forge", "max"] 55 | ) -> bool: 56 | with open(recipe_file, "r") as fh: 57 | recipe_data = yaml.safe_load(fh) 58 | name = recipe_data.get("package", {}).get("name", None) 59 | 60 | if name is None or len(name.strip()) == 0: 61 | eprint("Invalid recipe yaml") 62 | # No collisions possible 63 | return False 64 | 65 | cmd = ["conda", "search", "--quiet", "--skip-flexible-search"] 66 | for channel in channels: 67 | cmd.extend(["--channel", channel]) 68 | cmd.append(name.strip()) 69 | 70 | p = run_command_unchecked(cmd) 71 | if p.returncode == 0: 72 | # Name collides with something 73 | eprint("Name collision detected:") 74 | eprint(f"{p.stdout}") 75 | eprint(f"{p.stderr}") 76 | return True 77 | return False 78 | 79 | 80 | def run_command_unchecked(command: list[str]) -> subprocess.CompletedProcess[Any]: 81 | eprint(f"Run command: {' '.join(command)}") 82 | result = subprocess.run(command, capture_output=True, text=True) 83 | return result 84 | 85 | 86 | def run_command(command: list[str]) -> subprocess.CompletedProcess[Any]: 87 | result = run_command_unchecked(command) 88 | if result.returncode != 0: 89 | eprint("Command failed") 90 | print(f"stdout: {result.stdout}") 91 | eprint(f"stderr: {result.stderr}") 92 | sys.exit(result.returncode) 93 | 94 | return result 95 | -------------------------------------------------------------------------------- /recipes/mojo-websockets/README.md: -------------------------------------------------------------------------------- 1 | # mojo-websockets 2 | 3 | `mojo-websockets` is a lightweight library for handling WebSocket connections in Mojo. 4 | 5 | It aims to provide an similar interface as the [python-websockets](https://github.com/python-websockets/websockets) package for creating WebSocket servers and clients, with additional features for enhanced usability. 6 | 7 | ## Disclaimer ⚠️ 8 | 9 | This software is in a early stage of development, using the Mojo nightly version. Please DO NOT use yet for production ready services. 10 | 11 | ## Features 12 | 13 | - **WebSocket Server and Client**: Supports creating both WebSocket servers and clients. 14 | - **Compatibility**: API designed to be intuitive for developers familiar with the Python websockets library. 15 | - **Sans/IO Layer**: Implements a WebSocket Sans/IO layer pure Mojo and performs no I/O of its own. 16 | 17 | For a complete listing, see the [features](https://github.com/msaelices/mojo-websockets/docs/features.md) document. 18 | 19 | ## Installation 20 | 21 | 1. **Install [magic](https://docs.modular.com/magic#install-magic)** 22 | 23 | 2. **Add the WebSockets Package** (at the top level of your project): 24 | 25 | ```bash 26 | magic add websockets 27 | ``` 28 | ## Example of usage 29 | 30 | ### Server side 31 | 32 | ```mojo 33 | from websockets.aliases import Bytes 34 | from websockets.sync.server import serve, WSConnection 35 | from websockets.utils.bytes import bytes_to_str 36 | 37 | 38 | fn on_message(conn: WSConnection, data: Bytes) raises -> None: 39 | str_received = bytes_to_str(data) 40 | print("<<< ", str_received) 41 | conn.send_text(str_received) 42 | print(">>> ", str_received) 43 | 44 | 45 | fn main() raises: 46 | with serve(on_message, "127.0.0.1", 8000) as server: 47 | server.serve_forever() 48 | ``` 49 | 50 | ### Client side 51 | 52 | ```mojo 53 | from websockets.sync.client import connect 54 | from websockets.utils.bytes import bytes_to_str 55 | 56 | 57 | fn send_and_receive(msg: String) raises: 58 | with connect("ws://127.0.0.1:8000") as client: 59 | client.send_text(msg) 60 | print(">>> ", msg) 61 | response = client.recv() 62 | print("<<< ", bytes_to_str(response)) 63 | 64 | fn main() raises: 65 | send_and_receive("Hello world!") 66 | 67 | ``` 68 | 69 | ## TODO 70 | 71 | - [ ] Asynchronous non-blocking communication (waiting for the Mojo async/await support) 72 | - [ ] Implement automatic reconnection for clients 73 | - [ ] Get rid of Python dependencies and logic (e.g. no more `from python import ...`) 74 | - [ ] Make sure it passes all the tests in [Autobahn|Testsuite](https://github.com/crossbario/autobahn-testsuite/) 75 | - [ ] Implement subprotocols and extensions 76 | - [ ] Optimize performance for high-concurrency scenarios 77 | - [ ] TLS support 78 | 79 | See all the remaining features in the [features](https://github.com/msaelices/mojo-websockets/docs/features.md) document. 80 | 81 | ## Contributing 82 | 83 | Contributions are welcome! If you'd like to contribute, please follow the contribution guidelines in the [CONTRIBUTING.md](https://github.com/msaelices/mojo-websockets/CONTRIBUTING.md) file in the repository. 84 | 85 | ## Acknowledgments 86 | 87 | We have taken a lot of code from the amazing [lightbug_http](https://github.com/saviorand/lightbug_http) project. 88 | 89 | Also, we took inspiration and some code from the [python-websockets](https://github.com/websockets) project, specially for implementing the [WebSocket Sans/IO layer](https://websockets.readthedocs.io/en/stable/howto/sansio.html) and their tests. 90 | 91 | ## License 92 | 93 | mojo-websockets is licensed under the [MIT license](https://github.com/msaelices/mojo-websockets/LICENSE). 94 | -------------------------------------------------------------------------------- /scripts/build-all.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import datetime 3 | import argparse 4 | from scripts.common import ( 5 | load_failed_compatibility, 6 | recipe_name_collisions, 7 | run_command_unchecked, 8 | save_failed_compatibility, 9 | eprint, 10 | ) 11 | import sys 12 | import os 13 | 14 | # Channels are in priority order 15 | MODULAR_COMMUNITY_CHANNEL = "https://prefix.dev/modular-community" 16 | MAX_CHANNEL = "https://conda.modular.com/max" 17 | DEFAULT_CHANNELS = ["conda-forge", MAX_CHANNEL, MODULAR_COMMUNITY_CHANNEL] 18 | 19 | 20 | def main() -> None: 21 | parser = argparse.ArgumentParser(description="Build all recipes.") 22 | parser.add_argument( 23 | "--channel", 24 | action="append", 25 | help="The channels to use for building.", 26 | ) 27 | parser.add_argument( 28 | "--data-file", 29 | type=Path, 30 | default=os.environ.get("DATA_FILE"), 31 | help="Path to where the data should be stored. Nothing will be stored if that flag is not provided.", 32 | ) 33 | args = parser.parse_args() 34 | 35 | base_dir = Path("recipes") 36 | variant_config = "variants/variants.yaml" 37 | 38 | # Load existing failed compatibility data 39 | failed_compatibility = ( 40 | None if args.data_file is None else load_failed_compatibility(args.data_file) 41 | ) 42 | 43 | exit_code = 0 44 | default_channels_without_community = [ 45 | c for c in DEFAULT_CHANNELS if c != MODULAR_COMMUNITY_CHANNEL 46 | ] 47 | 48 | for recipe_dir in base_dir.iterdir(): 49 | recipe_file = recipe_dir / "recipe.yaml" 50 | if not recipe_file.is_file(): 51 | eprint(f"{recipe_dir} doesn't contain recipe.yaml") 52 | continue 53 | 54 | if recipe_name_collisions( 55 | recipe_file, channels=default_channels_without_community 56 | ): 57 | eprint( 58 | f"SKIPPING: {recipe_file} specifies a recipe whose name collides with another conda package in {default_channels_without_community}." 59 | ) 60 | continue 61 | 62 | command = [ 63 | "rattler-build", 64 | "build", 65 | ] 66 | 67 | for channel in DEFAULT_CHANNELS: 68 | command.extend(["--channel", channel]) 69 | 70 | if args.channel is not None: 71 | for channel in args.channel: 72 | if channel in DEFAULT_CHANNELS: 73 | continue 74 | command.extend(["--channel", channel]) 75 | 76 | command.extend( 77 | [ 78 | "--variant-config", 79 | variant_config, 80 | "--skip-existing=all", 81 | "--recipe", 82 | str(recipe_file), 83 | ] 84 | ) 85 | result = run_command_unchecked(command) 86 | if result.returncode != 0: 87 | eprint(f"Error building recipe in {recipe_dir}: {result.stderr}") 88 | exit_code = 1 89 | if failed_compatibility is not None: 90 | failed_compatibility[recipe_dir.name] = { 91 | "failed_at": datetime.now().isoformat() 92 | } 93 | else: 94 | print(f"Successfully built recipe {recipe_dir.name}") 95 | if failed_compatibility is not None: 96 | if recipe_dir.name in failed_compatibility: 97 | del failed_compatibility[recipe_dir.name] 98 | print(f"Removed {recipe_dir.name} from failed-compatibility.json") 99 | 100 | if failed_compatibility is not None: 101 | # Save updated failed compatibility data 102 | save_failed_compatibility(args.data_file, failed_compatibility) 103 | 104 | sys.exit(exit_code) 105 | 106 | 107 | if __name__ == "__main__": 108 | main() 109 | -------------------------------------------------------------------------------- /recipes/bridge/test_numpy.🔥: -------------------------------------------------------------------------------- 1 | from python import Python 2 | from layout import Layout, LayoutTensor, RuntimeLayout 3 | 4 | from testing import assert_equal, assert_raises, assert_true 5 | 6 | from bridge.numpy import ndarray_to_tensor, tensor_to_ndarray 7 | 8 | 9 | def test_ndarray_to_tensor(): 10 | """Test numpy array conversion to layouttensor for various tensor shapes.""" 11 | var np = Python.import_module("numpy") 12 | 13 | # 1) Test vectors 14 | var in_vector = np.arange(4.0) 15 | var out_vector = ndarray_to_tensor[order=1](in_vector) 16 | assert_equal(out_vector[1], 1.0) 17 | assert_equal(out_vector[3], 3.0) 18 | 19 | # 2) Test matrices 20 | var in_matrix = np.arange(4.0 * 3.0).reshape(3, 4) 21 | var out_matrix = ndarray_to_tensor[order=2](in_matrix) 22 | assert_equal(out_matrix[0, 0], 0.0) 23 | assert_equal(out_matrix[1, 1], 5.0) 24 | assert_equal(out_matrix[1, 3], 7.0) 25 | assert_equal(out_matrix[2, 1], 9.0) 26 | 27 | # Check that non-contiguous arrays raise exceptions. 28 | with assert_raises(): 29 | var in_matrix_col_major = np.asfortranarray(in_matrix) 30 | _ = ndarray_to_tensor[order=2](in_matrix_col_major) 31 | 32 | # 3) Test three-index tensors 33 | var in_tensor = np.arange(4.0 * 3.0).reshape(3, 1, 4) 34 | var out_tensor = ndarray_to_tensor[order=3](in_tensor) 35 | assert_equal(out_tensor[0, 0, 0], 0.0) 36 | assert_equal(out_tensor[1, 0, 1], 5.0) 37 | assert_equal(out_tensor[1, 0, 3], 7.0) 38 | assert_equal(out_tensor[2, 0, 1], 9.0) 39 | 40 | # 3) Test four-index tensors 41 | var in_4tensor = np.arange(4.0 * 3.0).reshape(2, 3, 1, 2) 42 | var out_4tensor = ndarray_to_tensor[order=4](in_4tensor) 43 | assert_equal(out_4tensor[0, 0, 0, 0], 0.0) 44 | assert_equal(out_4tensor[0, 1, 0, 1], 3.0) 45 | assert_equal(out_4tensor[1, 0, 0, 1], 7.0) 46 | assert_equal(out_4tensor[0, 2, 0, 0], 4.0) 47 | 48 | 49 | def test_memory_leaks(): 50 | """Test that we can safely remove the reference to the numpy array.""" 51 | var np = Python.import_module("numpy") 52 | var np_array = np.arange(6.0).reshape(3, 2) 53 | var tensor = ndarray_to_tensor[order=2](np_array) 54 | np_array.__del__() 55 | assert_equal(tensor[1, 0], 2.0) 56 | assert_equal(tensor[1, 1], 3.0) 57 | assert_equal(tensor[2, 1], 5.0) 58 | 59 | 60 | # def test_tensor_numpy_identity_transformation(): 61 | # """Test that `tensor_to_ndarray` is inverse of `ndarray_to_tensor`.""" 62 | # var values = InlineArray[Float64, 6](0.0, 1.0, 2.0, 3.0, 4.0, 5.0) 63 | # var ptr = values.unsafe_ptr() 64 | # var tensor = LayoutTensor[ 65 | # DType.float64, 66 | # Layout.row_major(2, 3), 67 | # # MutableAnyOrigin, 68 | # # __origin_of(ptr[]), 69 | # # __origin_of(ptr), 70 | # # origin = __origin_of(values), 71 | # # MutableAnyOrigin, 72 | # ](values) 73 | 74 | # np_array = tensor_to_ndarray(tensor) 75 | # out_layouttensor = ndarray_to_tensor[order=2](in_array) 76 | 77 | 78 | def test_numpy_tensor_identity_transformation(): 79 | """Test that `ndarray_to_tensor` is the inverse of `tensor_to_ndarray`.""" 80 | var np = Python.import_module("numpy") 81 | 82 | # 1) Test vectors 83 | # TODO: Add support for vectors! 84 | 85 | # 2) Test matrices 86 | var in_matrix = np.arange(4.0 * 3.0).reshape(3, 4) 87 | var layout_matrix = ndarray_to_tensor[order=2](in_matrix) 88 | var out_matrix = tensor_to_ndarray(layout_matrix) 89 | np.testing.assert_array_equal(in_matrix, out_matrix) 90 | 91 | # 3) Test three-index tensors 92 | var in_tensor = np.arange(4.0 * 3.0).reshape(3, 1, 4) 93 | var layout_tensor = ndarray_to_tensor[order=3](in_tensor) 94 | var out_tensor = tensor_to_ndarray(layout_tensor) 95 | np.testing.assert_array_equal(in_tensor, out_tensor) 96 | 97 | # 3) Test four-index tensors 98 | var in_4tensor = np.arange(4.0 * 3.0).reshape(2, 3, 1, 2) 99 | var layout_4tensor = ndarray_to_tensor[order=4](in_4tensor) 100 | var out_4tensor = tensor_to_ndarray(layout_4tensor) 101 | np.testing.assert_array_equal(in_4tensor, out_4tensor) 102 | -------------------------------------------------------------------------------- /scripts/remove-incompatible-packages.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from pathlib import Path 4 | from github import Github 5 | from scripts.common import ( 6 | commit_push_changes, 7 | eprint, 8 | load_failed_compatibility, 9 | run_command, 10 | ) 11 | from datetime import datetime, timedelta 12 | import yaml 13 | import uuid 14 | 15 | 16 | def main() -> None: 17 | github_token = os.getenv("GITHUB_TOKEN") 18 | github_repository = os.getenv("GITHUB_REPOSITORY") 19 | if not github_token or not github_repository: 20 | eprint("GITHUB_TOKEN and GITHUB_REPOSITORY environment variables are required.") 21 | sys.exit(1) 22 | 23 | gh = Github(github_token) 24 | repo = gh.get_repo(github_repository) 25 | 26 | # Gather all failed compatibility files 27 | failed_compatibility_files = list(Path("data").glob("failed-compatibility-*.json")) 28 | 29 | # Initialize a set to store recipes to remove 30 | recipes_to_remove = set() 31 | failed_compatibility = dict() 32 | 33 | # Filter recipes where the failure state is older than four weeks 34 | four_weeks_ago = datetime.now() - timedelta(weeks=4) 35 | 36 | for file_path in failed_compatibility_files: 37 | failed_compatibility[file_path] = load_failed_compatibility(file_path) 38 | for recipe_str, failure in failed_compatibility[file_path].items(): 39 | if datetime.fromisoformat(failure["failed_at"]) < four_weeks_ago: 40 | recipes_to_remove.add(Path("recipes", recipe_str)) 41 | 42 | exit_code = 0 43 | for recipe in recipes_to_remove: 44 | try: 45 | # Read the recipe.yaml file 46 | recipe_yaml_path = recipe / "recipe.yaml" 47 | if recipe_yaml_path.is_file(): 48 | with recipe_yaml_path.open("r") as file: 49 | recipe_data = yaml.safe_load(file) 50 | maintainers = recipe_data.get("extra", {}).get("maintainers", []) 51 | else: 52 | eprint(f"{recipe_yaml_path} does not exist") 53 | maintainers = [] 54 | 55 | # Prepare the PR body 56 | body = f"This PR deletes '{recipe.name}' since it failed compatibility testing four weeks ago.\n" 57 | if maintainers: 58 | body += f"Tagging maintainers: {' '.join([f'@{user}' for user in maintainers])}" 59 | else: 60 | body += "Maintainers couldn't be extracted from recipe.yaml." 61 | 62 | # Create a commit and push it 63 | hash = uuid.uuid4().hex[:7] 64 | branch_name = f"delete-recipe-{hash}" 65 | run_command(["git", "switch", "--create", branch_name]) 66 | run_command(["git", "rm", "-r", str(recipe)]) 67 | run_command( 68 | ["git", "commit", "--message", f"Delete recipe '{recipe.name}'"] 69 | ) 70 | run_command(["git", "push", "--set-upstream", "origin", branch_name]) 71 | 72 | # Create the pull request 73 | title = f"Delete recipe {recipe.name}" 74 | pr = repo.create_pull(title=title, body=body, head=branch_name, base="main") 75 | print(f"Created PR: {pr.html_url}") 76 | 77 | # Remove the failed_at entry for this recipe 78 | for key in failed_compatibility: 79 | if recipe.name in failed_compatibility[key]: 80 | del failed_compatibility[key][recipe.name] 81 | 82 | except Exception as e: 83 | # If there's an error, print it and move on to the next recipe 84 | eprint(f"Error processing {recipe}: {e}") 85 | exit_code = 1 86 | continue 87 | 88 | # Iterate through each failed compatibility file 89 | for failed_compatibility_file in failed_compatibility_files: 90 | # Save the updated failed compatibility data 91 | with failed_compatibility_file.open("w") as file: 92 | yaml.safe_dump(failed_compatibility[failed_compatibility_file], file) 93 | 94 | # Commit and push changes to the failed compatibility file 95 | run_command(["git", "add", str(failed_compatibility_file)]) 96 | 97 | # Commit and push all changes 98 | commit_push_changes("Update failed compatibility files", "main") 99 | 100 | sys.exit(exit_code) 101 | 102 | 103 | if __name__ == "__main__": 104 | main() 105 | -------------------------------------------------------------------------------- /recipes/mojo_csv/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ![language](https://img.shields.io/badge/language-mojo-orange) 6 | ![license](https://badgen.net/static/license/MIT/red) 7 | 8 | # Mojo Csv 9 | 10 | Csv parsing library written in pure Mojo 11 | 12 | ### usage 13 | 14 | Add the Modular community channel (https://repo.prefix.dev/modular-community) to your pixi.toml file in the channels section. 15 | 16 | ```title:pixi.toml 17 | channels = ["conda-forge", "https://conda.modular.com/max", "https://repo.prefix.dev/modular-community"] 18 | ``` 19 | 20 | 21 | ```sh 22 | pixi add mojo_csv 23 | ``` 24 | 25 | ## Usage 26 | 27 | 28 | By default uses all logical cores - 2 29 | ```mojo 30 | CsvReader( 31 | in_csv: Path, 32 | delimiter: String = ",", 33 | quotation_mark: String = '"', 34 | num_threads: Int = 0, # default = 0 = use all available cores - 2 35 | ) 36 | ``` 37 | 38 | ```mojo 39 | from mojo_csv import CsvReader 40 | from pathlib import Path 41 | from sys import exit 42 | 43 | fn main() raises: 44 | var csv_path = Path("path/to/csv/file.csv") 45 | try: 46 | var reader = CsvReader(csv_path) 47 | except: 48 | exit() 49 | for i in range(len(reader)): 50 | print(reader[i]) 51 | ``` 52 | 53 | 54 | ### Delimiters 55 | 56 | ```mojo 57 | CsvReader(csv_path, delimiter=";", quotation_mark='|') 58 | ``` 59 | 60 | ### Threads 61 | __force single threaded__ 62 | ```mojo 63 | CsvReader(csv_pash, num_threads = 1) 64 | ``` 65 | __use all the threads__ 66 | ```mojo 67 | from sys import num_logical_cores 68 | 69 | var reader = CsvReader( 70 | csv_path, num_threads = num_logical_cores() 71 | ) 72 | ``` 73 | 74 | 75 | ### Attributes 76 | 77 | ```mojo 78 | reader.raw : String # raw csv string 79 | reader.raw_length : Int # total number of Chars 80 | reader.headers : List[String] # first row of csv file 81 | reader.row_count : Int # total number of rows T->B 82 | reader.column_count : Int # total number of columns L->R 83 | reader.elements : List[String] # all delimited elements 84 | reader.length : Int # total number of elements 85 | ```` 86 | 87 | ### Indexing 88 | 89 | currently the array is only 1D, so indexing is fairly manual. 90 | 91 | ```Mojo 92 | reader[0] # first element 93 | ``` 94 | 95 | ### Performance 96 | 97 | - average times over 100-1k iterations 98 | - AMD 7950x@5.8ghz 99 | - single-threaded 100 | 101 | micro file benchmark (3 rows) 102 | mini (100 rows) 103 | small (1k rows) 104 | medium file benchmark (100k rows) 105 | large file benchmark (2m rows) 106 | 107 | ```log 108 | ✨ Pixi task (bench): mojo bench.mojo running benchmark for micro csv: 109 | average time in ms for micro file: 110 | 0.0094 ms 111 | ------------------------- 112 | running benchmark for mini csv: 113 | average time in ms for mini file: 114 | 0.0657 ms 115 | ------------------------- 116 | running benchmark for small csv: 117 | average time in ms for small file: 118 | 0.317 ms 119 | ------------------------- 120 | running benchmark for medium csv: 121 | average time in ms for medium file: 122 | 24.62 ms 123 | ------------------------- 124 | running benchmark for large csv: 125 | average time in ms for large file: 126 | 878.6 ms 127 | ``` 128 | 129 | #### CSV Reader Performance Comparison 130 | ``` 131 | Small file benchmark (1,000 rows): 132 | Single-threaded: 133 | Average time: 0.455 ms 134 | Multi-threaded: 135 | Average time: 0.3744 ms 136 | Speedup: 1.22 x 137 | 138 | Medium file benchmark (100,000 rows): 139 | Single-threaded: 140 | Average time: 37.37 ms 141 | Multi-threaded: 142 | Average time: 24.46 ms 143 | Speedup: 1.53 x 144 | 145 | Large file benchmark (2,000,000 rows): 146 | Single-threaded: 147 | Average time: 1210.3 ms 148 | Multi-threaded: 149 | Average time: 863.9 ms 150 | Speedup: 1.4 x 151 | 152 | Summary: 153 | Small file speedup: 1.22 x 154 | Medium file speedup: 1.53 x 155 | Large file speedup: 1.4 x 156 | ``` 157 | 158 | 159 | ## Future Improvements 160 | 161 | - [ ] 2D indexing 162 | - [ ] CsvWriter 163 | - [ ] CsvDictReader 164 | - [ ] SIMD optimization within each thread 165 | - [ ] Async Chunking 166 | - [ ] Streaming support for very large files 167 | - [ ] Memory pool for reduced allocations 168 | - [ ] Progress callbacks for long-running operation 169 | -------------------------------------------------------------------------------- /recipes/infrared/test.mojo: -------------------------------------------------------------------------------- 1 | from testing import assert_true, assert_false, assert_equal, assert_not_equal 2 | from infrared.algebra import * 3 | 4 | 5 | def main(): 6 | test_eq() 7 | test_ne() 8 | test_subspace_constructor() 9 | test_getattr() 10 | test_normalized() 11 | test_add() 12 | test_sub() 13 | test_mul() 14 | test_sandwich() 15 | 16 | 17 | def test_eq(): 18 | alias g3 = Signature(3, 0, 0) 19 | assert_true(Multivector[g3, g3.empty_mask()]().__eq__(Float64(0))) 20 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__eq__(Multivector[g3, g3.vector_mask()](1, 2, 3))) 21 | assert_false(Multivector[g3, g3.vector_mask()](1, 2, 3).__eq__(Multivector[g3, g3.vector_mask()](1, 4, 3))) 22 | assert_false(Multivector[g3, g3.vector_mask()](1, 2, 3).__eq__(Float64(1))) 23 | 24 | 25 | def test_ne(): 26 | alias g3 = Signature(3, 0, 0) 27 | assert_false(Multivector[g3, g3.empty_mask()]().__ne__(Float64(0))) 28 | assert_false(Multivector[g3, g3.vector_mask()](1, 2, 3).__ne__(Multivector[g3, g3.vector_mask()](1, 2, 3))) 29 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__ne__(Multivector[g3, g3.vector_mask()](1, 4, 3))) 30 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__ne__(Float64(1))) 31 | 32 | 33 | def test_subspace_constructor(): 34 | assert_true(scalar[G3](6) == Multivector[G3](6)) 35 | assert_true(scalar[G3](0) != Multivector[G3](6)) 36 | 37 | assert_true(vector[G3](1, 2, 3) == Multivector[G3, G3.vector_mask()](1, 2, 3)) 38 | assert_true(vector[G3](1, 0, 3) != Multivector[G3, G3.vector_mask()](1, 2, 3)) 39 | 40 | assert_true(bivector[G3](4, 5, 6) == Multivector[G3, G3.bivector_mask()](4, 5, 6)) 41 | assert_true(bivector[G3](4, 0, 6) != Multivector[G3, G3.bivector_mask()](4, 5, 6)) 42 | 43 | 44 | def test_getattr(): 45 | alias g3 = Signature(3, 0, 0) 46 | alias scalar_vector_mask = List(True, True, True, True, False, False, False, False) 47 | assert_equal(Multivector[g3](6).s, Float64(6)) 48 | assert_equal(Multivector[g3, g3.vector_mask()](7, 8, 9).s, Float64(0)) 49 | assert_equal(Multivector[g3, scalar_vector_mask](6, 7, 8, 9).s, Float64(6)) 50 | 51 | 52 | def test_normalized(): 53 | alias g3 = Signature(3, 0, 0) 54 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).normalized() == Multivector[g3, g3.vector_mask()](0.2672612419124244, 0.53452248382484879, 0.80178372573727319)) 55 | assert_true(Multivector[g3, g3.bivector_mask()](1, 2, 3).normalized() == Multivector[g3, g3.bivector_mask()](0.2672612419124244, 0.53452248382484879, 0.80178372573727319)) 56 | 57 | 58 | def test_add(): 59 | alias g3 = Signature(3, 0, 0) 60 | alias scalar_vector_mask = List(True, True, True, True, False, False, False, False) 61 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__add__(Multivector[g3, g3.vector_mask()](1, 2, 3)) == Multivector[g3, g3.vector_mask()](2, 4, 6)) 62 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__add__(Float64(1)) == Multivector[g3, scalar_vector_mask](1, 1, 2, 3)) 63 | 64 | 65 | def test_sub(): 66 | alias g3 = Signature(3, 0, 0) 67 | alias scalar_vector_mask = List(True, True, True, True, False, False, False, False) 68 | assert_true(Multivector[g3, g3.vector_mask()](2, 4, 6).__sub__(Multivector[g3, g3.vector_mask()](1, 2, 3)) == Multivector[g3, g3.vector_mask()](1, 2, 3)) 69 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__sub__(Float64(1)) == Multivector[g3, scalar_vector_mask](-1, 1, 2, 3)) 70 | 71 | 72 | def test_mul(): 73 | alias g3 = Signature(3, 0, 0) 74 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__mul__(Float64(2)) == Multivector[g3, g3.vector_mask()](2, 4, 6)) 75 | assert_true(Multivector[g3, g3.vector_mask()](1, 2, 3).__mul__(Multivector[g3, g3.antiscalar_mask()](1)) == Multivector[g3, g3.bivector_mask()](3, -2, 1)) 76 | 77 | alias ug3 = Signature(1, 1, 1, flip_ze = False) 78 | alias v1_mask = List(False, True, False, False, False, False, False, False) 79 | assert_true(Multivector[ug3, v1_mask](2).__mul__(Multivector[ug3, v1_mask](2)) == Float64(4)) 80 | alias v2_mask = List(False, False, True, False, False, False, False, False) 81 | assert_true(Multivector[ug3, v2_mask](2).__mul__(Multivector[ug3, v2_mask](2)) == Float64(-4)) 82 | alias v3_mask = List(False, False, False, True, False, False, False, False) 83 | assert_true(Multivector[ug3, v3_mask](2).__mul__(Multivector[ug3, v3_mask](2)) == Float64(0)) 84 | 85 | alias pg3 = Signature(3, 0, 1, flip_ze = True) 86 | alias niltrivector_mask = List(False, False, False, False, False, False, False, False, False, False, False, True, True, True, False, False) 87 | assert_true(Multivector[pg3, pg3.vector_mask()](1, 2, 3, 4).__mul__(Float64(2)) == Multivector[pg3, pg3.vector_mask()](2, 4, 6, 8)) 88 | assert_true(Multivector[pg3, pg3.vector_mask()](1, 2, 3, 4).__mul__(Multivector[pg3, pg3.antiscalar_mask()](1)) == Multivector[pg3, niltrivector_mask](-4, 3, -2)) 89 | 90 | 91 | def test_sandwich(): 92 | alias g3 = Signature(3, 0, 0) 93 | assert_true(Multivector[g3, g3.even_mask()](0, 1, 0, 0)(Multivector[g3, g3.vector_mask()](1, 2, 3)) == Multivector[g3, g3.vector_mask()](-1, -2, 3)) -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/Matrix.mojo: -------------------------------------------------------------------------------- 1 | from .mojmelo_matmul import matmul 2 | from memory import memcpy, memset_zero 3 | import random 4 | 5 | struct Matrix(Copyable, Movable, ImplicitlyCopyable, Sized): 6 | var height: Int 7 | var width: Int 8 | var size: Int 9 | var data: UnsafePointer[Float32, MutAnyOrigin] 10 | var order: String 11 | 12 | # initialize from UnsafePointer 13 | @always_inline 14 | fn __init__[src: DType = DType.float32](out self, data: UnsafePointer[Scalar[src], MutAnyOrigin], height: Int, width: Int, order: String = 'c'): 15 | self.height = height 16 | self.width = width 17 | self.size = height * width 18 | if src == DType.float32: 19 | self.data = data.bitcast[Float32]() 20 | else: 21 | self.data = cast[src=src, des=DType.float32, width=self.simd_width](data, self.size) 22 | data.free() 23 | self.order = order.lower() 24 | 25 | # initialize by copying from UnsafePointer 26 | @always_inline 27 | fn __init__(out self, height: Int, width: Int, data: UnsafePointer[Float32, MutAnyOrigin] = UnsafePointer[Float32, MutAnyOrigin](), order: String = 'c'): 28 | self.height = height 29 | self.width = width 30 | self.size = height * width 31 | self.data = alloc[Float32](self.size) 32 | self.order = order.lower() 33 | if data: 34 | memcpy(dest=self.data, src=data, count=self.size) 35 | 36 | fn __copyinit__(out self, other: Self): 37 | self.height = other.height 38 | self.width = other.width 39 | self.size = other.size 40 | self.data = alloc[Float32](self.size) 41 | self.order = other.order 42 | memcpy(dest=self.data, src=other.data, count=self.size) 43 | 44 | fn __moveinit__(out self, deinit existing: Self): 45 | self.height = existing.height 46 | self.width = existing.width 47 | self.size = existing.size 48 | self.data = existing.data 49 | self.order = existing.order 50 | #existing.height = existing.width = existing.size = 0 51 | #existing.order = '' 52 | #existing.data = UnsafePointer[Float32, MutAnyOrigin]() 53 | 54 | # access an element 55 | @always_inline 56 | fn __getitem__(self, row: Int, column: Int) raises -> Float32: 57 | var loc: Int 58 | if self.order == 'c': 59 | loc = (row * self.width) + column 60 | else: 61 | loc = (column * self.height) + row 62 | if loc > self.size - 1 or loc < 0: 63 | raise Error("Location is out of range!") 64 | return self.data[loc] 65 | 66 | @always_inline 67 | fn __del__(deinit self): 68 | if self.data: 69 | self.data.free() 70 | 71 | @always_inline 72 | fn __len__(self) -> Int: 73 | return self.size 74 | 75 | @always_inline 76 | fn __mul__(self, rhs: Self) raises -> Self: 77 | if self.width != rhs.height: 78 | raise Error('Error: Cannot multiply matrices with shapes (' + String(self.height) + ', ' + String(self.width) + ') and (' + String(rhs.height) + ', ' + String(rhs.width) + ')') 79 | 80 | if self.height == 1 and rhs.width == 1: 81 | # Dot product 82 | var mat = Self(1, 1) 83 | mat.data[0] = self.ele_mul(rhs.T()).sum() 84 | return mat^ 85 | 86 | if self.height * self.width * rhs.width <= 4096: 87 | # matmul naive 88 | var mat = Self(self.height, rhs.width) 89 | for i in range(self.size): 90 | var rhsr = i % self.width 91 | for j in range(rhsr * rhs.width, rhsr * rhs.width + rhs.width): 92 | if rhsr != 0: 93 | mat.data[(Int(i / self.width) * mat.width) + (j % rhs.width)] += self.data[i] * rhs.data[j] 94 | else: 95 | mat.data[(Int(i / self.width) * mat.width) + (j % rhs.width)] = self.data[i] * rhs.data[j] 96 | return mat^ 97 | var A = matmul.Matrix[DType.float32](self.data, (self.height, self.width)) 98 | var B = matmul.Matrix[DType.float32](rhs.data, (rhs.height, rhs.width)) 99 | var C = matmul.Matrix[DType.float32]((self.height, rhs.width)) 100 | memset_zero(C.data, self.height * rhs.width) 101 | matmul.matmul(self.height, self.width, rhs.width, C, A, B) 102 | return Matrix(C.data, self.height, rhs.width) 103 | 104 | @always_inline 105 | fn __imul__(mut self, rhs: Self) raises: 106 | self = self * rhs 107 | 108 | @staticmethod 109 | @always_inline 110 | fn zeros(height: Int, width: Int, order: String = 'c') -> Matrix: 111 | var mat = Matrix(height, width, order= order) 112 | memset_zero(mat.data, mat.size) 113 | return mat^ 114 | 115 | @staticmethod 116 | fn random(height: Int, width: Int, order: String = 'c') -> Matrix: 117 | random.seed() 118 | var mat = Matrix(height, width, order= order) 119 | random.rand(mat.data, mat.size, min=0.0, max=1.0) 120 | return mat^ 121 | -------------------------------------------------------------------------------- /recipes/mojo-regex/README.md: -------------------------------------------------------------------------------- 1 | # Mojo Regex 2 | Regular Expressions Library for Mojo 3 | 4 | `mojo-regex` is a regex library featuring a hybrid DFA/NFA engine architecture that automatically optimizes pattern matching based on complexity. 5 | 6 | It aims to provide a similar interface as the [re](https://docs.python.org/3/library/re.html) stdlib package while leveraging Mojo's performance capabilities. 7 | 8 | ## Disclaimer ⚠️ 9 | 10 | This software is in an early stage of development. Even though it is functional, it is not yet feature-complete and may contain bugs. Check the features section below and the TO-DO sections for the current status 11 | 12 | ## Implemented Features 13 | 14 | ### Basic Elements 15 | - ✅ Literal characters (`a`, `hello`) 16 | - ✅ Wildcard (`.`) - matches any character except newline 17 | - ✅ Whitespace (`\s`) - matches space, tab, newline, carriage return, form feed 18 | - ✅ Escape sequences (`\t` for tab, `\\` for literal backslash) 19 | 20 | ### Character Classes 21 | - ✅ Character ranges (`[a-z]`, `[0-9]`, `[A-Za-z0-9]`) 22 | - ✅ Negated ranges (`[^a-z]`, `[^0-9]`) 23 | - ✅ Mixed character sets (`[abc123]`) 24 | - ✅ Character ranges within groups (`(b|[c-n])`) 25 | 26 | ### Quantifiers 27 | - ✅ Zero or more (`*`) 28 | - ✅ One or more (`+`) 29 | - ✅ Zero or one (`?`) 30 | - ✅ Exact count (`{3}`) 31 | - ✅ Range count (`{2,4}`) 32 | - ✅ Minimum count (`{2,}`) 33 | - ✅ Quantifiers on all elements (characters, wildcards, ranges, groups) 34 | 35 | ### Anchors 36 | - ✅ Start of string (`^`) 37 | - ✅ End of string (`$`) 38 | - ✅ Anchors in OR expressions (`^na|nb$`) 39 | 40 | ### Groups and Alternation 41 | - ✅ Capturing groups (`(abc)`) 42 | - ✅ Alternation/OR (`a|b`) 43 | - ✅ Complex OR patterns (`(a|b)`, `na|nb`) 44 | - ✅ Nested alternations (`(b|[c-n])`) 45 | - ✅ Group quantifiers (`(a)*`, `(abc)+`) 46 | 47 | ### Engine Features 48 | - ✅ **Hybrid DFA/NFA Architecture** - Automatic engine selection for optimal performance 49 | - ✅ **O(n) Performance** - DFA engine for simple patterns (literals, basic quantifiers, character classes) 50 | - ✅ **Full Regex Support** - NFA engine with backtracking for complex patterns 51 | - ✅ **Pattern Complexity Analysis** - Intelligent routing between engines 52 | - ✅ **SIMD Optimization** - Vectorized character class matching 53 | - ✅ **Pattern Compilation Caching** - Pre-compiled patterns for reuse 54 | - ✅ **Match Position Tracking** - Precise start_idx, end_idx reporting 55 | - ✅ **Simple API**: `match_first(pattern, text) -> Optional[Match]` 56 | 57 | ## Installation 58 | 59 | 1. **Install [pixi](https://pixi.sh/latest/)** 60 | 61 | 2. **Add the Package** (at the top level of your project): 62 | 63 | ```bash 64 | pixi add mojo-regex 65 | ``` 66 | 67 | ## Example Usage 68 | 69 | ```mojo 70 | from regex import match_first, findall 71 | 72 | # Basic literal matching 73 | var result = match_first("hello", "hello world") 74 | if result: 75 | print("Match found:", result.value().match_text) 76 | 77 | # Find all matches 78 | var matches = findall("a", "banana") 79 | print("Found", len(matches), "matches:") 80 | for i in range(len(matches)): 81 | print(" Match", i, ":", matches[i].match_text, "at position", matches[i].start_idx) 82 | 83 | # Wildcard and quantifiers 84 | result = match_first(".*@.*", "user@domain.com") 85 | if result: 86 | print("Email found") 87 | 88 | # Find all numbers in text 89 | var numbers = findall("[0-9]+", "Price: $123, Quantity: 456, Total: $579") 90 | for i in range(len(numbers)): 91 | print("Number found:", numbers[i].match_text) 92 | 93 | # Character ranges 94 | result = match_first("[a-z]+", "hello123") 95 | if result: 96 | print("Letters:", result.value().match_text) 97 | 98 | # Groups and alternation 99 | result = match_first("(com|org|net)", "example.com") 100 | if result: 101 | print("TLD found:", result.value().match_text) 102 | 103 | # Find all domains in text 104 | var domains = findall("(com|org|net)", "Visit example.com or test.org for more info") 105 | for i in range(len(domains)): 106 | print("Domain found:", domains[i].match_text) 107 | 108 | # Anchors 109 | result = match_first("^https?://", "https://example.com") 110 | if result: 111 | print("Valid URL") 112 | 113 | # Complex patterns 114 | result = match_first("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", "user@example.com") 115 | if result: 116 | print("Valid email format") 117 | 118 | # Find all email addresses in text 119 | var emails = findall("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", "Contact john@example.com or mary@test.org") 120 | for i in range(len(emails)): 121 | print("Email found:", emails[i].match_text) 122 | ``` 123 | 124 | ## Building and Testing 125 | 126 | ```bash 127 | # Build the package 128 | ./tools/build.sh 129 | 130 | # Run tests 131 | ./tools/run-tests.sh 132 | 133 | # Or run specific test 134 | mojo test -I src/ tests/test_matcher.mojo 135 | 136 | # Run benchmarks to see performance including SIMD optimizations 137 | mojo benchmarks/bench_engine.mojo 138 | ``` 139 | 140 | ## TO-DO: Missing Features 141 | 142 | ### High Priority 143 | - [x] Global matching (`findall()`) 144 | - [x] Hybrid DFA/NFA engine architecture 145 | - [x] Pattern complexity analysis and optimization 146 | - [x] SIMD-accelerated character class matching 147 | - [x] SIMD-accelerated literal string search 148 | - [x] SIMD capability detection and automatic routing 149 | - [x] Vectorized quantifier processing for character classes 150 | - [ ] Non-capturing groups (`(?:...)`) 151 | - [ ] Named groups (`(?...)` or `(?P...)`) 152 | - [ ] Predefined character classes (`\d`, `\w`, `\S`, `\D`, `\W`) 153 | - [ ] Case insensitive matching options 154 | - [ ] Match replacement (`sub()`, `gsub()`) 155 | - [ ] String splitting (`split()`) 156 | 157 | ### Medium Priority 158 | - [ ] Non-greedy quantifiers (`*?`, `+?`, `??`) 159 | - [ ] Word boundaries (`\b`, `\B`) 160 | - [ ] Match groups extraction and iteration 161 | - [ ] Pattern compilation object 162 | - [ ] Unicode character classes (`\p{L}`, `\p{N}`) 163 | - [ ] Multiline mode (`^` and `$` match line boundaries) 164 | - [ ] Dot-all mode (`.` matches newlines) 165 | 166 | ### Advanced Features 167 | - [ ] Positive lookahead (`(?=...)`) 168 | - [ ] Negative lookahead (`(?!...)`) 169 | - [ ] Positive lookbehind (`(?<=...)`) 170 | - [ ] Negative lookbehind (`(?...)`) 173 | - [ ] Possessive quantifiers (`*+`, `++`) 174 | - [ ] Conditional expressions (`(?(condition)yes|no)`) 175 | - [ ] Recursive patterns 176 | - [ ] Subroutine calls 177 | 178 | ### Engine Improvements 179 | - [x] Hybrid DFA/NFA architecture with automatic engine selection 180 | - [x] O(n) DFA engine for simple patterns 181 | - [x] SIMD optimization for character class matching and literal string search 182 | - [x] Pattern complexity analysis for optimal routing 183 | - [x] SIMD capability detection for intelligent engine selection 184 | - [x] Vectorized operations for quantifiers and repetition counting 185 | - [ ] Additional DFA pattern support (more complex quantifiers and groups) 186 | - [ ] Compile-time pattern specialization for string literals 187 | - [ ] Aho-Corasick multi-pattern matching for alternations 188 | - [ ] Advanced NFA optimizations (lazy quantifiers, cut operators) 189 | - [ ] Parallel matching for multiple patterns 190 | 191 | ## Contributing 192 | 193 | Contributions are welcome! If you'd like to contribute, please follow the contribution guidelines in the [CONTRIBUTING.md](CONTRIBUTING.md) file in the repository. 194 | 195 | ## Acknowledgments 196 | 197 | Thanks to Claude Code for helping a lot with the implementation and testing of the mojo-regex library, and to the Mojo community for their support and feedback. 198 | 199 | ## License 200 | 201 | mojo is licensed under the [MIT license](LICENSE). 202 | -------------------------------------------------------------------------------- /recipes/numojo/README.md: -------------------------------------------------------------------------------- 1 | # NuMojo 2 | 3 | ![logo](./image.jpeg) 4 | 5 | NuMojo is a library for numerical computing in Mojo 🔥 similar to NumPy, SciPy in Python. 6 | 7 | **[Explore the docs»](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo-Examples-and-Benchmarks/blob/main/docs/README.md)** | **[Changelog»](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/docs/changelog.md)** | **[Check out our Discord»](https://discord.gg/NcnSH5n26F)** 8 | 9 | **[中文·简»](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/docs/readme_zhs.md)** | **[中文·繁»](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/docs/readme_zht.md)** | **[日本語»](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/docs/readme_jp.md)** 10 | 11 | **Table of Contents** 12 | 13 | 1. [About The Project](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#about-the-project) 14 | 2. [Goals](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#goals) 15 | 3. [Usage](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#usage) 16 | 4. [How to install](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#how-to-install) 17 | 5. [Contributing](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#contributing) 18 | 6. [Warnings](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#warnings) 19 | 7. [License](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#license) 20 | 8. [Acknowledgements](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#acknowledgments) 21 | 9. [Contributors](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/README.MD#Contributors) 22 | 23 | ## About the project 24 | 25 | NuMojo aims to encompass the extensive numerics capabilities found in Python packages such as NumPy, SciPy, and Scikit-learn. 26 | 27 | ======= 28 | 29 | ***What NuMojo is*** 30 | 31 | We seek to harness the full potential of Mojo, including vectorization, parallelization, and GPU acceleration (when available). Currently, NuMojo extends most (if not all) standard library math functions to support array inputs. 32 | 33 | Our vision for NuMojo is to serve as an essential building block for other Mojo packages needing fast math operations, without the additional weight of a machine learning back-propagation system. 34 | 35 | ***What NuMojo is not*** 36 | 37 | NuMojo is not a machine learning library and will never include back-propagation as part of the base library. 38 | 39 | ## Features and goals 40 | 41 | Our primary objective is to develop a fast, comprehensive numerics library in Mojo. Below are some features and long-term goals. Some have already been implemented, either fully or partially. 42 | 43 | Core data types: 44 | 45 | - Native n-dimensional array (`numojo.NDArray`). 46 | - Native 2-dimensional array, i.e., matrix (`numojo.Matrix`). 47 | - Native n-dimensional complex array (`numojo.ComplexNDArray`) 48 | - Native fixed-dimension array (to be implemented when trait parameterization is available). 49 | 50 | Routines and objects: 51 | 52 | - Array creation routines (`numojo.creation`) 53 | - Array manipulation routines (`numojo.manipulation`) 54 | - Input and output (`numojo.io`) 55 | - Linear algebra (`numojo.linalg`) 56 | - Logic functions (`numojo.logic`) 57 | - Mathematical functions (`numojo.math`) 58 | - Exponents and logarithms (`numojo.exponents`) 59 | - Extrema finding (`numojo.extrema`) 60 | - Rounding (`numojo.rounding`) 61 | - Trigonometric functions (`numojo.trig`) 62 | - Random sampling (`numojo.random`) 63 | - Sorting and searching (`numojo.sorting`, `numojo.searching`) 64 | - Statistics (`numojo.statistics`) 65 | - etc... 66 | 67 | Please find all the available functions and objects [here](docs/features.md). 68 | 69 | For a detailed roadmap, please refer to the [docs/roadmap.md](docs/roadmap.md) file. 70 | 71 | ## Usage 72 | 73 | An example of n-dimensional array (`NDArray` type) goes as follows. 74 | 75 | ```mojo 76 | import numojo as nm 77 | from numojo.prelude import * 78 | 79 | 80 | fn main() raises: 81 | # Generate two 1000x1000 matrices with random float64 values 82 | var A = nm.random.randn(Shape(1000, 1000)) 83 | var B = nm.random.randn(Shape(1000, 1000)) 84 | 85 | # Generate a 3x2 matrix from string representation 86 | var X = nm.fromstring[f32]("[[1.1, -0.32, 1], [0.1, -3, 2.124]]") 87 | 88 | # Print array 89 | print(A) 90 | 91 | # Array multiplication 92 | var C = A @ B 93 | 94 | # Array inversion 95 | var I = nm.inv(A) 96 | 97 | # Array slicing 98 | var A_slice = A[1:3, 4:19] 99 | 100 | # Get scalar from array 101 | var A_item = A[item(291, 141)] 102 | var A_item_2 = A.item(291, 141) 103 | ``` 104 | 105 | An example of matrix (`Matrix` type) goes as follows. 106 | 107 | ```mojo 108 | from numojo import Matrix 109 | from numojo.prelude import * 110 | 111 | 112 | fn main() raises: 113 | # Generate two 1000x1000 matrices with random float64 values 114 | var A = Matrix.rand(shape=(1000, 1000)) 115 | var B = Matrix.rand(shape=(1000, 1000)) 116 | 117 | # Generate 1000x1 matrix (column vector) with random float64 values 118 | var C = Matrix.rand(shape=(1000, 1)) 119 | 120 | # Generate a 4x3 matrix from string representation 121 | var F = Matrix.fromstring[i8]( 122 | "[[12,11,10],[9,8,7],[6,5,4],[3,2,1]]", shape=(4, 3) 123 | ) 124 | 125 | # Matrix slicing 126 | var A_slice = A[1:3, 4:19] 127 | var B_slice = B[255, 103:241:2] 128 | 129 | # Get scalar from matrix 130 | var A_item = A[291, 141] 131 | 132 | # Flip the column vector 133 | print(C[::-1, :]) 134 | 135 | # Sort and argsort along axis 136 | print(nm.sort(A, axis=1)) 137 | print(nm.argsort(A, axis=0)) 138 | 139 | # Sum the matrix 140 | print(nm.sum(B)) 141 | print(nm.sum(B, axis=1)) 142 | 143 | # Matrix multiplication 144 | print(A @ B) 145 | 146 | # Matrix inversion 147 | print(A.inv()) 148 | 149 | # Solve linear algebra 150 | print(nm.solve(A, B)) 151 | 152 | # Least square 153 | print(nm.lstsq(A, C)) 154 | ``` 155 | 156 | An example of ComplexNDArray is as follows, 157 | 158 | ```mojo 159 | import numojo as nm 160 | from numojo.prelude import * 161 | 162 | 163 | fn main() raises: 164 | # Create a complexscalar 5 + 5j 165 | var complexscalar = ComplexSIMD[cf32](re=5, im=5) 166 | # Create complex array filled with (5 + 5j) 167 | var A = nm.full[cf32](Shape(1000, 1000), fill_value=complexscalar) 168 | # Create complex array filled with (1 + 1j) 169 | var B = nm.ones[cf32](Shape(1000, 1000)) 170 | 171 | # Print array 172 | print(A) 173 | 174 | # Array slicing 175 | var A_slice = A[1:3, 4:19] 176 | 177 | # Array multiplication 178 | var C = A * B 179 | 180 | # Get scalar from array 181 | var A_item = A[item(291, 141)] 182 | # Set an element of the array 183 | A[item(291, 141)] = complexscalar 184 | ``` 185 | 186 | ## How to install 187 | 188 | There are three approach to install and use the Numojo package. 189 | 190 | ### Use pixi CLI 191 | 192 | You can use the following command in the terminal to install `numojo`. 193 | 194 | ```console 195 | pixi add numojo 196 | ``` 197 | 198 | ### Add in toml file 199 | 200 | You can add `pixi` in the dependencies section of your toml file. 201 | 202 | ```toml 203 | [dependencies] 204 | pixi = "==0.7.0" 205 | ``` 206 | 207 | ### Build package 208 | 209 | This approach involves building a standalone package file `mojopkg`. 210 | 211 | 1. Clone the repository. 212 | 2. Build the package using `pixi run package`. 213 | 3. Move the `numojo.mojopkg` into the directory containing the your code. 214 | 215 | ### Include NuMojo's path for compiler and LSP 216 | 217 | This approach does not require building a package file. Instead, when you compile your code, you can include the path of NuMojo repository with the following command: 218 | 219 | ```console 220 | mojo run -I "../NuMojo" example.mojo 221 | ``` 222 | 223 | This is more flexible as you are able to edit the NuMojo source files when testing your code. 224 | 225 | In order to allow VSCode LSP to resolve the imported `numojo` package, you can: 226 | 227 | 1. Go to preference page of VSCode. 228 | 2. Go to `Mojo › Lsp: Include Dirs` 229 | 3. Click `add item` and write the path where the Numojo repository is located, e.g. `/Users/Name/Programs/NuMojo`. 230 | 4. Restart the Mojo LSP server. 231 | 232 | Now VSCode can show function hints for the Numojo package! 233 | 234 | ## Contributing 235 | 236 | Any contributions you make are **greatly appreciated**. For more details and guidelines on contributions, please check [here](CONTRIBUTING.md) 237 | 238 | ## Warnings 239 | 240 | This library is still very much a work in progress and may change at any time. 241 | 242 | ## License 243 | 244 | Distributed under the Apache 2.0 License with LLVM Exceptions. See [LICENSE](https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo/blob/main/LICENSE) and the LLVM [License](https://llvm.org/LICENSE.txt) for more information. 245 | 246 | This project includes code from [Mojo Standard Library](https://github.com/modularml/mojo), licensed under the Apache License v2.0 with LLVM Exceptions (see the LLVM [License](https://llvm.org/LICENSE.txt)). MAX and Mojo usage and distribution are licensed under the [MAX & Mojo Community License](https://www.modular.com/legal/max-mojo-license). 247 | 248 | ## Acknowledgements 249 | 250 | Built in native [Mojo](https://github.com/modularml/mojo) which was created by [Modular](https://github.com/modularml). 251 | 252 | ## Contributors 253 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/setup.mojo: -------------------------------------------------------------------------------- 1 | from sys import external_call, CompilationTarget, argv 2 | from sys.ffi import * 3 | 4 | fn cachel1() -> Int32: 5 | var l1_cache_size: c_int = 0 6 | comptime length: c_size_t = 4 7 | # Get L1 Cache Size 8 | if external_call["sysctlbyname", c_int]("hw.perflevel0.l1dcachesize".unsafe_cstr_ptr(), UnsafePointer(to=l1_cache_size), UnsafePointer(to=length), OpaquePointer[MutOrigin.external](), 0) == 0: 9 | if l1_cache_size <= 1: 10 | if external_call["sysctlbyname", c_int]("hw.l1dcachesize".unsafe_cstr_ptr(), UnsafePointer(to=l1_cache_size), UnsafePointer(to=length), OpaquePointer[MutOrigin.external](), 0) == 0: 11 | if l1_cache_size <= 1: 12 | return 65536 13 | return l1_cache_size 14 | else: 15 | return 65536 16 | return l1_cache_size 17 | else: 18 | if external_call["sysctlbyname", c_int]("hw.l1dcachesize".unsafe_cstr_ptr(), UnsafePointer(to=l1_cache_size), UnsafePointer(to=length), OpaquePointer[MutOrigin.external](), 0) == 0: 19 | if l1_cache_size <= 1: 20 | return 65536 21 | return l1_cache_size 22 | else: 23 | return 65536 24 | 25 | 26 | fn cachel2() -> Int32: 27 | var l2_cache_size: c_int = 0 28 | comptime length: c_size_t = 4 29 | # Get L2 Cache Size 30 | if external_call["sysctlbyname", c_int]("hw.perflevel0.l2cachesize".unsafe_cstr_ptr(), UnsafePointer(to=l2_cache_size), UnsafePointer(to=length), OpaquePointer[MutOrigin.external](), 0) == 0: 31 | if l2_cache_size <= 1: 32 | if external_call["sysctlbyname", c_int]("hw.l2cachesize".unsafe_cstr_ptr(), UnsafePointer(to=l2_cache_size), UnsafePointer(to=length), OpaquePointer[MutOrigin.external](), 0) == 0: 33 | if l2_cache_size <= 1: 34 | return 4194304 35 | return l2_cache_size 36 | else: 37 | return 4194304 38 | return l2_cache_size 39 | else: 40 | if external_call["sysctlbyname", c_int]("hw.l2cachesize".unsafe_cstr_ptr(), UnsafePointer(to=l2_cache_size), UnsafePointer(to=length), OpaquePointer[MutOrigin.external](), 0) == 0: 41 | if l2_cache_size <= 1: 42 | return 4194304 43 | return l2_cache_size 44 | else: 45 | return 4194304 46 | 47 | 48 | fn initialize(cache_l1_size: Int, cache_l1_associativity: Int, cache_l2_size: Int, cache_l2_associativity: Int) raises: 49 | if cache_l1_associativity <= 1 or cache_l2_associativity <= 1: 50 | possible_l1_associativities = InlineArray[Int, 3](fill=0) 51 | if cache_l1_associativity > 1: 52 | possible_l1_associativities[0] = possible_l1_associativities[1] = possible_l1_associativities[2] = cache_l1_associativity 53 | else: 54 | possible_l1_associativities[0] = 4 if cache_l1_size < 65534 else 8 55 | possible_l1_associativities[1] = possible_l1_associativities[0] * 2 56 | possible_l1_associativities[2] = 12 57 | possible_l2_associativities = InlineArray[Int, 3](fill=0) 58 | if cache_l2_associativity > 1: 59 | possible_l2_associativities[0] = possible_l2_associativities[1] = possible_l2_associativities[2] = cache_l2_associativity 60 | else: 61 | possible_l2_associativities[0] = 4 if cache_l2_size <= 2097154 else 8 62 | possible_l2_associativities[1] = possible_l2_associativities[0] * 2 63 | possible_l2_associativities[2] = possible_l2_associativities[0] * 4 64 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 65 | code = 'comptime L1_CACHE_SIZE = ' + String(cache_l1_size) + '\n' 66 | code += 'comptime L1_ASSOCIATIVITY = ' + String(possible_l1_associativities[0]) + '\n' 67 | code += 'comptime L2_CACHE_SIZE = ' + String(cache_l2_size) + '\n' 68 | code += 'comptime L2_ASSOCIATIVITY = ' + String(possible_l2_associativities[0]) + '\n' 69 | f.write(code) 70 | for i in range(3): 71 | for j in range(1, 4): 72 | with open("./param" + String(i * 3 + j), "w") as f: 73 | code = 'comptime L1_CACHE_SIZE = ' + String(cache_l1_size) + '\n' 74 | code += 'comptime L1_ASSOCIATIVITY = ' + String(possible_l1_associativities[i]) + '\n' 75 | code += 'comptime L2_CACHE_SIZE = ' + String(cache_l2_size) + '\n' 76 | code += 'comptime L2_ASSOCIATIVITY = ' + String(possible_l2_associativities[j - 1]) + '\n' 77 | f.write(code) 78 | else: 79 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 80 | code = 'comptime L1_CACHE_SIZE = ' + String(cache_l1_size) + '\n' 81 | code += 'comptime L1_ASSOCIATIVITY = ' + String(cache_l1_associativity) + '\n' 82 | code += 'comptime L2_CACHE_SIZE = ' + String(cache_l2_size) + '\n' 83 | code += 'comptime L2_ASSOCIATIVITY = ' + String(cache_l2_associativity) + '\n' 84 | f.write(code) 85 | with open("./done", "w") as f: 86 | f.write("done") 87 | print('Setup initialization done!') 88 | 89 | fn main() raises: 90 | if len(argv()) == 1: 91 | cache_l1_size = 0 92 | cache_l2_size = 0 93 | cache_l1_associativity = 0 94 | cache_l2_associativity = 0 95 | if CompilationTarget.is_linux(): 96 | with open("/sys/devices/system/cpu/cpu0/cache/index0/size", "r") as f: 97 | txt = f.read() 98 | if txt.find('K') != -1: 99 | cache_l1_size = atol(txt.split('K')[0]) * 1024 100 | else: 101 | cache_l1_size = atol(txt.split('M')[0]) * 1048576 102 | try: 103 | with open("/sys/devices/system/cpu/cpu0/cache/index0/ways_of_associativity", "r") as f: 104 | cache_l1_associativity = atol(f.read()) 105 | except: 106 | cache_l1_associativity = 0 107 | with open("/sys/devices/system/cpu/cpu0/cache/index2/size", "r") as f: 108 | txt = f.read() 109 | if txt.find('K') != -1: 110 | cache_l2_size = atol(txt.split('K')[0]) * 1024 111 | else: 112 | cache_l2_size = atol(txt.split('M')[0]) * 1048576 113 | try: 114 | with open("/sys/devices/system/cpu/cpu0/cache/index2/ways_of_associativity", "r") as f: 115 | cache_l2_associativity = atol(f.read()) 116 | except: 117 | cache_l2_associativity = 0 118 | elif CompilationTarget.is_macos(): 119 | cache_l1_size = Int(cachel1()) 120 | cache_l2_size = Int(cachel2()) 121 | initialize(cache_l1_size, cache_l1_associativity, cache_l2_size, cache_l2_associativity) 122 | else: 123 | command = String(argv()[1]) 124 | 125 | import os 126 | 127 | if os.path.isfile('./done'): 128 | if command != '9': 129 | print('Setup', command + '/8', 'skipped!') 130 | else: 131 | os.remove("./done") 132 | print('Setup done!') 133 | return 134 | 135 | from mojmelo.utils.Matrix import Matrix 136 | import time 137 | 138 | comptime NUM_ITER = 16 139 | results = InlineArray[Int, 3](fill=0) 140 | var junk: Float32 = 0.0 141 | a = Matrix.random(512, 4096) 142 | b = Matrix.random(4096, 512) 143 | for i in range(NUM_ITER): 144 | start = time.perf_counter_ns() 145 | c = a * b 146 | finish = time.perf_counter_ns() 147 | junk += c[0, 0] 148 | if i != 0: 149 | results[0] += Int(finish - start) // (NUM_ITER - 1) 150 | a = Matrix.random(4096, 4096) 151 | b = Matrix.random(4096, 4096) 152 | for i in range(NUM_ITER): 153 | start = time.perf_counter_ns() 154 | c = a * b 155 | finish = time.perf_counter_ns() 156 | junk += c[0, 0] 157 | if i != 0: 158 | results[1] += Int(finish - start) // (NUM_ITER - 1) 159 | a = Matrix.random(4096, 512) 160 | b = Matrix.random(512, 4096) 161 | for i in range(NUM_ITER): 162 | start = time.perf_counter_ns() 163 | c = a * b 164 | finish = time.perf_counter_ns() 165 | junk += c[0, 0] 166 | if i != 0: 167 | results[2] += Int(finish - start) // (NUM_ITER - 1) 168 | if command != '9': 169 | with open("./results" + command, "w") as f: 170 | f.write(String(results[0]) + ',' + String(results[1]) + ',' + String(results[2]) + ',' + String(junk)) 171 | var code: String 172 | with open("./param" + String(Int(command) + 1), "r") as f: 173 | code = f.read() 174 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 175 | f.write(code) 176 | print('Setup', command + '/8', 'done!') 177 | else: 178 | results_list = List[InlineArray[Int, 3]]() 179 | for i in range(1, 9): 180 | with open("./results" + String(i), "r") as f: 181 | res = f.read().split(',') 182 | results_list.append(InlineArray[Int, 3](fill=0)) 183 | results_list[i - 1][0] = atol(res[0]) 184 | results_list[i - 1][1] = atol(res[1]) 185 | results_list[i - 1][2] = atol(res[2]) 186 | results_list.append(results) 187 | 188 | from collections import Counter 189 | 190 | votes = List[Int]() 191 | for i in range(3): 192 | _min = results_list[0][i] 193 | m_index = 0 194 | for j in range(9): 195 | if results_list[j][i] < _min: 196 | _min = results_list[j][i] 197 | m_index = j 198 | votes.append(m_index) 199 | 200 | var code: String 201 | with open("./param" + String(Counter[Int](votes).most_common(1)[0]._value + 1), "r") as f: 202 | code = f.read() 203 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 204 | f.write(code) 205 | 206 | for i in range(1, 10): 207 | os.remove("./param" + String(i)) 208 | if i != 9: 209 | os.remove("./results" + String(i)) 210 | print('Setup done!') 211 | -------------------------------------------------------------------------------- /recipes/decimojo/README.md: -------------------------------------------------------------------------------- 1 | # DeciMojo 2 | 3 | ![icon](icon_256x256.png) 4 | 5 | An arbitrary-precision decimal and integer mathematics library for [Mojo](https://www.modular.com/mojo). 6 | 7 | **[中文·漢字»](https://zhuyuhao.com/decimojo/docs/readme_zht.html)** | **[Repository on GitHub»](https://github.com/forfudan/decimojo)** | **[Changelog](https://zhuyuhao.com/decimojo/docs/changelog.html)** 8 | 9 | ## Overview 10 | 11 | DeciMojo provides an arbitrary-precision decimal and integer mathematics library for Mojo, delivering exact calculations for financial modeling, scientific computing, and applications where floating-point approximation errors are unacceptable. Beyond basic arithmetic, the library includes advanced mathematical functions with guaranteed precision. 12 | 13 | The core types are: 14 | 15 | - A 128-bit fixed-point decimal implementation (`Decimal`) supporting up to 29 significant digits with a maximum of 28 decimal places[^fixed]. It features a complete set of mathematical functions including logarithms, exponentiation, roots, etc. 16 | - An arbitrary-precision decimal implementation `BigDecimal` allowing for calculations with unlimited digits and decimal places[^arbitrary]. 17 | - A base-10 arbitrary-precision signed integer type (`BigInt`) and a base-10 arbitrary-precision unsigned integer type (`BigUInt`) supporting unlimited digits[^integer]. It features comprehensive arithmetic operations, comparison functions, and supports extremely large integer calculations efficiently. 18 | 19 | This repository includes [TOMLMojo](https://github.com/forfudan/decimojo/tree/main/src/tomlmojo), a lightweight TOML parser in pure Mojo. It parses configuration files and test data, supporting basic types, arrays, and nested tables. While created for DeciMojo's testing framework, it offers general-purpose structured data parsing with a clean, simple API. 20 | 21 | | type | alias | information | internal representation | 22 | | ------------ | ------- | ------------------------------------ | ----------------------------------- | 23 | | `BigUInt` | `BUInt` | arbitrary-precision unsigned integer | `List[UInt32]` | 24 | | `BigInt` | `BInt` | arbitrary-precision integer | `BigUInt`, `Bool` | 25 | | `Decimal` | `Dec` | 128-bit fixed-precision decimal | `UInt32`,`UInt32`,`UInt32`,`UInt32` | 26 | | `BigDecimal` | `BDec` | arbitrary-precision decimal | `BigUInt`, `Int`, `Bool` | 27 | 28 | ## Installation 29 | 30 | DeciMojo is available in the [modular-community](https://repo.prefix.dev/modular-community) package repository. You can install it using any of these methods: 31 | 32 | From the `magic` CLI, simply run ```magic add decimojo```. This fetches the latest version and makes it immediately available for import. 33 | 34 | For projects with a `mojoproject.toml`file, add the dependency ```decimojo = ">=0.3.0"```. Then run `magic install` to download and install the package. 35 | 36 | For the latest development version, clone the [GitHub repository](https://github.com/forfudan/decimojo) and build the package locally. 37 | 38 | | `decimojo` | `mojo` | 39 | | ---------- | ------ | 40 | | v0.1.0 | >=25.1 | 41 | | v0.2.0 | >=25.2 | 42 | | v0.3.0 | >=25.2 | 43 | 44 | ## Quick start 45 | 46 | Here is a comprehensive quick-start guide showcasing each major function of the `Decimal` type. 47 | 48 | ```mojo 49 | from decimojo import Decimal, RoundingMode 50 | 51 | fn main() raises: 52 | # === Construction === 53 | var a = Decimal("123.45") # From string 54 | var b = Decimal(123) # From integer 55 | var c = Decimal(123, 2) # Integer with scale (1.23) 56 | var d = Decimal.from_float(3.14159) # From floating-point 57 | 58 | # === Basic Arithmetic === 59 | print(a + b) # Addition: 246.45 60 | print(a - b) # Subtraction: 0.45 61 | print(a * b) # Multiplication: 15184.35 62 | print(a / b) # Division: 1.0036585365853658536585365854 63 | 64 | # === Rounding & Precision === 65 | print(a.round(1)) # Round to 1 decimal place: 123.5 66 | print(a.quantize(Decimal("0.01"))) # Format to 2 decimal places: 123.45 67 | print(a.round(0, RoundingMode.ROUND_DOWN)) # Round down to integer: 123 68 | 69 | # === Comparison === 70 | print(a > b) # Greater than: True 71 | print(a == Decimal("123.45")) # Equality: True 72 | print(a.is_zero()) # Check for zero: False 73 | print(Decimal("0").is_zero()) # Check for zero: True 74 | 75 | # === Type Conversions === 76 | print(Float64(a)) # To float: 123.45 77 | print(a.to_int()) # To integer: 123 78 | print(a.to_str()) # To string: "123.45" 79 | print(a.coefficient()) # Get coefficient: 12345 80 | print(a.scale()) # Get scale: 2 81 | 82 | # === Mathematical Functions === 83 | print(Decimal("2").sqrt()) # Square root: 1.4142135623730950488016887242 84 | print(Decimal("100").root(3)) # Cube root: 4.641588833612778892410076351 85 | print(Decimal("2.71828").ln()) # Natural log: 0.9999993273472820031578910056 86 | print(Decimal("10").log10()) # Base-10 log: 1 87 | print(Decimal("16").log(Decimal("2"))) # Log base 2: 3.9999999999999999999999999999 88 | print(Decimal("10").exp()) # e^10: 22026.465794806716516957900645 89 | print(Decimal("2").power(10)) # Power: 1024 90 | 91 | # === Sign Handling === 92 | print(-a) # Negation: -123.45 93 | print(abs(Decimal("-123.45"))) # Absolute value: 123.45 94 | print(Decimal("123.45").is_negative()) # Check if negative: False 95 | 96 | # === Special Values === 97 | print(Decimal.PI()) # π constant: 3.1415926535897932384626433833 98 | print(Decimal.E()) # e constant: 2.7182818284590452353602874714 99 | print(Decimal.ONE()) # Value 1: 1 100 | print(Decimal.ZERO()) # Value 0: 0 101 | print(Decimal.MAX()) # Maximum value: 79228162514264337593543950335 102 | 103 | # === Convenience Methods === 104 | print(Decimal("123.400").is_integer()) # Check if integer: False 105 | print(a.number_of_significant_digits()) # Count significant digits: 5 106 | print(Decimal("12.34").to_str_scientific()) # Scientific notation: 1.234E+1 107 | ``` 108 | 109 | [Click here for 8 key examples](https://zhuyuhao.com/decimojo/docs/examples) highlighting the most important features of the `Decimal` type. 110 | 111 | Here are some examples showcasing the arbitrary-precision feature of the `BigDecimal` type. 112 | 113 | ```mojo 114 | from decimojo import BDec, RM 115 | 116 | 117 | fn main() raises: 118 | var PRECISION = 100 119 | var a = BDec("123456789.123456789") 120 | var b = BDec("1234.56789") 121 | print(a.sqrt(precision=PRECISION)) 122 | # 11111.11106611111096943055498174930232833813065468909453818857935956641682120364106016272519460988485 123 | print(a.power(b, precision=PRECISION)) 124 | # 3.346361102419080234023813540078946868219632448203078657310495672766009862564151996325555496759911131748170844123475135377098326591508239654961E+9989 125 | print(a.log(b, precision=PRECISION)) 126 | # 2.617330026656548299907884356415293977170848626010103229392408225981962436022623783231699264341492663671325580092077394824180414301026578169909 127 | ``` 128 | 129 | Here is a comprehensive quick-start guide showcasing each major function of the `BigInt` type. 130 | 131 | ```mojo 132 | from decimojo import BigInt, BInt 133 | # BInt is an alias for BigInt 134 | 135 | fn main() raises: 136 | # === Construction === 137 | var a = BigInt("12345678901234567890") # From string 138 | var b = BInt(12345) # From integer 139 | 140 | # === Basic Arithmetic === 141 | print(a + b) # Addition: 12345678901234580235 142 | print(a - b) # Subtraction: 12345678901234555545 143 | print(a * b) # Multiplication: 152415787814108380241050 144 | 145 | # === Division Operations === 146 | print(a // b) # Floor division: 999650944609516 147 | print(a.truncate_divide(b)) # Truncate division: 999650944609516 148 | print(a % b) # Modulo: 9615 149 | 150 | # === Power Operation === 151 | print(BInt(2).power(10)) # Power: 1024 152 | print(BInt(2) ** 10) # Power (using ** operator): 1024 153 | 154 | # === Comparison === 155 | print(a > b) # Greater than: True 156 | print(a == BInt("12345678901234567890")) # Equality: True 157 | print(a.is_zero()) # Check for zero: False 158 | 159 | # === Type Conversions === 160 | print(a.to_str()) # To string: "12345678901234567890" 161 | 162 | # === Sign Handling === 163 | print(-a) # Negation: -12345678901234567890 164 | print(abs(BInt("-12345678901234567890"))) # Absolute value: 12345678901234567890 165 | print(a.is_negative()) # Check if negative: False 166 | 167 | # === Extremely large numbers === 168 | # 3600 digits // 1800 digits 169 | print(BInt("123456789" * 400) // BInt("987654321" * 200)) 170 | ``` 171 | 172 | ## Objective 173 | 174 | Financial calculations and data analysis require precise decimal arithmetic that floating-point numbers cannot reliably provide. As someone working in finance and credit risk model validation, I needed a dependable correctly-rounded, fixed-precision numeric type when migrating my personal projects from Python to Mojo. 175 | 176 | Since Mojo currently lacks a native Decimal type in its standard library, I decided to create my own implementation to fill that gap. 177 | 178 | This project draws inspiration from several established decimal implementations and documentation, e.g., [Python built-in `Decimal` type](https://docs.python.org/3/library/decimal.html), [Rust `rust_decimal` crate](https://docs.rs/rust_decimal/latest/rust_decimal/index.html), [Microsoft's `Decimal` implementation](https://learn.microsoft.com/en-us/dotnet/api/system.decimal.getbits?view=net-9.0&redirectedfrom=MSDN#System_Decimal_GetBits_System_Decimal_), [General Decimal Arithmetic Specification](https://speleotrove.com/decimal/decarith.html), etc. Many thanks to these predecessors for their contributions and their commitment to open knowledge sharing. 179 | 180 | ## Nomenclature 181 | 182 | DeciMojo combines "Deci" and "Mojo" - reflecting its purpose and implementation language. "Deci" (from Latin "decimus" meaning "tenth") highlights our focus on the decimal numeral system that humans naturally use for counting and calculations. 183 | 184 | Although the name emphasizes decimals with fractional parts, DeciMojo embraces the full spectrum of decimal mathematics. Our `BigInt` type, while handling only integers, is designed specifically for the decimal numeral system with its base-10 internal representation. This approach offers optimal performance while maintaining human-readable decimal semantics, contrasting with binary-focused libraries. Furthermore, `BigInt` serves as the foundation for our `BigDecimal` implementation, enabling arbitrary-precision calculations across both integer and fractional domains. 185 | 186 | The name ultimately emphasizes our mission: bringing precise, reliable decimal calculations to the Mojo ecosystem, addressing the fundamental need for exact arithmetic that floating-point representations cannot provide. 187 | 188 | ## Status 189 | 190 | Rome wasn't built in a day. DeciMojo is currently under active development. It has successfully progressed through the **"make it work"** phase and is now well into the **"make it right"** phase with many optimizations already in place. Bug reports and feature requests are welcome! If you encounter issues, please [file them here](https://github.com/forfudan/decimojo/issues). 191 | 192 | Regular benchmarks against Python's `decimal` module are available in the `bench/` folder, documenting both the performance advantages and the few specific operations where different approaches are needed. 193 | 194 | ## Tests and benches 195 | 196 | After cloning the repo onto your local disk, you can: 197 | 198 | - Use `magic run test` to run tests. 199 | - Use `magic run bench_decimal` to generate logs for benchmarking tests against `python.decimal` module. The log files are saved in `benches/decimal/logs/`. 200 | 201 | ## Citation 202 | 203 | If you find DeciMojo useful for your research, consider listing it in your citations. 204 | 205 | ```tex 206 | @software{Zhu.2025, 207 | author = {Zhu, Yuhao}, 208 | year = {2025}, 209 | title = {An arbitrary-precision decimal and integer mathematics library for Mojo}, 210 | url = {https://github.com/forfudan/decimojo}, 211 | version = {0.3.0}, 212 | note = {Computer Software} 213 | } 214 | ``` 215 | 216 | ## License 217 | 218 | This repository and its contributions are licensed under the Apache License v2.0. 219 | 220 | [^fixed]: The `Decimal` type can represent values with up to 29 significant digits and a maximum of 28 digits after the decimal point. When a value exceeds the maximum representable value (`2^96 - 1`), DeciMojo either raises an error or rounds the value to fit within these constraints. For example, the significant digits of `8.8888888888888888888888888888` (29 eights total with 28 after the decimal point) exceeds the maximum representable value (`2^96 - 1`) and is automatically rounded to `8.888888888888888888888888889` (28 eights total with 27 after the decimal point). DeciMojo's `Decimal` type is similar to `System.Decimal` (C#/.NET), `rust_decimal` in Rust, `DECIMAL/NUMERIC` in SQL Server, etc. 221 | [^integer]: The BigInt implementation uses a base-10 representation for users (maintaining decimal semantics), while internally using an optimized base-10^9 storage system for efficient calculations. This approach balances human-readable decimal operations with high-performance computing. It provides both floor division (round toward negative infinity) and truncate division (round toward zero) semantics, enabling precise handling of division operations with correct mathematical behavior regardless of operand signs. 222 | [^arbitrary]: Built on top of our completed BigInt implementation, BigDecimal will support arbitrary precision for both the integer and fractional parts, similar to `decimal` and `mpmath` in Python, `java.math.BigDecimal` in Java, etc. 223 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/mojmelo_matmul/matmul.mojo: -------------------------------------------------------------------------------- 1 | # Modified version of YichengDWu's matmul.mojo (https://github.com/YichengDWu/matmul.mojo) 2 | 3 | from algorithm import vectorize, parallelize 4 | from memory.memory import _malloc, stack_allocation 5 | from sys import CompilationTarget, num_performance_cores, simd_width_of, size_of 6 | from utils import IndexList 7 | import random 8 | from .params import * 9 | 10 | @always_inline 11 | fn roundup(a: Int, b: Int) -> Int: 12 | return ((a + b - 1) // b) * b 13 | 14 | 15 | @always_inline 16 | fn rounddown(a: Int, b: Int) -> Int: 17 | return (a // b) * b 18 | 19 | 20 | # math.sqrt doesn't work at compile time 21 | fn intsqrt[n: Int]() -> Int: 22 | @parameter 23 | if n == 0: 24 | return 0 25 | var x = n 26 | var y = (x + 1) // 2 27 | while y < x: 28 | x = y 29 | y = (n // x + x) // 2 30 | return x 31 | 32 | 33 | @register_passable("trivial") 34 | struct Layout(Copyable, Movable, Writable): 35 | var shape: IndexList[2] 36 | var strides: IndexList[2] 37 | 38 | fn __init__(out self, shape: Tuple[Int, Int], strides: Tuple[Int, Int]): 39 | self.shape = IndexList[2](shape[0], shape[1]) 40 | self.strides = IndexList[2](strides[0], strides[1]) 41 | 42 | fn __init__(out self, shape: Tuple[Int, Int]): 43 | self.strides = IndexList[2](shape[1], 1) 44 | self.shape = IndexList[2](shape[0], shape[1]) 45 | 46 | @always_inline("nodebug") 47 | fn __call__(self, i: Int, j: Int) -> Int: 48 | return i * self.strides[0] + j * self.strides[1] 49 | 50 | @always_inline("nodebug") 51 | fn size(self) -> Int: 52 | return self.shape[0] * self.shape[1] 53 | 54 | @always_inline("nodebug") 55 | fn write_to[W: Writer](self, mut writer: W): 56 | writer.write(self.shape, ":", self.strides, "\n") 57 | 58 | 59 | struct Matrix[Type: DType]: 60 | var data: UnsafePointer[Scalar[Type], MutAnyOrigin] 61 | var layout: Layout 62 | 63 | fn __init__(out self, shape: Tuple[Int, Int]): 64 | self.data = alloc[Scalar[Type]](shape[0] * shape[1]) 65 | self.layout = Layout(shape) 66 | 67 | @always_inline("nodebug") 68 | fn __init__( 69 | out self, data: UnsafePointer[Scalar[Type], MutAnyOrigin], var layout: Layout 70 | ): 71 | self.data = data 72 | self.layout = layout 73 | 74 | @always_inline("nodebug") 75 | fn __init__( 76 | out self, data: UnsafePointer[Scalar[Type], MutAnyOrigin], shape: Tuple[Int, Int] 77 | ): 78 | self.data = data 79 | self.layout = Layout(shape) 80 | 81 | @always_inline("nodebug") 82 | fn __getitem__( 83 | ref [_]self, i: Int, j: Int 84 | ) -> ref [origin_of(self)] Scalar[Type]: 85 | var offset = self.layout(i, j) 86 | return (self.data + offset)[] 87 | 88 | @always_inline("nodebug") 89 | fn slice(self, i: Int, j: Int, ir: Int, jr: Int) -> Self: 90 | var shape = (ir, jr) 91 | var strides = (self.layout.strides[0], self.layout.strides[1]) 92 | var offset = self.layout(i, j) 93 | return Matrix(self.data + offset, Layout(shape, strides)) 94 | 95 | @always_inline("nodebug") 96 | fn shape[dim: Int](self) -> Int: 97 | return self.layout.shape[dim] 98 | 99 | @always_inline("nodebug") 100 | fn stride[dim: Int](self) -> Int: 101 | return self.layout.strides[dim] 102 | 103 | fn rand(mut self): 104 | random.rand(self.data, self.layout.size()) 105 | 106 | @always_inline("nodebug") 107 | fn load[width: Int, *, dim: Int](self, i: Int, j: Int) -> SIMD[Type, width]: 108 | var offset = self.layout(i, j) 109 | var ptr = self.data + offset 110 | 111 | @parameter 112 | if dim == 0: 113 | return ptr.strided_load[width=width](self.layout.strides[0]) 114 | else: 115 | return ptr.load[width=width]() 116 | 117 | @always_inline("nodebug") 118 | fn store[ 119 | width: Int, *, dim: Int 120 | ](self, value: SIMD[Type, width], i: Int, j: Int): 121 | var offset = self.layout(i, j) 122 | var ptr = self.data + offset 123 | 124 | @parameter 125 | if dim == 0: 126 | ptr.strided_store[width=width](value, self.layout.strides[0]) 127 | else: 128 | ptr.store(value) 129 | 130 | fn write_to[W: Writer](self, mut writer: W): 131 | writer.write( 132 | "Matrix: ", 133 | String(self.data), 134 | ", Layout: ", 135 | self.layout, 136 | "\n", 137 | ) 138 | for i in range(self.layout.shape[0]): 139 | for j in range(self.layout.shape[1]): 140 | writer.write(self[i, j], " ") 141 | writer.write("\n") 142 | 143 | 144 | @always_inline 145 | fn pack_A[ 146 | Type: DType, //, mr: Int 147 | ](mc: Int, Ac_buffer: UnsafePointer[Scalar[Type], MutAnyOrigin], Ac: Matrix[Type]) -> Matrix[Type]: 148 | @parameter 149 | fn pack_panel(idx: Int): 150 | var i = idx * mr 151 | # for i in range(0, Ac.shape[0](), mr): 152 | var dst_ptr = Ac_buffer + i * Ac.shape[1]() 153 | var src_ptr = Ac.data + i * Ac.stride[0]() 154 | for _ in range(Ac.shape[1]()): 155 | 156 | @parameter 157 | fn pack_col[width: Int](l: Int): 158 | (dst_ptr + l).store( 159 | (src_ptr + l * Ac.stride[0]()).strided_load[ 160 | width=width 161 | ](Ac.stride[0]()), 162 | ) 163 | 164 | vectorize[pack_col, simd_width_of[Type]()](min(Ac.shape[0]() - i, mr)) 165 | 166 | for l in range(min(Ac.shape[0]() - i, mr), mr): 167 | dst_ptr[l] = Scalar[Type](0) 168 | 169 | dst_ptr = dst_ptr + mr 170 | src_ptr = src_ptr + 1 171 | 172 | parallelize[pack_panel]( 173 | (Ac.shape[0]() + mr - 1) // mr, num_performance_cores() 174 | ) 175 | 176 | var Ac_layout = Layout( 177 | (roundup(Ac.shape[0](), mr), Ac.shape[1]()), (1, mr) 178 | ) # NOTE: The stride is a lie and only used for slicing 179 | return Matrix(Ac_buffer, Ac_layout) 180 | 181 | 182 | @always_inline 183 | fn pack_B[ 184 | Type: DType, //, kc: Int, nr: Int 185 | ](Bc_buffer: UnsafePointer[Scalar[Type], MutAnyOrigin], Bc: Matrix[Type]) -> Matrix[Type]: 186 | var dst_ptr = Bc_buffer 187 | for i in range(0, Bc.shape[1](), nr): 188 | var src_ptr = Bc.data + i 189 | for _ in range(Bc.shape[0]()): 190 | 191 | @parameter 192 | fn pack_row[width: Int](l: Int): 193 | (dst_ptr + l).store[ 194 | alignment = size_of[Type]() * simd_width_of[Type]() 195 | ]( 196 | (src_ptr + l).load[width=width](), 197 | ) 198 | 199 | vectorize[ 200 | pack_row, 201 | simd_width_of[Type](), 202 | unroll_factor = nr // simd_width_of[Type](), 203 | ](min(Bc.shape[1]() - i, nr)) 204 | 205 | for l in range(min(Bc.shape[1]() - i, nr), nr): 206 | dst_ptr[l] = Scalar[Type](0) 207 | 208 | dst_ptr = dst_ptr + nr 209 | src_ptr = src_ptr + Bc.stride[0]() 210 | 211 | var Bc_layout = Layout( 212 | (Bc.shape[0](), roundup(Bc.shape[1](), nr)), (nr, 1) 213 | ) # NOTE: The stride is a lie and only used for slicing 214 | return Matrix[Type](Bc_buffer, Bc_layout) 215 | 216 | 217 | @always_inline 218 | fn matmul_impl[ 219 | Type: DType, //, 220 | kc: Int, 221 | mr: Int, 222 | nr: Int, 223 | ](mc: Int, nc: Int, mut C: Matrix[Type], A: Matrix[Type], B: Matrix[Type]): 224 | var Ac_buffer = _malloc[Scalar[Type]]( 225 | mc * kc * size_of[Type](), alignment=64 226 | ) 227 | 228 | var M = C.shape[0]() 229 | var N = C.shape[1]() 230 | var K = A.shape[1]() 231 | 232 | for i in range(0, A.shape[0](), mc): 233 | var Cb = C.slice(i, 0, min(M - i, mc), N) 234 | for p in range(0, A.shape[1](), kc): 235 | var Ac = pack_A[mr]( 236 | mc, Ac_buffer, A.slice(i, p, min(M - i, mc), min(K - p, kc)) 237 | ) 238 | 239 | var Bb = B.slice(p, 0, min(K - p, kc), N) 240 | loop_n[kc, mr, nr](nc, Cb, Ac, Bb) 241 | 242 | Ac_buffer.free() 243 | 244 | 245 | @always_inline 246 | fn loop_n[ 247 | Type: DType, //, 248 | kc: Int, 249 | mr: Int, 250 | nr: Int, 251 | ](nc: Int, mut C: Matrix[Type], A: Matrix[Type], B: Matrix[Type]): 252 | var max_threads = num_performance_cores() 253 | var nc_per_thread = nc if nc * max_threads <= B.shape[1]() else rounddown( 254 | B.shape[1]() // max_threads, nr 255 | ) 256 | var balanced_part = rounddown(B.shape[1](), nc_per_thread) 257 | 258 | var remainder = B.shape[1]() - balanced_part 259 | var remainder_per_thread = rounddown(remainder // max_threads, nr) 260 | remainder_per_thread = max(remainder_per_thread, nr) 261 | 262 | var items_remainder = ( 263 | remainder + remainder_per_thread - 1 264 | ) // remainder_per_thread 265 | 266 | @parameter 267 | fn parallelize_balanced_part(idx: Int): 268 | var Bc_buffer = UnsafePointer[Scalar[Type], MutAnyOrigin]( 269 | _malloc[Scalar[Type]]( 270 | kc * nc_per_thread * size_of[Type](), alignment=64 271 | ) 272 | ) 273 | 274 | var j = idx * nc_per_thread 275 | var Bc = pack_B[kc, nr]( 276 | Bc_buffer, 277 | B.slice(0, j, B.shape[0](), min(B.shape[1]() - j, nc_per_thread)), 278 | ) 279 | var Cc = C.slice( 280 | 0, j, C.shape[0](), min(C.shape[1]() - j, nc_per_thread) 281 | ) 282 | macro_kernel[mr, nr](Cc, A, Bc) 283 | Bc_buffer.free() 284 | 285 | parallelize[parallelize_balanced_part]( 286 | balanced_part // nc_per_thread, balanced_part // nc_per_thread 287 | ) 288 | 289 | @parameter 290 | fn parallelize_remainder(idx: Int): 291 | var Bc_buffer = UnsafePointer[Scalar[Type], MutAnyOrigin]( 292 | _malloc[Scalar[Type]]( 293 | kc * remainder_per_thread * size_of[Type](), alignment=64 294 | ) 295 | ) 296 | var j = balanced_part + idx * remainder_per_thread 297 | var Bc = pack_B[kc, nr]( 298 | Bc_buffer, 299 | B.slice( 300 | 0, j, B.shape[0](), min(B.shape[1]() - j, remainder_per_thread) 301 | ), 302 | ) 303 | var Cc = C.slice( 304 | 0, j, C.shape[0](), min(C.shape[1]() - j, remainder_per_thread) 305 | ) 306 | macro_kernel[mr, nr](Cc, A, Bc) 307 | Bc_buffer.free() 308 | 309 | parallelize[parallelize_remainder](items_remainder, items_remainder) 310 | 311 | _ = balanced_part 312 | _ = remainder_per_thread 313 | _ = nc_per_thread 314 | 315 | 316 | @always_inline 317 | fn macro_kernel[ 318 | Type: DType, //, 319 | mr: Int, 320 | nr: Int, 321 | ](mut Cc: Matrix[Type], Ac: Matrix[Type], Bc: Matrix[Type]): 322 | @parameter 323 | fn parallelize_ir(idx: Int): 324 | var ir = idx * mr 325 | var Ar = Matrix(Ac.data + ir * Ac.shape[1](), (mr, Ac.shape[1]())) 326 | for jr in range(0, Bc.shape[1](), nr): 327 | var Cr = Cc.slice( 328 | ir, 329 | jr, 330 | min(Cc.shape[0]() - ir, mr), 331 | min(Cc.shape[1]() - jr, nr), 332 | ) 333 | var Br = Matrix( 334 | Bc.data + jr * Bc.shape[0](), 335 | (Bc.shape[0](), nr), 336 | ) 337 | if Cr.shape[0]() == mr and Cr.shape[1]() == nr: 338 | micro_kernel[mr, nr, False](Cr, Ar, Br) 339 | else: 340 | micro_kernel[mr, nr, True](Cr, Ar, Br) 341 | 342 | parallelize[parallelize_ir]((Ac.shape[0]() + mr - 1) // mr, 2) 343 | 344 | 345 | @always_inline 346 | fn micro_kernel[ 347 | Type: DType, //, mr: Int, nr: Int, padding: Bool 348 | ](mut Cr: Matrix[Type], Ar: Matrix[Type], Br: Matrix[Type]): 349 | comptime simd_width = simd_width_of[Type]() 350 | constrained[nr % simd_width == 0, "nr must be multiple of simd_width"]() 351 | 352 | var Ar_ptr = Ar.data 353 | var Br_ptr = Br.data 354 | var Cr_ptr = Cr.data 355 | 356 | var ar: SIMD[Type, simd_width] 357 | var br = InlineArray[SIMD[Type, simd_width], nr // simd_width]( 358 | SIMD[Type, simd_width](0) 359 | ) 360 | var cr_ptr = stack_allocation[mr * nr, Scalar[Type], alignment=64]() 361 | 362 | @parameter 363 | if padding: 364 | 365 | @parameter 366 | for i in range(mr): 367 | if i < Cr.shape[0](): 368 | 369 | @parameter 370 | fn load_col[width: Int](j: Int): 371 | (cr_ptr + (i * nr + j)).store( 372 | (Cr_ptr + (i * Cr.stride[0]() + j)).load[width=width](), 373 | ) 374 | 375 | vectorize[load_col, simd_width](Cr.shape[1]()) 376 | else: 377 | 378 | @parameter 379 | for i in range(mr): 380 | 381 | @parameter 382 | for j in range(0, nr, simd_width): 383 | (cr_ptr + i * nr + j).store( 384 | (Cr_ptr + (i * Cr.stride[0]() + j)).load[width=simd_width](), 385 | ) 386 | 387 | for _ in range(Ar.shape[1]()): 388 | 389 | @parameter 390 | for j in range(0, nr, simd_width): 391 | br[j // simd_width] = (Br_ptr + j).load[ 392 | width=simd_width, alignment = size_of[Type]() * simd_width_of[Type]() 393 | ]() 394 | 395 | @parameter 396 | for i in range(mr): 397 | 398 | @parameter 399 | for j in range(0, nr, simd_width): 400 | ar = SIMD[Type, size=simd_width](Ar_ptr[]) 401 | cr_ptr.store( 402 | ar.fma( 403 | br[j // simd_width], 404 | cr_ptr.load[width=simd_width](), 405 | ), 406 | ) 407 | cr_ptr += simd_width 408 | Ar_ptr += 1 409 | 410 | Br_ptr += nr 411 | cr_ptr += -mr * nr 412 | 413 | @parameter 414 | if padding: 415 | 416 | @parameter 417 | for i in range(mr): 418 | if i < Cr.shape[0](): 419 | 420 | @parameter 421 | fn store_row[width: Int](j: Int): 422 | (Cr_ptr + (i * Cr.stride[0]() + j)).store( 423 | (cr_ptr + (i * nr + j)).load[width=width](), 424 | ) 425 | 426 | vectorize[store_row, simd_width](Cr.shape[1]()) 427 | else: 428 | 429 | @parameter 430 | for i in range(mr): 431 | 432 | @parameter 433 | for j in range(0, nr, simd_width): 434 | (Cr_ptr + (i * Cr.stride[0]() + j)).store( 435 | (cr_ptr + (i * nr + j)).load[width=simd_width](), 436 | ) 437 | 438 | 439 | @always_inline 440 | fn matmul_params[Type: DType]() -> IndexList[5]: 441 | comptime mc = 8192 // size_of[Type]() # fix this for simplicity 442 | comptime N = simd_width_of[Type]() 443 | 444 | comptime Vectors = 32 if CompilationTarget.has_avx512f() else 16 445 | 446 | @parameter 447 | fn compute_kc[mr: Int, nr: Int]() -> Int: 448 | comptime CBr = Int((L1_ASSOCIATIVITY - 1) / (1 + mr / nr)) 449 | return (CBr * L1_CACHE_SIZE) // (nr * size_of[Type]() * L1_ASSOCIATIVITY) 450 | 451 | @parameter 452 | fn compute_params[C: Int]() -> IndexList[5]: 453 | comptime p = C // (intsqrt[C]() + 1) 454 | comptime mr = C // p - 1 455 | comptime nr = p * N 456 | comptime CBr = Int((L1_ASSOCIATIVITY - 1) / (1 + mr / nr)) 457 | comptime kc = compute_kc[mr, nr]() 458 | comptime nc = (L2_ASSOCIATIVITY - 1) * L2_CACHE_SIZE // ( 459 | kc * size_of[Type]() * L2_ASSOCIATIVITY 460 | ) - mr 461 | return IndexList[5](mc, nc, kc, mr, nr) 462 | 463 | @parameter 464 | if Type.is_floating_point(): 465 | comptime TempVectors = 1 466 | return compute_params[Vectors - TempVectors]() 467 | else: 468 | 469 | @parameter 470 | if Type is DType.int64: 471 | 472 | @parameter 473 | if CompilationTarget.has_avx512f(): 474 | comptime TempVectors = 2 475 | return compute_params[Vectors - TempVectors]() 476 | else: 477 | comptime TempVectors = 3 478 | return compute_params[Vectors - TempVectors]() 479 | else: 480 | comptime TempVectors = 2 481 | return compute_params[Vectors - TempVectors]() 482 | 483 | 484 | fn matmul[ 485 | Type: DType 486 | ](m: Int, n: Int, k: Int, mut C: Matrix[Type], A: Matrix[Type], B: Matrix[Type]): 487 | comptime params = matmul_params[Type]() 488 | comptime mc = params[0] 489 | comptime nc = params[1] 490 | comptime kc = params[2] 491 | comptime mr = params[3] 492 | comptime nr = params[4] 493 | var resized_mc = roundup(min(mc, m), mr) 494 | var resized_nc = roundup(min(nc, n), nr) 495 | matmul_impl[kc, mr, nr](resized_mc, resized_nc, C, A, B) 496 | -------------------------------------------------------------------------------- /recipes/decimojo/tests.mojo: -------------------------------------------------------------------------------- 1 | """ 2 | Test Decimal arithmetic operations including addition, subtraction, and negation. 3 | """ 4 | 5 | from decimojo.prelude import * 6 | import testing 7 | 8 | 9 | fn test_add() raises: 10 | print("Testing decimal addition...") 11 | 12 | # Test case 1: Simple addition with same scale 13 | var a1 = Decimal("123.45") 14 | var b1 = Decimal("67.89") 15 | var result1 = a1 + b1 16 | testing.assert_equal( 17 | String(result1), "191.34", "Simple addition with same scale" 18 | ) 19 | 20 | # Test case 2: Addition with different scales 21 | var a2 = Decimal("123.4") 22 | var b2 = Decimal("67.89") 23 | var result2 = a2 + b2 24 | testing.assert_equal( 25 | String(result2), "191.29", "Addition with different scales" 26 | ) 27 | 28 | # Test case 3: Addition with negative numbers 29 | var a3 = Decimal("123.45") 30 | var b3 = Decimal("-67.89") 31 | var result3 = a3 + b3 32 | testing.assert_equal( 33 | String(result3), "55.56", "Addition with negative number" 34 | ) 35 | 36 | # Test case 4: Addition resulting in negative 37 | var a4 = Decimal("-123.45") 38 | var b4 = Decimal("67.89") 39 | var result4 = a4 + b4 40 | testing.assert_equal( 41 | String(result4), "-55.56", "Addition resulting in negative" 42 | ) 43 | 44 | # Test case 5: Addition with zero 45 | var a5 = Decimal("123.45") 46 | var b5 = Decimal("0.00") 47 | var result5 = a5 + b5 48 | testing.assert_equal(String(result5), "123.45", "Addition with zero") 49 | 50 | # Test case 6: Addition resulting in zero 51 | var a6 = Decimal("123.45") 52 | var b6 = Decimal("-123.45") 53 | var result6 = a6 + b6 54 | testing.assert_equal(String(result6), "0.00", "Addition resulting in zero") 55 | 56 | # Test case 7: Addition with large scales 57 | var a7 = Decimal("0.0000001") 58 | var b7 = Decimal("0.0000002") 59 | var result7 = a7 + b7 60 | testing.assert_equal( 61 | String(result7), "0.0000003", "Addition with large scales" 62 | ) 63 | 64 | # Test case 8: Addition with different large scales 65 | var a8 = Decimal("0.000001") 66 | var b8 = Decimal("0.0000002") 67 | var result8 = a8 + b8 68 | testing.assert_equal( 69 | String(result8), "0.0000012", "Addition with different large scales" 70 | ) 71 | 72 | # Additional edge cases for addition 73 | 74 | # Test case 9: Addition with many decimal places 75 | var a9 = Decimal("0.123456789012345678901234567") 76 | var b9 = Decimal("0.987654321098765432109876543") 77 | var result9 = a9 + b9 78 | testing.assert_equal( 79 | String(result9), 80 | "1.111111110111111111011111110", 81 | "Addition with many decimal places", 82 | ) 83 | 84 | # Test case 10: Addition with extreme scale difference 85 | var a10 = Decimal("123456789") 86 | var b10 = Decimal("0.000000000123456789") 87 | var result10 = a10 + b10 88 | testing.assert_equal( 89 | String(result10), 90 | "123456789.000000000123456789", 91 | "Addition with extreme scale difference", 92 | ) 93 | 94 | # Test case 11: Addition near maximum precision 95 | var a11 = Decimal("0." + "1" * 28) # 0.1111...1 (28 digits) 96 | var b11 = Decimal("0." + "9" * 28) # 0.9999...9 (28 digits) 97 | var result11 = a11 + b11 98 | testing.assert_equal( 99 | String(result11), 100 | "1.1111111111111111111111111110", 101 | "Addition near maximum precision", 102 | ) 103 | 104 | # Test case 12: Addition causing scale truncation 105 | var a12 = Decimal("0." + "1" * 27 + "1") # 0.1111...1 (28 digits) 106 | var b12 = Decimal("0.0" + "9" * 27) # 0.09999...9 (28 digits) 107 | var result12 = a12 + b12 108 | testing.assert_equal( 109 | String(result12), 110 | "0." + "2" + "1" * 26 + "0", 111 | "Addition causing scale truncation", 112 | ) 113 | 114 | # Test case 13: Addition of very small numbers 115 | var a13 = Decimal("0." + "0" * 27 + "1") # 0.0000...01 (1 at 28th place) 116 | var b13 = Decimal("0." + "0" * 27 + "2") # 0.0000...02 (2 at 28th place) 117 | var result13 = a13 + b13 118 | testing.assert_equal( 119 | String(result13), 120 | "0." + "0" * 27 + "3", 121 | "Addition of very small numbers", 122 | ) 123 | 124 | # Test case 14: Addition with alternating signs and scales 125 | var a14 = Decimal("1.01") 126 | var b14 = Decimal("-0.101") 127 | var result14 = a14 + b14 128 | testing.assert_equal( 129 | String(result14), "0.909", "Addition with alternating signs and scales" 130 | ) 131 | 132 | # Test case 15: Addition with large numbers (near limits) 133 | var a15 = Decimal("79228162514264337593543950334") # MAX() - 1 134 | var b15 = Decimal("1") 135 | var result15 = a15 + b15 136 | testing.assert_equal( 137 | String(result15), 138 | "79228162514264337593543950335", 139 | "Addition approaching maximum value", 140 | ) 141 | 142 | # Test case 16: Repeated addition to test cumulative errors 143 | var acc = Decimal("0") 144 | for _ in range(10): 145 | acc = acc + Decimal("0.1") 146 | testing.assert_equal(String(acc), "1.0", "Repeated addition of 0.1") 147 | 148 | # Test case 17: Edge case with alternating very large and very small values 149 | var a17 = Decimal("1234567890123456789.0123456789") 150 | var b17 = Decimal("0.0000000000000000009876543211") 151 | var result17 = a17 + b17 152 | testing.assert_equal( 153 | String(result17), 154 | "1234567890123456789.0123456789", 155 | "Addition with large and small values", 156 | ) 157 | 158 | print("Decimal addition tests passed!") 159 | 160 | 161 | fn test_negation() raises: 162 | print("Testing decimal negation...") 163 | 164 | # Test case 1: Negate positive number 165 | var a1 = Decimal("123.45") 166 | var result1 = -a1 167 | testing.assert_equal(String(result1), "-123.45", "Negating positive number") 168 | 169 | # Test case 2: Negate negative number 170 | var a2 = Decimal("-67.89") 171 | var result2 = -a2 172 | testing.assert_equal(String(result2), "67.89", "Negating negative number") 173 | 174 | # Test case 3: Negate zero 175 | var a3 = Decimal("0") 176 | var result3 = -a3 177 | testing.assert_equal(String(result3), "0", "Negating zero") 178 | 179 | # Test case 4: Negate number with trailing zeros 180 | var a4 = Decimal("123.4500") 181 | var result4 = -a4 182 | testing.assert_equal( 183 | String(result4), "-123.4500", "Negating with trailing zeros" 184 | ) 185 | 186 | # Test case 5: Double negation 187 | var a5 = Decimal("123.45") 188 | var result5 = -(-a5) 189 | testing.assert_equal(String(result5), "123.45", "Double negation") 190 | 191 | # Additional edge cases for negation 192 | 193 | # Test case 6: Negate very small number 194 | var a6 = Decimal("0." + "0" * 27 + "1") # 0.0000...01 (1 at 28th place) 195 | var result6 = -a6 196 | testing.assert_equal( 197 | String(result6), "-0." + "0" * 27 + "1", "Negating very small number" 198 | ) 199 | 200 | # Test case 7: Negate very large number 201 | var a7 = Decimal("79228162514264337593543950335") # MAX() 202 | var result7 = -a7 203 | testing.assert_equal( 204 | String(result7), 205 | "-79228162514264337593543950335", 206 | "Negating maximum value", 207 | ) 208 | 209 | # Test case 8: Triple negation 210 | var a8 = Decimal("123.45") 211 | var result8 = -(-(-a8)) 212 | testing.assert_equal(String(result8), "-123.45", "Triple negation") 213 | 214 | # Test case 9: Negate number with scientific notation (if supported) 215 | try: 216 | var a9 = Decimal("1.23e5") # 123000 217 | var result9 = -a9 218 | testing.assert_equal( 219 | String(result9), 220 | "-123000", 221 | "Negating number from scientific notation", 222 | ) 223 | except: 224 | print("Scientific notation not supported in this implementation") 225 | 226 | # Test case 10: Negate number with maximum precision 227 | var a10 = Decimal("0." + "1" * 28) # 0.1111...1 (28 digits) 228 | var result10 = -a10 229 | testing.assert_equal( 230 | String(result10), 231 | "-0." + "1" * 28, 232 | "Negating number with maximum precision", 233 | ) 234 | 235 | print("Decimal negation tests passed!") 236 | 237 | 238 | fn test_abs() raises: 239 | print("Testing decimal absolute value...") 240 | 241 | # Test case 1: Absolute value of positive number 242 | var a1 = Decimal("123.45") 243 | var result1 = abs(a1) 244 | testing.assert_equal( 245 | String(result1), "123.45", "Absolute value of positive number" 246 | ) 247 | 248 | # Test case 2: Absolute value of negative number 249 | var a2 = Decimal("-67.89") 250 | var result2 = abs(a2) 251 | testing.assert_equal( 252 | String(result2), "67.89", "Absolute value of negative number" 253 | ) 254 | 255 | # Test case 3: Absolute value of zero 256 | var a3 = Decimal("0") 257 | var result3 = abs(a3) 258 | testing.assert_equal(String(result3), "0", "Absolute value of zero") 259 | 260 | # Test case 4: Absolute value of negative zero (if supported) 261 | var a4 = Decimal("-0.00") 262 | var result4 = abs(a4) 263 | testing.assert_equal( 264 | String(result4), "0.00", "Absolute value of negative zero" 265 | ) 266 | 267 | # Test case 5: Absolute value with large number of decimal places 268 | var a5 = Decimal("-0.0000000001") 269 | var result5 = abs(a5) 270 | testing.assert_equal( 271 | String(result5), 272 | "0.0000000001", 273 | "Absolute value of small negative number", 274 | ) 275 | 276 | # Test case 6: Absolute value of very large number 277 | var a6 = Decimal("-9999999999.9999999999") 278 | var result6 = abs(a6) 279 | testing.assert_equal( 280 | String(result6), 281 | "9999999999.9999999999", 282 | "Absolute value of large negative number", 283 | ) 284 | 285 | # Test case 7: Absolute value of number with many significant digits 286 | var a7 = Decimal("-0.123456789012345678901234567") 287 | var result7 = abs(a7) 288 | testing.assert_equal( 289 | String(result7), 290 | "0.123456789012345678901234567", 291 | "Absolute value of high precision negative number", 292 | ) 293 | 294 | # Test case 8: Absolute value of maximum representable number 295 | try: 296 | var a8 = Decimal("79228162514264337593543950335") # Maximum value 297 | var result8 = abs(a8) 298 | testing.assert_equal( 299 | String(result8), 300 | "79228162514264337593543950335", 301 | "Absolute value of maximum value", 302 | ) 303 | 304 | var a9 = Decimal( 305 | "-79228162514264337593543950335" 306 | ) # Negative maximum value 307 | var result9 = abs(a9) 308 | testing.assert_equal( 309 | String(result9), 310 | "79228162514264337593543950335", 311 | "Absolute value of negative maximum value", 312 | ) 313 | except: 314 | print("Maximum value test not applicable") 315 | 316 | print("Decimal absolute value tests passed!") 317 | 318 | 319 | fn test_subtract() raises: 320 | print("Testing decimal subtraction...") 321 | 322 | # Test case 1: Simple subtraction with same scale 323 | var a1 = Decimal("123.45") 324 | var b1 = Decimal("67.89") 325 | var result1 = a1 - b1 326 | testing.assert_equal( 327 | String(result1), "55.56", "Simple subtraction with same scale" 328 | ) 329 | 330 | # Test case 2: Subtraction with different scales 331 | var a2 = Decimal("123.4") 332 | var b2 = Decimal("67.89") 333 | var result2 = a2 - b2 334 | testing.assert_equal( 335 | String(result2), "55.51", "Subtraction with different scales" 336 | ) 337 | 338 | # Test case 3: Subtraction resulting in negative 339 | var a3 = Decimal("67.89") 340 | var b3 = Decimal("123.45") 341 | var result3 = a3 - b3 342 | testing.assert_equal( 343 | String(result3), "-55.56", "Subtraction resulting in negative" 344 | ) 345 | 346 | # Test case 4: Subtraction of negative numbers 347 | var a4 = Decimal("123.45") 348 | var b4 = Decimal("-67.89") 349 | var result4 = a4 - b4 350 | testing.assert_equal( 351 | String(result4), "191.34", "Subtraction of negative number" 352 | ) 353 | 354 | # Test case 5: Subtraction with zero 355 | var a5 = Decimal("123.45") 356 | var b5 = Decimal("0.00") 357 | var result5 = a5 - b5 358 | testing.assert_equal(String(result5), "123.45", "Subtraction with zero") 359 | 360 | # Test case 6: Subtraction resulting in zero 361 | var a6 = Decimal("123.45") 362 | var b6 = Decimal("123.45") 363 | var result6 = a6 - b6 364 | testing.assert_equal( 365 | String(result6), "0.00", "Subtraction resulting in zero" 366 | ) 367 | 368 | # Test case 7: Subtraction with large scales 369 | var a7 = Decimal("0.0000003") 370 | var b7 = Decimal("0.0000002") 371 | var result7 = a7 - b7 372 | testing.assert_equal( 373 | String(result7), "0.0000001", "Subtraction with large scales" 374 | ) 375 | 376 | # Test case 8: Subtraction with different large scales 377 | var a8 = Decimal("0.000005") 378 | var b8 = Decimal("0.0000002") 379 | var result8 = a8 - b8 380 | testing.assert_equal( 381 | String(result8), "0.0000048", "Subtraction with different large scales" 382 | ) 383 | 384 | # Test case 9: Subtraction with small difference 385 | var a9 = Decimal("1.0000001") 386 | var b9 = Decimal("1.0000000") 387 | var result9 = a9 - b9 388 | testing.assert_equal( 389 | String(result9), "0.0000001", "Subtraction with small difference" 390 | ) 391 | 392 | # Test case 10: Subtraction of very small from very large 393 | var a10 = Decimal("9999999999.9999999") 394 | var b10 = Decimal("0.0000001") 395 | var result10 = a10 - b10 396 | testing.assert_equal( 397 | String(result10), 398 | "9999999999.9999998", 399 | "Subtraction of very small from very large", 400 | ) 401 | 402 | # Test case 11: Self subtraction for various values (expanded from list) 403 | # Individual test cases instead of iterating over a list 404 | var value1 = Decimal("0") 405 | testing.assert_equal( 406 | String(value1 - value1), 407 | String(round(Decimal("0"), value1.scale())), 408 | "Self subtraction should yield zero (0)", 409 | ) 410 | 411 | var value2 = Decimal("123.45") 412 | testing.assert_equal( 413 | String(value2 - value2), 414 | String(round(Decimal("0"), value2.scale())), 415 | "Self subtraction should yield zero (123.45)", 416 | ) 417 | 418 | var value3 = Decimal("-987.654") 419 | testing.assert_equal( 420 | String(value3 - value3), 421 | String(round(Decimal("0"), value3.scale())), 422 | "Self subtraction should yield zero (-987.654)", 423 | ) 424 | 425 | var value4 = Decimal("0.0001") 426 | testing.assert_equal( 427 | String(value4 - value4), 428 | String(round(Decimal("0"), value4.scale())), 429 | "Self subtraction should yield zero (0.0001)", 430 | ) 431 | 432 | var value5 = Decimal("-99999.99999") 433 | testing.assert_equal( 434 | String(value5 - value5), 435 | String(round(Decimal("0"), value5.scale())), 436 | "Self subtraction should yield zero (-99999.99999)", 437 | ) 438 | 439 | # Test case 12: Verify that a - b = -(b - a) 440 | var a12a = Decimal("123.456") 441 | var b12a = Decimal("789.012") 442 | var result12a = a12a - b12a 443 | var result12b = -(b12a - a12a) 444 | testing.assert_equal( 445 | String(result12a), String(result12b), "a - b should equal -(b - a)" 446 | ) 447 | 448 | print("Decimal subtraction tests passed!") 449 | 450 | 451 | fn test_multiplication() raises: 452 | print("Testing decimal multiplication...") 453 | 454 | # Test case 1: Simple multiplication with same scale 455 | var a1 = Decimal("12.34") 456 | var b1 = Decimal("5.67") 457 | var result1 = a1 * b1 458 | testing.assert_equal( 459 | String(result1), "69.9678", "Simple multiplication with same scale" 460 | ) 461 | 462 | # Test case 2: Multiplication with different scales 463 | var a2 = Decimal("12.3") 464 | var b2 = Decimal("5.67") 465 | var result2 = a2 * b2 466 | testing.assert_equal( 467 | String(result2), "69.741", "Multiplication with different scales" 468 | ) 469 | 470 | # Test case 3: Multiplication with negative numbers 471 | var a3 = Decimal("12.34") 472 | var b3 = Decimal("-5.67") 473 | var result3 = a3 * b3 474 | testing.assert_equal( 475 | String(result3), "-69.9678", "Multiplication with negative number" 476 | ) 477 | 478 | # Test case 4: Multiplication with both negative numbers 479 | var a4 = Decimal("-12.34") 480 | var b4 = Decimal("-5.67") 481 | var result4 = a4 * b4 482 | testing.assert_equal( 483 | String(result4), "69.9678", "Multiplication with both negative numbers" 484 | ) 485 | 486 | # Test case 5: Multiplication by zero 487 | var a5 = Decimal("12.34") 488 | var b5 = Decimal("0.00") 489 | var result5 = a5 * b5 490 | testing.assert_equal(String(result5), "0.0000", "Multiplication by zero") 491 | 492 | # Test case 6: Multiplication by one 493 | var a6 = Decimal("12.34") 494 | var b6 = Decimal("1.00") 495 | var result6 = a6 * b6 496 | testing.assert_equal(String(result6), "12.3400", "Multiplication by one") 497 | 498 | # Test case 7: Multiplication with large scales 499 | var a7 = Decimal("0.0001") 500 | var b7 = Decimal("0.0002") 501 | var result7 = a7 * b7 502 | testing.assert_equal( 503 | String(result7), "0.00000002", "Multiplication with large scales" 504 | ) 505 | 506 | # Test case 8: Multiplication resulting in scale truncation 507 | var a8 = Decimal("0.123456789") 508 | var b8 = Decimal("0.987654321") 509 | var result8 = a8 * b8 510 | testing.assert_equal( 511 | String(result8), 512 | "0.121932631112635269", 513 | "Multiplication with scale truncation", 514 | ) 515 | 516 | # Test case 9: Multiplication of large numbers 517 | var a9 = Decimal("1234567.89") 518 | var b9 = Decimal("9876543.21") 519 | var result9 = a9 * b9 520 | testing.assert_equal( 521 | String(result9), 522 | "12193263111263.5269", 523 | "Multiplication of large numbers", 524 | ) 525 | 526 | # Test case 10: Verify that a * b = b * a (commutative property) 527 | var a10 = Decimal("123.456") 528 | var b10 = Decimal("789.012") 529 | var result10a = a10 * b10 530 | var result10b = b10 * a10 531 | testing.assert_equal( 532 | String(result10a), 533 | String(result10b), 534 | "Multiplication should be commutative", 535 | ) 536 | 537 | # Test case 11: Multiplication near precision limits 538 | var a11 = Decimal("0." + "1" * 10) 539 | var b11 = Decimal("0." + "1" * 18) 540 | var result11 = a11 * b11 541 | testing.assert_equal( 542 | String(result11), 543 | "0.0123456790111111110987654321", 544 | "Multiplication near precision limits", 545 | ) 546 | 547 | # Test case 12: Multiplication with integers 548 | var a12 = Decimal("123") 549 | var b12 = Decimal("456") 550 | var result12 = a12 * b12 551 | testing.assert_equal( 552 | String(result12), "56088", "Multiplication with integers" 553 | ) 554 | 555 | # Test case 13: Multiplication with powers of 10 556 | var a13 = Decimal("12.34") 557 | var b13 = Decimal("10") 558 | var result13 = a13 * b13 559 | testing.assert_equal( 560 | String(result13), "123.40", "Multiplication by power of 10" 561 | ) 562 | 563 | # Test case 14: Multiplication with very small numbers 564 | var a14 = Decimal("0." + "0" * 25 + "1") 565 | var b14 = Decimal("0." + "0" * 25 + "1") 566 | var result14 = a14 * b14 567 | testing.assert_equal( 568 | String(result14), 569 | "0." + "0" * 28, 570 | "Multiplication of very small numbers", 571 | ) 572 | 573 | # Test case 15: Verify that a * 0 = 0 for various values - individual test cases 574 | var zero = Decimal("0") 575 | 576 | # Test 15a: Multiplication of zero by zero 577 | var value15a = Decimal("0") 578 | testing.assert_equal( 579 | String(value15a * zero), 580 | "0", 581 | "Multiplication by zero should yield zero (zero case)", 582 | ) 583 | 584 | # Test 15b: Multiplication of positive number by zero 585 | var value15b = Decimal("123.45") 586 | testing.assert_equal( 587 | String(value15b * zero), 588 | "0.00", 589 | "Multiplication by zero should yield zero (positive number case)", 590 | ) 591 | 592 | # Test 15d: Multiplication of small number by zero 593 | var value15d = Decimal("0.0001") 594 | testing.assert_equal( 595 | String(value15d * zero), 596 | "0.0000", 597 | "Multiplication by zero should yield zero (small number case)", 598 | ) 599 | 600 | print("Decimal multiplication tests passed!") 601 | 602 | 603 | fn test_division() raises: 604 | print("Testing decimal division...") 605 | 606 | # Test case 1: Simple division with same scale 607 | var a1 = Decimal("10.00") 608 | var b1 = Decimal("2.00") 609 | var result1 = a1 / b1 610 | testing.assert_equal( 611 | String(result1), "5", "Simple division with same scale" 612 | ) 613 | 614 | # Test case 2: Division with different scales 615 | var a2 = Decimal("10.5") 616 | var b2 = Decimal("2.1") 617 | var result2 = a2 / b2 618 | testing.assert_equal( 619 | String(result2), 620 | "5", 621 | "Division with different scales", 622 | ) 623 | 624 | # Test case 3: Division with negative numbers 625 | var a3 = Decimal("12.34") 626 | var b3 = Decimal("-2.0") 627 | var result3 = a3 / b3 628 | testing.assert_equal( 629 | String(result3), 630 | "-6.17", 631 | "Division with negative number", 632 | ) 633 | 634 | # Test case 4: Division with both negative numbers 635 | var a4 = Decimal("-12.34") 636 | var b4 = Decimal("-2.0") 637 | var result4 = a4 / b4 638 | testing.assert_equal( 639 | String(result4), 640 | "6.17", 641 | "Division with both negative numbers", 642 | ) 643 | 644 | # Test case 5: Division by one 645 | var a5 = Decimal("12.34") 646 | var b5 = Decimal("1.0") 647 | var result5 = a5 / b5 648 | testing.assert_equal(String(result5), "12.34", "Division by one") 649 | 650 | # Test case 6: Division resulting in repeating decimal 651 | var a6 = Decimal("10.0") 652 | var b6 = Decimal("3.0") 653 | var result6 = a6 / b6 654 | testing.assert_equal( 655 | String(result6).startswith("3.333333333"), 656 | True, 657 | "Division resulting in repeating decimal", 658 | ) 659 | 660 | # Test case 7: Division resulting in exact value 661 | var a7 = Decimal("10.0") 662 | var b7 = Decimal("5.0") 663 | var result7 = a7 / b7 664 | testing.assert_equal( 665 | String(result7), "2", "Division resulting in exact value" 666 | ) 667 | 668 | # Test case 8: Division of zero by non-zero 669 | var a8 = Decimal("0.0") 670 | var b8 = Decimal("5.0") 671 | var result8 = a8 / b8 672 | testing.assert_equal(String(result8), "0", "Division of zero by non-zero") 673 | 674 | # Test case 9: Division with small numbers 675 | var a9 = Decimal("0.001") 676 | var b9 = Decimal("0.01") 677 | var result9 = a9 / b9 678 | testing.assert_equal(String(result9), "0.1", "Division with small numbers") 679 | 680 | # Test case 10: Division with large numbers 681 | var a10 = Decimal("1000000.0") 682 | var b10 = Decimal("0.001") 683 | var result10 = a10 / b10 684 | testing.assert_equal( 685 | String(result10), "1000000000", "Division with large numbers" 686 | ) 687 | 688 | # Test case 11: Division requiring rounding 689 | var a11 = Decimal("1.0") 690 | var b11 = Decimal("7.0") 691 | var result11 = a11 / b11 692 | testing.assert_equal( 693 | String(result11).startswith("0.142857142857142857142857"), 694 | True, 695 | "Division requiring rounding", 696 | ) 697 | 698 | # Test case 12: Division with mixed precision 699 | var a12 = Decimal("123.456") 700 | var b12 = Decimal("0.1") 701 | var result12 = a12 / b12 702 | testing.assert_equal( 703 | String(result12), "1234.56", "Division with mixed precision" 704 | ) 705 | 706 | # Test case 14: Division of number by itself should be 1 707 | var a14 = Decimal("123.45") 708 | var result14 = a14 / a14 709 | testing.assert_equal( 710 | String(result14), 711 | "1", 712 | "Division of number by itself", 713 | ) 714 | 715 | # Test case 15: Division by zero should raise an error 716 | var a15 = Decimal("123.45") 717 | var b15 = Decimal("0.0") 718 | try: 719 | var result15 = a15 / b15 720 | testing.assert_equal( 721 | True, False, "Division by zero should raise an error" 722 | ) 723 | except: 724 | testing.assert_equal(True, True, "Division by zero correctly rejected") 725 | 726 | # ============= ADDITIONAL DIVISION TEST CASES ============= 727 | print("\nTesting additional division scenarios...") 728 | 729 | # Test case 16: Division with very large number by very small number 730 | var a16 = Decimal("1000000000") 731 | var b16 = Decimal("0.0001") 732 | var result16 = a16 / b16 733 | testing.assert_equal( 734 | String(result16), 735 | "10000000000000", 736 | "Large number divided by small number", 737 | ) 738 | 739 | # Test case 17: Division with very small number by very large number 740 | var a17 = Decimal("0.0001") 741 | var b17 = Decimal("1000000000") 742 | var result17 = a17 / b17 743 | testing.assert_true( 744 | String(result17).startswith("0.0000000000001"), 745 | "Small number divided by large number", 746 | ) 747 | 748 | # Test case 18: Division resulting in repeating decimal 749 | var a18 = Decimal("1") 750 | var b18 = Decimal("3") 751 | var result18 = a18 / b18 752 | testing.assert_true( 753 | String(result18).startswith("0.33333333"), 754 | "Division resulting in repeating decimal (1/3)", 755 | ) 756 | 757 | # Test case 19: Division by powers of 10 758 | var a19 = Decimal("123.456") 759 | var b19 = Decimal("10") 760 | var result19 = a19 / b19 761 | testing.assert_equal( 762 | String(result19), 763 | "12.3456", 764 | "Division by power of 10", 765 | ) 766 | 767 | # Test case 20: Division by powers of 10 (another case) 768 | var a20 = Decimal("123.456") 769 | var b20 = Decimal("0.01") 770 | var result20 = a20 / b20 771 | testing.assert_equal( 772 | String(result20), 773 | "12345.6", 774 | "Division by 0.01 (multiply by 100)", 775 | ) 776 | 777 | # Test case 21: Division of nearly equal numbers 778 | var a21 = Decimal("1.000001") 779 | var b21 = Decimal("1") 780 | var result21 = a21 / b21 781 | testing.assert_equal( 782 | String(result21), 783 | "1.000001", 784 | "Division of nearly equal numbers", 785 | ) 786 | 787 | # Test case 22: Division resulting in a number with many trailing zeros 788 | var a22 = Decimal("1") 789 | var b22 = Decimal("8") 790 | var result22 = a22 / b22 791 | testing.assert_true( 792 | String(result22).startswith("0.125"), 793 | "Division resulting in an exact decimal with trailing zeros", 794 | ) 795 | 796 | # Test case 23: Division with negative numerator 797 | var a23 = Decimal("-50") 798 | var b23 = Decimal("10") 799 | var result23 = a23 / b23 800 | testing.assert_equal( 801 | String(result23), 802 | "-5", 803 | "Division with negative numerator", 804 | ) 805 | 806 | # Test case 24: Division with negative denominator 807 | var a24 = Decimal("50") 808 | var b24 = Decimal("-10") 809 | var result24 = a24 / b24 810 | testing.assert_equal( 811 | String(result24), 812 | "-5", 813 | "Division with negative denominator", 814 | ) 815 | 816 | # Test case 25: Division with both negative 817 | var a25 = Decimal("-50") 818 | var b25 = Decimal("-10") 819 | var result25 = a25 / b25 820 | testing.assert_equal( 821 | String(result25), 822 | "5", 823 | "Division with both negative numbers", 824 | ) 825 | 826 | # Test case 26: Division resulting in exact integer 827 | var a26 = Decimal("96.75") 828 | var b26 = Decimal("4.5") 829 | var result26 = a26 / b26 830 | testing.assert_equal( 831 | String(result26), 832 | "21.5", 833 | "Division resulting in exact value", 834 | ) 835 | 836 | # Test case 27: Division with high precision numbers 837 | var a27 = Decimal("0.123456789012345678901234567") 838 | var b27 = Decimal("0.987654321098765432109876543") 839 | var result27 = a27 / b27 840 | testing.assert_true( 841 | String(result27).startswith("0.12499"), 842 | "Division of high precision numbers", 843 | ) 844 | 845 | # Test case 28: Division with extreme digit patterns 846 | var a28 = Decimal("9" * 15) # 999999999999999 847 | var b28 = Decimal("9" * 5) # 99999 848 | var result28 = a28 / b28 849 | testing.assert_equal( 850 | String(result28), 851 | "10000100001", 852 | "Division with extreme digit patterns (all 9's)", 853 | ) 854 | 855 | # Test case 29: Division where result is zero 856 | var a29 = Decimal("0") 857 | var b29 = Decimal("123.45") 858 | var result29 = a29 / b29 859 | testing.assert_equal( 860 | String(result29), 861 | "0", 862 | "Division where result is zero", 863 | ) 864 | 865 | # Test case 30: Division where numerator is smaller than denominator 866 | var a30 = Decimal("1") 867 | var b30 = Decimal("10000") 868 | var result30 = a30 / b30 869 | testing.assert_equal( 870 | String(result30), 871 | "0.0001", 872 | "Division where numerator is smaller than denominator", 873 | ) 874 | 875 | # Test case 31: Division resulting in scientific notation range 876 | var a31 = Decimal("1") 877 | var b31 = Decimal("1" + "0" * 20) # 10^20 878 | var result31 = a31 / b31 879 | testing.assert_true( 880 | String(result31).startswith("0.00000000000000000001"), 881 | "Division resulting in very small number", 882 | ) 883 | 884 | # Test case 32: Division with mixed precision 885 | var a32 = Decimal("1") 886 | var b32 = Decimal("3.33333333333333333333333333") 887 | var result32 = a32 / b32 888 | testing.assert_true( 889 | String(result32).startswith("0.3"), 890 | "Division with mixed precision numbers", 891 | ) 892 | 893 | # Test case 33: Division by fractional power of 10 894 | var a33 = Decimal("5.5") 895 | var b33 = Decimal("0.055") 896 | var result33 = a33 / b33 897 | testing.assert_equal( 898 | String(result33), 899 | "100", 900 | "Division by fractional power of 10", 901 | ) 902 | 903 | # Test case 34: Division with rounding at precision boundary 904 | var a34 = Decimal("2") 905 | var b34 = Decimal("3") 906 | var result34 = a34 / b34 907 | # Result should be about 0.66666... 908 | var expected34 = Decimal("0.66666666666666666666666666667") 909 | testing.assert_equal( 910 | result34, 911 | expected34, 912 | "Division with rounding at precision boundary", 913 | ) 914 | 915 | # Test case 35: Division by value very close to zero 916 | var a35 = Decimal("1") 917 | var b35 = Decimal("0." + "0" * 26 + "1") # 0.000...0001 (27 zeros) 918 | var result35 = a35 / b35 919 | testing.assert_true( 920 | String(result35).startswith("1" + "0" * 27), 921 | "Division by value very close to zero", 922 | ) 923 | 924 | print("Additional division tests passed!") 925 | 926 | print("Decimal division tests passed!") 927 | 928 | 929 | fn test_power_integer_exponents() raises: 930 | print("Testing power with integer exponents...") 931 | 932 | # Test case 1: Base cases: x^0 = 1 for any x except 0 933 | var a1 = Decimal("2.5") 934 | var result1 = a1**0 935 | testing.assert_equal( 936 | String(result1), "1", "Any number to power 0 should be 1" 937 | ) 938 | 939 | # Test case 2: 0^n = 0 for n > 0 940 | var a2 = Decimal("0") 941 | var result2 = a2**5 942 | testing.assert_equal( 943 | String(result2), "0", "0 to any positive power should be 0" 944 | ) 945 | 946 | # Test case 3: x^1 = x 947 | var a3 = Decimal("3.14159") 948 | var result3 = a3**1 949 | testing.assert_equal(String(result3), "3.14159", "x^1 should be x") 950 | 951 | # Test case 4: Positive integer powers 952 | var a4 = Decimal("2") 953 | var result4 = a4**3 954 | testing.assert_equal(String(result4), "8", "2^3 should be 8") 955 | 956 | # Test case 5: Test with scale 957 | var a5 = Decimal("1.5") 958 | var result5 = a5**2 959 | testing.assert_equal(String(result5), "2.25", "1.5^2 should be 2.25") 960 | 961 | # Test case 6: Larger powers 962 | var a6 = Decimal("2") 963 | var result6 = a6**10 964 | testing.assert_equal(String(result6), "1024", "2^10 should be 1024") 965 | 966 | # Test case 7: Negative base, even power 967 | var a7 = Decimal("-3") 968 | var result7 = a7**2 969 | testing.assert_equal(String(result7), "9", "(-3)^2 should be 9") 970 | 971 | # Test case 8: Negative base, odd power 972 | var a8 = Decimal("-3") 973 | var result8 = a8**3 974 | testing.assert_equal(String(result8), "-27", "(-3)^3 should be -27") 975 | 976 | # Test case 9: Decimal base, positive power 977 | var a9 = Decimal("0.1") 978 | var result9 = a9**3 979 | testing.assert_equal(String(result9), "0.001", "0.1^3 should be 0.001") 980 | 981 | # Test case 10: Large number to small power 982 | var a10 = Decimal("1000") 983 | var result10 = a10**2 984 | testing.assert_equal( 985 | String(result10), "1000000", "1000^2 should be 1000000" 986 | ) 987 | 988 | print("Integer exponent tests passed!") 989 | 990 | 991 | fn test_power_negative_exponents() raises: 992 | print("Testing power with negative integer exponents...") 993 | 994 | # Test case 1: Basic negative exponent 995 | var a1 = Decimal("2") 996 | var result1 = a1 ** (-2) 997 | testing.assert_equal(String(result1), "0.25", "2^(-2) should be 0.25") 998 | 999 | # Test case 2: Larger negative exponent 1000 | var a2 = Decimal("10") 1001 | var result2 = a2 ** (-3) 1002 | testing.assert_equal(String(result2), "0.001", "10^(-3) should be 0.001") 1003 | 1004 | # Test case 3: Negative base, even negative power 1005 | var a3 = Decimal("-2") 1006 | var result3 = a3 ** (-2) 1007 | testing.assert_equal(String(result3), "0.25", "(-2)^(-2) should be 0.25") 1008 | 1009 | # Test case 4: Negative base, odd negative power 1010 | var a4 = Decimal("-2") 1011 | var result4 = a4 ** (-3) 1012 | testing.assert_equal( 1013 | String(result4), "-0.125", "(-2)^(-3) should be -0.125" 1014 | ) 1015 | 1016 | # Test case 5: Decimal base, negative power 1017 | var a5 = Decimal("0.5") 1018 | var result5 = a5 ** (-2) 1019 | testing.assert_equal(String(result5), "4", "0.5^(-2) should be 4") 1020 | 1021 | # Test case 6: 1^(-n) = 1 1022 | var a6 = Decimal("1") 1023 | var result6 = a6 ** (-5) 1024 | testing.assert_equal(String(result6), "1", "1^(-5) should be 1") 1025 | 1026 | print("Negative exponent tests passed!") 1027 | 1028 | 1029 | fn test_power_special_cases() raises: 1030 | print("Testing power function special cases...") 1031 | 1032 | # Test case 1: 0^0 (typically defined as 1) 1033 | var a1 = Decimal("0") 1034 | try: 1035 | var result1 = a1**0 1036 | testing.assert_equal(String(result1), "1", "0^0 should be defined as 1") 1037 | except: 1038 | print("0^0 raises an exception (mathematically undefined)") 1039 | 1040 | # Test case 2: 0^(-n) (mathematically undefined) 1041 | var a2 = Decimal("0") 1042 | try: 1043 | var result2 = a2 ** (-2) 1044 | print("WARNING: 0^(-2) didn't raise an exception, got", result2) 1045 | except: 1046 | print("0^(-2) correctly raises an exception") 1047 | 1048 | # Test case 3: 1^n = 1 for any n 1049 | var a3 = Decimal("1") 1050 | var result3a = a3**100 1051 | var result3b = a3 ** (-100) 1052 | testing.assert_equal(String(result3a), "1", "1^100 should be 1") 1053 | testing.assert_equal(String(result3b), "1", "1^(-100) should be 1") 1054 | 1055 | # Test case 4: High precision result with rounding 1056 | # TODO: Implement __gt__ 1057 | # var a4 = Decimal("1.1") 1058 | # var result4 = a4**30 1059 | # testing.assert_true( 1060 | # result4 > Decimal("17.4") and result4 < Decimal("17.5"), 1061 | # "1.1^30 should be approximately 17.449", 1062 | # ) 1063 | 1064 | print("Special cases tests passed!") 1065 | 1066 | 1067 | fn test_power_decimal_exponents() raises: 1068 | print("Testing power with decimal exponents...") 1069 | 1070 | # Try a few basic decimal exponents if supported 1071 | try: 1072 | var a1 = Decimal("4") 1073 | var e1 = Decimal("0.5") # Square root 1074 | var result1 = a1**e1 1075 | testing.assert_equal(String(result1), "2", "4^0.5 should be 2") 1076 | 1077 | var a2 = Decimal("8") 1078 | var e2 = Decimal("1.5") # Cube root of square 1079 | var result2 = a2**e2 1080 | testing.assert_equal( 1081 | String(result2)[:4], "22.6", "8^1.5 should be approximately 22.6" 1082 | ) 1083 | except: 1084 | print("Decimal exponents not supported in this implementation") 1085 | 1086 | print("Decimal exponent tests passed!") 1087 | 1088 | 1089 | fn test_power_precision() raises: 1090 | print("Testing power precision...") 1091 | 1092 | # These tests assume we have overloaded the ** operator 1093 | # and we have a way to control precision similar to pow() 1094 | try: 1095 | # Test with precision control 1096 | var a1 = Decimal("1.5") 1097 | var result1 = a1**2 1098 | # Test equality including precision 1099 | testing.assert_equal( 1100 | String(result1), "2.25", "1.5^2 should be exactly 2.25" 1101 | ) 1102 | 1103 | # Check scale 1104 | testing.assert_equal( 1105 | result1.scale(), 1106 | 2, 1107 | "Result should maintain precision of 2 decimal places", 1108 | ) 1109 | except: 1110 | print("Precision parameters not supported with ** operator") 1111 | 1112 | print("Precision tests passed!") 1113 | 1114 | 1115 | fn test_extreme_cases() raises: 1116 | print("Testing extreme cases...") 1117 | 1118 | # Test case 1: Addition that results in exactly zero with high precision 1119 | var a1 = Decimal("0." + "1" * 28) # 0.1111...1 (28 digits) 1120 | var b1 = Decimal("-0." + "1" * 28) # -0.1111...1 (28 digits) 1121 | var result1 = a1 + b1 1122 | testing.assert_equal( 1123 | String(result1), 1124 | "0." + "0" * 28, 1125 | "High precision addition resulting in zero", 1126 | ) 1127 | 1128 | # Test case 2: Addition that should trigger overflow handling 1129 | try: 1130 | var a2 = Decimal("79228162514264337593543950335") # MAX() 1131 | var b2 = Decimal("1") 1132 | var result2 = a2 + b2 1133 | print("WARNING: Addition beyond MAX() didn't raise an error") 1134 | except: 1135 | print("Addition overflow correctly detected") 1136 | 1137 | # Test case 3: Addition with mixed precision zeros 1138 | var a3 = Decimal("0.00") 1139 | var b3 = Decimal("0.000000") 1140 | var result3 = a3 + b3 1141 | testing.assert_equal( 1142 | String(result3), "0.000000", "Addition of different precision zeros" 1143 | ) 1144 | 1145 | # Test case 4: Addition with boundary values involving zeros 1146 | var a4 = Decimal("0.0") 1147 | var b4 = Decimal("-0.00") 1148 | var result4 = a4 + b4 1149 | testing.assert_equal( 1150 | String(result4), "0.00", "Addition of positive and negative zero" 1151 | ) 1152 | 1153 | # Test case 5: Adding numbers that require carry propagation through many places 1154 | var a5 = Decimal("9" * 20 + "." + "9" * 28) # 99...9.99...9 1155 | var b5 = Decimal("0." + "0" * 27 + "1") # 0.00...01 1156 | var result5 = a5 + b5 1157 | # The result should be 10^20 exactly, since all 9s carry over 1158 | testing.assert_equal( 1159 | String(result5), 1160 | "100000000000000000000.00000000", 1161 | "Addition with extensive carry propagation", 1162 | ) 1163 | 1164 | print("Extreme case tests passed!") 1165 | 1166 | 1167 | fn main() raises: 1168 | print("Running decimal arithmetic tests") 1169 | 1170 | # Run addition tests 1171 | test_add() 1172 | 1173 | # Run negation tests 1174 | test_negation() 1175 | 1176 | # Run absolute value tests 1177 | test_abs() 1178 | 1179 | # Run subtraction tests 1180 | test_subtract() 1181 | 1182 | # Run multiplication tests 1183 | test_multiplication() 1184 | 1185 | # Run division tests 1186 | test_division() 1187 | 1188 | # Run power tests with integer exponents 1189 | test_power_integer_exponents() 1190 | 1191 | # Run power tests with negative exponents 1192 | 1193 | test_power_negative_exponents() 1194 | 1195 | # Run power tests for special cases 1196 | test_power_special_cases() 1197 | 1198 | # Run power tests with decimal exponents 1199 | test_power_decimal_exponents() 1200 | 1201 | # Run power precision tests 1202 | test_power_precision() 1203 | 1204 | # Run extreme cases tests 1205 | test_extreme_cases() 1206 | 1207 | print("All decimal arithmetic tests passed!") 1208 | --------------------------------------------------------------------------------