Friday, 4 October 2024

Segments in Mojo

After implementing points in Mojo, with some assistance from ChatGPT and Phind, we now turn our attention to segments, which represent the next fundamental geometric structure preceding polylines (or simply lines).

A segment consists of a start point and an end point, rendering it an oriented entity. It is implemented as a Mojo structure, that implements a few methods, such as returning the segment start and end points, calculating the total or horizontal lenght, and so on.

To facilitate point copying when returning a segment's start or end point (via the start_point and end_point methods), the __copyinit__ method was introduced to the Point structure: 

  fn __copyinit__(inout self, existing: Point): self.coords = existing.coords 

 This method allows for efficient copying of point coordinates, which is crucial when working with segments. 

Moving on to the Segment structure, apart from incorporating several basic methods (such as dx, midx, length, horizontal_length, and others), two significant methods were added for calculating the azimuth and plunge of a Segment instance. These concepts are extensively utilized in geological contexts. 

The azimuth refers to the angle, within the horizontal plane, between the horizontal projection of the segment and the Y-axis. This angle is measured clockwise, commencing from the Y-axis, spanning from 0° to 360°. 

The plunge represents the dip of the segment in the vertical plane. It signifies the vertical angle between the horizontal plane and the segment. Its range extends from -90° to +90°, where positive values denote a downward dip, and negative values signify an upward dip. 

Both azimuth and plunge are expressed as Float64 values, but their values may remain undefined under specific circumstances. The azimuth becomes undefined when the segment is vertical or possesses zero length (i.e., when the start and end points coincide). Similarly, the plunge remains undefined when the segment's length is zero. 

To address these edge cases, both the azimuth() and plunge() methods return an Optional[Float64], with Optional being imported from the standard library's collections module. If the value is invalid, None is returned; otherwise, the valid value is returned. 

Notably, the valid return can also be encapsulated into an Optional — both approaches are considered valid. When presenting the result, the value must be extracted using the or_else method, where the input to this method serves as a placeholder for missing data. 

In the example code, a conventional no-data value frequently employed in Geographic Information Systems (GIS) was utilized by defining an alias: 

   alias NULL_ORIENTATION = -999999999.99999 

 This alias is subsequently applied as follows:

   print("Segment plunge:", segment.plunge().or_else(NULL_ORIENTATION)) 

In this scenario, when the result is valid, it will be printed; conversely, if the result is invalid, the no-data value defined in NULL_ORIENTATION will be displayed. 

By implementing these features, the Segment structure becomes more robust and versatile, capable of handling various geometric calculations essential in geological applications and beyond. 

 

The code is available at: https://gitlab.com/mauroalberti/kira

 

Tuesday, 17 September 2024

An example of defining a point structure in Mojo

Mojo is a relatively new language (introduced in 2022) designed for parallel computing. It is compatible with SIMD (Single Instruction Multiple Data) architectures and is intentionally interoperable with Python. SIMD allows for the application of vectorized operations, resulting in significant speedups in processing.

Currently, Mojo is available only for Ubuntu and macOS.

In this example, we will define a point geometry and implement it using Mojo.

In this context, using SIMD to store point coordinates is advantageous because it enables the application of vectorized operations to those coordinates.

To define a SIMD variable, it’s necessary to specify both the data type and the number of stored values. Here is an example of initializing a SIMD variable:


var vec = SIMD[DType.int8, 4](1, 2, 3, 4)

In Mojo, you cannot use Float32 or Float64 directly to define the variable type. Instead, you need to use DType.float32 or DType.float64 for floating-point values.

Another requirement for SIMD variables is that the number of elements must be a power of two. Therefore, it is not possible to use SIMD[DType.float64, 3] to store just the x, y, and z coordinates of a point. To meet the minimum size requirement of 4 elements, you can add a time variable (t) of type Float64 to the point coordinates. This avoids wasting memory space.

The point coordinates are stored as follows:


var coords: SIMD[DType.float64, 4]

Similar to Python, a class-like structure in Mojo is initialized using the __init__ method. To indicate that the instance is mutable, the self parameter is marked as inout:


fn __init__(inout self, x: Float64, y: Float64, z: Float64, t: Float64 = 0.0)

Since the values are stored in a SIMD variable, the elements are accessed by their index:


fn x(self) -> Float64:
	return self.coords[0]
    

To calculate the distance between two points, you cannot use the sum() or sqrt() functions directly on a SIMD variable. Instead, you must extract the scalar values by index and perform the scalar operations manually.

The square root function is imported from the math module like this:


from math import sqrt

Here is the complete implementation of the point structure:



from math import sqrt

struct Point :

    var coords: SIMD[DType.float64, 4]

    fn __init__(inout self, x: Float64, y: Float64, z: Float64, t: Float64 = 0.0):

        self.coords = SIMD[DType.float64, 4](x, y, z, t)

    fn x(self) -> Float64:
        return self.coords[0]

    fn y(self) -> Float64:
        return self.coords[1]

    fn z(self) -> Float64:
        return self.coords[2]

    fn t(self) -> Float64:
        return self.coords[3]

    fn distance_to(self, other: Point) -> Float64:

        var delta = self.coords - other.coords
        var sum_of_squares = delta[0]*delta[0] + delta[1]*delta[1] + delta[2]*delta[2];
        return sqrt(sum_of_squares[0])
    
    
