Getting Started

Any Fython package must be part of a Python package. This way Fython can leverage Python automatic package detection system.

In this tutorial, we will create a Fython package stat that illustrates most of Fython Functionalities.

The code for this tutorial is in fython/example/stat.

The Host Python package

First, we create the folder structure for the Python package where our Fython package will reside

stat/

  setup.py

  stat/

    mean/
      mean.fy
      mean_test.py

    variance/
      variance.fy
      variance_spec.fy
      variance_test.py

    newspaper/
      print.fy
      print_test.py
      read.fy
      read_test.fy

    class/
      class.py
      class_test.fy

    imports/
      __init__.fy
      imports.fy
      imports_test.fy

    pycessor/
      pycessor.fy
      pycessor_test.py
  
    baygon/
      baygon.fy      
      baygon_test.py
      force_test.py
      release_test.py
      verbose_test.py
      
    backus/
      fortran.py
      so.py
language:shell

The content of setup.py is

from setuptools import setup, find_packages

setup(
	packages = find_packages(),
	name = 'stat',
) 
language:python

We register the package in Python with

python3 setup.py develop

Mean

The Mean module illustrate the basic semantic of Fython. We write three kind of mean functions.

In Fython, the arguments of a function must have the intent attribute in, out or inout.

mean.fy

real cons boltzman = 10 # a constant

int plank = 8 # a global variable

def cube_mean:
  real in: x y z
  real out r

  r = x + y + z
  r /= 3 
  r **= 3

  ## 
    Yes, Fython has all the augmented assignment operators (+=, -=, *=, /=, **=).
    Yes, this is a multiline comment.
  ## 

def moving_mean:
  int in n 
  real inout x(n)
  int i 

  # let's forget about the edges
  for i in [2, n-1]:
    x[i] = sum( x[i-1:i+1] ) / 3

  if x[1] > 5:
    print 'x[1] is bigger than 5: x[1]={:x[1]}'
    print """
      All the values in x are:  
      {v:x}
    """

  ## 
    The Python vector x will be modified in-place.

    The print format mini-language is that of Python, plus a few additions.
    The v directive eases the printing of vector.

  ## 

def string_mean:
  char(3) in x(3)
  real out r
  int: i j 

  # a mean on strings? creative.
  for i in [1, 3]:
    for j in [1, 3]:
      r += ichar( x[i][j:j] )

  r /= 10

  r += boltzman + plank # essential constants for any calculation, aren't they?

  ##
    The ichar function gives the ascii code of a character.

    The ichar function is an intrinsic Fortran function.
    Yes, you have access to all of them.
  ##

Testing Mean

Let’s test drive our mean module

mean.py

from fython import *

m = load('.mean')

# accessing a global variable
p = m.plank()

# setting a global variable
p[:] = 6

# cube mean
print('cube mean')
x = Real(value=1)
y = Real(value=2)
z = Real(value=3)
r = Real()

m.cube_mean(x, y, z, r)

print(' result in python', r[:])

# moving mean
print('\nmoving mean 1')

n = Int(value=5)
x = Real(value=[1, 2, 3, 4, 5])
m.moving_mean(n, x)

print('  result in python', x[:])

# an other time
print('\nmoving mean 2')
x[0] = 10
x[1] = 20
m.moving_mean(n, x)

print('  result in python', x[:])

# string mean
x = Char(size=3, value=['abc', 'xyz', 'ijk'])
m.string_mean(x, r)

print('string mean', r[:])
language:python

We did not attempt to access the constant bolztman. This is because variable defined with as constant are not accessible from fython.

That’s it for the mean module. Now you can write a standalone Fython module. In preparation for those days where you need more, let’s see the variance module.

Variance

In Fython the specification and the implementation of a function or a class can be separated. Similar to Python, we start by working on our implementation of the variance function, deferring the spec to the variance_spec import

variance.fy

import .variance_spec(*)

def variance:
  cube_mean(x, y, z, u)
  r = x - u + maxwell # this is the official formula on planet kawabunga

When we are satisfied with our algorithm, we write the specification

variance_spec.fy

import stat.mean.mean(*)

def variance:
  real in: x y z
  real out r
  real u
  int cons maxwell = 8

The specification contains all the imports and the definitions we need in variance.fy. We test with

variance_test.py

from fython import *

m = load('.variance')

# cube mean
print('calling variance')
x = Real(value=1)
y = Real(value=2)
z = Real(value=3)
r = Real()

m.variance(x, y, z, r)

