|
| 1 | +# ===----------------------------------------------------------------------=== # |
| 2 | +# Copyright (c) 2025, Modular Inc. All rights reserved. |
| 3 | +# |
| 4 | +# Licensed under the Apache License v2.0 with LLVM Exceptions: |
| 5 | +# https://llvm.org/LICENSE.txt |
| 6 | +# |
| 7 | +# Unless required by applicable law or agreed to in writing, software |
| 8 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 9 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 10 | +# See the License for the specific language governing permissions and |
| 11 | +# limitations under the License. |
| 12 | +# ===----------------------------------------------------------------------=== # |
| 13 | + |
| 14 | +from osimport abort |
| 15 | + |
| 16 | +from memoryimport UnsafePointer, alloc |
| 17 | +from utilsimport IndexList |
| 18 | +from pythonimport Python, PythonObject |
| 19 | +from python._cpythonimport PyObjectPtr, Py_LT, Py_GT |
| 20 | +from python.bindingsimport ( |
| 21 | + PythonModuleBuilder, |
| 22 | + PyTypeObjectSlot, |
| 23 | +) |
| 24 | + |
| 25 | +comptime Coord1DColumn= List[Float64] |
| 26 | + |
| 27 | + |
| 28 | +fn_extent(pos: Coord1DColumn) -> Tuple[Float64, Float64]: |
| 29 | +"""Return the min and max value in the buffer.""" |
| 30 | + v_min= Float64.MAX |
| 31 | + v_max= Float64.MIN |
| 32 | +for vin pos: |
| 33 | + v_min=min(v_min, v) |
| 34 | + v_max=max(v_max, v) |
| 35 | +return (v_min, v_max) |
| 36 | + |
| 37 | + |
| 38 | +fn_compute_bounding_box_area( |
| 39 | +pos_x: Coord1DColumn, |
| 40 | +pos_y: Coord1DColumn, |
| 41 | +) -> Float64: |
| 42 | +iflen(pos_x)==0: |
| 43 | +return0.0 |
| 44 | + ext_x= _extent(pos_x) |
| 45 | + ext_y= _extent(pos_y) |
| 46 | +return (ext_x[1]- ext_x[0])* (ext_y[1]- ext_y[0]) |
| 47 | + |
| 48 | + |
| 49 | +structDataFrame(Defaultable,Movable,Representable): |
| 50 | +"""A simple columnar data structure. |
| 51 | +
|
| 52 | + This struct contains points with the x,y coordinates stored in columns. Some algorithms |
| 53 | + are a lot more efficient in this representation. |
| 54 | +""" |
| 55 | + |
| 56 | +varpos_x: Coord1DColumn |
| 57 | +varpos_y: Coord1DColumn |
| 58 | + |
| 59 | +# The bounding box area that contains all points. |
| 60 | +var_bounding_box_area: Float64 |
| 61 | + |
| 62 | +fn__init__(outself): |
| 63 | +"""Default initializer.""" |
| 64 | +self.pos_x= [] |
| 65 | +self.pos_y= [] |
| 66 | +self._bounding_box_area=0 |
| 67 | + |
| 68 | +fn__init__( |
| 69 | + outself, |
| 70 | + varx: Coord1DColumn, |
| 71 | + vary: Coord1DColumn, |
| 72 | + ): |
| 73 | +self._bounding_box_area= _compute_bounding_box_area(x, y) |
| 74 | +self.pos_x= x^ |
| 75 | +self.pos_y= y^ |
| 76 | + |
| 77 | +@staticmethod |
| 78 | +fn_get_self_ptr( |
| 79 | +py_self: PythonObject, |
| 80 | + ) -> UnsafePointer[Self, MutAnyOrigin]: |
| 81 | +try: |
| 82 | +return py_self.downcast_value_ptr[Self]() |
| 83 | +except e: |
| 84 | + abort( |
| 85 | + String( |
| 86 | + ( |
| 87 | +"Python method receiver object did not have the" |
| 88 | +" expected type:" |
| 89 | + ), |
| 90 | + e, |
| 91 | + ) |
| 92 | + ) |
| 93 | + |
| 94 | +@staticmethod |
| 95 | +fnwith_columns( |
| 96 | +pos_x: PythonObject,pos_y: PythonObject |
| 97 | + )raises -> PythonObject: |
| 98 | +varlen_x= Int(pos_x.__len__()) |
| 99 | +varlen_y= Int(pos_y.__len__()) |
| 100 | +if len_x!= len_y: |
| 101 | +raise Error("The length of the two columns does not match.") |
| 102 | + |
| 103 | +# Allocate memory for the buffers |
| 104 | +varptr_x= Coord1DColumn(capacity=len_x) |
| 105 | +varptr_y= Coord1DColumn(capacity=len_y) |
| 106 | + |
| 107 | +# Copy values from Python objects |
| 108 | +for valuein pos_x: |
| 109 | + ptr_x.append(Float64(value)) |
| 110 | +for valuein pos_y: |
| 111 | + ptr_y.append(Float64(value)) |
| 112 | + |
| 113 | +# Create NDBuffers with dynamic shape |
| 114 | +# var m_x = NDBuffer[DType.float64, 1, MutAnyOrigin]( |
| 115 | +# ptr_x, IndexList[1](len_x) |
| 116 | +# ) |
| 117 | +# var m_y = NDBuffer[DType.float64, 1, MutAnyOrigin]( |
| 118 | +# ptr_y, IndexList[1](len_x) |
| 119 | +# ) |
| 120 | +# |
| 121 | +return PythonObject(alloc=DataFrame(ptr_x^, ptr_y^)) |
| 122 | + |
| 123 | +@staticmethod |
| 124 | +fnpy__len__(py_self: PythonObject)raises -> Int: |
| 125 | +"""For __len__ we return the number of rows in the DataFrame.""" |
| 126 | +varself_ptr= Self._get_self_ptr(py_self) |
| 127 | +returnlen(self_ptr[].pos_x) |
| 128 | + |
| 129 | +@staticmethod |
| 130 | +fnpy__getitem__( |
| 131 | +py_self: PythonObject,index: PythonObject |
| 132 | + )raises -> PythonObject: |
| 133 | +"""For __getitem__ we will return a row of the DataFrame.""" |
| 134 | +varself_ptr= Self._get_self_ptr(py_self) |
| 135 | +varindex_mojo= Int(index) |
| 136 | +return Python().tuple( |
| 137 | + self_ptr[].pos_x[index_mojo], self_ptr[].pos_y[index_mojo] |
| 138 | + ) |
| 139 | + |
| 140 | +@staticmethod |
| 141 | +fnpy__setitem__( |
| 142 | +py_self: PythonObject,index: PythonObject,value: PythonObject |
| 143 | + )raises ->None: |
| 144 | +"""For __setitem__ we set the x and y values at the given index.""" |
| 145 | +varself_ptr= Self._get_self_ptr(py_self) |
| 146 | +varindex_mojo= Int(index) |
| 147 | +# Expect value to be a tuple of (x, y) |
| 148 | + self_ptr[].pos_x[index_mojo]= Float64(value[0]) |
| 149 | + self_ptr[].pos_y[index_mojo]= Float64(value[1]) |
| 150 | + |
| 151 | +fn__repr__(self) -> String: |
| 152 | +return String("DataFrame( length=",len(self.pos_x),")") |
| 153 | + |
| 154 | +@staticmethod |
| 155 | +fnrich_compare( |
| 156 | +self_ptr: PythonObject,other: PythonObject,op: Int |
| 157 | + )raises -> Bool: |
| 158 | +"""Implement the rich compare functionality. |
| 159 | +
|
| 160 | + We use the bounding box area to order the DataFrame objects. |
| 161 | +""" |
| 162 | +varself_df= Self._get_self_ptr(self_ptr) |
| 163 | +varother_df= Self._get_self_ptr(other) |
| 164 | +if op== Py_LT: |
| 165 | +return self_df[]._bounding_box_area< other_df[]._bounding_box_area |
| 166 | +elif op== Py_GT: |
| 167 | +return self_df[]._bounding_box_area> other_df[]._bounding_box_area |
| 168 | +# For other comparisons, return False |
| 169 | +returnFalse |
| 170 | + |
| 171 | + |
| 172 | +@export |
| 173 | +fnPyInit_mojo_module() -> PythonObject: |
| 174 | +"""Create a Python module with a type binding for `DataFrame`.""" |
| 175 | + |
| 176 | +try: |
| 177 | +varb= PythonModuleBuilder("mojo_module") |
| 178 | + _= ( |
| 179 | + b.add_type[DataFrame]("DataFrame") |
| 180 | + .def_init_defaultable[DataFrame]() |
| 181 | + .def_staticmethod[DataFrame.with_columns]("with_columns") |
| 182 | +# Mapping protocol. |
| 183 | + .def_method[DataFrame.py__len__, PyTypeObjectSlot.mp_length]() |
| 184 | + .def_method[DataFrame.py__getitem__, PyTypeObjectSlot.mp_getitem]() |
| 185 | + .def_method[DataFrame.py__setitem__, PyTypeObjectSlot.mp_setitem]() |
| 186 | + .def_method[ |
| 187 | + DataFrame.rich_compare, PyTypeObjectSlot.tp_richcompare |
| 188 | + ]() |
| 189 | + ) |
| 190 | +return b.finalize() |
| 191 | +except e: |
| 192 | + abort(String("failed to create Python module:", e)) |