├── .gitattributes ├── .github ├── actionlint.yaml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── build-pr.yml │ ├── build-upload.yml │ ├── docs.yml │ ├── pre-commit.yml │ ├── remove-incompatible-packages.yml │ └── update-lockfiles.yml ├── .gitignore ├── .pre-commit-config.yaml ├── DEVELOPERS.md ├── README.md ├── data ├── failed-compatibility-macos-latest.json ├── failed-compatibility-magic_arm64_8core.json └── failed-compatibility-ubuntu-latest.json ├── docs └── index.md ├── mkdocs.yml ├── mypy.ini ├── pixi.lock ├── pixi.toml ├── recipes ├── ExtraMojo │ ├── image.jpeg │ ├── recipe.yaml │ └── tests │ │ ├── test_bstr.mojo │ │ ├── test_file.mojo │ │ └── test_regex.mojo ├── decimojo │ ├── README.md │ ├── image.jpeg │ ├── recipe.yaml │ └── tests.mojo ├── emberjson │ ├── image.jpeg │ └── recipe.yaml ├── infrared │ ├── README.md │ ├── avatar.jpeg │ ├── image.jpeg │ ├── recipe.yaml │ └── test.mojo ├── ish │ ├── image.jpeg │ └── recipe.yaml ├── kelvin │ ├── image.jpeg │ └── recipe.yaml ├── lightbug_http │ ├── image.jpeg │ └── recipe.yaml ├── mimage │ ├── README.md │ ├── image.jpeg │ ├── recipe.yaml │ └── tests │ │ ├── __init__.mojo │ │ ├── images │ │ └── hopper.png │ │ ├── test_open_png.mojo │ │ └── testing_utils.mojo ├── mist │ ├── image.jpeg │ └── recipe.yaml ├── mojmelo │ ├── image.jpeg │ ├── recipe.yaml │ └── tests │ │ ├── mojmelo │ │ ├── __init__.mojo │ │ └── utils │ │ │ ├── Matrix.mojo │ │ │ ├── __init__.mojo │ │ │ └── mojmelo_matmul │ │ │ ├── __init__.mojo │ │ │ ├── matmul.mojo │ │ │ └── params.mojo │ │ ├── setup.mojo │ │ └── setup.sh ├── mojo-libc │ ├── README.md │ ├── image.jpeg │ └── recipe.yaml ├── mojo-websockets │ ├── README.md │ ├── image.jpeg │ └── recipe.yaml ├── mosaic │ ├── image.jpeg │ └── recipe.yaml ├── nabla │ ├── image.jpeg │ ├── recipe.yaml │ └── test.mojo ├── nanoid │ ├── image.jpeg │ └── recipe.yaml ├── numojo │ ├── README.md │ ├── image.jpeg │ ├── recipe.yaml │ └── tests.mojo └── small_time │ ├── image.jpeg │ └── recipe.yaml ├── ruff.toml ├── scripts ├── __init__.py ├── build-all.py ├── commit-changes.py ├── common.py ├── configure-git.py ├── remove-incompatible-packages.py └── upload.py └── variants └── variants.yaml /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub syntax highlighting 2 | pixi.lock linguist-language=YAML linguist-generated=true 3 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | fetch-depth: 2 24 | - uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8 25 | with: 26 | pixi-version: v0.37.0 27 | 28 | - name: Build packages 29 | shell: bash 30 | run: pixi run build-all --channel https://prefix.dev/modular-community --channel https://conda.modular.com/max 31 | env: 32 | RATTLER_BUILD_ENABLE_GITHUB_INTEGRATION: true 33 | RATTLER_BUILD_COLOR: always 34 | -------------------------------------------------------------------------------- /.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | with: 27 | fetch-depth: 2 28 | - uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8 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 --channel https://prefix.dev/modular-community --channel https://conda.modular.com/max 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 | -------------------------------------------------------------------------------- /.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 14 | - uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | with: 18 | fetch-depth: 2 19 | - uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8 20 | with: 21 | pixi-version: v0.37.0 22 | 23 | - name: Run pre-commit 24 | run: pixi run lint 25 | -------------------------------------------------------------------------------- /.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 18 | with: 19 | fetch-depth: 2 20 | - uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8 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 | -------------------------------------------------------------------------------- /.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - name: Set up pixi 18 | uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 Magic 🪄 4 | 5 | We’re currently running an Early Access Program, and we’d love for you to join the fun! Whether you want to install packages, contribute your own, or review creations from fellow community members, it’s easy to get started. Simply [join our forum](https://forum.modular.com/t/community-channel-early-access/213) and [opt in](https://forum.modular.com/t/community-channel-early-access/213) – you'll receive a DM shortly with more info. 6 | 7 | ## Installing a package 8 | 9 | ### Add the Modular community channel to your `mojoproject.toml` file 10 | 11 | Before you can install a community package, you’ll need to add the Modular community channel to your `mojoproject.toml` or `pixi.toml` file. 12 | 13 | Add the Modular community channel (https://repo.prefix.dev/modular-community) to your `mojoproject.toml` file or `pixi.toml file` in the channels section: 14 | 15 | ``` 16 | # mojoproject.toml or pixi.toml 17 | 18 | [project] 19 | channels = ["conda-forge", "https://conda.modular.com/max", "https://repo.prefix.dev/modular-community"] 20 | description = "Add a short description here" 21 | name = "my-mojo-project" 22 | platforms = ["osx-arm64"] 23 | version = "0.1.0" 24 | 25 | [tasks] 26 | 27 | [dependencies] 28 | max = ">=24.5.0,<25" 29 | ``` 30 | 31 | ### **Install a package** 32 | 33 | To install a package from the Modular community channel, simply enter the following in the command line: 34 | ``` 35 | magic add String: 12 | """Convert bytes to a String.""" 13 | var buffer = String() 14 | buffer.write_bytes(bytes) 15 | return buffer 16 | 17 | 18 | # Sometimes useful for digging into the memchr function 19 | # from ir_utils.dump import dump_ir 20 | # fn main() raises: 21 | # var static_str = "hi" 22 | # dump_ir[ 23 | # find_chr_next_occurrence[__origin_of(static_str)], 24 | # "find_chr_next_occurrence", 25 | # ]() 26 | # test_find_chr_next_occurance() 27 | 28 | 29 | fn test_memchr() raises: 30 | alias check = InlineArray[Bool, 2](True, False) 31 | 32 | @parameter 33 | for do_alignment in range(0, len(check)): 34 | var cases = List[(String, Int)]( 35 | ( 36 | String( 37 | "enlivened,unleavened,Arnulfo's,Unilever's,unloved|Anouilh,analogue,analogy" 38 | ), 39 | 49, 40 | ), 41 | ( 42 | String( 43 | "enlivened,unleavened,Arnulfo's,Unilever's,unloved,Anouilh,analogue,analogy,enlivened,unleavened,Arnulfo's,Unilever's,unloved|Anouilh,analogue,analogy" 44 | ), 45 | 124, 46 | ), 47 | ) 48 | 49 | for kase in cases: 50 | var index = memchr[do_alignment = check[do_alignment]]( 51 | kase[][0].as_bytes(), ord("|") 52 | ) 53 | assert_equal( 54 | index, 55 | kase[][1], 56 | "Expected " 57 | + String(kase[][1]) 58 | + " Found " 59 | + String(index) 60 | + " in " 61 | + kase[][0], 62 | ) 63 | 64 | 65 | fn test_memchr_wide() raises: 66 | var cases = List[(String, Int)]( 67 | ( 68 | String( 69 | "enlivened,unleavened,Arnulfo's,Unilever's,unloved|Anouilh,analogue,analogy" 70 | ), 71 | 49, 72 | ), 73 | ( 74 | String( 75 | "enlivened,unleavened,Arnulfo's,Unilever's,unloved,Anouilh,analogue,analogy,enlivened,unleavened,Arnulfo's,Unilever's,unloved|Anouilh,analogue,analogy" 76 | ), 77 | 124, 78 | ), 79 | ) 80 | 81 | for kase in cases: 82 | var index = memchr_wide(kase[][0].as_bytes(), ord("|")) 83 | assert_equal( 84 | index, 85 | kase[][1], 86 | "Expected " 87 | + String(kase[][1]) 88 | + " Found " 89 | + String(index) 90 | + " in " 91 | + kase[][0], 92 | ) 93 | 94 | 95 | fn test_lowercase_short() raises: 96 | var example = List("ABCdefgHIjklmnOPQRSTUVWXYZ".as_bytes()) 97 | var answer = "abcdefghijklmnopqrstuvwxyz" 98 | to_ascii_lowercase(example) 99 | assert_equal(s(example), s(answer.as_bytes())) 100 | 101 | 102 | fn test_uppercase_short() raises: 103 | var example = List("ABCdefgHIjklmnOPQRSTUVWXYZ".as_bytes()) 104 | var answer = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 105 | to_ascii_uppercase(example) 106 | assert_equal(s(example), s(answer.as_bytes())) 107 | 108 | 109 | fn test_lowercase() raises: 110 | var example = List( 111 | "ABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZ" 112 | .as_bytes() 113 | ) 114 | var answer = "abcdefghijklmnopqrstuvwxyz;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;abcdefghijklmnopqrstuvwxyz" 115 | to_ascii_lowercase(example) 116 | assert_equal(s(example), s(answer.as_bytes())) 117 | 118 | 119 | fn test_uppercase() raises: 120 | var example = List( 121 | "ABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZ" 122 | .as_bytes() 123 | ) 124 | var answer = "ABCDEFGHIJKLMNOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCDEFGHIJKLMNOPQRSTUVWXYZ" 125 | to_ascii_uppercase(example) 126 | assert_equal(s(example), s(answer.as_bytes())) 127 | 128 | 129 | fn test_lowercase_long() raises: 130 | var example = List( 131 | "ABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZ" 132 | .as_bytes() 133 | ) 134 | var answer = "abcdefghijklmnopqrstuvwxyz;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;abcdefghijklmnopqrstuvwxyz" 135 | to_ascii_lowercase(example) 136 | assert_equal(s(example), s(answer.as_bytes())) 137 | 138 | 139 | fn test_uppercase_long() raises: 140 | var example = List( 141 | "ABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZABCdefgHIjklmnOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCdefgHIjklmnOPQRSTUVWXYZ" 142 | .as_bytes() 143 | ) 144 | var answer = "ABCDEFGHIJKLMNOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ABCDEFGHIJKLMNOPQRSTUVWXYZ" 145 | to_ascii_uppercase(example) 146 | assert_equal(s(example), s(answer.as_bytes())) 147 | 148 | 149 | fn test_find_short() raises: 150 | var haystack = "ABCDEFGhijklmnop".as_bytes() 151 | var expected = 4 152 | var answer = find(haystack, "EFG".as_bytes()).value() 153 | assert_equal(answer, expected) 154 | 155 | 156 | fn test_find_medium() raises: 157 | var haystack = "ABCDEFGhijklmnop0123456789TheKindIguana\nJumpedOver the angry weird fense as it ran away from the seething moon that was swooping down to scoop it up and bring it to outer space.".as_bytes() 158 | var expected = 171 159 | var answer = find(haystack, "space".as_bytes()).value() 160 | assert_equal(answer, expected) 161 | 162 | 163 | fn test_find_long() raises: 164 | var haystack = "ABCDEFGhijklmnop0123456789TheKindIguana\nJumpedOver the angry weird fense as it ran away from the seething moon that was swooping down to scoop it up and bring it to outer space.\nThen a really weird thing happened and suddenly 64 moons were swooping down at the Iguana. It tried to turn and tell them it was scalar, but they didn't care all tried to scoop it at once, which resulted in a massive Iguana lock contention.".as_bytes() 165 | var expected = 373 166 | var answer = find(haystack, "result".as_bytes()).value() 167 | assert_equal(answer, expected) 168 | 169 | 170 | fn test_find_long_variable_start() raises: 171 | var haystack = "ABCDEFGhijklmnop0123456789TheKindIguana\nJumpedOver the angry weird fense as it ran away from the seething moon that was swooping down to scoop it up and bring it to outer space.\nThen a really weird thing happened and suddenly 64 moons were swooping down at the Iguana. It tried to turn and tell them it was scalar, but they didn't care all tried to scoop it at once, which resulted in a massive IguanaZ lock contention.".as_bytes() 172 | for i in range(0, len(haystack)): 173 | var answer = memchr(haystack, ord("Z"), i) 174 | if i <= 401: 175 | assert_equal(401, answer) 176 | else: 177 | assert_equal(-1, answer) 178 | # assert_equal(answer, expected) 179 | 180 | 181 | fn test_spilt_iterator() raises: 182 | var input = "ABCD\tEFGH\tIJKL\nMNOP".as_bytes() 183 | var expected = List( 184 | "ABCD".as_bytes(), "EFGH".as_bytes(), "IJKL\nMNOP".as_bytes() 185 | ) 186 | var output = List[Span[UInt8, StaticConstantOrigin]]() 187 | for value in SplitIterator(input, ord("\t")): 188 | output.append(value) 189 | for i in range(len(expected)): 190 | assert_equal(s(output[i]), s(expected[i]), "Not equal") 191 | 192 | 193 | fn test_spilt_iterator_peek() raises: 194 | var input = "ABCD\tEFGH\tIJKL\nMNOP".as_bytes() 195 | var expected = List( 196 | "ABCD".as_bytes(), "EFGH".as_bytes(), "IJKL\nMNOP".as_bytes() 197 | ) 198 | var iter = SplitIterator(input, ord("\t")) 199 | var first = iter.__next__() 200 | var peek = iter.peek() 201 | var second = iter.__next__() 202 | assert_equal(s(peek.value()), s(second)) 203 | assert_equal(s(first), s(expected[0])) 204 | assert_equal(s(second), s(expected[1])) 205 | 206 | 207 | fn test_spilt_iterator_long() raises: 208 | var input = "ABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ\tABCD\tEFGH\tIJKL\nMNOP\tQRST\tUVWXYZ".as_bytes() 209 | var expected = List( 210 | "ABCD".as_bytes(), 211 | "EFGH".as_bytes(), 212 | "IJKL\nMNOP".as_bytes(), 213 | "QRST".as_bytes(), 214 | "UVWXYZ".as_bytes(), 215 | "ABCD".as_bytes(), 216 | "EFGH".as_bytes(), 217 | "IJKL\nMNOP".as_bytes(), 218 | "QRST".as_bytes(), 219 | "UVWXYZ".as_bytes(), 220 | "ABCD".as_bytes(), 221 | "EFGH".as_bytes(), 222 | "IJKL\nMNOP".as_bytes(), 223 | "QRST".as_bytes(), 224 | "UVWXYZ".as_bytes(), 225 | "ABCD".as_bytes(), 226 | "EFGH".as_bytes(), 227 | "IJKL\nMNOP".as_bytes(), 228 | "QRST".as_bytes(), 229 | "UVWXYZ".as_bytes(), 230 | "ABCD".as_bytes(), 231 | "EFGH".as_bytes(), 232 | "IJKL\nMNOP".as_bytes(), 233 | "QRST".as_bytes(), 234 | "UVWXYZ".as_bytes(), 235 | "ABCD".as_bytes(), 236 | "EFGH".as_bytes(), 237 | "IJKL\nMNOP".as_bytes(), 238 | "QRST".as_bytes(), 239 | "UVWXYZ".as_bytes(), 240 | "ABCD".as_bytes(), 241 | "EFGH".as_bytes(), 242 | "IJKL\nMNOP".as_bytes(), 243 | "QRST".as_bytes(), 244 | "UVWXYZ".as_bytes(), 245 | "ABCD".as_bytes(), 246 | "EFGH".as_bytes(), 247 | "IJKL\nMNOP".as_bytes(), 248 | "QRST".as_bytes(), 249 | "UVWXYZ".as_bytes(), 250 | "ABCD".as_bytes(), 251 | "EFGH".as_bytes(), 252 | "IJKL\nMNOP".as_bytes(), 253 | "QRST".as_bytes(), 254 | "UVWXYZ".as_bytes(), 255 | "ABCD".as_bytes(), 256 | "EFGH".as_bytes(), 257 | "IJKL\nMNOP".as_bytes(), 258 | "QRST".as_bytes(), 259 | "UVWXYZ".as_bytes(), 260 | "ABCD".as_bytes(), 261 | "EFGH".as_bytes(), 262 | "IJKL\nMNOP".as_bytes(), 263 | "QRST".as_bytes(), 264 | "UVWXYZ".as_bytes(), 265 | "ABCD".as_bytes(), 266 | "EFGH".as_bytes(), 267 | "IJKL\nMNOP".as_bytes(), 268 | "QRST".as_bytes(), 269 | "UVWXYZ".as_bytes(), 270 | "ABCD".as_bytes(), 271 | "EFGH".as_bytes(), 272 | "IJKL\nMNOP".as_bytes(), 273 | "QRST".as_bytes(), 274 | "UVWXYZ".as_bytes(), 275 | "ABCD".as_bytes(), 276 | "EFGH".as_bytes(), 277 | "IJKL\nMNOP".as_bytes(), 278 | "QRST".as_bytes(), 279 | "UVWXYZ".as_bytes(), 280 | "ABCD".as_bytes(), 281 | "EFGH".as_bytes(), 282 | "IJKL\nMNOP".as_bytes(), 283 | "QRST".as_bytes(), 284 | "UVWXYZ".as_bytes(), 285 | "ABCD".as_bytes(), 286 | "EFGH".as_bytes(), 287 | "IJKL\nMNOP".as_bytes(), 288 | "QRST".as_bytes(), 289 | "UVWXYZ".as_bytes(), 290 | "ABCD".as_bytes(), 291 | "EFGH".as_bytes(), 292 | "IJKL\nMNOP".as_bytes(), 293 | "QRST".as_bytes(), 294 | "UVWXYZ".as_bytes(), 295 | "ABCD".as_bytes(), 296 | "EFGH".as_bytes(), 297 | "IJKL\nMNOP".as_bytes(), 298 | "QRST".as_bytes(), 299 | "UVWXYZ".as_bytes(), 300 | ) 301 | var output = List[Span[UInt8, StaticConstantOrigin]]() 302 | for value in SplitIterator(input, ord("\t")): 303 | output.append(value) 304 | for i in range(len(expected)): 305 | assert_equal(s(output[i]), s(expected[i]), "Not equal") 306 | -------------------------------------------------------------------------------- /recipes/ExtraMojo/tests/test_file.mojo: -------------------------------------------------------------------------------- 1 | from collections import Dict 2 | from pathlib import Path 3 | from python import Python 4 | from tensor import Tensor 5 | from testing import * 6 | 7 | from ExtraMojo.bstr.bstr import SplitIterator 8 | from ExtraMojo.io.delimited import ( 9 | DelimReader, 10 | FromDelimited, 11 | ToDelimited, 12 | DelimWriter, 13 | ) 14 | from ExtraMojo.io.buffered import ( 15 | BufferedReader, 16 | read_lines, 17 | for_each_line, 18 | BufferedWriter, 19 | ) 20 | 21 | 22 | fn s(bytes: Span[UInt8]) -> String: 23 | """Convert bytes to a String.""" 24 | var buffer = String() 25 | buffer.write_bytes(bytes) 26 | return buffer 27 | 28 | 29 | fn strings_for_writing(size: Int) -> List[String]: 30 | var result = List[String]() 31 | for i in range(size): 32 | result.append( 33 | "Line: " + String(i) + " X" + ("-" * 64) 34 | ) # make lines long 35 | return result 36 | 37 | 38 | fn test_read_until(file: Path, expected_lines: List[String]) raises: 39 | var buffer_capacities = List(10, 100, 200, 500) 40 | for cap in buffer_capacities: 41 | var fh = open(file, "r") 42 | var reader = BufferedReader(fh^, buffer_capacity=cap[]) 43 | var buffer = List[UInt8]() 44 | var counter = 0 45 | while reader.read_until(buffer) != 0: 46 | assert_equal(List(expected_lines[counter].as_bytes()), buffer) 47 | counter += 1 48 | buffer.clear() 49 | 50 | assert_equal(counter, len(expected_lines)) 51 | print( 52 | String("Successful read_until with buffer capacity of {}").format( 53 | cap[] 54 | ) 55 | ) 56 | 57 | 58 | fn test_read_until_return_trailing( 59 | file: Path, expected_lines: List[String] 60 | ) raises: 61 | var fh = open(file, "r") 62 | var reader = BufferedReader(fh^, buffer_capacity=200) 63 | var buffer = List[UInt8]() 64 | var counter = 0 65 | while reader.read_until(buffer) != 0: 66 | assert_equal(List(expected_lines[counter].as_bytes()), buffer) 67 | counter += 1 68 | buffer.clear() 69 | assert_equal(counter, len(expected_lines)) 70 | print("Successful read_until_return_trailing") 71 | 72 | 73 | fn test_read_bytes(file: Path) raises: 74 | var fh = open(file, "r") 75 | var reader = BufferedReader(fh^, buffer_capacity=50) 76 | var buffer = List[UInt8](capacity=125) 77 | for _ in range(0, 125): 78 | buffer.append(0) 79 | var found_file = List[UInt8]() 80 | 81 | # Read bytes from the buf reader, copy to found 82 | var bytes_read = 0 83 | while True: 84 | bytes_read = reader.read_bytes(buffer) 85 | if bytes_read == 0: 86 | break 87 | found_file.extend(buffer[0:bytes_read]) 88 | # Last usage of reader, meaning it should call __del__ here. 89 | 90 | var expected = open(file, "r").read().as_bytes() 91 | assert_equal(len(expected), len(found_file)) 92 | for i in range(0, len(expected)): 93 | assert_equal( 94 | expected[i], found_file[i], msg="Unequal at byte: " + String(i) 95 | ) 96 | print("Successful read_bytes") 97 | 98 | 99 | fn test_context_manager_simple(file: Path, expected_lines: List[String]) raises: 100 | var buffer = List[UInt8]() 101 | var counter = 0 102 | with BufferedReader(open(file, "r"), buffer_capacity=200) as reader: 103 | while reader.read_until(buffer) != 0: 104 | assert_equal(List(expected_lines[counter].as_bytes()), buffer) 105 | counter += 1 106 | buffer.clear() 107 | assert_equal(counter, len(expected_lines)) 108 | print("Successful read_until") 109 | 110 | 111 | fn test_read_lines(file: Path, expected_lines: List[String]) raises: 112 | var lines = read_lines(String(file)) 113 | assert_equal(len(lines), len(expected_lines)) 114 | for i in range(0, len(lines)): 115 | assert_equal(lines[i], List(expected_lines[i].as_bytes())) 116 | print("Successful read_lines") 117 | 118 | 119 | fn test_for_each_line(file: Path, expected_lines: List[String]) raises: 120 | var counter = 0 121 | var found_bad = False 122 | 123 | @parameter 124 | fn inner(buffer: Span[UInt8], start: Int, end: Int) capturing -> None: 125 | if s(buffer[start:end]) != expected_lines[counter]: 126 | found_bad = True 127 | counter += 1 128 | 129 | for_each_line[inner](String(file)) 130 | assert_false(found_bad) 131 | print("Successful for_each_line") 132 | 133 | 134 | @value 135 | struct SerDerStruct(ToDelimited, FromDelimited): 136 | var index: Int 137 | var name: String 138 | 139 | fn write_to_delimited(read self, mut writer: DelimWriter) raises: 140 | writer.write_record(self.index, self.name) 141 | 142 | fn write_header(read self, mut writer: DelimWriter) raises: 143 | writer.write_record("index", "name") 144 | 145 | @staticmethod 146 | fn from_delimited( 147 | mut data: SplitIterator, 148 | read header_values: Optional[List[String]] = None, 149 | ) raises -> Self: 150 | var index = Int(StringSlice(unsafe_from_utf8=data.__next__())) 151 | var name = String() # String constructor expected nul terminated byte span 152 | name.write_bytes(data.__next__()) 153 | return Self(index, name) 154 | 155 | 156 | fn test_delim_reader_writer(file: Path) raises: 157 | var to_write = List[SerDerStruct]() 158 | for i in range(0, 1000): 159 | to_write.append(SerDerStruct(i, String("MyNameIs" + String(i)))) 160 | var writer = DelimWriter( 161 | BufferedWriter(open(String(file), "w")), delim="\t", write_header=True 162 | ) 163 | for item in to_write: 164 | writer.serialize(item[]) 165 | writer.flush() 166 | writer.close() 167 | 168 | var reader = DelimReader[SerDerStruct]( 169 | BufferedReader(open(String(file), "r")), 170 | delim=ord("\t"), 171 | has_header=True, 172 | ) 173 | var count = 0 174 | for item in reader^: 175 | assert_equal(to_write[count].index, item.index) 176 | assert_equal(to_write[count].name, item.name) 177 | count += 1 178 | assert_equal(count, len(to_write)) 179 | print("Successful delim_writer") 180 | 181 | 182 | @value 183 | struct ThinWrapper(ToDelimited, FromDelimited): 184 | var stuff: Dict[String, Int] 185 | 186 | fn write_to_delimited(read self, mut writer: DelimWriter) raises: 187 | var seen = 1 188 | for value in self.stuff.values(): # Relying on stable iteration order 189 | writer.write_field(value[], is_last=seen == len(self.stuff)) 190 | seen += 1 191 | 192 | fn write_header(read self, mut writer: DelimWriter) raises: 193 | var seen = 1 194 | for header in self.stuff.keys(): # Relying on stable iteration order 195 | writer.write_field(header[], is_last=seen == len(self.stuff)) 196 | seen += 1 197 | 198 | @staticmethod 199 | fn from_delimited( 200 | mut data: SplitIterator, 201 | read header_values: Optional[List[String]] = None, 202 | ) raises -> Self: 203 | var result = Dict[String, Int]() 204 | for header in header_values.value(): 205 | result[header[]] = Int( 206 | StringSlice(unsafe_from_utf8=data.__next__()) 207 | ) 208 | return Self(result) 209 | 210 | 211 | fn test_delim_reader_writer_dicts(file: Path) raises: 212 | var to_write = List[ThinWrapper]() 213 | var headers = List( 214 | String("a"), String("b"), String("c"), String("d"), String("e") 215 | ) 216 | for i in range(0, 1000): 217 | var stuff = Dict[String, Int]() 218 | for header in headers: 219 | stuff[header[]] = i 220 | to_write.append(ThinWrapper(stuff)) 221 | var writer = DelimWriter( 222 | BufferedWriter(open(String(file), "w")), 223 | delim="\t", 224 | write_header=True, 225 | ) 226 | for item in to_write: 227 | writer.serialize(item[]) 228 | writer.flush() 229 | writer.close() 230 | 231 | var reader = DelimReader[ThinWrapper]( 232 | BufferedReader(open(String(file), "r")), 233 | delim=ord("\t"), 234 | has_header=True, 235 | ) 236 | var count = 0 237 | for item in reader^: 238 | for header in headers: 239 | assert_equal(to_write[count].stuff[header[]], item.stuff[header[]]) 240 | count += 1 241 | assert_equal(count, len(to_write)) 242 | print("Successful delim_writer") 243 | 244 | 245 | fn test_buffered_writer(file: Path, expected_lines: List[String]) raises: 246 | var fh = BufferedWriter(open(String(file), "w"), buffer_capacity=128) 247 | for i in range(len(expected_lines)): 248 | fh.write_bytes(expected_lines[i].as_bytes()) 249 | fh.write_bytes("\n".as_bytes()) 250 | fh.flush() 251 | fh.close() 252 | 253 | test_read_until(String(file), expected_lines) 254 | 255 | 256 | fn create_file(path: String, lines: List[String]) raises: 257 | with open(path, "w") as fh: 258 | for i in range(len(lines)): 259 | fh.write(lines[i]) 260 | fh.write(String("\n")) 261 | 262 | 263 | fn create_file_no_trailing_newline(path: String, lines: List[String]) raises: 264 | with open(path, "w") as fh: 265 | for i in range(len(lines)): 266 | fh.write(lines[i]) 267 | if i != len(lines) - 1: 268 | fh.write(String("\n")) 269 | 270 | 271 | fn main() raises: 272 | var tempfile = Python.import_module("tempfile") 273 | var tempdir = tempfile.TemporaryDirectory() 274 | var file = Path(String(tempdir.name)) / "lines.txt" 275 | var file_no_trailing_newline = Path( 276 | String(tempdir.name) 277 | ) / "lines_no_trailing_newline.txt" 278 | var strings = strings_for_writing(10000) 279 | create_file(String(file), strings) 280 | create_file_no_trailing_newline(String(file_no_trailing_newline), strings) 281 | 282 | # Tests 283 | test_read_until(String(file), strings) 284 | test_read_until_return_trailing(String(file_no_trailing_newline), strings) 285 | test_read_bytes(String(file)) 286 | test_read_lines(String(file), strings) 287 | test_for_each_line(String(file), strings) 288 | var buf_writer_file = Path(String(tempdir.name)) / "buf_writer.txt" 289 | test_buffered_writer(String(buf_writer_file), strings) 290 | var delim_file = Path(String(tempdir.name)) / "delim.txt" 291 | test_delim_reader_writer(String(delim_file)) 292 | var delim_dict_file = Path(String(tempdir.name)) / "delim_dict.txt" 293 | test_delim_reader_writer_dicts(String(delim_dict_file)) 294 | print("SUCCESS") 295 | 296 | _ = tempdir.cleanup() 297 | -------------------------------------------------------------------------------- /recipes/ExtraMojo/tests/test_regex.mojo: -------------------------------------------------------------------------------- 1 | from ExtraMojo.regex.simple_re import * 2 | from testing import * 3 | 4 | 5 | fn main() raises: 6 | test_start_anchor() 7 | test_end_anchor() 8 | test_dot() 9 | test_star() 10 | test_literal() 11 | test_dot_star() 12 | test_all() 13 | print("SUCCESS") 14 | 15 | 16 | fn test_start_anchor() raises: 17 | var re = "^cat" 18 | assert_true(is_match(re, "cats of a feather")) 19 | assert_false(is_match(re, "bird cats of a cat")) 20 | 21 | 22 | fn test_end_anchor() raises: 23 | var re = "what$" 24 | assert_true(is_match(re, "It is what")) 25 | assert_false(is_match(re, "what is in the box")) 26 | 27 | 28 | fn test_dot() raises: 29 | var re = "w.t" 30 | assert_true(is_match(re, "Is that a witty remark?")) 31 | assert_false(is_match(re, "wt is that what thing there")) 32 | 33 | 34 | fn test_star() raises: 35 | var re = "wha*" 36 | assert_true(is_match(re, "what am I doing here")) 37 | assert_true(is_match(re, "whaaaaaaat am I doing here")) 38 | assert_true(is_match(re, "wht am I doing here")) 39 | assert_false(is_match(re, "wt am I doing here")) 40 | 41 | 42 | fn test_literal() raises: 43 | var re = "ACTG" 44 | assert_true(is_match(re, "TGGGACTGCCCACTG")) 45 | assert_true(is_match(re, "CTGGGACGCCCACTG")) 46 | assert_false(is_match(re, "CTGGGACGCCCACG")) 47 | 48 | 49 | fn test_dot_star() raises: 50 | var re = "STAR.*" 51 | assert_true(is_match(re, "STAR")) 52 | assert_true(is_match(re, "I'M A STAR")) 53 | assert_true(is_match(re, "I'M A STARXXXXXXX")) 54 | assert_true(is_match(re, "I'M A STARS")) 55 | assert_true(is_match(re, "I'M A STAR!!!!!")) 56 | assert_false(is_match(re, "I'm not a STArsss")) 57 | 58 | 59 | fn test_all() raises: 60 | assert_true(is_match("^cat.*$", "catsssssss")) 61 | assert_false(is_match("^cat.*$", "many catsssssss")) 62 | -------------------------------------------------------------------------------- /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/decimojo/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/decimojo/image.jpeg -------------------------------------------------------------------------------- /recipes/decimojo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.3.0" 3 | 4 | package: 5 | name: "decimojo" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/forFudan/decimojo.git 10 | rev: f2930f0832f1096207cb93bb2cc52b028b06b429 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.2 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.2 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/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 13: Verify mathematical identity (a/b)*b ≈ a within rounding error 707 | var a13 = Decimal("123.45") 708 | var b13 = Decimal("7.89") 709 | var div_result = a13 / b13 710 | var mul_result = div_result * b13 711 | # Because of rounding, we don't expect exact equality, so check if the difference is small 712 | var diff = a13 - mul_result 713 | var abs_diff = -diff if diff.is_negative() else diff 714 | var is_close = Float64(String(abs_diff)) < 0.0001 715 | testing.assert_equal(is_close, True, "(a/b)*b should approximately equal a") 716 | 717 | # Test case 14: Division of number by itself should be 1 718 | var a14 = Decimal("123.45") 719 | var result14 = a14 / a14 720 | testing.assert_equal( 721 | String(result14), 722 | "1", 723 | "Division of number by itself", 724 | ) 725 | 726 | # Test case 15: Division by zero should raise an error 727 | var a15 = Decimal("123.45") 728 | var b15 = Decimal("0.0") 729 | try: 730 | var result15 = a15 / b15 731 | testing.assert_equal( 732 | True, False, "Division by zero should raise an error" 733 | ) 734 | except: 735 | testing.assert_equal(True, True, "Division by zero correctly rejected") 736 | 737 | # ============= ADDITIONAL DIVISION TEST CASES ============= 738 | print("\nTesting additional division scenarios...") 739 | 740 | # Test case 16: Division with very large number by very small number 741 | var a16 = Decimal("1000000000") 742 | var b16 = Decimal("0.0001") 743 | var result16 = a16 / b16 744 | testing.assert_equal( 745 | String(result16), 746 | "10000000000000", 747 | "Large number divided by small number", 748 | ) 749 | 750 | # Test case 17: Division with very small number by very large number 751 | var a17 = Decimal("0.0001") 752 | var b17 = Decimal("1000000000") 753 | var result17 = a17 / b17 754 | testing.assert_true( 755 | String(result17).startswith("0.0000000000001"), 756 | "Small number divided by large number", 757 | ) 758 | 759 | # Test case 18: Division resulting in repeating decimal 760 | var a18 = Decimal("1") 761 | var b18 = Decimal("3") 762 | var result18 = a18 / b18 763 | testing.assert_true( 764 | String(result18).startswith("0.33333333"), 765 | "Division resulting in repeating decimal (1/3)", 766 | ) 767 | 768 | # Test case 19: Division by powers of 10 769 | var a19 = Decimal("123.456") 770 | var b19 = Decimal("10") 771 | var result19 = a19 / b19 772 | testing.assert_equal( 773 | String(result19), 774 | "12.3456", 775 | "Division by power of 10", 776 | ) 777 | 778 | # Test case 20: Division by powers of 10 (another case) 779 | var a20 = Decimal("123.456") 780 | var b20 = Decimal("0.01") 781 | var result20 = a20 / b20 782 | testing.assert_equal( 783 | String(result20), 784 | "12345.6", 785 | "Division by 0.01 (multiply by 100)", 786 | ) 787 | 788 | # Test case 21: Division of nearly equal numbers 789 | var a21 = Decimal("1.000001") 790 | var b21 = Decimal("1") 791 | var result21 = a21 / b21 792 | testing.assert_equal( 793 | String(result21), 794 | "1.000001", 795 | "Division of nearly equal numbers", 796 | ) 797 | 798 | # Test case 22: Division resulting in a number with many trailing zeros 799 | var a22 = Decimal("1") 800 | var b22 = Decimal("8") 801 | var result22 = a22 / b22 802 | testing.assert_true( 803 | String(result22).startswith("0.125"), 804 | "Division resulting in an exact decimal with trailing zeros", 805 | ) 806 | 807 | # Test case 23: Division with negative numerator 808 | var a23 = Decimal("-50") 809 | var b23 = Decimal("10") 810 | var result23 = a23 / b23 811 | testing.assert_equal( 812 | String(result23), 813 | "-5", 814 | "Division with negative numerator", 815 | ) 816 | 817 | # Test case 24: Division with negative denominator 818 | var a24 = Decimal("50") 819 | var b24 = Decimal("-10") 820 | var result24 = a24 / b24 821 | testing.assert_equal( 822 | String(result24), 823 | "-5", 824 | "Division with negative denominator", 825 | ) 826 | 827 | # Test case 25: Division with both negative 828 | var a25 = Decimal("-50") 829 | var b25 = Decimal("-10") 830 | var result25 = a25 / b25 831 | testing.assert_equal( 832 | String(result25), 833 | "5", 834 | "Division with both negative numbers", 835 | ) 836 | 837 | # Test case 26: Division resulting in exact integer 838 | var a26 = Decimal("96.75") 839 | var b26 = Decimal("4.5") 840 | var result26 = a26 / b26 841 | testing.assert_equal( 842 | String(result26), 843 | "21.5", 844 | "Division resulting in exact value", 845 | ) 846 | 847 | # Test case 27: Division with high precision numbers 848 | var a27 = Decimal("0.123456789012345678901234567") 849 | var b27 = Decimal("0.987654321098765432109876543") 850 | var result27 = a27 / b27 851 | testing.assert_true( 852 | String(result27).startswith("0.12499"), 853 | "Division of high precision numbers", 854 | ) 855 | 856 | # Test case 28: Division with extreme digit patterns 857 | var a28 = Decimal("9" * 15) # 999999999999999 858 | var b28 = Decimal("9" * 5) # 99999 859 | var result28 = a28 / b28 860 | testing.assert_equal( 861 | String(result28), 862 | "10000100001", 863 | "Division with extreme digit patterns (all 9's)", 864 | ) 865 | 866 | # Test case 29: Division where result is zero 867 | var a29 = Decimal("0") 868 | var b29 = Decimal("123.45") 869 | var result29 = a29 / b29 870 | testing.assert_equal( 871 | String(result29), 872 | "0", 873 | "Division where result is zero", 874 | ) 875 | 876 | # Test case 30: Division where numerator is smaller than denominator 877 | var a30 = Decimal("1") 878 | var b30 = Decimal("10000") 879 | var result30 = a30 / b30 880 | testing.assert_equal( 881 | String(result30), 882 | "0.0001", 883 | "Division where numerator is smaller than denominator", 884 | ) 885 | 886 | # Test case 31: Division resulting in scientific notation range 887 | var a31 = Decimal("1") 888 | var b31 = Decimal("1" + "0" * 20) # 10^20 889 | var result31 = a31 / b31 890 | testing.assert_true( 891 | String(result31).startswith("0.00000000000000000001"), 892 | "Division resulting in very small number", 893 | ) 894 | 895 | # Test case 32: Division with mixed precision 896 | var a32 = Decimal("1") 897 | var b32 = Decimal("3.33333333333333333333333333") 898 | var result32 = a32 / b32 899 | testing.assert_true( 900 | String(result32).startswith("0.3"), 901 | "Division with mixed precision numbers", 902 | ) 903 | 904 | # Test case 33: Division by fractional power of 10 905 | var a33 = Decimal("5.5") 906 | var b33 = Decimal("0.055") 907 | var result33 = a33 / b33 908 | testing.assert_equal( 909 | String(result33), 910 | "100", 911 | "Division by fractional power of 10", 912 | ) 913 | 914 | # Test case 34: Division with rounding at precision boundary 915 | var a34 = Decimal("2") 916 | var b34 = Decimal("3") 917 | var result34 = a34 / b34 918 | # Result should be about 0.66666... 919 | var expected34 = Decimal("0.66666666666666666666666666667") 920 | testing.assert_equal( 921 | result34, 922 | expected34, 923 | "Division with rounding at precision boundary", 924 | ) 925 | 926 | # Test case 35: Division by value very close to zero 927 | var a35 = Decimal("1") 928 | var b35 = Decimal("0." + "0" * 26 + "1") # 0.000...0001 (27 zeros) 929 | var result35 = a35 / b35 930 | testing.assert_true( 931 | String(result35).startswith("1" + "0" * 27), 932 | "Division by value very close to zero", 933 | ) 934 | 935 | print("Additional division tests passed!") 936 | 937 | print("Decimal division tests passed!") 938 | 939 | 940 | fn test_power_integer_exponents() raises: 941 | print("Testing power with integer exponents...") 942 | 943 | # Test case 1: Base cases: x^0 = 1 for any x except 0 944 | var a1 = Decimal("2.5") 945 | var result1 = a1**0 946 | testing.assert_equal( 947 | String(result1), "1", "Any number to power 0 should be 1" 948 | ) 949 | 950 | # Test case 2: 0^n = 0 for n > 0 951 | var a2 = Decimal("0") 952 | var result2 = a2**5 953 | testing.assert_equal( 954 | String(result2), "0", "0 to any positive power should be 0" 955 | ) 956 | 957 | # Test case 3: x^1 = x 958 | var a3 = Decimal("3.14159") 959 | var result3 = a3**1 960 | testing.assert_equal(String(result3), "3.14159", "x^1 should be x") 961 | 962 | # Test case 4: Positive integer powers 963 | var a4 = Decimal("2") 964 | var result4 = a4**3 965 | testing.assert_equal(String(result4), "8", "2^3 should be 8") 966 | 967 | # Test case 5: Test with scale 968 | var a5 = Decimal("1.5") 969 | var result5 = a5**2 970 | testing.assert_equal(String(result5), "2.25", "1.5^2 should be 2.25") 971 | 972 | # Test case 6: Larger powers 973 | var a6 = Decimal("2") 974 | var result6 = a6**10 975 | testing.assert_equal(String(result6), "1024", "2^10 should be 1024") 976 | 977 | # Test case 7: Negative base, even power 978 | var a7 = Decimal("-3") 979 | var result7 = a7**2 980 | testing.assert_equal(String(result7), "9", "(-3)^2 should be 9") 981 | 982 | # Test case 8: Negative base, odd power 983 | var a8 = Decimal("-3") 984 | var result8 = a8**3 985 | testing.assert_equal(String(result8), "-27", "(-3)^3 should be -27") 986 | 987 | # Test case 9: Decimal base, positive power 988 | var a9 = Decimal("0.1") 989 | var result9 = a9**3 990 | testing.assert_equal(String(result9), "0.001", "0.1^3 should be 0.001") 991 | 992 | # Test case 10: Large number to small power 993 | var a10 = Decimal("1000") 994 | var result10 = a10**2 995 | testing.assert_equal( 996 | String(result10), "1000000", "1000^2 should be 1000000" 997 | ) 998 | 999 | print("Integer exponent tests passed!") 1000 | 1001 | 1002 | fn test_power_negative_exponents() raises: 1003 | print("Testing power with negative integer exponents...") 1004 | 1005 | # Test case 1: Basic negative exponent 1006 | var a1 = Decimal("2") 1007 | var result1 = a1 ** (-2) 1008 | testing.assert_equal(String(result1), "0.25", "2^(-2) should be 0.25") 1009 | 1010 | # Test case 2: Larger negative exponent 1011 | var a2 = Decimal("10") 1012 | var result2 = a2 ** (-3) 1013 | testing.assert_equal(String(result2), "0.001", "10^(-3) should be 0.001") 1014 | 1015 | # Test case 3: Negative base, even negative power 1016 | var a3 = Decimal("-2") 1017 | var result3 = a3 ** (-2) 1018 | testing.assert_equal(String(result3), "0.25", "(-2)^(-2) should be 0.25") 1019 | 1020 | # Test case 4: Negative base, odd negative power 1021 | var a4 = Decimal("-2") 1022 | var result4 = a4 ** (-3) 1023 | testing.assert_equal( 1024 | String(result4), "-0.125", "(-2)^(-3) should be -0.125" 1025 | ) 1026 | 1027 | # Test case 5: Decimal base, negative power 1028 | var a5 = Decimal("0.5") 1029 | var result5 = a5 ** (-2) 1030 | testing.assert_equal(String(result5), "4", "0.5^(-2) should be 4") 1031 | 1032 | # Test case 6: 1^(-n) = 1 1033 | var a6 = Decimal("1") 1034 | var result6 = a6 ** (-5) 1035 | testing.assert_equal(String(result6), "1", "1^(-5) should be 1") 1036 | 1037 | print("Negative exponent tests passed!") 1038 | 1039 | 1040 | fn test_power_special_cases() raises: 1041 | print("Testing power function special cases...") 1042 | 1043 | # Test case 1: 0^0 (typically defined as 1) 1044 | var a1 = Decimal("0") 1045 | try: 1046 | var result1 = a1**0 1047 | testing.assert_equal(String(result1), "1", "0^0 should be defined as 1") 1048 | except: 1049 | print("0^0 raises an exception (mathematically undefined)") 1050 | 1051 | # Test case 2: 0^(-n) (mathematically undefined) 1052 | var a2 = Decimal("0") 1053 | try: 1054 | var result2 = a2 ** (-2) 1055 | print("WARNING: 0^(-2) didn't raise an exception, got", result2) 1056 | except: 1057 | print("0^(-2) correctly raises an exception") 1058 | 1059 | # Test case 3: 1^n = 1 for any n 1060 | var a3 = Decimal("1") 1061 | var result3a = a3**100 1062 | var result3b = a3 ** (-100) 1063 | testing.assert_equal(String(result3a), "1", "1^100 should be 1") 1064 | testing.assert_equal(String(result3b), "1", "1^(-100) should be 1") 1065 | 1066 | # Test case 4: High precision result with rounding 1067 | # TODO: Implement __gt__ 1068 | # var a4 = Decimal("1.1") 1069 | # var result4 = a4**30 1070 | # testing.assert_true( 1071 | # result4 > Decimal("17.4") and result4 < Decimal("17.5"), 1072 | # "1.1^30 should be approximately 17.449", 1073 | # ) 1074 | 1075 | print("Special cases tests passed!") 1076 | 1077 | 1078 | fn test_power_decimal_exponents() raises: 1079 | print("Testing power with decimal exponents...") 1080 | 1081 | # Try a few basic decimal exponents if supported 1082 | try: 1083 | var a1 = Decimal("4") 1084 | var e1 = Decimal("0.5") # Square root 1085 | var result1 = a1**e1 1086 | testing.assert_equal(String(result1), "2", "4^0.5 should be 2") 1087 | 1088 | var a2 = Decimal("8") 1089 | var e2 = Decimal("1.5") # Cube root of square 1090 | var result2 = a2**e2 1091 | testing.assert_equal( 1092 | String(result2)[:4], "22.6", "8^1.5 should be approximately 22.6" 1093 | ) 1094 | except: 1095 | print("Decimal exponents not supported in this implementation") 1096 | 1097 | print("Decimal exponent tests passed!") 1098 | 1099 | 1100 | fn test_power_precision() raises: 1101 | print("Testing power precision...") 1102 | 1103 | # These tests assume we have overloaded the ** operator 1104 | # and we have a way to control precision similar to pow() 1105 | try: 1106 | # Test with precision control 1107 | var a1 = Decimal("1.5") 1108 | var result1 = a1**2 1109 | # Test equality including precision 1110 | testing.assert_equal( 1111 | String(result1), "2.25", "1.5^2 should be exactly 2.25" 1112 | ) 1113 | 1114 | # Check scale 1115 | testing.assert_equal( 1116 | result1.scale(), 1117 | 2, 1118 | "Result should maintain precision of 2 decimal places", 1119 | ) 1120 | except: 1121 | print("Precision parameters not supported with ** operator") 1122 | 1123 | print("Precision tests passed!") 1124 | 1125 | 1126 | fn test_extreme_cases() raises: 1127 | print("Testing extreme cases...") 1128 | 1129 | # Test case 1: Addition that results in exactly zero with high precision 1130 | var a1 = Decimal("0." + "1" * 28) # 0.1111...1 (28 digits) 1131 | var b1 = Decimal("-0." + "1" * 28) # -0.1111...1 (28 digits) 1132 | var result1 = a1 + b1 1133 | testing.assert_equal( 1134 | String(result1), 1135 | "0." + "0" * 28, 1136 | "High precision addition resulting in zero", 1137 | ) 1138 | 1139 | # Test case 2: Addition that should trigger overflow handling 1140 | try: 1141 | var a2 = Decimal("79228162514264337593543950335") # MAX() 1142 | var b2 = Decimal("1") 1143 | var result2 = a2 + b2 1144 | print("WARNING: Addition beyond MAX() didn't raise an error") 1145 | except: 1146 | print("Addition overflow correctly detected") 1147 | 1148 | # Test case 3: Addition with mixed precision zeros 1149 | var a3 = Decimal("0.00") 1150 | var b3 = Decimal("0.000000") 1151 | var result3 = a3 + b3 1152 | testing.assert_equal( 1153 | String(result3), "0.000000", "Addition of different precision zeros" 1154 | ) 1155 | 1156 | # Test case 4: Addition with boundary values involving zeros 1157 | var a4 = Decimal("0.0") 1158 | var b4 = Decimal("-0.00") 1159 | var result4 = a4 + b4 1160 | testing.assert_equal( 1161 | String(result4), "0.00", "Addition of positive and negative zero" 1162 | ) 1163 | 1164 | # Test case 5: Adding numbers that require carry propagation through many places 1165 | var a5 = Decimal("9" * 20 + "." + "9" * 28) # 99...9.99...9 1166 | var b5 = Decimal("0." + "0" * 27 + "1") # 0.00...01 1167 | var result5 = a5 + b5 1168 | # The result should be 10^20 exactly, since all 9s carry over 1169 | testing.assert_equal( 1170 | String(result5), 1171 | "100000000000000000000.00000000", 1172 | "Addition with extensive carry propagation", 1173 | ) 1174 | 1175 | print("Extreme case tests passed!") 1176 | 1177 | 1178 | fn main() raises: 1179 | print("Running decimal arithmetic tests") 1180 | 1181 | # Run addition tests 1182 | test_add() 1183 | 1184 | # Run negation tests 1185 | test_negation() 1186 | 1187 | # Run absolute value tests 1188 | test_abs() 1189 | 1190 | # Run subtraction tests 1191 | test_subtract() 1192 | 1193 | # Run multiplication tests 1194 | test_multiplication() 1195 | 1196 | # Run division tests 1197 | test_division() 1198 | 1199 | # Run power tests with integer exponents 1200 | test_power_integer_exponents() 1201 | 1202 | # Run power tests with negative exponents 1203 | 1204 | test_power_negative_exponents() 1205 | 1206 | # Run power tests for special cases 1207 | test_power_special_cases() 1208 | 1209 | # Run power tests with decimal exponents 1210 | test_power_decimal_exponents() 1211 | 1212 | # Run power precision tests 1213 | test_power_precision() 1214 | 1215 | # Run extreme cases tests 1216 | test_extreme_cases() 1217 | 1218 | print("All decimal arithmetic tests passed!") 1219 | -------------------------------------------------------------------------------- /recipes/emberjson/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/emberjson/image.jpeg -------------------------------------------------------------------------------- /recipes/emberjson/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: 0.1.5 3 | 4 | package: 5 | name: "emberjson" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/bgreni/EmberJson.git 10 | rev: afbb34ebeb14a2927c798db593055fe4b7091098 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package emberjson -o ${{ PREFIX }}/lib/mojo/emberjson.mojopkg 16 | 17 | requirements: 18 | host: 19 | - max =25.3 20 | run: 21 | - ${{ pin_compatible('max') }} 22 | 23 | tests: 24 | - script: 25 | - if: unix 26 | then: 27 | - mojo test 28 | 29 | about: 30 | homepage: https://github.com/bgreni/EmberJson 31 | license: Apache-2.0 32 | license_file: LICENSE 33 | summary: A lightweight JSON parsing library written in pure Mojo 34 | repository: https://github.com/bgreni/EmberJson 35 | 36 | extra: 37 | maintainers: 38 | - bgreni 39 | project_name: 40 | - EmberJson -------------------------------------------------------------------------------- /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/infrared/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/infrared/avatar.jpeg -------------------------------------------------------------------------------- /recipes/infrared/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/infrared/image.jpeg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/ish/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/ish/image.jpeg -------------------------------------------------------------------------------- /recipes/ish/recipe.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/prefix-dev/recipe-format/main/schema.json 2 | 3 | context: 4 | version: "1.1.1" 5 | 6 | package: 7 | name: "ish" 8 | version: ${{ version }} 9 | 10 | source: 11 | - git: https://github.com/BioRadOpenSource/ish.git 12 | rev: 707e862d216cd2b9cae05cc65caaa78ca36f7311 13 | 14 | build: 15 | number: 0 16 | script: 17 | - mkdir -p ${{ PREFIX }}/share/mojo 18 | # Needed for tests 19 | - mojo package -o ${{ PREFIX }}/share/mojo/ishlib.mojopkg ./ishlib 20 | - mkdir -p ${{ PREFIX }}/bin 21 | - mojo build -D ISH_SIMD_TARGET=baseline -D ISH_LOG_LEVEL=info -o ${{ PREFIX }}/bin/ish main.mojo 22 | # Need to remove these or install_name_tool tries and fails to update paths 23 | - find ${{ PREFIX }} -type d -name .mojo_cache -exec rm -rf {} + 24 | 25 | requirements: 26 | host: 27 | - max =25.3 28 | - extramojo =0.14.0 29 | 30 | run: 31 | - ${{ pin_compatible('max') }} 32 | 33 | tests: 34 | - script: 35 | - if: unix 36 | then: 37 | - mojo test -I ${{ PREFIX }}/share/mojo/ishlib.mojopkg tests/test_global.mojo 38 | - mojo test -I ${{ PREFIX }}/share/mojo/ishlib.mojopkg tests/test_local_and_profile.mojo 39 | - mojo test -I ${{ PREFIX }}/share/mojo/ishlib.mojopkg tests/test_searcher.mojo 40 | - mojo test -I ${{ PREFIX }}/share/mojo/ishlib.mojopkg tests/test_semi_global.mojo 41 | requirements: 42 | run: 43 | - max =25.3 44 | - extramojo =0.14.0 45 | 46 | files: 47 | recipe: 48 | - tests/test_global.mojo 49 | - tests/test_local_and_profile.mojo 50 | - tests/test_searcher.mojo 51 | - tests/test_semi_global.mojo 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 -------------------------------------------------------------------------------- /recipes/kelvin/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/kelvin/image.jpeg -------------------------------------------------------------------------------- /recipes/kelvin/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: 0.1.0 3 | 4 | package: 5 | name: "kelvin" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/bgreni/Kelvin.git 10 | rev: 63e9affda4e4b09b1f147f240ad660980ba6daa1 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package kelvin -o ${{ PREFIX }}/lib/mojo/kelvin.mojopkg 16 | 17 | requirements: 18 | host: 19 | - max =25.3 20 | run: 21 | - ${{ pin_compatible('max') }} 22 | 23 | tests: 24 | - script: 25 | - if: unix 26 | then: 27 | - mojo test 28 | # Can't convince PR build to find this file 29 | # - python3 scripts/run_reject_tests.py 30 | 31 | about: 32 | homepage: https://github.com/bgreni/Kelvin 33 | license: Apache-2.0 34 | license_file: LICENSE 35 | summary: A powerful dimensional analysis library written in Mojo 36 | repository: https://github.com/bgreni/Kelvin 37 | 38 | extra: 39 | maintainers: 40 | - bgreni 41 | project_name: 42 | - Kelvin -------------------------------------------------------------------------------- /recipes/lightbug_http/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/lightbug_http/image.jpeg -------------------------------------------------------------------------------- /recipes/lightbug_http/recipe.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/prefix-dev/recipe-format/main/schema.json 2 | 3 | context: 4 | version: "25.3.0" 5 | 6 | package: 7 | name: "lightbug_http" 8 | version: ${{ version }} 9 | 10 | source: 11 | - git: https://github.com/saviorand/lightbug_http.git 12 | rev: 5f116e3383010198b859cae83a4c8e55b879f1ee 13 | 14 | build: 15 | number: 0 16 | script: 17 | - mkdir -p ${PREFIX}/lib/mojo 18 | - mojo package lightbug_http -o ${{ PREFIX }}/lib/mojo/lightbug_http.mojopkg 19 | 20 | requirements: 21 | host: 22 | - max =25.3 23 | 24 | run: 25 | - ${{ pin_compatible('max') }} 26 | 27 | tests: 28 | - script: 29 | - if: unix 30 | then: 31 | - mojo test 32 | requirements: 33 | run: 34 | - max =25.3 35 | 36 | files: 37 | recipe: 38 | - tests/lightbug_http/io/test_bytes.mojo 39 | - tests/lightbug_http/test_client.mojo 40 | - tests/lightbug_http/test_cookie.mojo 41 | - tests/lightbug_http/test_header.mojo 42 | - tests/lightbug_http/test_http.mojo 43 | - tests/lightbug_http/test_net.mojo 44 | - tests/lightbug_http/test_uri.mojo 45 | 46 | about: 47 | homepage: https://lightbug.site 48 | license: MIT 49 | license_file: LICENSE 50 | summary: Lightbug is a simple and sweet HTTP framework for Mojo 51 | repository: https://github.com/saviorand/lightbug_http 52 | 53 | extra: 54 | maintainers: 55 | - saviorand 56 | - bgreni 57 | - thatstoasty 58 | project_name: lightbug_http 59 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /recipes/mimage/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mimage/image.jpeg -------------------------------------------------------------------------------- /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/mimage/tests/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .testing_utils import * 2 | -------------------------------------------------------------------------------- /recipes/mimage/tests/images/hopper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mimage/tests/images/hopper.png -------------------------------------------------------------------------------- /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/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/mist/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mist/image.jpeg -------------------------------------------------------------------------------- /recipes/mist/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "25.3.0" 3 | 4 | package: 5 | name: "mist" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/thatstoasty/mist.git 10 | rev: 44b33ed6fad24c11a593cecb7a9515b034d73a11 11 | 12 | build: 13 | number: 0 14 | script: 15 | - mojo package src/mist -o ${{ PREFIX }}/lib/mojo/mist.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 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 | -------------------------------------------------------------------------------- /recipes/mojmelo/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mojmelo/image.jpeg -------------------------------------------------------------------------------- /recipes/mojmelo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.0.65" 3 | 4 | package: 5 | name: "mojmelo" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/yetalit/mojmelo.git 10 | rev: 82991dfa4b71b7ed63b468e01be8d5798f4ba61b 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 | - max =25.3 20 | run: 21 | - ${{ pin_compatible('max') }} 22 | 23 | tests: 24 | - script: 25 | - if: unix 26 | then: 27 | - mojo tests/setup.mojo 28 | requirements: 29 | run: 30 | - max =25.3 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 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mojmelo/tests/mojmelo/__init__.mojo -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/Matrix.mojo: -------------------------------------------------------------------------------- 1 | from .mojmelo_matmul import matmul 2 | from memory import memcpy, memset_zero, UnsafePointer 3 | import random 4 | 5 | struct Matrix: 6 | var height: Int 7 | var width: Int 8 | var size: Int 9 | var data: UnsafePointer[Float32] 10 | var order: String 11 | 12 | # initialize from UnsafePointer 13 | @always_inline 14 | fn __init__(out self, data: UnsafePointer[Float32], height: Int, width: Int, order: String = 'c'): 15 | self.height = height 16 | self.width = width 17 | self.size = height * width 18 | self.data = data 19 | self.order = order.lower() 20 | 21 | # initialize by copying from UnsafePointer 22 | @always_inline 23 | fn __init__(out self, height: Int, width: Int, data: UnsafePointer[Float32] = UnsafePointer[Float32](), order: String = 'c'): 24 | self.height = height 25 | self.width = width 26 | self.size = height * width 27 | self.data = UnsafePointer[Float32].alloc(self.size) 28 | self.order = order.lower() 29 | if data: 30 | memcpy(self.data, data, self.size) 31 | 32 | fn __copyinit__(out self, other: Self): 33 | self.height = other.height 34 | self.width = other.width 35 | self.size = other.size 36 | self.data = UnsafePointer[Float32].alloc(self.size) 37 | self.order = other.order 38 | memcpy(self.data, other.data, self.size) 39 | 40 | fn __moveinit__(out self, owned existing: Self): 41 | self.height = existing.height 42 | self.width = existing.width 43 | self.size = existing.size 44 | self.data = existing.data 45 | self.order = existing.order 46 | existing.height = existing.width = existing.size = 0 47 | existing.order = '' 48 | existing.data = UnsafePointer[Float32]() 49 | 50 | # access an element 51 | @always_inline 52 | fn __getitem__(self, row: Int, column: Int) raises -> Float32: 53 | var loc: Int 54 | if self.order == 'c': 55 | loc = (row * self.width) + column 56 | else: 57 | loc = (column * self.height) + row 58 | if loc > self.size - 1: 59 | raise Error("Error: Location is out of range!") 60 | return self.data[loc] 61 | 62 | @always_inline 63 | fn __del__(owned self): 64 | if self.data: 65 | self.data.free() 66 | 67 | @always_inline 68 | fn __len__(self) -> Int: 69 | return self.size 70 | 71 | @always_inline 72 | fn __mul__(self, rhs: Self) raises -> Self: 73 | if self.width != rhs.height: 74 | raise Error('Error: Cannot multiply matrices with shapes (' + String(self.height) + ', ' + String(self.width) + ') and (' + String(rhs.height) + ', ' + String(rhs.width) + ')') 75 | var A = matmul.Matrix[DType.float32](self.data, (self.height, self.width)) 76 | var B = matmul.Matrix[DType.float32](rhs.data, (rhs.height, rhs.width)) 77 | var C = matmul.Matrix[DType.float32]((self.height, rhs.width)) 78 | memset_zero(C.data, self.height * rhs.width) 79 | matmul.matmul(self.height, self.width, rhs.width, C, A, B) 80 | return Matrix(C.data, self.height, rhs.width) 81 | 82 | @always_inline 83 | fn __imul__(mut self, rhs: Self) raises: 84 | self = self * rhs 85 | 86 | @staticmethod 87 | @always_inline 88 | fn zeros(height: Int, width: Int, order: String = 'c') -> Matrix: 89 | var mat = Matrix(height, width, order= order) 90 | memset_zero(mat.data, mat.size) 91 | return mat^ 92 | 93 | @staticmethod 94 | @always_inline 95 | fn random(height: Int, width: Int, order: String = 'c') -> Matrix: 96 | random.seed() 97 | var mat = Matrix(height, width, order= order) 98 | random.rand(mat.data, mat.size, min=0.0, max=1.0) 99 | return mat^ 100 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mojmelo/tests/mojmelo/utils/__init__.mojo -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/mojmelo_matmul/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mojmelo/tests/mojmelo/utils/mojmelo_matmul/__init__.mojo -------------------------------------------------------------------------------- /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 memory import UnsafePointer 6 | from sys import has_avx512f, num_performance_cores, simdwidthof, sizeof 7 | import benchmark 8 | from testing import assert_equal 9 | from utils import IndexList 10 | import random 11 | from .params import * 12 | 13 | @always_inline 14 | fn roundup(a: Int, b: Int) -> Int: 15 | return ((a + b - 1) // b) * b 16 | 17 | 18 | @always_inline 19 | fn rounddown(a: Int, b: Int) -> Int: 20 | return (a // b) * b 21 | 22 | 23 | # math.sqrt doesn't work at compile time 24 | fn intsqrt[n: Int]() -> Int: 25 | @parameter 26 | if n == 0: 27 | return 0 28 | var x = n 29 | var y = (x + 1) // 2 30 | while y < x: 31 | x = y 32 | y = (n // x + x) // 2 33 | return x 34 | 35 | 36 | @value 37 | @register_passable("trivial") 38 | struct Layout: 39 | var shape: IndexList[2] 40 | var strides: IndexList[2] 41 | 42 | fn __init__(out self, shape: (Int, Int), strides: (Int, Int)): 43 | self.shape = IndexList[2](shape[0], shape[1]) 44 | self.strides = IndexList[2](strides[0], strides[1]) 45 | 46 | fn __init__(out self, shape: (Int, Int)): 47 | self.strides = IndexList[2](shape[1], 1) 48 | self.shape = IndexList[2](shape[0], shape[1]) 49 | 50 | @always_inline("nodebug") 51 | fn __call__(self, i: Int, j: Int) -> Int: 52 | return i * self.strides[0] + j * self.strides[1] 53 | 54 | @always_inline("nodebug") 55 | fn size(self) -> Int: 56 | return self.shape[0] * self.shape[1] 57 | 58 | @always_inline("nodebug") 59 | fn write_to[W: Writer](self, mut writer: W): 60 | writer.write(self.shape, ":", self.strides, "\n") 61 | 62 | 63 | struct Matrix[Type: DType]: 64 | var data: UnsafePointer[Scalar[Type]] 65 | var layout: Layout 66 | 67 | fn __init__(out self, shape: (Int, Int)): 68 | self.data = UnsafePointer[Scalar[Type]].alloc(shape[0] * shape[1]) 69 | self.layout = Layout(shape) 70 | 71 | @always_inline("nodebug") 72 | fn __init__( 73 | out self, data: UnsafePointer[Scalar[Type]], owned layout: Layout 74 | ): 75 | self.data = UnsafePointer[Scalar[Type]](data) 76 | self.layout = layout 77 | 78 | @always_inline("nodebug") 79 | fn __init__( 80 | out self, data: UnsafePointer[Scalar[Type]], shape: (Int, Int) 81 | ): 82 | self.data = data 83 | self.layout = Layout(shape) 84 | 85 | @always_inline("nodebug") 86 | fn __getitem__( 87 | ref [_]self, i: Int, j: Int 88 | ) -> ref [__origin_of(self)] Scalar[Type]: 89 | var offset = self.layout(i, j) 90 | return (self.data + offset)[] 91 | 92 | @always_inline("nodebug") 93 | fn slice(self, i: Int, j: Int, ir: Int, jr: Int) -> Self: 94 | var shape = (ir, jr) 95 | var strides = (self.layout.strides[0], self.layout.strides[1]) 96 | var offset = self.layout(i, j) 97 | return Matrix(self.data + offset, Layout(shape, strides)) 98 | 99 | @always_inline("nodebug") 100 | fn shape[dim: Int](self) -> Int: 101 | return self.layout.shape[dim] 102 | 103 | @always_inline("nodebug") 104 | fn stride[dim: Int](self) -> Int: 105 | return self.layout.strides[dim] 106 | 107 | fn rand(mut self): 108 | random.rand(self.data, self.layout.size()) 109 | 110 | @always_inline("nodebug") 111 | fn load[width: Int, *, dim: Int](self, i: Int, j: Int) -> SIMD[Type, width]: 112 | var offset = self.layout(i, j) 113 | var ptr = self.data + offset 114 | 115 | @parameter 116 | if dim == 0: 117 | return ptr.strided_load[width=width](self.layout.strides[0]) 118 | else: 119 | return ptr.load[width=width]() 120 | 121 | @always_inline("nodebug") 122 | fn store[ 123 | width: Int, *, dim: Int 124 | ](self, value: SIMD[Type, width], i: Int, j: Int): 125 | var offset = self.layout(i, j) 126 | var ptr = self.data + offset 127 | 128 | @parameter 129 | if dim == 0: 130 | ptr.strided_store[width=width](value, self.layout.strides[0]) 131 | else: 132 | ptr.store(value) 133 | 134 | fn write_to[W: Writer](self, mut writer: W): 135 | writer.write( 136 | "Matrix: ", 137 | String(self.data), 138 | ", Layout: ", 139 | self.layout, 140 | "\n", 141 | ) 142 | for i in range(self.layout.shape[0]): 143 | for j in range(self.layout.shape[1]): 144 | writer.write(self[i, j], " ") 145 | writer.write("\n") 146 | 147 | 148 | @always_inline 149 | fn pack_A[ 150 | Type: DType, //, mr: Int 151 | ](mc: Int, Ac_buffer: UnsafePointer[Scalar[Type]], Ac: Matrix[Type]) -> Matrix[Type]: 152 | @parameter 153 | fn pack_panel(idx: Int): 154 | var i = idx * mr 155 | # for i in range(0, Ac.shape[0](), mr): 156 | var dst_ptr = Ac_buffer + i * Ac.shape[1]() 157 | var src_ptr = Ac.data + i * Ac.stride[0]() 158 | for _ in range(Ac.shape[1]()): 159 | 160 | @parameter 161 | fn pack_col[width: Int](l: Int): 162 | (dst_ptr + l).store( 163 | (src_ptr + l * Ac.stride[0]()).strided_load[ 164 | width=width 165 | ](Ac.stride[0]()), 166 | ) 167 | 168 | vectorize[pack_col, simdwidthof[Type]()](min(Ac.shape[0]() - i, mr)) 169 | 170 | for l in range(min(Ac.shape[0]() - i, mr), mr): 171 | dst_ptr[l] = Scalar[Type](0) 172 | 173 | dst_ptr = dst_ptr + mr 174 | src_ptr = src_ptr + 1 175 | 176 | parallelize[pack_panel]( 177 | (Ac.shape[0]() + mr - 1) // mr, num_performance_cores() 178 | ) 179 | 180 | var Ac_layout = Layout( 181 | (roundup(Ac.shape[0](), mr), Ac.shape[1]()), (1, mr) 182 | ) # NOTE: The stride is a lie and only used for slicing 183 | return Matrix(Ac_buffer, Ac_layout) 184 | 185 | 186 | @always_inline 187 | fn pack_B[ 188 | Type: DType, //, kc: Int, nr: Int 189 | ](Bc_buffer: UnsafePointer[Scalar[Type]], Bc: Matrix[Type]) -> Matrix[Type]: 190 | var dst_ptr = Bc_buffer 191 | for i in range(0, Bc.shape[1](), nr): 192 | var src_ptr = Bc.data + i 193 | for _ in range(Bc.shape[0]()): 194 | 195 | @parameter 196 | fn pack_row[width: Int](l: Int): 197 | (dst_ptr + l).store[ 198 | alignment = sizeof[Type]() * simdwidthof[Type]() 199 | ]( 200 | (src_ptr + l).load[width=width](), 201 | ) 202 | 203 | vectorize[ 204 | pack_row, 205 | simdwidthof[Type](), 206 | unroll_factor = nr // simdwidthof[Type](), 207 | ](min(Bc.shape[1]() - i, nr)) 208 | 209 | for l in range(min(Bc.shape[1]() - i, nr), nr): 210 | dst_ptr[l] = Scalar[Type](0) 211 | 212 | dst_ptr = dst_ptr + nr 213 | src_ptr = src_ptr + Bc.stride[0]() 214 | 215 | var Bc_layout = Layout( 216 | (Bc.shape[0](), roundup(Bc.shape[1](), nr)), (nr, 1) 217 | ) # NOTE: The stride is a lie and only used for slicing 218 | return Matrix[Type](Bc_buffer, Bc_layout) 219 | 220 | 221 | @always_inline 222 | fn matmul_impl[ 223 | Type: DType, //, 224 | kc: Int, 225 | mr: Int, 226 | nr: Int, 227 | ](mc: Int, nc: Int, mut C: Matrix[Type], A: Matrix[Type], B: Matrix[Type]): 228 | var Ac_buffer = _malloc[Scalar[Type], alignment=64]( 229 | mc * kc * sizeof[Type]() 230 | ) 231 | 232 | var M = C.shape[0]() 233 | var N = C.shape[1]() 234 | var K = A.shape[1]() 235 | 236 | for i in range(0, A.shape[0](), mc): 237 | var Cb = C.slice(i, 0, min(M - i, mc), N) 238 | for p in range(0, A.shape[1](), kc): 239 | var Ac = pack_A[mr]( 240 | mc, Ac_buffer, A.slice(i, p, min(M - i, mc), min(K - p, kc)) 241 | ) 242 | 243 | var Bb = B.slice(p, 0, min(K - p, kc), N) 244 | loop_n[kc, mr, nr](nc, Cb, Ac, Bb) 245 | 246 | Ac_buffer.free() 247 | 248 | 249 | @always_inline 250 | fn loop_n[ 251 | Type: DType, //, 252 | kc: Int, 253 | mr: Int, 254 | nr: Int, 255 | ](nc: Int, mut C: Matrix[Type], A: Matrix[Type], B: Matrix[Type]): 256 | var max_threads = num_performance_cores() 257 | var nc_per_thread = nc if nc * max_threads <= B.shape[1]() else rounddown( 258 | B.shape[1]() // max_threads, nr 259 | ) 260 | var balanced_part = rounddown(B.shape[1](), nc_per_thread) 261 | 262 | var remainder = B.shape[1]() - balanced_part 263 | var remainder_per_thread = rounddown(remainder // max_threads, nr) 264 | remainder_per_thread = max(remainder_per_thread, nr) 265 | 266 | var items_remainder = ( 267 | remainder + remainder_per_thread - 1 268 | ) // remainder_per_thread 269 | 270 | @parameter 271 | fn parallelize_balanced_part(idx: Int): 272 | var Bc_buffer = UnsafePointer[Scalar[Type]]( 273 | _malloc[Scalar[Type], alignment=64]( 274 | kc * nc_per_thread * sizeof[Type]() 275 | ) 276 | ) 277 | 278 | var j = idx * nc_per_thread 279 | var Bc = pack_B[kc, nr]( 280 | Bc_buffer, 281 | B.slice(0, j, B.shape[0](), min(B.shape[1]() - j, nc_per_thread)), 282 | ) 283 | var Cc = C.slice( 284 | 0, j, C.shape[0](), min(C.shape[1]() - j, nc_per_thread) 285 | ) 286 | macro_kernel[mr, nr](Cc, A, Bc) 287 | Bc_buffer.free() 288 | 289 | parallelize[parallelize_balanced_part]( 290 | balanced_part // nc_per_thread, balanced_part // nc_per_thread 291 | ) 292 | 293 | @parameter 294 | fn parallelize_remainder(idx: Int): 295 | var Bc_buffer = UnsafePointer[Scalar[Type]]( 296 | _malloc[Scalar[Type], alignment=64]( 297 | kc * remainder_per_thread * sizeof[Type]() 298 | ) 299 | ) 300 | var j = balanced_part + idx * remainder_per_thread 301 | var Bc = pack_B[kc, nr]( 302 | Bc_buffer, 303 | B.slice( 304 | 0, j, B.shape[0](), min(B.shape[1]() - j, remainder_per_thread) 305 | ), 306 | ) 307 | var Cc = C.slice( 308 | 0, j, C.shape[0](), min(C.shape[1]() - j, remainder_per_thread) 309 | ) 310 | macro_kernel[mr, nr](Cc, A, Bc) 311 | Bc_buffer.free() 312 | 313 | parallelize[parallelize_remainder](items_remainder, items_remainder) 314 | 315 | _ = balanced_part 316 | _ = remainder_per_thread 317 | _ = nc_per_thread 318 | 319 | 320 | @always_inline 321 | fn macro_kernel[ 322 | Type: DType, //, 323 | mr: Int, 324 | nr: Int, 325 | ](mut Cc: Matrix[Type], Ac: Matrix[Type], Bc: Matrix[Type]): 326 | @parameter 327 | fn parallelize_ir(idx: Int): 328 | var ir = idx * mr 329 | var Ar = Matrix(Ac.data + ir * Ac.shape[1](), (mr, Ac.shape[1]())) 330 | for jr in range(0, Bc.shape[1](), nr): 331 | var Cr = Cc.slice( 332 | ir, 333 | jr, 334 | min(Cc.shape[0]() - ir, mr), 335 | min(Cc.shape[1]() - jr, nr), 336 | ) 337 | var Br = Matrix( 338 | Bc.data + jr * Bc.shape[0](), 339 | (Bc.shape[0](), nr), 340 | ) 341 | if Cr.shape[0]() == mr and Cr.shape[1]() == nr: 342 | micro_kernel[mr, nr, False](Cr, Ar, Br) 343 | else: 344 | micro_kernel[mr, nr, True](Cr, Ar, Br) 345 | 346 | parallelize[parallelize_ir]((Ac.shape[0]() + mr - 1) // mr, 2) 347 | 348 | 349 | @always_inline 350 | fn micro_kernel[ 351 | Type: DType, //, mr: Int, nr: Int, padding: Bool 352 | ](mut Cr: Matrix[Type], Ar: Matrix[Type], Br: Matrix[Type]): 353 | alias simd_width = simdwidthof[Type]() 354 | constrained[nr % simd_width == 0, "nr must be multiple of simd_width"]() 355 | 356 | var Ar_ptr = Ar.data 357 | var Br_ptr = Br.data 358 | var Cr_ptr = Cr.data 359 | 360 | var ar: SIMD[Type, simd_width] 361 | var br = InlineArray[SIMD[Type, simd_width], nr // simd_width]( 362 | SIMD[Type, simd_width](0) 363 | ) 364 | var cr_ptr = stack_allocation[mr * nr, Scalar[Type], alignment=64]() 365 | 366 | @parameter 367 | if padding: 368 | 369 | @parameter 370 | for i in range(mr): 371 | if i < Cr.shape[0](): 372 | 373 | @parameter 374 | fn load_col[width: Int](j: Int): 375 | (cr_ptr + (i * nr + j)).store( 376 | (Cr_ptr + (i * Cr.stride[0]() + j)).load[width=width](), 377 | ) 378 | 379 | vectorize[load_col, simd_width](Cr.shape[1]()) 380 | else: 381 | 382 | @parameter 383 | for i in range(mr): 384 | 385 | @parameter 386 | for j in range(0, nr, simd_width): 387 | (cr_ptr + i * nr + j).store( 388 | (Cr_ptr + (i * Cr.stride[0]() + j)).load[width=simd_width](), 389 | ) 390 | 391 | for _ in range(Ar.shape[1]()): 392 | 393 | @parameter 394 | for j in range(0, nr, simd_width): 395 | br[j // simd_width] = (Br_ptr + j).load[ 396 | width=simd_width, alignment = sizeof[Type]() * simdwidthof[Type]() 397 | ]() 398 | 399 | @parameter 400 | for i in range(mr): 401 | 402 | @parameter 403 | for j in range(0, nr, simd_width): 404 | ar = SIMD[Type, size=simd_width](Ar_ptr[]) 405 | cr_ptr.store( 406 | ar.fma( 407 | br[j // simd_width], 408 | cr_ptr.load[width=simd_width](), 409 | ), 410 | ) 411 | cr_ptr += simd_width 412 | Ar_ptr += 1 413 | 414 | Br_ptr += nr 415 | cr_ptr += -mr * nr 416 | 417 | @parameter 418 | if padding: 419 | 420 | @parameter 421 | for i in range(mr): 422 | if i < Cr.shape[0](): 423 | 424 | @parameter 425 | fn store_row[width: Int](j: Int): 426 | (Cr_ptr + (i * Cr.stride[0]() + j)).store( 427 | (cr_ptr + (i * nr + j)).load[width=width](), 428 | ) 429 | 430 | vectorize[store_row, simd_width](Cr.shape[1]()) 431 | else: 432 | 433 | @parameter 434 | for i in range(mr): 435 | 436 | @parameter 437 | for j in range(0, nr, simd_width): 438 | (Cr_ptr + (i * Cr.stride[0]() + j)).store( 439 | (cr_ptr + (i * nr + j)).load[width=simd_width](), 440 | ) 441 | 442 | 443 | @always_inline 444 | fn matmul_params[Type: DType]() -> IndexList[5]: 445 | alias mc = 8192 // sizeof[Type]() # fix this for simplicity 446 | alias N = simdwidthof[Type]() 447 | 448 | alias Vectors = 32 if has_avx512f() else 16 449 | 450 | @parameter 451 | fn compute_kc[mr: Int, nr: Int]() -> Int: 452 | alias CBr = Int((L1_ASSOCIATIVITY - 1) / (1 + mr / nr)) 453 | return (CBr * L1_CACHE_SIZE) // (nr * sizeof[Type]() * L1_ASSOCIATIVITY) 454 | 455 | @parameter 456 | fn compute_params[C: Int]() -> IndexList[5]: 457 | alias p = C // (intsqrt[C]() + 1) 458 | alias mr = C // p - 1 459 | alias nr = p * N 460 | alias CBr = Int((L1_ASSOCIATIVITY - 1) / (1 + mr / nr)) 461 | alias kc = compute_kc[mr, nr]() 462 | alias nc = (L2_ASSOCIATIVITY - 1) * L2_CACHE_SIZE // ( 463 | kc * sizeof[Type]() * L2_ASSOCIATIVITY 464 | ) - mr 465 | return IndexList[5](mc, nc, kc, mr, nr) 466 | 467 | @parameter 468 | if Type.is_floating_point(): 469 | alias TempVectors = 1 470 | return compute_params[Vectors - TempVectors]() 471 | else: 472 | 473 | @parameter 474 | if Type is DType.int64: 475 | 476 | @parameter 477 | if has_avx512f(): 478 | alias TempVectors = 2 479 | return compute_params[Vectors - TempVectors]() 480 | else: 481 | alias TempVectors = 3 482 | return compute_params[Vectors - TempVectors]() 483 | else: 484 | alias TempVectors = 2 485 | return compute_params[Vectors - TempVectors]() 486 | 487 | 488 | fn matmul[ 489 | Type: DType 490 | ](m: Int, n: Int, k: Int, mut C: Matrix[Type], A: Matrix[Type], B: Matrix[Type]): 491 | alias params = matmul_params[Type]() 492 | alias mc = params[0] 493 | alias nc = params[1] 494 | alias kc = params[2] 495 | alias mr = params[3] 496 | alias nr = params[4] 497 | var resized_mc = roundup(min(mc, m), mr) 498 | var resized_nc = roundup(min(nc, n), nr) 499 | matmul_impl[kc, mr, nr](resized_mc, resized_nc, C, A, B) 500 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/mojmelo/utils/mojmelo_matmul/params.mojo: -------------------------------------------------------------------------------- 1 | alias L1_CACHE_SIZE = 32768 2 | alias L1_ASSOCIATIVITY = 8 3 | alias L2_CACHE_SIZE = 262144 4 | alias L2_ASSOCIATIVITY = 4 5 | -------------------------------------------------------------------------------- /recipes/mojmelo/tests/setup.mojo: -------------------------------------------------------------------------------- 1 | from sys import external_call, os_is_linux, os_is_macos, argv 2 | from sys.ffi import * 3 | from memory import UnsafePointer 4 | 5 | fn cachel1() -> Int32: 6 | var l1_cache_size: c_int = 0 7 | alias length: c_size_t = 4 8 | # Get L1 Cache Size 9 | if external_call["sysctlbyname", c_int]("hw.perflevel0.l1dcachesize".unsafe_cstr_ptr(), UnsafePointer(to=l1_cache_size), UnsafePointer(to=length), OpaquePointer(), 0) == 0: 10 | if l1_cache_size <= 1: 11 | if external_call["sysctlbyname", c_int]("hw.l1dcachesize".unsafe_cstr_ptr(), UnsafePointer(to=l1_cache_size), UnsafePointer(to=length), OpaquePointer(), 0) == 0: 12 | if l1_cache_size <= 1: 13 | return 65536 14 | return l1_cache_size 15 | else: 16 | return 65536 17 | return l1_cache_size 18 | else: 19 | if external_call["sysctlbyname", c_int]("hw.l1dcachesize".unsafe_cstr_ptr(), UnsafePointer(to=l1_cache_size), UnsafePointer(to=length), OpaquePointer(), 0) == 0: 20 | if l1_cache_size <= 1: 21 | return 65536 22 | return l1_cache_size 23 | else: 24 | return 65536 25 | 26 | 27 | fn cachel2() -> Int32: 28 | var l2_cache_size: c_int = 0 29 | alias length: c_size_t = 4 30 | # Get L2 Cache Size 31 | if external_call["sysctlbyname", c_int]("hw.perflevel0.l2cachesize".unsafe_cstr_ptr(), UnsafePointer(to=l2_cache_size), UnsafePointer(to=length), OpaquePointer(), 0) == 0: 32 | if l2_cache_size <= 1: 33 | if external_call["sysctlbyname", c_int]("hw.l2cachesize".unsafe_cstr_ptr(), UnsafePointer(to=l2_cache_size), UnsafePointer(to=length), OpaquePointer(), 0) == 0: 34 | if l2_cache_size <= 1: 35 | return 4194304 36 | return l2_cache_size 37 | else: 38 | return 4194304 39 | return l2_cache_size 40 | else: 41 | if external_call["sysctlbyname", c_int]("hw.l2cachesize".unsafe_cstr_ptr(), UnsafePointer(to=l2_cache_size), UnsafePointer(to=length), OpaquePointer(), 0) == 0: 42 | if l2_cache_size <= 1: 43 | return 4194304 44 | return l2_cache_size 45 | else: 46 | return 4194304 47 | 48 | 49 | fn initialize(cache_l1_size: Int, cache_l1_associativity: Int, cache_l2_size: Int, cache_l2_associativity: Int) raises: 50 | if cache_l1_associativity <= 1 or cache_l2_associativity <= 1: 51 | possible_l1_associativities = InlineArray[Int, 3](fill=0) 52 | if cache_l1_associativity > 1: 53 | possible_l1_associativities[0] = possible_l1_associativities[1] = possible_l1_associativities[2] = cache_l1_associativity 54 | else: 55 | possible_l1_associativities[0] = 4 if cache_l1_size < 65534 else 8 56 | possible_l1_associativities[1] = possible_l1_associativities[0] * 2 57 | possible_l1_associativities[2] = 12 58 | possible_l2_associativities = InlineArray[Int, 3](fill=0) 59 | if cache_l2_associativity > 1: 60 | possible_l2_associativities[0] = possible_l2_associativities[1] = possible_l2_associativities[2] = cache_l2_associativity 61 | else: 62 | possible_l2_associativities[0] = 4 if cache_l2_size <= 2097154 else 8 63 | possible_l2_associativities[1] = possible_l2_associativities[0] * 2 64 | possible_l2_associativities[2] = possible_l2_associativities[0] * 4 65 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 66 | code = 'alias L1_CACHE_SIZE = ' + String(cache_l1_size) + '\n' 67 | code += 'alias L1_ASSOCIATIVITY = ' + String(possible_l1_associativities[0]) + '\n' 68 | code += 'alias L2_CACHE_SIZE = ' + String(cache_l2_size) + '\n' 69 | code += 'alias L2_ASSOCIATIVITY = ' + String(possible_l2_associativities[0]) + '\n' 70 | f.write(code) 71 | for i in range(3): 72 | for j in range(1, 4): 73 | with open("./param" + String(i * 3 + j), "w") as f: 74 | code = 'alias L1_CACHE_SIZE = ' + String(cache_l1_size) + '\n' 75 | code += 'alias L1_ASSOCIATIVITY = ' + String(possible_l1_associativities[i]) + '\n' 76 | code += 'alias L2_CACHE_SIZE = ' + String(cache_l2_size) + '\n' 77 | code += 'alias L2_ASSOCIATIVITY = ' + String(possible_l2_associativities[j - 1]) + '\n' 78 | f.write(code) 79 | else: 80 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 81 | code = 'alias L1_CACHE_SIZE = ' + String(cache_l1_size) + '\n' 82 | code += 'alias L1_ASSOCIATIVITY = ' + String(cache_l1_associativity) + '\n' 83 | code += 'alias L2_CACHE_SIZE = ' + String(cache_l2_size) + '\n' 84 | code += 'alias L2_ASSOCIATIVITY = ' + String(cache_l2_associativity) + '\n' 85 | f.write(code) 86 | with open("./done", "w") as f: 87 | f.write("done") 88 | print('Setup initialization done!') 89 | 90 | fn main() raises: 91 | if len(argv()) == 1: 92 | cache_l1_size = 0 93 | cache_l2_size = 0 94 | cache_l1_associativity = 0 95 | cache_l2_associativity = 0 96 | if os_is_linux(): 97 | with open("/sys/devices/system/cpu/cpu0/cache/index0/size", "r") as f: 98 | txt = f.read() 99 | if txt.find('K') != -1: 100 | cache_l1_size = atol(txt.split('K')[0]) * 1024 101 | else: 102 | cache_l1_size = atol(txt.split('M')[0]) * 1048576 103 | try: 104 | with open("/sys/devices/system/cpu/cpu0/cache/index0/ways_of_associativity", "r") as f: 105 | cache_l1_associativity = atol(f.read()) 106 | except: 107 | cache_l1_associativity = 0 108 | with open("/sys/devices/system/cpu/cpu0/cache/index2/size", "r") as f: 109 | txt = f.read() 110 | if txt.find('K') != -1: 111 | cache_l2_size = atol(txt.split('K')[0]) * 1024 112 | else: 113 | cache_l2_size = atol(txt.split('M')[0]) * 1048576 114 | try: 115 | with open("/sys/devices/system/cpu/cpu0/cache/index2/ways_of_associativity", "r") as f: 116 | cache_l2_associativity = atol(f.read()) 117 | except: 118 | cache_l2_associativity = 0 119 | elif os_is_macos(): 120 | cache_l1_size = Int(cachel1()) 121 | cache_l2_size = Int(cachel2()) 122 | initialize(cache_l1_size, cache_l1_associativity, cache_l2_size, cache_l2_associativity) 123 | else: 124 | command = String(argv()[1]) 125 | 126 | from python import Python 127 | os_py = Python.import_module("os") 128 | os_path_py = Python.import_module("os.path") 129 | if os_path_py.isfile('./done'): 130 | if command != '9': 131 | print('Setup', command + '/8', 'skipped!') 132 | else: 133 | os_py.remove("./done") 134 | print('Setup done!') 135 | return 136 | 137 | from mojmelo.utils.Matrix import Matrix 138 | import time 139 | 140 | alias NUM_ITER = 16 141 | results = InlineArray[Int, 3](fill=0) 142 | var junk: Float32 = 0.0 143 | a = Matrix.random(512, 4096) 144 | b = Matrix.random(4096, 512) 145 | for i in range(NUM_ITER): 146 | start = time.perf_counter_ns() 147 | c = a * b 148 | finish = time.perf_counter_ns() 149 | junk += c[0, 0] 150 | if i != 0: 151 | results[0] += (finish - start) // (NUM_ITER - 1) 152 | a = Matrix.random(4096, 4096) 153 | b = Matrix.random(4096, 4096) 154 | for i in range(NUM_ITER): 155 | start = time.perf_counter_ns() 156 | c = a * b 157 | finish = time.perf_counter_ns() 158 | junk += c[0, 0] 159 | if i != 0: 160 | results[1] += (finish - start) // (NUM_ITER - 1) 161 | a = Matrix.random(4096, 512) 162 | b = Matrix.random(512, 4096) 163 | for i in range(NUM_ITER): 164 | start = time.perf_counter_ns() 165 | c = a * b 166 | finish = time.perf_counter_ns() 167 | junk += c[0, 0] 168 | if i != 0: 169 | results[2] += (finish - start) // (NUM_ITER - 1) 170 | if command != '9': 171 | with open("./results" + command, "w") as f: 172 | f.write(String(results[0]) + ',' + String(results[1]) + ',' + String(results[2]) + ',' + String(junk)) 173 | var code: String 174 | with open("./param" + String(Int(command) + 1), "r") as f: 175 | code = f.read() 176 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 177 | f.write(code) 178 | print('Setup', command + '/8', 'done!') 179 | else: 180 | results_list = List[InlineArray[Int, 3]]() 181 | for i in range(1, 9): 182 | with open("./results" + String(i), "r") as f: 183 | res = f.read().split(',') 184 | results_list.append(InlineArray[Int, 3](fill=0)) 185 | results_list[i - 1][0] = atol(res[0]) 186 | results_list[i - 1][1] = atol(res[1]) 187 | results_list[i - 1][2] = atol(res[2]) 188 | results_list.append(results) 189 | 190 | from collections import Counter 191 | 192 | votes = List[Int]() 193 | for i in range(3): 194 | _min = results_list[0][i] 195 | m_index = 0 196 | for j in range(9): 197 | if results_list[j][i] < _min: 198 | _min = results_list[j][i] 199 | m_index = j 200 | votes.append(m_index) 201 | 202 | var code: String 203 | with open("./param" + String(Counter[Int](votes).most_common(1)[0]._value + 1), "r") as f: 204 | code = f.read() 205 | with open("./mojmelo/utils/mojmelo_matmul/params.mojo", "w") as f: 206 | f.write(code) 207 | 208 | for i in range(1, 10): 209 | os_py.remove("./param" + String(i)) 210 | if i != 9: 211 | os_py.remove("./results" + String(i)) 212 | print('Setup done!') 213 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /recipes/mojo-libc/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mojo-libc/image.jpeg -------------------------------------------------------------------------------- /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 == 24.6 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/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 | -------------------------------------------------------------------------------- /recipes/mojo-websockets/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mojo-websockets/image.jpeg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /recipes/mosaic/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/mosaic/image.jpeg -------------------------------------------------------------------------------- /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: 9be5ccdc0c00b5bc90e5d72db6774a068c925ae4 20 | 21 | build: 22 | number: 3 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.3.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/nabla/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/nabla/image.jpeg -------------------------------------------------------------------------------- /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/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/nanoid/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/nanoid/image.jpeg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 magic CLI 191 | 192 | You can use the following command in the terminal to install `numojo`. 193 | 194 | ```console 195 | magic add numojo 196 | ``` 197 | 198 | ### Add in toml file 199 | 200 | You can add `numojo` in the dependencies section of your toml file. 201 | 202 | ```toml 203 | [dependencies] 204 | numojo = "==0.6" 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 `magic 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/numojo/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/numojo/image.jpeg -------------------------------------------------------------------------------- /recipes/numojo/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "0.6.1" 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: f0dea371bfd15df65e917e013e11d6177bfc975a 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.1.1 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.1.1 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/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/small_time/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/recipes/small_time/image.jpeg -------------------------------------------------------------------------------- /recipes/small_time/recipe.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | version: "25.3.0" 3 | 4 | package: 5 | name: "small_time" 6 | version: ${{ version }} 7 | 8 | source: 9 | - git: https://github.com/thatstoasty/small-time.git 10 | rev: 28848acaef5e1b1aa76f6befa8994fee5628a5a5 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.3 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 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | [lint] 2 | # See https://docs.astral.sh/ruff/rules/ 3 | fixable = ["I"] 4 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modular/modular-community/c4ae0c826e44669fe1731d980abfe4a45777e0b7/scripts/__init__.py -------------------------------------------------------------------------------- /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 | run_command_unchecked, 7 | save_failed_compatibility, 8 | eprint, 9 | ) 10 | import sys 11 | import os 12 | 13 | 14 | def main() -> None: 15 | parser = argparse.ArgumentParser(description="Build all recipes.") 16 | parser.add_argument( 17 | "--channel", 18 | action="append", 19 | required=True, 20 | help="The channels to use for building.", 21 | ) 22 | parser.add_argument( 23 | "--data-file", 24 | type=Path, 25 | default=os.environ.get("DATA_FILE"), 26 | help="Path to where the data should be stored. Nothing will be stored if that flag is not provided.", 27 | ) 28 | args = parser.parse_args() 29 | 30 | base_dir = Path("recipes") 31 | variant_config = "variants/variants.yaml" 32 | 33 | # Load existing failed compatibility data 34 | failed_compatibility = ( 35 | None if args.data_file is None else load_failed_compatibility(args.data_file) 36 | ) 37 | 38 | exit_code = 0 39 | 40 | for recipe_dir in base_dir.iterdir(): 41 | recipe_file = recipe_dir / "recipe.yaml" 42 | if not recipe_file.is_file(): 43 | eprint(f"{recipe_dir} doesn't contain recipe.yaml") 44 | continue 45 | 46 | command = [ 47 | "rattler-build", 48 | "build", 49 | ] 50 | 51 | for channel in args.channel: 52 | command.extend(["--channel", channel]) 53 | command.extend( 54 | [ 55 | "--channel", 56 | "conda-forge", 57 | "--variant-config", 58 | variant_config, 59 | "--skip-existing=all", 60 | "--recipe", 61 | str(recipe_file), 62 | ] 63 | ) 64 | result = run_command_unchecked(command) 65 | if result.returncode != 0: 66 | eprint(f"Error building recipe in {recipe_dir}: {result.stderr}") 67 | exit_code = 1 68 | if failed_compatibility is not None: 69 | failed_compatibility[recipe_dir.name] = { 70 | "failed_at": datetime.now().isoformat() 71 | } 72 | else: 73 | print(f"Successfully built recipe {recipe_dir.name}") 74 | if failed_compatibility is not None: 75 | if recipe_dir.name in failed_compatibility: 76 | del failed_compatibility[recipe_dir.name] 77 | print(f"Removed {recipe_dir.name} from failed-compatibility.json") 78 | 79 | if failed_compatibility is not None: 80 | # Save updated failed compatibility data 81 | save_failed_compatibility(args.data_file, failed_compatibility) 82 | 83 | sys.exit(exit_code) 84 | 85 | 86 | if __name__ == "__main__": 87 | main() 88 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /scripts/common.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypedDict 2 | import json 3 | from pathlib import Path 4 | import sys 5 | import subprocess 6 | 7 | 8 | class RecipeFailure(TypedDict): 9 | failed_at: str 10 | 11 | 12 | def load_failed_compatibility(file_path: Path) -> dict[str, RecipeFailure]: 13 | if file_path.exists(): 14 | with file_path.open("r") as file: 15 | return dict(json.load(file)) 16 | return {} 17 | 18 | 19 | def save_failed_compatibility(file_path: Path, data: dict[str, RecipeFailure]) -> None: 20 | if not file_path.parent.exists(): 21 | file_path.parent.mkdir(parents=True, exist_ok=True) 22 | with file_path.open("w") as file: 23 | json.dump(data, file, indent=4) 24 | 25 | 26 | def eprint(*args: Any, **kwargs: Any) -> None: 27 | print(*args, file=sys.stderr, **kwargs) 28 | 29 | 30 | def commit_push_changes(message: str, branch_name: str) -> None: 31 | """ 32 | Commit and push changes to the specified branch with a given commit message. 33 | If there are no changes, do nothing. 34 | """ 35 | 36 | # Switch to branch 37 | run_command(["git", "switch", branch_name]) 38 | 39 | # Check if there are changes to commit 40 | result = run_command_unchecked(["git", "diff-index", "--quiet", "HEAD"]) 41 | if result.returncode == 0: 42 | eprint("No changes to commit.") 43 | return 44 | 45 | # Commit, pull and push the changes 46 | run_command(["git", "pull", "origin", branch_name]) 47 | run_command(["git", "commit", "--message", message, "--no-verify"]) 48 | run_command(["git", "push", "--set-upstream", "origin", branch_name]) 49 | 50 | 51 | def run_command_unchecked(command: list[str]) -> subprocess.CompletedProcess[Any]: 52 | eprint(f'Run command: {" ".join(command)}') 53 | result = subprocess.run(command, capture_output=True, text=True) 54 | return result 55 | 56 | 57 | def run_command(command: list[str]) -> subprocess.CompletedProcess[Any]: 58 | result = run_command_unchecked(command) 59 | if result.returncode != 0: 60 | eprint("Command failed") 61 | print(f"stdout: {result.stdout}") 62 | eprint(f"stderr: {result.stderr}") 63 | sys.exit(result.returncode) 64 | 65 | return result 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /variants/variants.yaml: -------------------------------------------------------------------------------- 1 | max: 2 | - "24.5.0" 3 | - "24.6.0" 4 | --------------------------------------------------------------------------------