Classes¶

Classes allow to combine functions with data. To exemplify classes we will make a class that is used to hold geometries and calculates the area and circumference

In [1]:
import ROOT
In [2]:
import fortranmagic
%load_ext fortranmagic
import os
import sys

import numpy as np

if sys.platform.startswith("win"):
        # Depends of system, python builds, and compilers compatibility.
        # See below.
    f_config = "--fcompiler=gnu95 --compiler=mingw32"
else:
        # For Unix, compilers are usually more compatible.
    f_config = ""

    # Disable only deprecated NumPy API warning without disable any APIs.
f_config += " --extra '-DNPY_NO_DEPRECATED_API=0'"

%fortran_config {f_config}
%load_ext rpy2.ipython
New default arguments for %fortran:
	 --extra '-DNPY_NO_DEPRECATED_API=0'
Error importing in API mode: ImportError('/home/k/miniforge3/envs/ROOT/lib/python3.14/site-packages/_rinterface_cffi_api.abi3.so: undefined symbol: R_ClosureEnv')
Trying to import in ABI mode.

Object oriented programming¶

c++¶

if you use a baseclass pointer as we want to do in 7.1 and 7.2. Then you need to use a default virtual destructor in the base class.

In [3]:
%%cpp -d
#include <iostream>
#include <memory>

using namespace std;
class Geometry {
public:
    Geometry() {}
    virtual ~Geometry() = default;//the default constructor for base class is needed
    virtual double getArea() = 0;
    virtual double getCircumference() = 0;
};

class Rectangle1 :public Geometry {
public:
    Rectangle1(double width, double height) :width(width), height(height) {//when exist c calles the standard constructor of the parent class
    }
    //virtual ~Rectangle() {}//destructor if not used leave it out
    virtual double getArea() override {
        return width * height;
    }
    virtual double getCircumference() override {
        return 2 * width + height;
    }
protected: //can be only accessed by this class and children

private: //can only be accessed by this class
    double width;
    double height;
};

class Square :public Rectangle1 {
public:
    Square(double l) :Rectangle1(l, l) {
    }
};

class Disc :public Geometry {
public:
    Disc(double r) :r(r) {
    }
    virtual double getArea() {
        return 3.1415926 * r * r;
    }
    virtual double getCircumference() {
        return 2 * 3.1415926 * r;
    }
protected:
    double r;
};
In [4]:
%%cpp
//void main()
{
    Rectangle1 r(2.1, 3.1);
    Square s(3.1);
    Disc d(2.1);

    cout << r.getArea() << endl;
    cout << r.getCircumference() << endl;
    cout << endl;

    cout << s.getArea() << endl;
    cout << s.getCircumference() << endl;
    cout << endl;

    cout << d.getArea() << endl;
    cout << d.getCircumference() << endl;
}
6.51
7.3

9.61
9.3

13.8544
13.1947

c¶

In [5]:
%%cpp -d

#include <math.h>

typedef struct {
    double width;
    double height;
} Rectangle2;

typedef struct{
    double r;
} Disc2;

void Rectangle_init(Rectangle2 *_this,double width, double height){
    _this->width=width;
    _this->height=height;
}

double Rectangle_getArea(Rectangle2 *_this){
    return _this->width*_this->height;
}
double Rectangle_getCircumference(Rectangle2 *_this){
    return 2*_this->width+_this->height;
}

void Square_init(Rectangle2 *_this,double r){
    _this->width=r;
    _this->height=r;
}

double Square_getArea(Rectangle2 *_this){
    return Rectangle_getArea(_this);
}
double Square_getCircumference(Rectangle2 *_this){
    return Rectangle_getCircumference(_this);
}

void Disc_init(Disc2 *_this,double r){
    _this->r=r;
}

double Disc_getArea(Disc2 *_this){
    return M_PI*_this->r*_this->r;
}
double Disc_getCircumference(Disc2 *_this){
    return 2*M_PI*_this->r;
}
In [6]:
%%cpp

//void main(){
Rectangle2 r;
Rectangle2 s;
Disc2 d;

Rectangle_init(&r,2.1,3.1);
Square_init(&s,3.1);
Disc_init(&d,2.1);

printf("%lf\n",Rectangle_getArea(&r));
printf("%lf\n",Rectangle_getCircumference(&r));
printf("\n");

printf("%lf\n",Square_getArea(&s));
printf("%lf\n",Square_getCircumference(&s));
printf("\n");

printf("%lf\n",Disc_getArea(&d));
printf("%lf\n",Disc_getCircumference(&d));

//}
6.510000
7.300000

9.610000
9.300000

13.854424
13.194689

javascript¶

In [7]:
%%js //the next line is only necessary in jupyter notebooks
element.setAttribute('style', 'white-space: pre;');console.log=function(text){element.textContent+=text+"\n"}

class Geometry{
    constructor() {
        if (this.constructor == Geometry) {
            throw new Error("Abstract classes can't be instantiated.");
        }
    }
    getArea(){
        throw new Error("Method 'getArea()' must be implemented.");
    }
    getCircumference(){
        throw new Error("Method 'getCircumference()' must be implemented.");
    }
}

