generated from GEOS-ESM/geos-template-repo
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Python/Fortran interface generator: bases (#27)
* Generate bridge and basic hook * Move to jinja2 template generator * Fix setup for template deploy and new dependencies * Copy support file Hook/Py interface use an object for re-entry * Sanitize variable name for Python Add partial cmake example Remove forced include to keep interface minimal (files are still copied) Copy header twice so it's present for both lib link and source compile * Fixes * Read yaml data as class: Argument Cleaner data_conversion Test * Validation: generate a validator call Clean data_conversion for direct usage Refactor: break code in smaller files & clean up * Python interface unit test Add __init__.py in the interface for python guidelines * Automatic array translation for validation
- Loading branch information
1 parent
bbd629d
commit 6ab929f
Showing
22 changed files
with
1,915 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Python <-> Fortan interface generator | ||
|
||
## Porting validation generator | ||
|
||
Generates a call that will compare the outputs of the reference and the ported code. | ||
|
||
WARNING: the generator work on the hypothesis that the fortran<>python interface and the reference fortran share the argument signature. | ||
|
||
Yaml schema: | ||
|
||
```yaml | ||
bridge: | ||
- name: function_that_will_be_validated | ||
arguments: | ||
inputs: ... | ||
inouts: ... | ||
outputs: ... | ||
validation: | ||
reference: | ||
call: fortran_routine_name | ||
mod: fortran_module_name_to_use_to_get_to_call | ||
``` |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
from typing import Optional | ||
import yaml | ||
|
||
|
||
class Argument: | ||
def __init__(self, name: str, type: str, dims: Optional[int] = None) -> None: | ||
self._name = name | ||
self._type = type | ||
self._dims = dims | ||
|
||
@property | ||
def name(self) -> str: | ||
return self._name | ||
|
||
@property | ||
def name_sanitize(self) -> str: | ||
if self._name in ["is", "in"]: | ||
return f"_{self._name}" | ||
return self.name | ||
|
||
@property | ||
def f90_dims_definition(self) -> str: | ||
# (:,:) | ||
return f"({(':,' * self._dims)[:-1]})" if self._dims else "" | ||
|
||
@property | ||
def f90_size_per_dims(self) -> str: | ||
# size(var_name, 1), size(var_name, 2) | ||
if not self._dims: | ||
return "" | ||
s = "" | ||
for dim in range(0, self._dims): | ||
s += f"size({self.name}, {dim+1})," | ||
|
||
return f"({s[:-1]})" | ||
|
||
@property | ||
def f90_dims_and_size(self) -> str: | ||
# 2, size(var_name, 1), size(var_name, 2) | ||
if not self._dims: | ||
return "" | ||
s = "" | ||
for dim in range(0, self._dims): | ||
s += f"size({self.name}, {dim+1})," | ||
|
||
return f"{self._dims},{s[:-1]}" | ||
|
||
@property | ||
def yaml_type(self) -> str: | ||
return self._type | ||
|
||
@property | ||
def c_type(self) -> str: | ||
if self._type.startswith("array_"): | ||
return self._type[len("array_") :] + "*" | ||
if self._type.startswith("MPI"): | ||
return "void*" | ||
return self._type | ||
|
||
@property | ||
def py_type_hint(self) -> str: | ||
if self._type.startswith("array_"): | ||
return "'cffi.FFI.CData'" | ||
if self._type == "MPI": | ||
return "MPI.Intercomm" | ||
return self._type | ||
|
||
@property | ||
def f90_type_definition(self) -> str: | ||
if self._type == "int": | ||
return "integer(kind=c_int), value" | ||
elif self._type == "float": | ||
return "real(kind=c_float), value" | ||
elif self._type == "double": | ||
return "real(kind=c_double), value" | ||
elif self._type == "array_int": | ||
return "integer(kind=c_int), dimension(*)" | ||
elif self._type == "array_float": | ||
return "real(kind=c_float), dimension(*)" | ||
elif self._type == "array_double": | ||
return "real(kind=c_double), dimension(*)" | ||
elif self._type == "MPI": | ||
return "integer(kind=c_int), value" | ||
else: | ||
raise RuntimeError(f"ERROR_DEF_TYPE_TO_FORTRAN: {self._type}") | ||
|
||
|
||
def argument_constructor( | ||
loader: yaml.SafeLoader, node: yaml.nodes.MappingNode | ||
) -> Argument: | ||
return Argument(**loader.construct_mapping(node)) # noqa | ||
|
||
|
||
def get_argument_yaml_loader(): | ||
loader = yaml.SafeLoader | ||
loader.add_constructor("!Argument", argument_constructor) | ||
return loader |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import jinja2 | ||
from typing import Any, Dict, List | ||
import textwrap | ||
|
||
from geosongpu_ci.tools.py_ftn_interface.argument import Argument | ||
|
||
|
||
class Function: | ||
def __init__( | ||
self, | ||
name: str, | ||
inputs: List[Argument], | ||
inouts: List[Argument], | ||
outputs: List[Argument], | ||
) -> None: | ||
self.name = name | ||
self._inputs = inputs | ||
self._inouts = inouts | ||
self._outputs = outputs | ||
|
||
@property | ||
def inputs(self) -> List[Argument]: | ||
return self._inputs | ||
|
||
@property | ||
def outputs(self) -> List[Argument]: | ||
return self._outputs | ||
|
||
@property | ||
def inouts(self) -> List[Argument]: | ||
return self._inouts | ||
|
||
@property | ||
def arguments(self) -> List[Argument]: | ||
return self._inputs + self._inouts + self._outputs | ||
|
||
@staticmethod | ||
def c_arguments_for_jinja2(arguments: List[Argument]) -> List[Dict[str, Any]]: | ||
"""Transform yaml input for the template renderer""" | ||
return [ | ||
{ | ||
"type": argument.c_type, | ||
"name": argument.name, | ||
"dims": argument._dims, | ||
} | ||
for argument in arguments | ||
] | ||
|
||
@staticmethod | ||
def fortran_arguments_for_jinja2(arguments: List[Argument]) -> List[Dict[str, str]]: | ||
"""Transform yaml input for the template renderer""" | ||
return [ | ||
{ | ||
"name": argument.name, | ||
"type": argument.f90_type_definition, | ||
"dims_f90_defs": argument.f90_dims_definition, | ||
"size_f90_per_dims": argument.f90_size_per_dims, | ||
"f90_dims_and_size": argument.f90_dims_and_size, | ||
} | ||
for argument in arguments | ||
] | ||
|
||
@staticmethod | ||
def py_arguments_for_jinja2(arguments: List[Argument]) -> List[Dict[str, str]]: | ||
"""Transform yaml input for the template renderer""" | ||
return [ | ||
{"type": argument.py_type_hint, "name": argument.name_sanitize} | ||
for argument in arguments | ||
] | ||
|
||
def py_init_code(self) -> List[str]: | ||
code = [] | ||
for argument in self.arguments: | ||
if argument.yaml_type == "MPI": | ||
code.append( | ||
textwrap.dedent( | ||
f"""\ | ||
# Comm translate to python | ||
comm_py = MPI.Intracomm() # new comm, internal MPI_Comm handle is MPI_COMM_NULL | ||
comm_ptr = MPI._addressof(comm_py) # internal MPI_Comm handle | ||
comm_ptr = ffi.cast('{{_mpi_comm_t}}*', comm_ptr) # make it a CFFI pointer | ||
comm_ptr[0] = {argument.name} # assign comm_c to comm_py's MPI_Comm handle | ||
{argument.name} = comm_py # Override the symbol name to make life easier for code gen""" # noqa | ||
) | ||
) | ||
return code | ||
|
||
def c_init_code(self) -> List[str]: | ||
prolog_code = [] | ||
for argument in self.arguments: | ||
if argument.yaml_type == "MPI": | ||
prolog_code.append( | ||
f"MPI_Comm {argument.name}_c = MPI_Comm_f2c({argument.name});" | ||
) | ||
return prolog_code | ||
|
||
def arguments_name(self) -> List[str]: | ||
return [argument.name_sanitize for argument in self.arguments] | ||
|
||
@staticmethod | ||
def _fortran_type_declaration(def_type: str) -> str: | ||
if def_type == "int": | ||
return "integer(kind=c_int), value" | ||
elif def_type == "float": | ||
return "real(kind=c_float), value" | ||
elif def_type == "double": | ||
return "real(kind=c_double), value" | ||
elif def_type == "array_int": | ||
return "integer(kind=c_int), dimension(*)" | ||
elif def_type == "array_float": | ||
return "real(kind=c_float), dimension(*)" | ||
elif def_type == "array_double": | ||
return "real(kind=c_double), dimension(*)" | ||
elif def_type == "MPI": | ||
return "integer(kind=c_int), value" | ||
else: | ||
raise RuntimeError(f"ERROR_DEF_TYPE_TO_FORTRAN: {def_type}") | ||
|
||
|
||
class InterfaceConfig: | ||
def __init__( | ||
self, | ||
directory_path: str, | ||
prefix: str, | ||
function_defines: List[Function], | ||
template_env: jinja2.Environment, | ||
) -> None: | ||
self._directory_path = directory_path | ||
self._prefix = prefix | ||
self._hook_obj = prefix | ||
self._hook_class = prefix.upper() | ||
self._functions = function_defines | ||
self._template_env = template_env |
Oops, something went wrong.