print(' result in python', r[:])

What we did above is called an implicit spec interpolation. We can also do explicit spec interpolation with the spec modifier

explicit_spec_interpolation.fy


def f:
  real in x
  real out y
  y = x + 2

def spec(f) g:
  y = x * 10

We test with

explicit_spec_interpolation_test.fy

from fython import *

m = load('.explicit_spec_interpolation')

print('calling f')
x = Real(value=1)
y = Real()
m.f(x, y)

print(' result in python', y[:])

print('calling g')
x = Real(value=1)
y = Real()
m.g(x, y)

print(' result in python', y[:])

Newspaper

Any good bug is remove by several usage of the print statement. With Fython the print statement output can be standard out, a file on the Python path, or a path on the file system

print.fy

int i
real x(10)

for i in [1, 10]:
  x[i] = i
  print 'x({:i}) = {:x[i]}'

print .x_final 'x is {v:x}'

# string to specify the path
print './x_transformed.out' """
  x+10 is: {vc:x+10}
  x-10 is : {vc:x-10}
  """

# Yes, Python multiline string

The Python url .x_final tells Fython to create a file ‘x_final.out’ in the same directory than the Fython module. A string can also be used to specify the path.

print_test.fy

import os
from fython import *

os.system('rm *.out') # cleaning any previous run

m = load('.print', force=1)

print('\nfinal x')
print(open('./x_final.out', 'r').read())

print('\nx transformed')
print(open('./x_transformed.out', 'r').read())

Since all bugs originates from data, we use the read statement to keep ourselves busy

read.fy

int: x y z
int u(3)

print .data mode(w) '1, 2, 3' # explicitly creating a new file with the mode modifier

read .data: x y z

print 'x {:x}'
print 'y {:y}'
print 'z {:z}'

# vectors too
read .data u
print 'u is {v:u}'

The possible mode for printing to files are mode(a) for appending, and mode(w) for overwriting. The default mode is appending. We test with

read_test.py

from fython import *

m = load('.read')

Class

Similar to Python, the first argument to any class method must be self. You can use any name for the self argument, the only rule is that the first argument is the self argument.

class.fy

class Magnetism:
  real maxwell = 8
  real pointer tesla(:) => null()
  real allocatable bohr(:)

  def energy:
    self in # first argument is always self
    real res r
    r = self.maxwell + sum(self.tesla)
      
  def pget courant:
    self in
    real res r
    r = self.maxwell + sum(self.bohr)

  def pset courant:
    s inout # any name is allow for the self argument
    real in value
    s.bohr = value

real target x(10) = 1
Magnetism m

m.tesla => x # pointer assignment
print 'energy {:m.energy()}'

# happy allocation
alloc m.bohr(8) 
print 'bohr {v:m.bohr}'

# getter/setter
m.courant = 4.
print 'courant {:m.courant}'

# happy deallocation
dealloc m.bohr

Inheritance and spec interpolation are also possible. See the language section for more details.

class_test.py

from fython import *

m = load('.class')

Imports

Three kinds of imports are possible in Fython. With a star import all the names are imported. With an aliased namespace import, the names of the target module are acessible through the alias. With a slice import, only the stated names are imported, possibly aliased.

imports.fy

import ..mean.mean(*) 

import ..variance.variance = v

import ..newspaper.print(
    i = counter, # aliasing name i 
    x, # no alias 
)

import ..imports = package_import # when the directory as a __init__.fy file

print 'mean boltzman {:boltzman}'

counter = 1
v.variance(1., 2., 3., x[counter])
print 'variance {:x[counter]}'

# triggering package main code
package_import.main()

imports_test.py

from fython import *

load('.imports')

It is possible to import a directory when it contains a __init__.fy file. This is usefull as your package grow. The content of our __init__.fy for the imports directory is

__init__.fy

import iso_c_binding(*)
print 'package export'

When importing a package, the main code of the package needs to be triggered manually. The main code of package is any code that is not part of a function and that is not a specification (variable, class and interface specification).

Pycessor

A Pycession is the fythonic term for Python preprocessor interpolation.

Pycessions can be used to define compile time constant or to avoid writing lines of code that are similar.

Since any Python code is allowed in pycession, a more exotic usage can be to run a Makefile script that produce a shared library used in your module.

For clarity, we write python imports together with the other fython imports at the top of a module.

pycessor.fy

import os
import numpy 
# or any python module on your path

real pi = |numpy.pi| # atan what? no more