class Rectangle extends Geometry{
    constructor(width, height){
        super();
        this.width=width
        this.height=height
    }
    getArea(){
        return this.width*this.height;
    }
    getCircumference(){
        return 2*this.width+this.height;
    }
}

class Square extends Rectangle{
     constructor(l){
        super(l,l);
    }
}

class Disc extends Geometry{
     constructor(r){
        super();
        this.r=r;
    }
    getArea(){
        return Math.PI*this.r*this.r;
    }
    getCircumference(){
        return 2*Math.PI*this.r;
    }
}

let r=new Rectangle(2.1,3.1)
let s=new Square(3.1)
let d=new Disc(2.1)

console.log(r.getArea())
console.log(r.getCircumference())
console.log("")

console.log(s.getArea())
console.log(s.getCircumference())
console.log("")

console.log(d.getArea())
console.log(d.getCircumference())
console.log("")

Python¶

In [8]:
from abc import ABC, abstractmethod
import math

class Geometry(ABC):
    @abstractmethod
    def getArea(self):
        pass
    def getCircumference(self):
        pass

class Rectangle(Geometry):
    def __init__(self,width, height):
        self.width=width
        self.height=height
        super().__init__()
    def getArea(self):
        return self.width*self.height
    def getCircumference(self):
        return 2*self.width+self.height

class Square(Rectangle):
    def __init__(self,l):
        super().__init__(l,l);

class Disc(Geometry):
    def __init__(self,r):
        self.r=r
        super().__init__();
    def getArea(self):
        return math.pi*self.r*self.r
    def getCircumference(self):
        return 2*math.pi*self.r;

r=Rectangle(2.1,3.1)
s=Square(3.1)
d=Disc(2.1)

print(r.getArea())
print(r.getCircumference())
print("")

print(s.getArea())
print(s.getCircumference())
print("")

print(d.getArea())
print(d.getCircumference())
print("")
6.510000000000001
7.300000000000001

9.610000000000001
9.3

13.854423602330987
13.194689145077131

In [12]:
%%R
square <- function(d) {
  return(d * d)
}

print(square(22))
[1] 484
In [13]:
%%R

library(R6)

Geometry <- R6Class(
  "Geometry",
  public = list(
    getArea = function() {
      stop("Method 'getArea()' must be implemented.")
    },
    getCircumference = function() {
      stop("Method 'getCircumference()' must be implemented.")
    }
  )
)

Rectangle <- R6Class(
  "Rectangle",
  inherit = Geometry,
  public = list(
    width = NULL,
    height = NULL,
    initialize = function(width, height) {
      self$width <- width
      self$height <- height
    },
    getArea = function() {
      self$width * self$height
    },
    getCircumference = function() {
      2 * self$width + self$height
    }
  )
)

Square <- R6Class(
  "Square",
  inherit = Rectangle,
  public = list(
    initialize = function(l) {
      super$initialize(l, l)
    }
  )
)

Disc <- R6Class(
  "Disc",
  inherit = Geometry,
  public = list(
    r = NULL,
    initialize = function(r) {
      self$r <- r
    },
    getArea = function() {
      pi * self$r * self$r
    },
    getCircumference = function() {
      2 * pi * self$r
    }
  )
)

r <- Rectangle$new(2.1, 3.1)
s <- Square$new(3.1)
d <- Disc$new(2.1)

print(r$getArea())
print(r$getCircumference())
print("")

print(s$getArea())
print(s$getCircumference())
print("")

print(d$getArea())
print(d$getCircumference())
print("")
[1] 6.51
[1] 7.3
[1] ""
[1] 9.61
[1] 9.3
[1] ""
[1] 13.85442
[1] 13.19469
[1] ""

Why R6 for this translation¶

R does not have Python-style classes and inheritance in base syntax. The R6 package is used here because it gives a class-based object model with methods, inheritance, and super$... calls that closely matches the Python example.

Fortran 95¶

in Fortran 95 you can use structures. https://en.wikibooks.org/wiki/Fortran/structures Since Fortan 2003 you can use classes with polymorphism: https://annefou.github.io/Fortran/classes/classes.html

In [ ]:
%%fortran 
module test_m
    implicit none
    private
    public test_type
    type test_type
        integer :: i
    contains
        procedure, nopass :: print_hello
        procedure         :: print_int
    end type
contains
    !> do not process type specific data => nopass
    subroutine print_hello
        print *, "hello"
    end subroutine

    !> process type specific data => first argument is "this" of type "class(test_type)"
    !! use class and not type below !!!!
    subroutine print_int(this)
        class(test_type), intent(in) :: this

        print *, "i", this%i
  end subroutine
end module

! program and subroutine exchanged due to jupyternotebook

! program main
subroutine main()
    use test_m
    implicit none
    type (test_type) :: obj

    obj%i = 1           ! is like obj.i in c++,c and javascript and python
    call obj%print_hello
    call obj%print_int
! end program
end subroutine main
In [ ]:
main()