Language Reference¶
This section describes in details the Fython language.
In code examples
- shell code is indicated with a dollar
$
- Python code is indicated with triple quote
>>>
- Fortran code is indicated with triple bangs
!!!
When no mention of the language is made, assume it is Fython.
The term shared library is often simply referenced by so
.
Syntax¶
In Fython a statement is formed by a keyword, modifiers and target
keyword modifier* target
The keyword is the action performed by the statement. The modifiers mutate the default behavior of the actions, The action dictated by the keyword and modifiers is then applied to all the target.
real pointer x
real pointer: x y z
real pointer: x, y, z
real pointer:
x
y
x
real pointer:
x, y, z
a, b, c
The comma is necessary for target that are non atomic
real cons: x=1, y=2, z=3
The only exception to the statement construction are in-place assignment operation
real: x y
x = y + 1
x += 10
Modifiers are also called attributes.
Any modifier that is allowed in Fortran can also be used in Fython
real pointer x
int allocatable contiguous y
Arrays are defined by indicating their dimensions
real: x(10) y(1:10, 0:5)
Array elements are accessed with the slice notation
x[1:6] => y[2, :]
You can initialize an array or use an array in place with the bracket notation
real x(3) = [1, 2, 3]
f([1, 2, 3])
Imports¶
Three kinds of imports are possible in Fython. Aliased namespace import, star import and slice import.
import pca
import stat.mean = m
import stat.variance(*)
import stat.newspaper(x, y=z)
When the module url is composed of only one name,
such as the pca
imports.
The statement is equivalent to
import pca = pca
With an aliased namespace import, the object in the
module are access with a dot .
import stat.mean = m
m.cube_mean(1, 2, 3)
With a star import all the object of the imported module are avalaible
import stat.mean(*)
cube_mean(1, 2, 3)
With a slice import only the stated names are imported, optionally aliased
import stat.mean(cube_mean, char_mean= cm)
cube_mean(1, 2, 3)
char_mean('abc', 'def', 'ijk')
For all imports it is necessary that you have write permission to the directoty that contains the imported module. This is because Fython needs to maintain build products in the same directory. The only exception to this rule are shared library import, as no build product needs to be maintained in these case.
The url of a module is its qualified Python name
import numpy.random.uniform
This implies that for a file to be importable, it must be on your Python path.
The first way to put a file on your Python is to create a host Python package and registering it to Python with a setup script
$ python3 setup.py develop
See the Getting Started section for the details.
The other method is to modify your path directly
#>>>
import sys
import fython
sys.path.append('/opt/nag')
m = fython.load('random.sobolev')
In a Python url, the file extension cannot be part of the url. You should then take into account the following resolution order. In a compile time import, Fython first search for
- a Fython file (
.fy, __init__.fy
) - a Fortran file (
.f90, .f03, ...
and many other) - a So File (
.so
)
In a pycessor time import, Fython search for
- a Python file (
.py, __init__.py
).
For print and read statement that uses an url,
the assumed extension is .out
.
For Fortran, only star and slice imports are allowed. For So only star imports are allowed.
Usefull modifiers of the import statements are
import asis payoff_defs_provider(*)
import noforce mkl.include.mkl_vsl(*)
The asis
modifier prevents any package interpolation to happen during the import.
This is usefull when designing a packaged meant to be a template.
See the Template section.
The noforce
modifier prevents a forcefull recompilation.
If the module alreasy exists, it is not recompile,
even if it was loaded with load(url, force=1
.
This is usefull to avoid recompilation of heavy module,
that anyway never changes.
Declaration¶
The declarations order follows Fortran convention. Variables, Classes and interface declaration shoud appear first within a scoping unit, then functions.
A scoping unit is the entire module, the body of a class, or the body of a function.
For this release, nested class or nested function are not supported.
Operator¶
Fython has the augmented assignment operators, the logical operator, the bitwise operators, and the pointer operator.
x += 1
x -= 1
x *= 2
x /= 2
x <<= 1
x &= 1
x ^= 1
x |= 1
x >>= 1
x < <= == != => > y # this is an invalid syntax
x and y or b not c
x >> 1 + y << 4
x => y # pointer
The min and max operator are often convenient.
x ++= y # x = max(x, y)
x --= y # x = min(x, y)
Variable Declaration¶
In Fython the elementary types have a Python flavor
real x
int y
char z
bool a
complex b
Constant are declared with the attribute cons
.
real cons x
Classes are instantiated by using there name
class A:
pass
A a
String variable can be assign a value with a multiline string
char(100) x
x = """
extra leading space
at the beggining remove
"""
x = '''
triple quote
'''
x = 'single line'
x = "double quote"
Any newline or tab character in the string will be honored.
For procedure argument, use the proc
modifier
Coarray¶
A coarray is defined by specifying its codimension in bracket
real x[*]
real y(10)[*]
A coarray is accessed with the slice notation
x = 1 # this_image() x
x[2] = 2 # x on image 2
y[1] = 1 # this_image() y
y[:][2] = 1 # y on image 2
To use coarray in Fython, you need to set the compiler to use with the
set_compiler
function
#>>>
from fython import *
set_compiler(
cmd = 'ifort',
prefix = '',
infix = '_mp_',
suffix = '_',
debug = '-coarray -coarray-num-images=5',
release = '-coarray -coarray-num-images=5',
error_regex = 'error:',
)
m = load('.coarray_test')
Function¶
A function is declared with the keyword def
def f:
real in x
real res r
For a variable to be recognized as an argument, it must have one of the intent modifier
in
out
inout
The return value of a function must be indicated with the modifier
res
When no argument has the modifier res
, the function has no return value.
You can separate the implementation and the specification of you function with spec interpolation
def pure f:
real in x
real res r
def f:
r = x + 3
The spec for f
can be in the same module or originate from an import.
You can also explicitlye specify the spec to use with the spec
.
def elemental f:
real in x
real res r
def spec(f) g:
r = x + 10
def spec(f, g) h:
pass # multiple spec parent
You can use the inline
instruction to include verbatim the definition
of one function into another
def f:
x += 1
def g:
inline f
The modifier debug
or release
can be use to specify in which mode to include
the code.
This is usefull for conditional inclusion of logging code for example.
inline debug f
inline release f
When no modifier is specified, the code is inlined in all compilation mode.
Automatic argument completion dispense for the need to write all the arguments of a function provided the name of the argument is the same than a name in the current scope.
real x = 1
real y = 10
def f:
real in x
real in y
f(y=1.) # x added automatically
f() # both x and y added
Automatic arguments completion works for keyword arguments call only. It cannot be mixed with positional argument code.
# with f as above
f(y) # not supported
f(y=1.) # supported
If a function should not be compiled,
used the noprod
modifier.
This is usefull when the function is only used as a spec provider,
and that the function should not be compiled.
def noprod f:
real x(n) # n is not defined, this would give an error if compiled
def spec(f) g:
int in n
x += 1 # definition of x is provided by the spec of f
The noprod
modifier is not inherited
during a spec interpolation.
So, only f
is not compiled.
To not compile g
, explicitly use the modifier noprod
.
to help distinguish between pure and non-pure function used the modifiers
pure
and sidef
def pure f:
pass
def sidef g:
pass
The modifier sidef
has no effect during compilation.
The modifier clearly states the intent of the coder:
that the function g
has side-effects,
and cannot be marked as pure.
Interface¶
An interface is declared with the interface keyword
interface:
def f:
real in x
real res r
To facilitate the definition of C procedure
the modifier iso(c)
can be used
interface:
def iso(c) f:
real in x
The iso(c)
modifier can be used on any function declaration
and is not restricted to interface declaration.
The effect of the modifier is to produce
!!!
subroutine f(x) bind(c)
use iso_c_binding
real, intent(in) :: x
end subroutine
Class¶
A class is defined with the class
keyword.
class A:
real x
def f:
self in
real res r
r = self.x
def pget y:
s in
real res r
r = s.x
def pset y:
s inout
real in value
s.x = value
The first argument of any class method must be the self
argument.
The name used for the self
argument can be anything.
Above we used s
instead of self
for the getter and setter.
Getter and Setter are defined with the pget
and pset
modifiers.
Inheritance is indicated with parenthesis after the class name
class C(B, A):
pass
You can separate the specification and the implementation of a class with the spec interpolation
# spec.fy
class A:
real x
def pure f:
self in
real res r
# code.fy
import spec(*)
class A:
def f:
r = self.x + 10
You can explicitly state the spec to use with the spec modifier
class A:
real x
class spec(A) B:
pass
You can use the inline
statement to include verbatim the definition of
a class or a function into your class
class A:
real x
class B:
inline A
Allocation¶
Memory is allocated and deallocated with the alloc
and dealloc
keyword
alloc: x y(n) z
alloc(n):
x
y
z(m)
dealloc: x y z
When the keyword alloc
has an argument, it is used
as the default size for any variable where no size is specified.
Control Flow¶
Fython has if
, for
, fop
, while
and where
statement
if x < 1:
y += 1
elif x < 1:
y += 2
else:
y = 0
The third argument in the bracket of a for
statement is the step size
for i in [1, 2]:
r += x[i]
for i in [0, 10, 2]:
r += x[i] # 0, 2, 4, ...
The fop
loop is a parallel for loop. The Fortran equivalent is a do concurrent loop.
fop i in [1, 2]:
r += x[i]
The while loop is
while x < 10:
x += 1
The where statement is
where x > 1:
x = y
elwhere x < 1:
x -= 1
else:
x = 0
Print¶
Printing to the console needs no modifier
print 'x {:x}'
When an url is used the file extension is assumed to be .out
print .simulation 'x {:x}'
A file system path can also be used
print './simulation.out' 'x {:x}'
You can then choose any extension you want.
You can print to a character variable when its name does not contain a dot
char(100) r
print r 'x {:x}'
If the name contains a dot use the unit
modifier
print unit(atom.name) 'proton'
The unit modifier can also be used if you opened a file by yourself
int u = 10
open(unit=u, file='./simulation.out')
print unit(u) 'x {:x}'
If you use a number, the unit modifier is not necessary
print 10 'x {:x}'
You can control the mode in which the file is written to during a print statment with the mode modifier
print mode(a) 'x {:x}'
print mode(w) 'overwrite any previous content'
The default mode is a
.
To continue printing on the same line, use the c
modifier
print c 'start '
print c ' on same line'
print ' ending the line'
print 'this one on a new line'
The format mini-language is that of Fortran plus several additions
print """
{:x} : general format used
{f5.2:x} : float format
{i5:x} : int format
{v:y} : vector format: [1, 2, 3, ]
{vc:y} : vector content format: 1, 2, 3,
{va:y} : vector format: array([1, 2, 3, ]) ; usefull for python post-processing
"""
The additions are the v
, vc
and va
formats that facilitates the printing of vectors.
Format that helps printing to the JSON format are also avalaible. The JSON formats avoid typing the name of a variable twice, and helps to deal with comma.
print """
{jn:x} : json no comma before: "x": x
{j:x} : json with comma before: ,"x":x
{jv:x} : json vector: "x":[1, 2, 3]
{jvn:x} : json vector no comma before: ,"x":[1,2,3]
{j_tag:x} : json with specified tag: ,"tag":x
{jv_tag:x} : json vector with specified tag: ,"tag":[1,2,3]
{jn_tag:x} : json no comma before with specified tag: "tag":x
{jvn_tag:x} : json no comma before vector with specified tag: "tag":[1,2,3]
"""
In a Typical printing with JSON format, the first element is explicitly specified without leading comma, then the remaining elements are added, prepended by a comma.
print """
[
{ } # first element no comma
,{ } # any addition prepended by a comma
,{
{jn:x} # no comma
{j:y} # prepended by a comma
}
]
"""
If a print statement is used only in debug mode, use the xip
instruction
xip 'printed only in debug mode'
print 'printed in both debug and release mode'
The xip
takes the same modifiers than the print
instruction.
The xip
instruction is usefull for debugging.
Read¶
You can read a file by specifying its url.
The extension is then assumed to be .out
read .data: x y z
You can specify a path in the file system with a string
read './data.out': x y z
You are then free to use any extension you want.
The read statement in Fython supports csv-like formats automatically. In Fortran, this is a called a list-directed read. For this release, the other kind of read statement are not supported.
You can use the name of variable that does not contains a dot for the read source
char(100) data
read data: x y z
If the name of the variable contains a dot, use the unit
modifier
read unit(mendeleiv.table): x y z
You can read into a vector or any other variable
read .data:
x[:, i]
atom.name
FyTypes¶
Only three kinds of data type can travel back and forth between Python and Fython
#>>>
from fython import *
x = Real()
y = Int()
z = Char(size=100)
m = load('.mean')
m.f(x, y, z)
Only function that have no return value can be called from Python.
In Fortran term, f
must be a subroutine.
Fython can modify in-place the element send by Python. The change will be seen in Python. The same is true in Python. In change made to a fytype in Python, will be seen in Fython.
The value of a fytype is always accesses with a slice, wheter the fytype is a scalar or a vector
#>>>
x = Real()
y = Real(value=[1, 2, 3])
x[:] = 9
y[:1] = 10 + x[:]
The three Fytypes all have the optional arguments size
, shape
and ``value.
They are shown below with their default value
#>>>
x = Real(size=4, shape=[], value=None)
y = Int(size=4, shape=[], value=None)
z = Char(size=100, shape=[], value=None)
An empty list indicates a scalar. A vector is defined by specifying the number of element in each dimension.
#>>>
x = Real(shape=[10, 10])
The argument value
is used to connect a fytype to a Python object.
Any change made to the fytype will be reflected in the Python object
#>>>
x = [1, 2, 3]
y = Real(value=x)
To access a global variable, use its name
#>>>
from fython import *
m = load('.mean')
tolerance = m.tolerance()
Fython will automatically detects the type of the variable and use the default fytype initializer above. You can specify the variable specification yourself
#>>>
x = m.x(size=8, shape=[10])
Once setted the shape of fytype cannot change. This limitation can be overcome by letting Python and Fython share informations
#>>>
m = load('.mean')
m.compute()
n = m.result_size()
result = m.result(size=n[:])
Callback¶
In Fython, it is possible to call a Python function. Such callable function are called callback. The trick is to pass the address of the callback. This address is a simple integer, so, in fython, we cast it to a function pointer.
The code below shows how to transfer integer, real and arrays of these two.
The Fython code goes as follows.
import:
iso_c_binding(*)
interface:
def iso(c) py_fct:
int(c_int) value in:
xint
real(c_float) value in:
xreal
int(c_int) value in:
nintv
nrealv
int(c_int) in:
xintv(*)
real(c_float) in:
xrealv(*)
def f:
int(8) in:
py_fct_pointer_int
c_funptr:
py_fct_pointer
proc(py_fct) pointer:
pyf
int:
x
xv(2)
real:
y
yv(2)
print 'fython start: {:py_fct_pointer_int}'
py_fct_pointer = transfer(py_fct_pointer_int, py_fct_pointer)
c_f_procpointer(py_fct_pointer, pyf)
print 'pyf called'
x = 1
y = 0.5
xv = 10
yv = 5.5
pyf(x, y, 2, 2, xv, yv)
print 'fython exit'
The Python goes like this
#>>>
from mttc import *
from ctypes import *
import numpy as np
m = load('.pycall', force = 2)
def f(xint, xreal, nintv, nrealv, xintv, xrealv):
array_type = c_int*nintv
addr = addressof(xintv.contents)
xintv = np.array(array_type.from_address(addr), copy=0)
array_type = c_float*nrealv
addr = addressof(xrealv.contents)
xrealv = np.array(array_type.from_address(addr), copy=0)
print('hello from python', xint, xreal, nintv, nrealv, xintv, xrealv)
c_fun_dec = CFUNCTYPE(None, c_int, c_float, c_int, c_int, POINTER(c_int), POINTER(c_float))
c_fun = c_fun_dec(f)
c_fun_int = cast(c_fun, c_void_p).value
m.f(
py_fct_pointer_int = Int8(c_fun_int),
)
Load Function¶
The python api side load
function has the following default arguments
#>>>
load(
fython_module_url,
force = 0,
release = 0,
)
The force
argument is used to force a refresh of the Fython dependency cache.
The release
argument indicates wheter to run in debugging mode (0
),
or release mode (1
).
In debug mode, Fython tries to detect errors.
In release mode, all the compiler optimization are enabled.
When a Fython module is loaded, you access its objects with there name.
#>>>
from fython import *
m = load('.mean')
x = m.global_variable()
y = m.global_variable_with_explicit_shape(shape=[10])
# function call
m.mean(x, y)
# keyword call
m.mean(
offset = x,
vec = y,
)
When a global variable is accessed, Fython will automatically determine its fytype. For function however, only minimal arguments consistency is made. You should make sure that you invoke your Fython function with the right argument order and the right fytype. The argument order consistency can be alleviated by using a keyword argument call. Fython will then call your Fython function with the argument in the right order.
Pycessor¶
Pycession instruction are specified within bars. The Python imports necessary for the pycession at the top of your Fython module
import numpy = np
pi = |np.py|
A pycession can be multiline
|
for T in ['real', 'int']:
write('def f_{T:s} = g(T = {T:s})'.format(T)
|
The signature of the write
function is
#>>>
write(
string,
*args,
end = '\n',
**kwargs,
)
The positional and keyword arguments are used to format the string
argument.
When a pycession is a Python expression, its value is directly inserted
in your Fython code.
The write
function is necessary when the pycession is not an expression.
Each string passed to the write
function is inserted in your Fython code.
The end
argument is appended to the string.
Any valid Python code is possible in a Pycession.
The Pycessor is a preprocessor. Do not use it to pass arguments to Fython because the dependency system will not see any post-compilation change in your Python module. The Pycessor is meant to facilitate the generation of tedious code, or to trigger any kind of necessary preparation before compilation.
Template¶
A function template is defined with the def
statement
def f:
T in x
T res r
r += x
def g = f(T=real)
To templatize a whole package use the import statement
import quicksort(*)
||type_provide=stat.mean, target_class=KMean||
Package interpolation can also be multilines
import quicksort(*)
||
type_provide = stat.mean
target_class = KMean
||
To disable package interpolation during a package import,
use the asis
modifier.
import asis random
This is usefull to avoid further interpolation during an ongoing package interpolation.