-
Notifications
You must be signed in to change notification settings - Fork 22
[NuMojo] Update NuMojo to v0.8.0 #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This PR updates NuMojo to 25.4 in the prev-0.8 branch so that other updates can be pushed to prev-0.8 branch. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
) This pull request includes several changes across workflows, pre-commit configurations, and multiple files in the `numojo` library. The changes focus on simplifying code formatting, improving workflows, and enhancing maintainability by removing unnecessary parentheses and restructuring code for better readability.
# New Error Handling System for NuMojo
This PR introduces a structured and user-friendly error handling
framework for NuMojo, aimed at improving error traceability and
developer experience.
#### Key Additions and Changes
* **New Base Error Type**
Introduced a unified base error type: `NumojoError`, which serves as the
foundation for all error types within the NuMojo ecosystem.
* **Derived Common Error Types**
Defined several commonly used error types—such as `IndexError`,
`ShapeError`, and `IOError`—all derived from `NumojoError`. These
provide semantic clarity and simplify error handling across different
contexts.
#### Error Struct Design
The `Error` struct includes the following fields to offer detailed
diagnostic information:
* `category`: Specifies the error category (e.g., `"IndexError"`,
`"ShapeError"`).
* `message`: A concise description of what went wrong.
* `location` *(optional)*: The location in code where the error
occurred.
* `suggestion` *(optional)*: Helpful guidance on how the issue might be
resolved.
---
#### Example: Usage in `NDArray.store`
The `NDArray.store` method now includes index validation using the new
error system:
```mojo
fn store[
width: Int = 1
](mut self, *indices: Int, val: SIMD[dtype, width]) raises:
if len(indices) != self.ndim:
raise Error(
IndexError(
message=String(
"Mismatch in number of indices: expected {} indices"
" (one per dimension) but received {}."
).format(self.ndim, len(indices)),
suggestion=String(
"Provide exactly {} indices to correctly index into the"
" array."
).format(self.ndim),
location=String(
"NDArray.store[width: Int](*indices: Int, val:"
" SIMD[dtype, width])"
),
)
)
for i in range(self.ndim):
if (indices[i] < 0) or (indices[i] >= self.shape[i]):
raise Error(
IndexError(
message=String(
"Invalid index at dimension {}: index {} is out of"
" bounds [0, {})."
).format(i, indices[i], self.shape[i]),
suggestion=String(
"Ensure that index is within the valid range"
" [0, {})"
).format(self.shape[i]),
location=String(
"NDArray.store[width: Int](*indices: Int, val:"
" SIMD[dtype, width])"
),
)
)
```
These checks ensure the input indices conform to the expected
dimensionality and range constraints.
---
#### Example Output
For the code below:
```mojo
var arr = nm.ones[nm.f32](nm.Shape(3,3,3))
print(arr)
arr.store(1, 1, 5, val=Scalar[nm.f32](15.0))
print(arr)
```
The following error is raised:
```console
Unhandled exception caught during execution: NuMojo Error
Category : IndexError
Message : Invalid index at dimension 2: index 5 is out of bounds [0, 3).
Location : NDArray.store[width: Int](*indices: Int, val: SIMD[dtype, width])
Suggestion: Ensure that index is within the valid range [0, 3)
```
This structured error output gives a clear explanation of the problem,
including where it occurred and how to resolve it. Will slowly replace
all errors in NuMojo to follow the same convention and make sure we have
a clean error handling experience.
---
# Added native save file method for NuMojo
This PR also adds a `savenpy` method that writes the array natively to a
file without calling the numpy backend. But it's still in development
and hence it's not the main `save` method yet. We can use a native
`.nmj` or just use`.npy` file extension as required in the future with
this backend option.
---------
Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
…DArray & ComplexNDArray (#262) This PR updates NuMojo to work with Mojo 25.5 and also updates all the legacy errors in NDArray and ComplexNDArray with clear and useful error messages. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
This PR does the following two improvements: 1) Improves the existing `__getitem__(idx: Int) -> Self` and `__setitem__(idx: Int, val: Self)` methods in `NDArray` and `ComplexNDArray`. These new updates check for many possible edge cases and provides clean errors, speeds up the C contiguous and F contiguous calculation methods. 2) Adds tests for the new getter and setter methods. 3) Adds slicing method `__getitem__(slice: Slice) -> Self` for `NDArrayShape` which is useful in certain situations. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
This PR improves the printing options of `NDArray` and `ComplexNDArray`. Added `print_options: PrintOptions` as an internal field of `NDArray` and `ComplexNDArray` temporarily since Mojo doesn't support global variables yet. We will migrate to using a global variable once Mojo allows it which will simplify the syntax a lot. Most of the print options work as expected. But due to `print_options` being internal field of the array, they have to be individually fixed to modify the printing method. An example is as follows, ```mojo var arr = nm.zeros[nm.f32](nm.Shape(3, 4)) print(arr) # prints with default values arr.print_options.set_options(precision = 2) print(arr) # prints with precision 2 for floating values ``` It's a bit verbose currently and doesn't work well with context managers. Once Mojo support global variables, we can simplify this part. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
…mplexNDArray (#266) This PR enhances the slicing capabilities of both NDArray and ComplexNDArray to get NumPy compatibility. The improvements focus on edge case handling, negative indexing support, and slice adjustment logic and reworking the slicing function to improve its speed and memory usage. ## Key Improvements: 1. Edge case handling for consistent results across slice operations 2. Negative indexing support (e.g., arr[::-1], arr[5:1:-1]) 3. Out-of-bounds clamping to valid ranges (NumPy-compatible behavior, e.g., arr[100:200]) 4. Reworked slicing logic for better performance and memory efficiency ## Updated __getitem__ Implementations 1. __getitem__(slice_list: List[Slice]) 2. __getitem__(*slices: Slice) 3. __getitem__(*slices: Variant[Slice, Int]) ## Slicing Behavior 1. Forward slicing → arr[1:5], arr[:3], arr[2:] (with bounds clamping) 2. Reverse slicing → full negative stride support, e.g. arr[::-1], arr[5:1:-1] 3. Out-of-bounds slicing → automatically clamps to valid ranges, matching NumPy ## Known Limitation The current __getitem__(*slices: Variant[Slice, Int]) overload in Mojo has limitations when mixing Int and Slice. When they are combined, all slices must be explicitly wrapped with Slice(). Example: ```mojo import numojo as nm nm_arr = nm.arange[nm.f32](0.0, 24.0, step=1).reshape(nm.Shape(2, 3, 4)) nm_slice1 = nm_arr[0, 0:3, 0:3] # ❌ compiler cannot resolve this nm_slice1 = nm_arr[0, Slice(0,3), Slice(0,4)] # ✅ works (Ints + Slice need explicit Slice) nm_slice1 = nm_arr[0:1, 0:3, 0:4] # ✅ works as expected ``` ## Other Updates 1. Added CScalar alias for simpler creation of ComplexSIMD values with width 1 (mirrors Mojo’s default Scalar). 2. Expanded slicing test coverage. 3. Improved error messages for clarity and explicitness. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
# Tooling This PR introduces the **`pixi-build-mojo` backend** to NuMojo. With this change, users can add NuMojo as a dependency directly from the NuMojo GitHub repository—without needing to rely on Modular Community, Prefix.dev, or Conda channels. Key benefits: * Automatically adds the NuMojo package to the path when using `pixi shell`. * Provides LSP support in VS Code. NuMojo can be added either: * From a specific branch in the GitHub repo, or * From a local path where the repo has been cloned. ## Example Here’s a sample `pixi.toml` file from another project using NuMojo as a dependancy: ```toml [workspace] authors = ["photon <[email protected]>"] channels = [ "conda-forge", "https://conda.modular.com/max", "https://repo.prefix.dev/modular-community", "https://prefix.dev/pixi-build-backends", ] platforms = ["osx-arm64", "linux-64"] preview = ["pixi-build"] [package] name = "scijo" version = "0.1.0" [package.build] backend = { name = "pixi-build-mojo", version = "0.*"} [package.build.config.pkg] name = "scijo" [package.host-dependencies] modular = ">=25.5.0,<26" [package.build-dependencies] modular = ">=25.5.0,<26" numojo = { git = "https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo.git", branch = "main"} # numojo = { path = "$LOCAL_PATH/NuMojo"} # To use locally cloned NuMojo folder [package.run-dependencies] modular = ">=25.5.0,<26" numojo = { git = "https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo.git", branch = "main"} # numojo = { path = "$LOCAL_PATH/NuMojo"} # To use locally cloned NuMojo folder [dependencies] modular = ">=25.5.0,<26" numojo = { git = "https://github.com/Mojo-Numerics-and-Algorithms-group/NuMojo.git", branch = "main"} # numojo = { path = "$LOCAL_PATH/NuMojo"} # To use locally cloned NuMojo folder ``` ## Details - run 'pixi install' after modifying the .toml file to install numojo as a dependancy. - Use branch = "main" can be specified to use latest stable release or Use the latest branch = "pre-x.y" for the NuMojo compatible with latest Mojo version (Currently 25.5.0). ## Note The version of NuMojo available through this Pixi build backend method after this PR is the one in the `pre-0.8` branch. Older versions must still be installed through the methods described in the README. # Docs - Updated the readme files with updated installation procedure and adds Korean readme. - Updated the roadmap. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
This PR brings back the parameter based distinction between real values/arrays and complex values/arrays. All the real DType values available in NuMojo such as `i8`, `i32, `u32`, `f64` now have equivalent complex types `ComplexDType` such `ci8`, `ci32`, `cu32`, `cf64` with the addition of prefix `c`. Thus any function that accepts `DType` parameter has an equivalent function which accepts`ComplexDType` and the inputs to such functions can be only NDArray, CompexNDArray respectively. Following are some examples on how the distinction works for scalars. ```mojo import numojo as nm from numojo.prelude import * var scalar = Scalar[f32](1.0) print(scalar) # 1.0 var complex_scalar = CScalar[cf32](1.0, 2.0) print(complex_scalar) # 1.0 + 2.0 j var complex_simd = ComplexSIMD[cf32](1.0, 2.0) # Another way to define complex values var complex_simd_width_2 = ComplexSIMD[cf32, 2](SIMD[f32](1.0, 1.0), SIMD[f32](2.0, 2.0)) ``` Following are some examples on how NDArray and ComplexNDArray can be created with creation routines. ```mojo import numojo as nm from numojo.prelude import * var array = nm.arange[f32](1.0, 10.0, 1.0) # returns a NDArray instance print(array) # [1.0000 2.0000 3.0000 4.0000 5.0000 6.0000 7.0000 8.0000 9.0000] # 1D-array Shape(9) Strides(1) DType: f32 C-cont: True F-cont: True own data: True var complex_array = nm.arange[cf32](CScalar[cf32](1.0), CScalar[cf32](10.0), CScalar[cf32](1.0)) # returns a ComplexNDArray instance where both real and imaginary values range from 0 to 9 with step size 1. print(complex_array) # [(1.0000 + 1.0000j) (2.0000 + 2.0000j) (3.0000 + 3.0000j) (4.0000 + 4.0000j) (5.0000 + 5.0000j) (6.0000 + 6.0000j) (7.0000 + 7.0000j) (8.0000 + 8.0000j) (9.0000 + 9.0000j)] # 1D-array Shape(9) Strides(1) DType: cf32 C-cont: True F-cont: True own data: True ``` --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
# Key Changes ## Implicit vs. Explicit Copies 1. Mojo 0.25.6 introduces a clearer distinction between implicit and explicit copies. 2. NuMojo currently performs many unnecessary copies. This PR reduces some of them by favoring in-place operations where possible and ensuring correct handling of mutable vs. immutable references. 3. This PR fixes everything so that NuMojo works with Mojo 0.25.6. Hence not all redundant copies are removed yet, further cleanup will follow in future PRs. ## Copyable Types 1. Large structures like `NDArray` and `Matrix` now require explicit copies via`.copy()` method, and both implement the Copyable trait. 2. Lightweight structs such as `Item`, `NDArrayShape`, `NDArrayStrides`, `_NDIter` etc are implicitly copyable, as their duplication has negligible performance impact and improves usability. 3. This design choice can be revisited as needed. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
This PR does the following, 1) Unifies all main creation and manipulation methods of `Item`, `Shape` and `Strides` struct 2) Implement all the correct Error types and fixes some memory errors in above structs. 3) Adds news dtype aliases. 4) Adds type information to variables in many places. 5) Fixes some docstring errors that contained legacy code. 6) Remove `isize` and `intp` in favor of `int` as Mojo doesn't support those anymore. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
## Pull Request Overview (From Copilot) This PR enhances ComplexNDArray functionality by adding comparison operators, trait methods, statistical/reduction methods, and array manipulation capabilities. It also introduces temporary Int conversions for strides/shape operations and implements SIMD load/store methods for vectorized calculations. ### Key Changes - Added trait implementations (ImplicitlyCopyable, Movable) and conversion methods (__bool__, __int__, __float__) for ComplexNDArray - Implemented magnitude-based comparison operators (__lt__, __le__, __gt__, __ge__) for complex arrays - Added statistical methods (all, any, sum, prod, mean, max, min, argmax, argmin, cumsum, cumprod) and array manipulation methods (flatten, fill, row, col, clip, round, T, diagonal, trace, tolist, resize) - Changed internal buffer types from `UnsafePointer[Int]` to `UnsafePointer[Scalar[DType.int]]` in NDArrayShape, NDArrayStrides, and Item structs - Added SIMD load/store methods (load, store, unsafe_load, unsafe_store) for Item, Shape, and Strides <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | numojo/routines/indexing.mojo | Added Int conversions for stride operations in compress function | | numojo/routines/creation.mojo | Removed duplicate import statements | | numojo/core/ndstrides.mojo | Changed buffer type to Scalar[DType.int], updated __setitem__ validation, added SIMD load/store methods | | numojo/core/ndshape.mojo | Changed buffer type to Scalar[DType.int], updated __setitem__ validation, added SIMD load/store methods, modified size_of_array calculation | | numojo/core/ndarray.mojo | Added Int conversions for stride/shape buffer accesses throughout | | numojo/core/item.mojo | Changed buffer type to Scalar[DType.int], removed Item.__init__(idx, shape) constructor and offset() method, added SIMD load/store methods | | numojo/core/complex/complex_simd.mojo | Added ImplicitlyCopyable and Movable traits to ComplexSIMD | | numojo/core/complex/complex_ndarray.mojo | Added comparison operators, conversion methods, power operations, statistical methods, and array manipulation methods; added Int conversions for stride operations | </details> --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
This PR implements the first two steps as discussed in #279 to ensure minimal damage in code while migrating. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
For more details on the new Matrix view model, refer to #279 where I have explained the updated (2025/11/22) model as of Mojo 25.7 --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
This PR improves the docstrings in `matrix.mojo` by adding clear explanations for all variables, methods and adding examples. It also standardizes the format of the docstring. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
This PR expands complex-datatype support, improves ComplexSIMD and ComplexNDArray usability, introduces new convenience APIs, and enhances documentation across the complex-number ecosystem. ## Added ### ComplexDType & DType System * Support for additional complex dtypes (`ComplexDType.int16`, `ComplexDType.int32`). ### ComplexSIMD * Added `component_bitwidth()` to distinguish SIMD bitwidth from the bitwidth of its `real` and `imag` components. * Added arithmetic operator overloads for more complete ComplexSIMD math support. * Added `elem_pow` for per-component exponentiation (distinct from `__pow__`). * Added `all_close()` for comparing real and imaginary components across two ComplexSIMD instances. * Added broadcasting support for scalar complex values, simplifying scalar construction: ```mojo var a = CScalar(1.0) # 1.0 + 1.0j var b = ComplexSIMD[f32, 2](1.0) # [1.0, 1.0] + [1.0, 1.0]j ``` * Added convenience constructors: * `ComplexSIMD[cf64].zero()` * `ComplexSIMD[cf64].one()` * `ComplexSIMD[cf64].I()` * `ComplexSIMD[cf64].from_polar(2.0, 0.5)` ### ComplexNDArray * Expanded and clarified documentation for all `ComplexNDArray` methods. ### Python-like Complex Literal * Introduce a `ImaginaryUnit` type and the alias ``1j`` to enable natural, Pythonic complex number creation in NuMojo. Previously, creating complex numbers required explicit constructor calls which were pretty long, but now users can write mathematical expressions using familiar notation. The `1j` constant automatically adapts to match operand types and SIMD widths. It also has full support for all operations between `1j` and scalars, SIMD vectors, and other complex numbers. It is by default of type ComplexDType.float64. ```mojo from numojo import `1j` # Scalar complex numbers var c1 = 3 + 4 * `1j` # ComplexScalar[cint]: (3 + 4j) var c2 = 2.0 * `1j` # ComplexScalar[cf64]: (0 + 2j) var c3 = 5 - `1j` # ComplexScalar[cint]: (5 - 1j) # SIMD complex vectors var c4 = SIMD[f32, 4](1.0) + `1j` * SIMD[f32, 4](2.0) # ComplexSIMD[cf32, 4] var c5 = SIMD[f64, 2](3.0, 4.0) + `1j` # ComplexSIMD[cf64, 2] var d = SIMD[f32, 2](1) + SIMD[f32, 2](2) * `1j` # creates [( 1 + 2 j) (1 + 2 j)] # Mathematical properties var c6 = `1j` * `1j` # -1 (Scalar[f64]) var c7 = `1j` ** 3 # (0 - 1j) (ComplexScalar[cf64]) var c8 = (1 + `1j`) / `1j` # (1 - 1j) (ComplexScalar[cf64]) ``` * Added documentation for ImaginaryUnit methods. ### NDArray Improvements * Added `normalize` for handling negative indices. * Improved constructor documentation. --- ## Changed * Enhanced documentation for `ComplexSIMD`, including clearer conceptual explanations and usage examples. * Change the behaviour of `__getitem__`, `__setitem__`, `item`, `itemset` methods of `ComplexSIMD`. The earlier method were confusing to use and were not flexible enough to access all parts of the complex SIMD vectors. The current methods allow accessing the ComplexSIMD vectors in the following manner, ```mojo var complex_simd = ComplexSIMD[cf32, 4](1.0, 2.0) # All lanes set to 1+2i # Lane-wise access var lane2 = complex_simd[2] # Get ComplexScalar at lane 2 complex_simd[1] = ComplexScalar[cf32](3.0, 4.0) # Set lane 1 to 3+4i # Component access var real_part = complex_simd.item["re"](2) # Get real part of lane 2 complex_simd.itemset["im"](1, 5.0) # Set imaginary part of lane 1 to 5.0 # Bulk access var all_reals = complex_simd.re # Get all real parts as SIMD vector var all_imags = complex_simd.im # Get all imaginary parts as SIMD vector ``` --- ## 4. Removed * N/A --- --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
…ray operations (#271) This PR adds foundational support for future NDArray indexing features, introduces several new array operations, refines internal APIs to align with Mojo standard library conventions, and fixes correctness issues in edge cases such as zero-dimensional arrays. # Added 1) Support for `Ellipsis` and a `NewAxis` to enable upcoming indexing and slicing functionality. 2) `deep_copy` methods for Item, NDArrayShape, and NDArrayStrides, returning a copy with a new origin and identical elements. 3) Array comparison methods `allclose`, `isclose`, and `array_equal` for ndarray and matrix. 4) Infinity checks `isneginf` and `isposinf` for ndarray and matrix. 5) Logical operations `logical_and, logical_or, logical_not, and logical_xor` for ndarray, matrix, and complexndarray. meshgrid function for NDArray inputs. # Changed 1) Updated Item, NDArrayShape, and NDArrayStrides to use the latest UnsafePointer API with `MutOrigin.external`. 2) Renamed internal data type fields in Item, NDArrayShape, and NDArrayStrides to `element_type` to align with Mojo standard library conventions. 3) Improved and expanded docstrings across modified modules. # Fixed 1) Correct handling of ndim = 0 cases in Item, NDArrayShape, and NDArrayStrides by allocating a single dimension. # Notes 1) Several changes are preparatory for future indexing and slicing enhancements. 2) Internal refactors are intended to improve consistency with Mojo stdlib APIs and may affect downstream internal extensions relying on private fields. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
As the title. --------- Co-authored-by: ZHU Yuhao 朱宇浩 <[email protected]>
|
Kind of a nit pick but calling this 0.71.0 deviates from the SemVer versioning that we have been using. Eventually being at 0.71.0 would be fine (Numba is at 0.63), though hopefully Mojo and NuMojo can hit 1.0 soon. I think it would be better to go to 0.8.0 then just increment the second number (minor version) more frequently and accept that it will be large. @forfudan and @shivasankarka What do you think? We are not currently planning to support any version other than the one on main, I don't see any harm in incrementing the minor version fairly routinely. Obviously, we are breaking the convention on breaking changes incrementing the major version, but that is fairly standard pre 1.0. |
|
@MadAlex1997 yep, that plan sounds good. But I might suggest going to v0.7.1 in that case since most of it are minor updates. |
The last number in the sequence is for patches, like bug-fix type of stuff. Since we are basically treating minor versions as major versions, I guess it is not really a big deal if we increment the patch number here. I just don't want to confuse users too much. I'll leave it up to you. |
|
@shivasankarka Adding my 2-cents: If NuMojo becomes 1.* though than yeah I would expect minor version changes to not break stuff. |
@MadAlex1997 @shivasankarka, I agree with the version number 0.8.0. Two reasons:
There are still two things to be solved:
Another important thing is that: When we merege this PR, we should NOT squash the commits but just merge it. |
|
@forfudan @MadAlex1997 Sorry, I had confusion on my part. I agree with going to v0.8.0! I'll make a small PR to fix the pixi.toml. I think pixi.lock file should be kept for reproducibility and consistency. |
This PR updates NuMojo to v0.8.0 (Mojo v25.7). Please check the changelog and corresponding PR for updates and corresponding examples.