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 hourspycessor_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 / 0baygon_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
.