# lazy initialization of exotic random variate
real v(1e3)

|
  x = numpy.random.uniform(0, 1, 1000) 

  for i in range(1000):
    write('v[i] = {:f}'.format(x[i]))
|

# running a make file
|os.system("echo 'compiling boost ... done'")| # for real? isn't it 2 hours

pycessor_test.py

from fython import *

load('.pycessor')

When a pycession is an expression such as 1+2 or f(1), its returned value is inserted in your fython code. When a pycession contains several lines, you need to explicitly state wich string to include in your code. The special pycessor function write serves this purpose.

Baygon

When an error occurs in your code, Fython will usually detect it and produce a stack trace.

baygon.fy

def boom:
	real x
	subboom(x)	

def subboom:
	real inout x
	x = 1 / 0

baygon_test.py

from fython import *

m = load('.baygon')

# shocking hazard
m.boom()

If Fython error detection system is overriden by your compiler or simply fails, you can use the verbose function of a Fython module, The verbose function tells Fython to print the location of every line of code that are run. You can then easily spot that wonderfull bug.

verbose_test.py

from fython import *

m = load('.baygon')

m.verbose()

m.boom()

Sometimes Fython may fails to detect changes in your code since the last compilation. If that happens, simply load your module with the force option to trigger a refresh of the Fython cache

force_test.py

from fython import *

m = load('.baygon', force=1)

m.boom()

When your code is bug free, the release keyword tells Fython to run your code at full Fortran speed, with all optimizations enables

release_test.py

from fython import *

m = load('.baygon', release=1)

m.boom()

print('a bad idea here as the division by zero will go unseen')

Template

Function template are usefull to create overload of a function

function.fy

def temp f:
  T in x
  print 'x is {:x}'

def f_real = f(T=real)
def f_int = f(T=int)


f_real(1.5)
f_int(1)

function_test.py

from fython import *
load('.function')

The template function needs to me marked with the modifier temp. The principle is the same for class

class.fy

class temp Atom:
  T x

  def lt:
    self in
    self in other
    bool res r
    r = self.x > other.x

def Electron = Atom(T = real)

Electron e
Electron p

e.x = 10
p.x = 1

print '{:e.lt(p)}'

class_test.py

from fython import *
load('.class')

You can also templatize a whole package

package.fy

import .class(*)
import .quicksort(*)
||type_provider=.class, target_class=Electron||

Electron e(10)
int i

for i in [10, 1, -1]:
	e.x = i

quicksort(e)


When the module quicksort and all of its dependency is imported any occurence of type_provider and target_class will be replaced by the package interpolation provided at the import statement.

The content of quicksort is

quicksort.fy

import asis type_provider(target_class=T)

def quicksort:
  T dimension(10) in x
  int: i r
  for i in [1, 9]:
    r = x[i].lt( x[i+1] )
    print 'i {:r}'

To prevent any package interpolation to happen during the import of type_provider, the import has the asis modifier.

We test with

package_test.py

from fython import *
load('.package')

Backus

You can import a Fortran module in Fython. For this example, we use the writer function of Fython.

fortran.py

from fython import *

writer("""
.brent.f90
  module brent
    integer, parameter :: tolerance = 1e-3

  contains

    function root(x) result(r)
      real, dimension(10) :: x
      real :: r
      integer :: i

      r = 0
      
      do i = 1, 10
        r = r + x(i)
      end do

    end function

  end module


.consummer.fy

  import .brent(*)

  real x(10) = 1

  print 'brent says {:root(x)}'
""")

load('.consummer')

The writer function turns a Python script into a playground for languages. The function creates the file .brent.f90 in the current directory. The content of the file is the indented content after the file name.

What we did is that we created the file brent.f90 and the file consummer.fy. The Fython consummer module imports the Fortran brent module. We then test the Fython consummer module with the load function.

we can also imports shared library in Fython

so.py

import os
from fython import *

writer("""
.ritchie.f90
  module ritchie
    real, bind(c) :: c = 10

    contains

      subroutine compile() bind(c)
        write(*, *) 'c is ', c
      end subroutine

  end module

.backus.fy
  import .ritchie(*)

  c = 20

  compile()
""")

os.system('gfortran ritchie.f90 -shared -fpic -o ritchie.so')

load('.backus')

In ritchie, we use the bind(c) attribute to emulate the standard naming convention in shared library. We then compile ritchie into a shared library with gfortran. After that we load the Fython module backus into Python. The backus module then places a class to compile.