And here’s an example of how to use this structure in a main function:

fn main():

    var pt1 = Point(1.0, 2.0, 3.0, 1222.34)
    var pt2 = Point(2.0, 3.0, 3.0, 22.34)
    print(pt1.distance_to(pt2))
      
You can also view this example on GitLab:

Mojo Point Structure Example

Monday, 26 August 2024

geogst, a new Python module for Structural Geology

 

geogst is a new Python module for structural geology available on PyPi, making it easily installable via the classic command pip install geogst (or python -m pip install geogst).

This module was primarily developed to facilitate the creation of stereonets for geological data, determining the intersection between geological planes and topographic surfaces (expressed through Digital Elevation Models, DEMs), and generating the skeletons of geological profiles. Among its main features, the module allows for the calculation of topographic profiles, adding geological attitudes, and determining the intersections of geological traces with these profiles.


 

Additionally, geogst includes example geospatial datasets that can be used to explore its functionalities, such as creating profiles directly within Jupyter Notebooks.

Here are a few examples of Jupyter Notebooks for creating geological profiles:

This module will form the foundation for the modules in the qgSurf plugin for QGIS. Currently, qgSurf directly includes geogst as a submodule, so no separate installation is required. In future versions of qgSurf (initially experimental), the geogst module will be automatically installed if it is not already present.

Since QGIS does not allow the upload of compiled code in Python modules, a key advantage of using geogst as a separate module in qgSurf is the potential to incorporate compiled code in Fortran, C++, and Rust, significantly improving the speed and efficiency of these tools.

Conclusion and call to action: If you are a geologist or a developer working with geological data, we invite you to explore geogst and contribute to its development. Your experiences and feedback are crucial to improving and growing the community that uses ggSurf.

Saturday, 17 August 2024

Mojo

A new AI-oriented language is on the scene: Mojo, which aims to become a superset of Python. It follows Python semantics but, thanks to recent and advanced compilation techniques, might achieve speedups as large as 100,000 times or more compared to standard Python.

Mojo was created by Chris Lattner, the creator of LLVM, Clang, Swift, Swift for TensorFlow and MLIR.

Currently, Mojo is only supported on Linux and macOS.

The source code is hosted at GitHub, from where you can clone the repository locally. Within the downloaded repository, the 'examples/notebooks' directory contains Jupyter Notebooks that you can run using Mojo (provided that Mojo and the Jupyter plugin are installed).

A very brief introduction to the Mojo language is provided in HelloMojo.ipynb. Another notebook, Matmul.ipynb, implements a matrix multiplication example in both Python and Mojo. The example showcases how optimization in Mojo can lead to a remarkable speedup, reportedly around 455,127x compared to Python. It's important to note that achieving such optimized Mojo code requires considerable effort and a solid understanding of Mojo.

That said, I tried running the code in that notebook on my old HP laptop, which lacks a GPU, and has the following specifications:

inxi -Fxxxzr

System:
Kernel: 5.15.0-118-generic x86_64 bits: 64 compiler: gcc v: 11.4.0
Desktop: Xfce 4.18.1 tk: Gtk 3.24.33 info: xfce4-panel wm: xfwm 4.18.0
vt: 7 dm: LightDM 1.30.0 Distro: Linux Mint 21.3 Virginia
base: Ubuntu 22.04 jammy
Machine:
Type: Laptop System: HP product: HP Laptop 15-bs0xx v: Type1ProductConfigId
serial: <superuser required> Chassis: type: 10 serial: <superuser required>
Mobo: HP model: 832B v: 23.37 serial: <superuser required> UEFI: Insyde
v: F.21 date: 07/04/2017
CPU:
Info: dual core model: Intel Core i5-7200U bits: 64 type: MT MCP
smt: enabled arch: Amber/Kaby Lake note: check rev: 9 cache: L1: 128 KiB
L2: 512 KiB L3: 3 MiB
Speed (MHz): avg: 1134 high: 1245 min/max: 400/3100 cores: 1: 1040
2: 1097 3: 1245 4: 1157 bogomips: 21599

Even though the speedup I achieved was an order of magnitude lower than the original 455,127x reported in the notebook, it was still impressive: 10,032x! It’s likely that using a newer machine, possibly equipped with a GPU, I could achieve speedups on the order of 100,000x.

As reported, tools like Cython or Numba achieve speedups typically in the range of 10-100x

Moreover, the Mojo code ran without any issue on my old laptop running Linux Mint.

So Mojo is a very interesting language, even in its infancy.

Other AI-oriented languages to explore, as described in the insightful post by James Thomason in VentureBeat, "Mojo Rising: The resurgence of AI-first programming languages" include Bend and JAX.

 

Note: the text was checked in ChatGPT.