├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── fmodpy
├── __init__.py
├── __main__.py
├── about
│ ├── author.txt
│ ├── classifiers.txt
│ ├── description.txt
│ ├── keywords.txt
│ ├── on_pypi.txt
│ ├── requirements.txt
│ ├── version.txt
│ └── version_history.md
├── config.py
├── development
│ ├── strange_type_example.f90
│ ├── todo.py
│ └── type.py
├── exceptions.py
├── fmodpy.py
├── parsing
│ ├── __init__.py
│ ├── _old_code.txt
│ ├── argument.py
│ ├── character.py
│ ├── code.py
│ ├── complex.py
│ ├── file.py
│ ├── function.py
│ ├── implicit_none.py
│ ├── integer.py
│ ├── interface.py
│ ├── logical.py
│ ├── module.py
│ ├── procedure.py
│ ├── real.py
│ ├── subroutine.py
│ ├── type.py
│ ├── use.py
│ └── util.py
└── test
│ ├── .coverage
│ ├── character
│ ├── __init__.py
│ └── test_character.f03
│ ├── complex128
│ ├── __init__.py
│ └── test_complex128.f03
│ ├── complex256
│ ├── __init__.py
│ ├── test_complex256.f03
│ └── test_complex256
│ │ ├── test_complex256_c_wrapper.f90
│ │ └── test_complex256_python_wrapper.py
│ ├── complex64
│ ├── __init__.py
│ └── test_complex64.f03
│ ├── double_precision
│ ├── __init__.py
│ └── test_double_precision.f03
│ ├── fixed_format
│ ├── __init__.py
│ └── test_fixed_format.f
│ ├── int32
│ ├── __init__.py
│ └── test_int32.f03
│ ├── int64
│ ├── __init__.py
│ └── test_int64.f03
│ ├── logical
│ ├── __init__.py
│ └── test_logical.f03
│ ├── misc
│ ├── __init__.py
│ ├── implicit_shape.f90
│ ├── simple.f03
│ └── subroutine_with_type.f90
│ ├── module
│ ├── __init__.py
│ ├── extra_module.f03
│ └── test_module.f03
│ ├── procedure
│ ├── __init__.py
│ ├── og_procedure
│ │ ├── procedure.f03
│ │ ├── procedure.pyx
│ │ └── procedure_c_to_f.f90
│ └── procedure.f03
│ ├── real32
│ ├── __init__.py
│ └── test_real32.f03
│ ├── real64
│ ├── __init__.py
│ └── test_real64.f03
│ ├── test.py
│ └── type
│ ├── __init__.py
│ ├── derived_type_error.f03
│ ├── test_type.f03
│ └── test_type_aux.f03
├── readme.md
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | __pycache__
3 | *.pyc
4 | *.o
5 | *.so
6 | *.mod
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Thomas C.H. Lux
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include fmodpy *
2 |
--------------------------------------------------------------------------------
/fmodpy/__init__.py:
--------------------------------------------------------------------------------
1 | # Set up some package-related things.
2 | import os
3 | DIRECTORY = os.path.dirname(os.path.abspath(__file__))
4 | ABOUT_DIR = os.path.join(DIRECTORY, "about")
5 | with open(os.path.join(ABOUT_DIR,"version.txt")) as f:
6 | __version__ = f.read().strip()
7 | del f
8 |
9 | # Steal the documentation from .fmodpy, instead of duplicating here.
10 | from .fmodpy import __doc__
11 |
12 | # Import the main configuration (needed to initialize the configuration).
13 | import fmodpy.config
14 |
15 | # Import the main features of this package.
16 | from .fmodpy import fimport, configure, load_symbol
17 |
18 | # Set "__all__" so that "from fmodpy import *" returns expected stuff.
19 | __all__ = ["fimport", "configure", "load_symbol"]
20 |
21 |
--------------------------------------------------------------------------------
/fmodpy/__main__.py:
--------------------------------------------------------------------------------
1 | # Making this module accessible by being called directly from command line.
2 | import os, sys, traceback
3 |
4 | # Import all of the fmodpy module
5 | from .fmodpy import __doc__, fimport, configure
6 |
7 | if len(sys.argv) < 2:
8 | import fmodpy
9 | help(fmodpy)
10 | exit()
11 | else:
12 | file_path = os.path.abspath(sys.argv[1])
13 |
14 | # Pretty error handling when this file is executed directly
15 | def custom_excepthook(exc_type, value, tb):
16 | l = ''.join(traceback.format_exception(exc_type, value, tb))
17 | print(l)
18 | sys.excepthook = custom_excepthook
19 |
20 | # Read command line arguments after the path to the source file.
21 | command_line_args = {}
22 | for arg in sys.argv[2:]:
23 | if ("=" not in arg):
24 | from fmodpy.exceptions import UnrecognizedConfiguration
25 | raise(UnrecognizedConfiguration(
26 | f"Command line argument {str([arg])[1:-1]} must be formatted"+
27 | " as '=', but no '=' found."))
28 | first_equals = arg.index('=')
29 | setting, value = arg[:first_equals], arg[first_equals+1:]
30 | command_line_args[setting] = value
31 |
32 | # Call "fimport" providing the given arguments as configurations.
33 | fimport(file_path, **command_line_args)
34 |
--------------------------------------------------------------------------------
/fmodpy/about/author.txt:
--------------------------------------------------------------------------------
1 | Thomas C.H. Lux
2 | thomas.ch.lux@gmail.com
3 | tchlux
4 |
--------------------------------------------------------------------------------
/fmodpy/about/classifiers.txt:
--------------------------------------------------------------------------------
1 | % How mature is this project? Common values are
2 | % 3 - Alpha
3 | % 4 - Beta
4 | % 5 - Production/Stable
5 | Development Status :: 4 - Beta
6 | % Indicate who your project is intended for
7 | Intended Audience :: Developers
8 | Topic :: Software Development :: Build Tools
9 | % Pick your license as you wish (should match "license" above)
10 | License :: OSI Approved :: MIT License
11 | % Specify the Python versions you support here. In particular, ensure
12 | % that you indicate whether you support Python 2, Python 3 or both.
13 | Programming Language :: Python :: 3.6
14 | Programming Language :: Python :: 3.7
15 | Programming Language :: Python :: 3.8
16 |
--------------------------------------------------------------------------------
/fmodpy/about/description.txt:
--------------------------------------------------------------------------------
1 | A lightweight, efficient, highly automated, fortran wrapper for python.
2 |
--------------------------------------------------------------------------------
/fmodpy/about/keywords.txt:
--------------------------------------------------------------------------------
1 | python
2 | python3
3 | fortran
4 | wrapper
5 |
--------------------------------------------------------------------------------
/fmodpy/about/on_pypi.txt:
--------------------------------------------------------------------------------
1 | True
2 |
--------------------------------------------------------------------------------
/fmodpy/about/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy>=1.13
2 |
--------------------------------------------------------------------------------
/fmodpy/about/version.txt:
--------------------------------------------------------------------------------
1 | 1.7.5
2 |
--------------------------------------------------------------------------------
/fmodpy/about/version_history.md:
--------------------------------------------------------------------------------
1 | | 1.1.0
December 2020 | Supports MODULE wrapping and basic types. |
2 | | 1.2.0
February 2021 | Fixing array size and dimension checks for optional
array arguments. Working towards support of types. |
3 | | 1.2.1
February 2021 | Fixing install issue with setup.py |
4 | | 1.2.2
May 2021 | Added support for complex types. |
5 | | 1.3.0
May 2021 | Removed OG fmodpy, added complex256 tests work in
progress. |
6 | | 1.3.1
June 2021 | Fixing minor bug in argument parsing. |
7 | | 1.3.2
June 2021 | Adding ability to parse Fortran files with renamed
types. |
8 | | 1.3.3
September 2021 | Fixed numerous minor bugs and typos. Added basic
support for characters, as long as they have no
length. |
9 | | 1.3.4
October 2021 | Refactored parsing of SIZE(...) in assumed shape
array allocation during wrapping. Now supports
slightly more complicated arguments to the SIZE
function. |
10 | | 1.4.0
January 2022 | Updated documentation parsing to not clear comments
on new lines after subroutine definition. Fixed
character array tests. Added basic support for
Fortran derived types as long as they are BIND(C)
already. |
11 | | 1.4.1
February 2022 | Fixed usability bugs with derived types, added final
directory cleaning, fixed bug in module dependencies
that relied on .mod file, found bug in gfortran when
trying to support parameterized types. |
12 | | 1.4.2
February 2022 | Derived type struct fields in Python are lower case
to match python-wrapped behaviors in the rest of the
package. |
13 | | 1.4.3
March 2022 | Added warnings for long compilation, many optional
arguments, and included architecture in compiled
file names. |
14 | | 1.4.3
March 2022 | Logical byte size adjustment from 03-27 that
supports C-bool types with only one byte. |
15 | | 1.4.4
March 2022 | Logical byte size adjustment from 03-27 that
supports C-bool types with only one byte. |
16 | | 1.4.5
April 2022 | Updated size measurements for arrays to 64 bit
integers. |
17 | | 1.4.6
April 2022 | Name change of depends_files to dependencies. Missed
this upload last weekend. |
18 | | 1.4.7
April 2022 | Updated dependency handling, automatic compilation
now starts with dependencies in order, source code
modification times are checked for all dependencies. |
19 | | 1.4.8
May 2022 | Added support for types defined in subroutines in
standalone files. Updated test case to reflect new
addition. Added 'verbose_module' parameter for
setting the default verbosity of generated modules. |
20 | | 1.5.0
July 2022 | Added the ability to load symbols from system
libraries, improves automatic dependency resolution. |
21 | | 1.5.1
July 2022 | Minor modifications to fix some compilation and
import bugs when supporting generic search for
symbols. Symbol dependencies are now loaded within
the generated wrapper, to ensure correct operation
after the initial build. |
22 | | 1.5.2
July 2022 | Added some additional standard library search paths
and renamed 'delete_destination' as 'overwrite'.
Also changed default configurations that were
strings being split into proper Python lists. |
23 | | 1.5.3
March 2023 | Minor update. Changed some logic in fimport to more
correctly support different system configurations.
Added comments drescribing most configurable
settings. Added support for TARGET and POINTER
types. |
24 | | 1.5.4
March 2023 | Added support for '$' as a line continuation
character in the fifth column of fixed format files.
Switched 'LOGICAL' types to be 'C_BOOL' |
25 | | 1.5.5
March 2023 | Updated error message for unnamed END statements to
include configuration suggestion. Made fixed format
Fortran files set 'end_is_named=False' by default. |
26 | | 1.5.6
March 2023 | Making some 'os.remove' operations safer by checking
for path existence. Refactored 'f_compiler_args'
into two more parts, 'optimization_level' and
'shared_object_args', so that the typical '-fPIC
-shared' do not need to be included when adding
custom compilation arguments. |
27 | | 1.5.6
March 2023 | Making some 'os.remove' operations safer by checking
for path existence. Refactored 'f_compiler_args'
into two more parts, 'optimization_level' and
'shared_object_args', so that the typical '-fPIC
-shared' do not need to be included when adding
custom compilation arguments. |
28 | | 1.5.7
April 2023 | Delayed warning message about LOGICAL arrays until
those arguments actually have a python interface
generated. This is to prevent parsed-but-unused
subroutine arguments from creating noise. |
29 | | 1.6.0
June 2023 | Added support for Fortran strings. Updated the
generated codes to automatically check for source
code modifications when loading and recompile if the
source code was modified. |
30 | | 1.7.0
November 2023 | Added support for parsing PARAMETER lines in fixed
format Fortran. |
31 | | 1.7.1
January 2024 | Fixed bug in type of return argument for allocatable
arrays. Updated messaging about implicit typing to
be more useful. |
32 | | 1.7.2
March 2024 | Updating command execution to use a list of
arguments instead of string join and 'shell=True'.
Moved compile options to the end of the argument
list to ensure that libraries are included correctly
in compiled shared object. |
33 | | 1.7.3
March 2024 | A commensurate update to size check compilations
that was made in the previous update. Moving library
specification to the back of the list of arguments
for the compiler to ensure dependencies are included
correctly. |
34 | | 1.7.4
May 2024 | Add support for 'size(a,1,kind=int64)' style size
specifications of inputs. |
35 |
--------------------------------------------------------------------------------
/fmodpy/config.py:
--------------------------------------------------------------------------------
1 | import os, sysconfig
2 |
3 | # Try to include the NumPy path, ignore if it doesn't exist.
4 | try:
5 | import numpy as NP
6 | numpy_path = NP.__path__
7 | except:
8 | numpy_path = []
9 |
10 | # Default configurable variables.
11 | omp = False # True if OpenMP libraries should be linked for the given program.
12 | blas = False # True if BLAS should be linked for the given program.
13 | lapack = False # True if LAPACK should be linked for the given program.
14 | verbose = False # True if fmodpy should show logs during wrapper construction.
15 | verbose_module = True # True if the generated wrapper module should print to stdout when it is compiling.
16 | autocompile = True # True if fmodpy should attempt to automatically compile Fortran source files while creating the wrapper.
17 | wrap = True # True if fmodpy should build the wrapper (Fortran file and Python file that constitutes the translation layer).
18 | rebuild = False # True if the source file should be parse and wrapper built regardless of file modification times.
19 | show_warnings = True #
20 | debug_line_numbers = False # True if fmodpy source line numbers should be prepended to printed logs.
21 | implicit_typing = False # True if types of undeclared arguments should be inferred.
22 | end_is_named = True # True if the END of code blocks includes the name of the block.
23 | overwrite = False # hard-delete destination directory if it already exists.
24 | log_file = os.devnull # The "file" object that logging statements should be directed.
25 | f_compiler = "gfortran" # The command used to execute the fortran program.
26 | f_compiler_args = [] # Any special arguments for compiling.
27 | optimization_level = "-O3" # The level of compiler optimization to use for the final module.
28 | shared_object_args = ["-fPIC", "-shared"] # All arguments required for a functioning shared object.
29 | link_omp = ["-fopenmp"] # The arguments for enabling OpenMP support at compile and link times.
30 | link_blas = ["-lblas"] # The argument(s) for enabling BLAS routines at link time.
31 | link_lapack = ["-lblas", "-llapack"] # The argument(s) for enabling LAPACK routines at link time.
32 | home_directory = os.path.expanduser("~") # The user home directory to search for a global configuration file.
33 | libraries = numpy_path + [ # Paths to be checked for dependent "symbols" / routine defintions.
34 | "/usr/lib",
35 | "/lib",
36 | "/usr/lib64",
37 | "/lib64",
38 | "/usr/lib32",
39 | "/lib32",
40 | "/opt/homebrew/Cellar/openblas",
41 | "/opt/homebrew/Cellar/libomp"
42 | ]
43 | library_recursion = 2 # Maximum recursion depth when searching libraries for symbol definitions.
44 | library_extensions = ["so", "dylib", "dll"] # Files to be checked as linkable shared libraries.
45 | symbol_command = 'nm -gU "{path}" 2> /dev/null || nm -gD "{path}" 2> /dev/null' # Commad used to enumerate available symbols in a shared object.
46 | config_file = ".fmodpy.py" # Name of the fmodpy configuration file to look for.
47 | wait_warning_sec = 5 # Number of seconds to wait before warning about automatic compilation.
48 |
49 | # --------------------------------------------------------------------
50 | # Development globals, not intended to be changed by users.
51 | #
52 | # All of these variables should have expected types.
53 | BOOL_CONFIG_VARS = ["omp", "blas", "lapack", "verbose", "autocompile",
54 | "wrap", "rebuild", "show_warnings",
55 | "debug_line_numbers", "implicit_typing",
56 | "end_is_named", "overwrite"]
57 | LIST_CONFIG_VARS = ["f_compiler_args", "shared_object_args", "link_omp", "link_blas",
58 | "link_lapack", "libraries", "library_extensions"]
59 | # File related maniplation arguments
60 | PY_EXT = ".py"
61 | FORT_EXT = ".f90"
62 | PYTHON_WRAPPER_EXT = "_python_wrapper"
63 | FORT_WRAPPER_EXT = "_c_wrapper"+FORT_EXT
64 | GET_SIZE_PROG_FILE = "fmodpy_get_size"+FORT_EXT
65 | GET_SIZE_EXEC_FILE = "fmodpy_get_size"
66 | GET_SIZE_VARIABLE_PREFIX = "SIZE_OF_"
67 | # --------------------------------------------------------------------
68 |
69 | # Automatically handle printing for status updates.
70 | # WARNING: Do not use this function for warnings. Use `warnings.warn`.
71 | # WARNING: Do not use this function for errors. Use `raise(...)`.
72 | #
73 | # Custom print function (allows for line numbers to be automatically
74 | # added to all print statements, easily controls verbosity level).
75 | def fmodpy_print(*args, **kwargs):
76 | # Skip all print statements if verbosity is off.
77 | global verbose
78 | if (not verbose): return
79 | # Set the log file.
80 | global log_file
81 | if (log_file == os.devnull):
82 | import sys
83 | log_file = sys.stdout
84 | # Add information about where the bit is printed from, if turned on.
85 | global debug_line_numbers
86 | if debug_line_numbers:
87 | import inspect
88 | # Add the calling file and line to the print statement
89 | calling_function = inspect.stack()[1]
90 | calling_file = calling_function.filename
91 | calling_file = os.path.basename(calling_file)
92 | calling_line = calling_function.lineno
93 | args += (f'({calling_file} line {calling_line})',)
94 | # For all print operations, force a "flush" output.
95 | kwargs["file"] = log_file
96 | kwargs["flush"] = True
97 | print(*args, **kwargs)
98 |
99 |
100 | # Execute a blocking command with a subprocess, on completion provide
101 | # the return code, stdout as string, and stderr as string. This should
102 | # work across all Python3.x as well as cross-platform.
103 | # INPUT:
104 | # command -- A list of strings or string (space separated) describing
105 | # a standard command as would be given to subprocess.Popen
106 | #
107 | # OUTPUT:
108 | # return_code -- Straight from the subprocess returncode
109 | # stdout -- A list of strings that are the lines of stdout
110 | # produced by the execution of
111 | # stderr -- A list of strings that are the lines of stderr
112 | # produced by the execution of
113 | def run(command, **popen_kwargs):
114 | import sys, subprocess
115 | # For Python 3.x and x < 6, the encoding is a string by default
116 | # For Python 3.6 and later the encoding can be given as an arguemnt
117 | if sys.version_info >= (3,6):
118 | popen_kwargs.update( dict(encoding="UTF-8") )
119 | proc = subprocess.Popen(command, stdout=subprocess.PIPE,
120 | stderr=subprocess.PIPE, **popen_kwargs)
121 | stdout, stderr = proc.communicate()
122 | # Before python 3.6, the encoding had to be handled after collection
123 | if (sys.version_info < (3,6)):
124 | if (type(stdout) != str): stdout = str(stdout, encoding="UTF-8")
125 | if (type(stderr) != str): stderr = str(stderr, encoding="UTF-8")
126 | # Remove Windows specific characters and split by the new line.
127 | if stdout: stdout = stdout.replace("\r","").split("\n")
128 | else: stdout = ""
129 | if stderr: stderr = stderr.replace("\r","").split("\n")
130 | else: stderr = ""
131 | # Return the exit code, standard out, and standard error.
132 | return proc.returncode, stdout, stderr
133 |
134 |
135 | # Configure the current 'fmodpy' runtime. Start with default values in
136 | # this file, load any global defaults over those, and finally override
137 | # again with provided keyword arguments.
138 | def load_config(**kwargs):
139 | # Get the "globals" for this file, this is the configuration of fmodpy.
140 | fmodpy_config = globals()
141 |
142 | # Identify those elements of "fmodpy.config" that should not be
143 | # set or considered when printing out configuration.
144 | modules = {"os", "sysconfig"}
145 | func_type = type(lambda:None)
146 | functions = {k for k in fmodpy_config if
147 | type(fmodpy_config[k]) == func_type}
148 | # Set the default configuration as the current configuration.
149 | config = { k:fmodpy_config[k] for k in fmodpy_config
150 | if (k[0].isalpha() and k.islower()) and
151 | (k not in modules) and (k not in functions) }
152 |
153 | # Check to make sure that all variables given specify only those
154 | # that are allowed to be specified.
155 | for var in kwargs:
156 | if (var not in config):
157 | from fmodpy.exceptions import UnrecognizedConfiguration
158 | raise(UnrecognizedConfiguration(f"Configuration for '{var}' is given, but that variable does not exist."))
159 | if (var[0].isupper() or (not var[0].isalpha())):
160 | from fmodpy.exceptions import IllegalConfiguration
161 | raise(IllegalConfiguration(f"The variable '{var}' is not allowed to be configured."))
162 |
163 | # Update the 'config' dictionary with the provided keyword arguments.
164 | config.update( kwargs )
165 |
166 | # Make sure the path names do not have spaces.
167 | if any(' ' in str(config[k]) for k in ('f_compiler',)):
168 | from fmodpy.exceptions import NotAllowedPath
169 | raise(NotAllowedPath("Spaces cannot be included in compiler or linker paths."))
170 |
171 | # Convert list-type configurations into lists (from strings).
172 | for var in LIST_CONFIG_VARS:
173 | if (type(config[var]) is str):
174 | config[var] = config[var].split()
175 | elif (type(config[var]) is not list):
176 | from fmodpy.exceptions import IllegalConfiguration
177 | raise(IllegalConfiguration(f"The variable '{var}' is supposed to be a list or string, but is neither."))
178 |
179 | # Make sure that boolean-type configurations are booleans (from strings).
180 | for var in BOOL_CONFIG_VARS:
181 | if (type(config[var]) is str):
182 | config[var] = (config[var].lower().strip() == 'true')
183 | elif (type(config[var]) is not bool):
184 | from fmodpy.exceptions import IllegalConfiguration
185 | raise(IllegalConfiguration(f"The variable '{var}' is supposed to be a bool or string, but is neither."))
186 |
187 | # If 'lblas' is True, then add BLAS compilation and
188 | # link arguments to the list of arguments already.
189 | if config["blas"]:
190 | for l in config["link_blas"]:
191 | if (l not in config["f_compiler_args"]):
192 | config["f_compiler_args"] += [l]
193 |
194 | # If 'llapack' is True, then add LAPACK compilation and
195 | # link arguments to the list of arguments already.
196 | if config["lapack"]:
197 | for l in config["link_lapack"]:
198 | if (l not in config["f_compiler_args"]):
199 | config["f_compiler_args"] += [l]
200 |
201 | # If 'omp' is True, then add OpenMP compilation and link arguments
202 | # to the list of arguments already.
203 | if config["omp"]:
204 | for l in config["link_omp"]:
205 | if (l not in config["f_compiler_args"]):
206 | config["f_compiler_args"] += [l]
207 |
208 | # Make sure any 'f_compiler_args' are not redundant with other args.
209 | config["f_compiler_args"] = [
210 | a for a in config["f_compiler_args"]
211 | if a not in [config["optimization_level"]] + config["shared_object_args"]
212 | ]
213 |
214 | # Set all of the configuration variables as module-wide globals.
215 | for var in config: fmodpy_config[var] = config[var]
216 |
217 | # Return the current configuration.
218 | return config
219 |
220 |
221 | # --------------------------------------------------------------------
222 | # --------------------------------------------------------------------
223 |
224 |
225 | # Read a configuration file into a Python dictionary.
226 | def read_config_file(path):
227 | with open(path) as f:
228 | # Get all lines that have one "=" split and stripped into nice strings.
229 | # Strip quote characters from edges of strings (when user assigned a string).
230 | lines = ((' '.join(var.strip().strip('\'"').split()) for var in l.strip().split('='))
231 | for l in f.readlines() if (l.count('=') == 1))
232 | return dict(lines)
233 |
234 | # Load local settings if they are available.
235 | if os.path.exists(os.path.join(home_directory,config_file)):
236 | global_config = read_config_file(
237 | os.path.join(home_directory,config_file))
238 | else: global_config = {}
239 | # Make sure all provided settings are recognized (and allowable).
240 | for var in global_config:
241 | if (var.strip()[:1] == "#"): continue # Skip commented lines.
242 | if var not in globals():
243 | from fmodpy.exceptions import UnrecognizedConfiguration
244 | raise(UnrecognizedConfiguration(f"Configuration for '{var}' is given in config file, but that variable does not exist."))
245 | # Update the configuration of fmodpy.
246 | globals().update(global_config)
247 | # Delete the local variables (so they don't hang around).
248 | if (len(global_config) > 0): del var
249 | del read_config_file
250 | del global_config
251 | # Load in the global configuration file.
252 | load_config()
253 |
--------------------------------------------------------------------------------
/fmodpy/development/strange_type_example.f90:
--------------------------------------------------------------------------------
1 |
2 | MODULE HOLDER
3 | IMPLICIT NONE
4 |
5 | TYPE INTERNAL
6 | REAL :: R
7 | INTEGER :: I, J
8 | END TYPE INTERNAL
9 |
10 | CONTAINS
11 |
12 | SUBROUTINE TEST(A, B)
13 | REAL, INTENT(OUT), OPTIONAL :: A
14 | REAL, INTENT(IN) :: B
15 | IF (PRESENT(A)) THEN
16 | A = TEST_INTERNAL()
17 | END IF
18 | CONTAINS
19 | FUNCTION TEST_INTERNAL()
20 | REAL :: TEST_INTERNAL
21 | TEST_INTERNAL = 2.0 * B
22 | END FUNCTION TEST_INTERNAL
23 | END SUBROUTINE TEST
24 |
25 | SUBROUTINE PROC_TEST(A)
26 | ABSTRACT INTERFACE
27 | FUNCTION PTINT(B)
28 | TYPE USEFUL
29 | REAL :: R, S
30 | END TYPE USEFUL
31 | TYPE(USEFUL), INTENT(IN) :: B
32 | REAL PTINT
33 | END FUNCTION PTINT
34 | END INTERFACE
35 | PROCEDURE(PTINT) :: A
36 |
37 | END SUBROUTINE PROC_TEST
38 |
39 | END MODULE HOLDER
40 |
41 |
42 |
43 |
44 |
45 |
46 | PROGRAM TRY_SOMETHING
47 | USE HOLDER
48 | IMPLICIT NONE
49 | REAL :: FIRST, SECOND
50 | TYPE(INTERNAL), DIMENSION(2,2) :: CUSTOM_TYPE
51 |
52 | CUSTOM_TYPE%I = 1
53 | CUSTOM_TYPE%J = 0
54 | CUSTOM_TYPE(:,:)%R = 1.0
55 | PRINT *, CUSTOM_TYPE(:,:)
56 |
57 | ! FIRST = 1.0
58 | ! SECOND = 2.0
59 |
60 | ! PRINT *, FIRST
61 | ! PRINT *, SECOND
62 | ! CALL TEST(A=FIRST, B=SECOND)
63 | ! PRINT *, FIRST
64 | ! PRINT *, SECOND
65 |
66 | END PROGRAM TRY_SOMETHING
67 |
--------------------------------------------------------------------------------
/fmodpy/development/todo.py:
--------------------------------------------------------------------------------
1 |
2 | # Custom types:
3 | # - read in the "arguments" for the type, those are the sizes
4 | # - allow "LEN" arguments
5 | # - prohibit "KIND" arguments
6 | # - define a "struct generator" in python that creates a struct
7 | # of the same shape as in Fortran given the same arguments
8 | # - check for "issubclass" instead of exact type match for structs
9 | #
10 |
11 |
12 |
13 | # --------------------------------------------------------------------
14 | #
15 | # DEVELOPMENT
16 | # PLAN
17 | #
18 | # - support custom types defined in Fortran
19 | # --- parse the custom type and determine its contents in terms of basic types
20 | # --- generate a matching C struct using ctypes to the custom type
21 | # --- identify proper allocation schema in numpy to make sure memory
22 | # maps correctly into Fortran
23 | #
24 | # - support procedures as arguments
25 | # --- create a registry of "fmodpy wrapped functions" with the memory
26 | # addresses of all functions wrapped by fmodpy
27 | # --- map the memory address to the actual function in the Fortran
28 | # wrapper
29 | #
30 | # --- construct C function in Python (with ctypes) that translates C
31 | # style data into Python compatible data, calls the Python
32 | # function that was passed in as an argument, then translates the
33 | # results back into C style data for return
34 | # --- construct a BIND(C) Fortran wrapper function that translates
35 | # arguments from Fortran style into C style and calls the C
36 | # (Python) function from Fortran, nested in a MODULE with PUBLIC
37 | # variables that assign the address of the C function
38 | # --- pass the memory address of the C function in Python to Fortran
39 | # wrapper, use that address to assign a local Fortran function
40 | # in the BIND(C) wrapper module that calls Python C function
41 | #
42 | # - extend test cases to achieve (near) 100% coverage of all lines
43 | #
44 | #
45 | # --------------------------------------------------------------------
46 | # TODO LISTS
47 | #
48 | # FMODPY
49 | # - Create WARNING for when link files are "not found" in compiled
50 | # projects, asking if the links are still alive.
51 | # - Make __init__.py the source file and the _python_wrapper.py the
52 | # link file, so that projects put on GitHub will work better.
53 | # - Write a code that "recovers links" instituted by GitHub, insert
54 | # that into the generated fmodpy wrappers.
55 | # - Create an archive of the source files when the wrapper was originally
56 | # built, so that those can be recovered even if the links are lost.
57 | # - add option to continue wrapping on failures, and to simply skip
58 | # wrappers for the things that cannot be wrapped (when possible)
59 | # - assign implicit_typing to True and named_end to False for '.f' files
60 | # - assign correct shape (from shape[0] ...) for implicit array arguments
61 | # - check the memory contiguity of OPTIONAL input arrays
62 | # - LOGICAL array warning should only happen *after* non-arguments are deleted
63 | # - compile dependencies first when 'dependencies' are given and 'autocompile=True'
64 | # - remove automatic size option in wrapper for optional arguments with unknown shape
65 | # - handle automatic shapes that have a ":" inside of them
66 | # - check for existence of Fortran compiler on install, offer instruction
67 | # - support lists of compilation commands to run before making final object
68 | # - support list of files to include in final module compilation (like objects)
69 | # - support multiple file input, for specifying all files that are part
70 | # of a module or may contain important types or constants
71 | # - empty classes should not be added into wrappers, they should be skipped
72 | # (i.e., a class that only defines interfaces)
73 | # - automatic documentation of the types of inputs in Python, included
74 | # in addition to original Fortran documentation.
75 | # - replace names of builtins like "global" and "lambda" in the
76 | # python wrapper (when the original Fortran uses these names).
77 | # - IMPLICIT NONE should be added to interfaces for functions
78 | # - integer arguments that are used as sizes should be declared before
79 | # the array arguments that use them as sizes, regardless of their
80 | # order in the actual list of arguments
81 | #
82 | # TYPE
83 | # - find the definition of this Type, and all of its contents.
84 | # - the type could be declared in a module that is "USED" in
85 | # another file, will have to scan other files for modules
86 | # with type definitions?
87 | # in the same subroutine or function
88 | # in the same module
89 | # in a USED module, potentially in another file
90 | # - define a C struct in the appropriate context
91 | # - cycle all arugments in type, initializing them correctly
92 | # - store all the initialized values into a C struct
93 | #
94 | #
95 | # PROCEDURE
96 | # - The interface being used by a subroutine-as-an-argument could
97 | # be defined in another module. Need to check for that.
98 | # Need to disallow "EXTERNAL" subroutines-as-arguments.
99 | # - Need to write fort_to_py versions of interface functions in
100 | # fortran wrapper and in python wrapper
101 | # - Have a way to bypass Python when a fmodpy wrapped fortran routine
102 | # is given. Maybe define a get_address function for all source
103 | # functions in Fortran wrappers?
104 | #
105 |
--------------------------------------------------------------------------------
/fmodpy/development/type.py:
--------------------------------------------------------------------------------
1 | # "util" should perform compilation of fortran codes in setup, saving requires admin privaleges.
2 | # Passed functions store python functions associated with random integer
3 | # identifier, that is used to look up correct function in cython.
4 |
5 | # An example of how to create containers of structures in Numpy
6 | # https://stackoverflow.com/questions/50822183/efficient-conversion-from-c-array-of-struct-to-numpy-array-with-ctypes
7 | # https://stackoverflow.com/questions/40063080/casting-an-array-of-c-structs-to-a-numpy-array
8 |
9 | from ctypes import Structure, c_char_p, c_int, c_double, POINTER
10 | import numpy as np
11 |
12 | def c_func(): pass
13 |
14 | class info(Structure):
15 | _fields_ = [ ("name", c_char_p),
16 | ("arr_len", c_int),
17 | ("real_data", POINTER(c_double)) ]
18 |
19 | c_func.restype = info
20 | ret_val = c_func()
21 | # data = np.ctypeslib.as_array(ret_val.contents.real_data
22 |
--------------------------------------------------------------------------------
/fmodpy/exceptions.py:
--------------------------------------------------------------------------------
1 | # Custom errors that can be raised during the fmodpy process
2 |
3 | class BadKeywordArgument(Exception): pass
4 | class BuildAndLinkError(Exception): pass
5 | class CompileError(Exception): pass
6 | class FortranError(Exception): pass
7 | class IllegalConfiguration(Exception): pass
8 | class LinkError(Exception): pass
9 | class NameError(Exception): pass
10 | class NotAllowedPath(Exception): pass
11 | class NotSupportedError(Exception): pass
12 | class ParseError(Exception): pass
13 | class SizeError(Exception): pass
14 | class UnrecognizedConfiguration(Exception): pass
15 |
--------------------------------------------------------------------------------
/fmodpy/parsing/__init__.py:
--------------------------------------------------------------------------------
1 | # Add spaces to syntax in lines (for easier splitting by white-space)
2 | FORT_TEXT_REPLACEMENTS = {
3 | "DOUBLE PRECISION": "REAL ( KIND ( 0.0D0 ) )",
4 | "END DO": "ENDDO",
5 | "END IF": "ENDIF",
6 | ",":" , ",
7 | "(":" ( ",
8 | ")":" ) ",
9 | ":":" : ",
10 | "+":" + ",
11 | "-":" - ",
12 | "*":" * ",
13 | "/":" / ",
14 | "=":" = ",
15 | }
16 |
17 | # Some spaces that were created above were misplaced, so this fixes them.
18 | FORT_TEXT_FIXES = {
19 | "= >":"=>",
20 | }
21 |
22 | # Keep all comment lines and all lines from a file that start with the
23 | # following expressions (the file is converted to all upper case).
24 | ACCEPTABLE_LINE_STARTS = {'ABSTRACT', 'CHARACTER', 'END', # 'EXTERNAL',
25 | 'INTEGER', 'LOGICAL', 'REAL', 'COMPLEX',
26 | 'IMPLICIT', 'INTERFACE', 'MODULE', 'FUNCTION',
27 | 'OPTIONAL', 'PRIVATE', 'PROCEDURE',
28 | 'PUBLIC', 'PURE', 'RECURSIVE',
29 | 'SUBROUTINE', 'TYPE', 'USE', 'PARAMETER'}
30 | LINE_STARTS_TO_REMOVE = {'PURE', 'RECURSIVE'}
31 |
32 | # Immediately exclude a file from automatic compilation if it has a
33 | # line starting with the following.
34 | IMMEDIATELY_EXCLUDE = {"PROGRAM"}
35 |
36 | please_report_to = f"\nIf this is syntactically correct Fortran, please report this\n"+\
37 | f"as an issue with the relevant Fortran code at\n\n"+\
38 | f" https://github.com/tchlux/fmodpy\n"
39 |
40 |
41 | from .util import after_dot, before_dot, class_name, \
42 | legal_module_name, simplify_fortran_file, pop_group, \
43 | wrap_long_lines
44 |
45 | # --------------------------------------------------------------------
46 |
47 | # Define a function for parsing an Interface. Returns a list of instances
48 | def parse_interface(list_of_lines, comments, parent):
49 | from .interface import Interface
50 | return parse_code(Interface, list_of_lines, comments, parent)
51 |
52 | # Define a function for parsing a Module. Returns a list of instances
53 | def parse_module(list_of_lines, comments, parent):
54 | from .module import Module
55 | return parse_code(Module, list_of_lines, comments, parent)
56 |
57 | # Define a function for parsing a Subroutine. Returns a list of instances
58 | def parse_subroutine(list_of_lines, comments, parent):
59 | from .subroutine import Subroutine
60 | return parse_code(Subroutine, list_of_lines, comments, parent)
61 |
62 | # Define a function for parsing a Function. Returns a list of instances
63 | def parse_function(list_of_lines, comments, parent):
64 | from .function import Function
65 | return parse_code(Function, list_of_lines, comments, parent)
66 |
67 | # Define a function for parsing a Type. Returns a list of instances
68 | def parse_type(list_of_lines, comments, parent):
69 | from .type import TypeDeclaration
70 | return parse_code(TypeDeclaration, list_of_lines, comments, parent)
71 |
72 | # Parse a PUBLIC line, identifying routines that are public.
73 | def parse_public(list_of_lines, comments, parent, keyword="PUBLIC"):
74 | # Skip if there are no lines to process.
75 | if (len(list_of_lines) == 0): return []
76 | line = list_of_lines[0].strip().split()
77 | # Remove any comments on the line that may exist.
78 | if ("!" in line): line = line[:line.index("!")]
79 | # Skip empty lines.
80 | if (len(line) == 0):
81 | list_of_lines.pop(0)
82 | return []
83 | # Check to see if the first element is 'PUBLIC'.
84 | if (line[0] == keyword):
85 | # If there is only the word 'PUBLIC', then it is a status.
86 | if (len(line) == 1):
87 | parent.status = keyword
88 | # Remove this line from the list (because it has been parsed).
89 | list_of_lines.pop(0)
90 | return []
91 | # Strip out the double colon if it exists (it's optional).
92 | if (''.join(line[1:3]) == "::"): line = line[3:]
93 | # Remove all commas from the line.
94 | commas = [i for i in range(len(line)) if (line[i] == ",")]
95 | for i in reversed(commas): line.pop(i)
96 | # Remove this line from the list (because it has been parsed).
97 | list_of_lines.pop(0)
98 | # Only variable and function names remain, they are the instances.
99 | return line
100 | # Otherwise, no 'PUBLIC' line found, return empty list.
101 | return []
102 |
103 | # Parse a PRIVATE line, identifying routines that are private.
104 | def parse_private(list_of_lines, comments, parent):
105 | return parse_public(list_of_lines, comments, parent, keyword="PRIVATE")
106 |
107 | # Parse a USE line, return a string in a list.
108 | def parse_use(list_of_lines, comments, parent):
109 | # Skip if there are no lines to process.
110 | if (len(list_of_lines) == 0): return []
111 | line = list_of_lines[0].strip().split()
112 | # Skip empty lines.
113 | if (len(line) == 0): return []
114 | # Check to see if the first element is "USE".
115 | # If it is, then return a list with one string (the line).
116 | if (line[0] == "USE"): return [list_of_lines.pop(0).replace(": :","::")]
117 | # Otherwise, no USE line found, return empty list.
118 | else: return []
119 |
120 | # Parse a line with IMPLICIT NONE, return string in a list.
121 | def parse_implicit(list_of_lines, comments, parent):
122 | # Skip if there are no lines to process.
123 | if (len(list_of_lines) == 0): return []
124 | line = list_of_lines[0].strip().split()
125 | # Skip empty lines.
126 | if (len(line) == 0): return []
127 | # Check to see if the first element is "IMPLICIT".
128 | # If it is, then return a list with one string (the line).
129 | if (line[0] == "IMPLICIT"):
130 | if (len(line) > 1):
131 | if (line[1] == "NONE"): return [list_of_lines.pop(0)]
132 | else:
133 | from fmodpy.exceptions import ParseError
134 | raise(ParseError("Found phrase 'IMPLICIT' but it was not followed by 'NONE'."))
135 | # Otherwise, no IMPLICIT line found, return empty list.
136 | else: return []
137 |
138 | # Only initialize a "code" object if the line is not empty and the
139 | # type is matched (after removing any prefixes).
140 | def parse_code(code, list_of_lines, comments, parent):
141 | # Skip if there are no lines to process.
142 | if (len(list_of_lines) == 0): return []
143 | line = list_of_lines[0].strip().split()
144 | # Skip empty lines.
145 | if (len(line) == 0): return []
146 | # Remove a prefix if it is at the front of the line.
147 | for p in code.prefixes:
148 | if (line[0] == p):
149 | line = line[1:]
150 | break
151 | # Check for an empty line (this shouldn't happen).
152 | if (len(line) == 0):
153 | import warnings
154 | text = f"\nAn enexpected thing just happened when parsing.\n"+\
155 | f"After removing {class_name(code)} prefix '{p}', the line was empty.\n"
156 | warnings.warn(text+please_report_to)
157 | # Check for a match, if it matches complete instance initialization.
158 | elif (line[0] == code.type):
159 | parsed_code = code(list_of_lines, comments, parent)
160 | if (parsed_code.lines > 0): return [parsed_code]
161 | else: return []
162 | # No objects were found, return empty list of instances.
163 | return []
164 |
165 | # Given a list of strings that represent lines of a file, determine if
166 | # it is a recognizable declaration. If so, define Argument object(s),
167 | # return the list of Argument(s).
168 | def parse_argument(list_of_lines, comments, parent):
169 | line = list_of_lines[0].strip().split()
170 | success = False
171 | if (len(line) == 0): return []
172 | # Try out all possible declarations.
173 | from .real import Real
174 | from .integer import Integer
175 | from .logical import Logical
176 | from .character import Character
177 | from .complex import Complex
178 | from .type import TypeArgument
179 | for arg_type in [Real, Integer, Logical, Character, Complex, TypeArgument]:
180 | if (line[0] == arg_type.type):
181 | success = True
182 | break
183 | else: return []
184 | # If an argument type was identified, then finish parsing.
185 | double_colon = [i for i in range(len(line)-1)
186 | if line[i] == line[i+1] == ":"]
187 | # If there is no "::", then variable names will immediately follow type(kind).
188 | # TODO: This might be a parsing error, or a FortranLanguageError.
189 | if (len(double_colon) > 1): raise(NotImplementedError)
190 | elif (len(double_colon) == 1):
191 | # There is a double colon in this line.
192 | double_colon = double_colon[0]
193 | base = line[:double_colon]
194 | tail = line[double_colon+2:]
195 | else:
196 | # If there is a KIND, then include that in the base.
197 | if ((len(line) > 2) and (line[1]) == '('):
198 | # Check to see of there is a paranthetical group after
199 | # the arugment type (this would be for a KIND).
200 | kind, tail = pop_group(line[1:], open_with="(", close_with=")")
201 | base = [line[0]] + ["("] + kind + [")"]
202 | else:
203 | base = line[:1]
204 | tail = line[1:]
205 | # Check to make sure there are variable names after the TYPE.
206 | if (len(tail) == 0): raise(NotImplementedError)
207 | # Now we are guaranteed to use this line to define an argument.
208 | list_of_lines.pop(0)
209 | # Get all names from the tail (with their dimensions, if given).
210 | names = [tail.pop(0)]
211 | while (len(tail) > 0):
212 | # First, check to see of there is a paranthetical group after
213 | # the previous argument name (this would be for a size, like
214 | # ARG(10,2), but it could have expresssions inside as well).
215 | group, tail = pop_group(tail, open_with="(", close_with=")")
216 | if (len(group) > 0): names[-1] += " ( "+" ".join(group)+" )"
217 | # If there is a "," it indicates more arguments follow.
218 | elif (tail[0] == ","): tail.pop(0)
219 | # If this is a PARAMETER assignment, there could be an "= value"
220 | # after the name. There will only be other arguments after
221 | # this one if there is a "," in the line. If no "," then stop.
222 | elif (tail[0] == "="):
223 | if (("," in tail) and ("=" in tail[1:])):
224 | tail = tail[tail.index(",")+1:]
225 | else: break
226 | # Finally, if it is not a comma, group, or value, it must be an argument name.
227 | else: names.append(tail.pop(0))
228 | # Cycle the names, converting all into proper Argument objects.
229 | args = []
230 | for n in names:
231 | args.append( arg_type(base[:]) )
232 | args[-1].parent = parent
233 | # Split the dimension out of the name, if it is there.
234 | if "(" in n:
235 | begin_dim = n.index("(")
236 | n, group = n[:begin_dim].strip(), n[begin_dim:].strip().split()
237 | group, _ = pop_group(group)
238 | dimension = [""]
239 | num_open = 0
240 | while (len(group) > 0):
241 | next_value = group.pop(0)
242 | if (next_value == ",") and (num_open == 0):
243 | dimension.append("")
244 | else: dimension[-1] += next_value
245 | if (next_value == "("): num_open += 1
246 | if (next_value == ")"): num_open -= 1
247 | # Overwrite the dimension.
248 | args[-1].dimension = dimension
249 | # Overwrite the name.
250 | args[-1].name = n
251 | # Return the final list of declared arguments, and success.
252 | return args
253 |
--------------------------------------------------------------------------------
/fmodpy/parsing/_old_code.txt:
--------------------------------------------------------------------------------
1 | # TEMPORARY.
2 | _ = ', '.join([a.fort_call_name() for a in present])
3 | lines += [f"{indent}PRINT *, 'PRESENT: {_}'"]
4 |
5 |
6 | # TEMPORARY.
7 | _ = ', '.join([a.fort_call_name() for a in present])
8 |
9 |
10 |
11 | module = line_str_list[1]
12 | if (len(line_str_list) > 2):
13 | if (tuple(line_str_list[2:5]) != (",","ONLY",":")):
14 | from fmodpy.exceptions import FortranError
15 | raise(FortranError("Expected long 'USE' statment to have structure 'USE , ONLY: '.\n Recieved instead '{' '.join(line_str_list)}'."))
16 | names = line_str_list[5:]
17 | while "," in names: names.remove(",")
18 |
19 |
20 |
21 | # The string that will construct a corresponding Parameter object
22 | # as part of a Python signature.
23 | def py_signature(self):
24 | param = [f"'{self.name.lower()}'","inspect.Parameter.POSITIONAL_OR_KEYWORD"]
25 | # Get annotation based on size.
26 | if (self.size in self.np_types):
27 | if (self.dimension is None): dim = ""
28 | else: dim = str(self.dimension).lower().replace(' ','').replace("'",'')
29 | annotation = f"{self.np_types[self.size]}{dim}"
30 | param += [f"annotation='{annotation}'"]
31 | # Add the default value (if it is applicable).
32 | if self._is_optional(): param += ["default=None"]
33 | # Return the Parameter string that assumes "inspect" is imported.
34 | return [f"inspect.Parameter({', '.join(param)})"]
35 |
36 |
37 | # Given a simplified fortran file (list of strings, where each
38 | # element is a line from a file with only the necessary Fortran
39 | # constructs to define the interface with python), return a Fortran
40 | # object (a python-object abstraction of the Fortran file).
41 | def parse_fortran_file(fortran_file):
42 | from fmodpy.parsing.file import Fortran
43 | raise(NotImplementedError)
44 | return
45 |
46 |
47 | # Argument()
48 | # .name = ""
49 | # .type = ""
50 | # .size = ""
51 | # .kind = ""
52 | # .intent = ""
53 | # .message = ""
54 | # .defined = True / False
55 | # .allocatable = True / False
56 | # .optional = True / False
57 | # .dim = ["", ...]
58 | # ._is_optional()
59 | # ._is_present()
60 | # ._to_copy_args()
61 | # ._to_needed_dim()
62 | # .to_py_input_doc_string()
63 | # .to_py_output_doc_string()
64 | # .to_py_input()
65 | # .to_py_prepare()
66 | # .to_py_call()
67 | # .to_py_after()
68 | # .to_py_return()
69 | # .to_c_input()
70 | # .to_c_prepare()
71 | # .to_c_call()
72 | # .to_c_after()
73 | # .to_fort_input()
74 | # .to_fort_declare()
75 | # .to_fort_prepare()
76 | # .to_fort_call()
77 | # .to_fort_after()
78 | #
79 | # Real(Argument)
80 | #
81 | # Integer(Argument)
82 | #
83 | # Logical(Argument)
84 | #
85 | # Character(Argument)
86 | # .len = ""
87 | #
88 | # Procedure(Argument)
89 | # .interface = Interface()
90 | #
91 | # Type(Argument)
92 | # .contains = [, ...]
93 | #
94 |
95 |
96 |
--------------------------------------------------------------------------------
/fmodpy/parsing/character.py:
--------------------------------------------------------------------------------
1 | from .argument import Argument
2 |
3 | class Character(Argument):
4 | type = "CHARACTER"
5 | c_types = {"1":"ctypes.c_char"}
6 | default_singleton = "0"
7 |
8 | # Add a warning about logical arrays.
9 | def __init__(self, *args, **kwargs):
10 | super().__init__(*args, **kwargs)
11 | if (self.dimension is not None):
12 | # TODO: Change the C-type of this character be a "uint8" for arrays.
13 | # self.c_types = self.c_types.copy()
14 | # self.c_types["1"] = "ctypes.c_char" # Needs to be uint8 in numpy.
15 | # Show a warning to the user.
16 | from fmodpy.config import show_warnings
17 | if show_warnings:
18 | import warnings
19 | warnings.warn("Fortran CHARACTER arrays are not formally supported yet. Raise an issue with an example to encourage development.")
20 | # If there is a "len", make it the first dimension.
21 | if (self.kind_prefix.startswith("LEN")):
22 | self.dimension = [self.kind] + (self.dimension if self.dimension is not None else [])
23 |
--------------------------------------------------------------------------------
/fmodpy/parsing/complex.py:
--------------------------------------------------------------------------------
1 | from .argument import Argument
2 |
3 | COMPLEX_STRUCT = """
4 | # This defines a C structure that can be used to hold the complex C type.
5 | class c_complex_{type}(ctypes.Structure):
6 | _fields_ = [("real", ctypes.c_{type}), ("imag", ctypes.c_{type})]
7 | # Define an "__init__" that can take a complex number as input.
8 | def __init__(self, real=0.0, imag=None):
9 | # If the provided first value was a complex number, expand it out.
10 | if (hasattr(real, "imag") and hasattr(real, "real") and (imag is None)):
11 | imag = real.imag
12 | real = real.real
13 | # Assign the internal fields.
14 | self.real = real
15 | self.imag = imag
16 | # Define a "value" so that it can be cleanly retreived as a single complex number.
17 | @property
18 | def value(self):
19 | return complex(self.real, self.imag)
20 | """
21 |
22 | COMPLEX_FLOAT = COMPLEX_STRUCT.format(type="float")
23 | COMPLEX_DOUBLE = COMPLEX_STRUCT.format(type="double")
24 | COMPLEX_LONG_DOUBLE = COMPLEX_STRUCT.format(type="longdouble")
25 |
26 | class Complex(Argument):
27 | type = "COMPLEX"
28 | default_singleton = "1"
29 | c_types = {"32":"c_complex_double", "16":"c_complex_double", "8":"c_complex_float"}
30 | c_types_arrays = {"32":"numpy.complex128", "16":"numpy.complex128", "8":"numpy.complex64"}
31 | py_types = {"32":COMPLEX_DOUBLE, "16":COMPLEX_DOUBLE, "8":COMPLEX_FLOAT}
32 |
33 | @property
34 | def py_type(self):
35 | if (self.size not in self.py_types):
36 | raise(NotImplementedError(f"\n\nUnrecognized size '{self.size}' for argument '{self.name}', no known corresponding Pythonx type."))
37 | return self.py_types[self.size]
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/fmodpy/parsing/file.py:
--------------------------------------------------------------------------------
1 | from .code import Code
2 | from . import parse_subroutine, parse_function, parse_module, parse_type
3 |
4 | # --------------------------------------------------------------------
5 | # Class for holding all relevant information in a Fortran file.
6 | class Fortran(Code):
7 | docs = ""
8 | type = "FILE"
9 | needs_end = False
10 | allowed_unknown = True
11 | can_contain = [(parse_subroutine, "subroutines"),
12 | (parse_function, "functions"),
13 | (parse_module, "modules"),
14 | (parse_type, "types"),
15 | ]
16 |
17 | # Generate a string describing this file.
18 | def __str__(self):
19 | out = f"{self.type} {self.name}"
20 | for (_, name) in self.can_contain:
21 | if (len(getattr(self, name)) > 0): out += "\n"
22 | for obj in getattr(self,name):
23 | for line in str(obj).split("\n"):
24 | out += " "+line+"\n"
25 | return out
26 |
27 |
28 | # Create Fortran Subroutines with "BIND(C)" that are
29 | # accessible from C, translate arguments to Fortran, call the
30 | # Fortran code, and translates arguments back (if necessary).
31 | def generate_fortran(self):
32 | lines = ["! This automatically generated Fortran wrapper file allows codes",
33 | "! written in Fortran to be called directly from C and translates all",
34 | "! C-style arguments into expected Fortran-style arguments (with",
35 | "! assumed size, local type declarations, etc.).",
36 | ""]
37 | lines += super().generate_fortran()
38 | # Return the list of lines for this object (add an extra newline).
39 | from . import wrap_long_lines
40 | # Return the final Fortran file as a string.
41 | return "\n".join(wrap_long_lines(lines+['']))
42 |
43 | # Generate a string representation of the python code for this wrapper.
44 | def generate_python(self):
45 | # Define the header to this file. Some necessary variables are
46 | # left to be filled by formatting, namely:
47 | # f_compiler -- string
48 | # shared_object_name -- string
49 | # f_compiler_args -- string
50 | # dependencies -- list of strings
51 | # module_name -- string
52 | lines = ["'''This Python code is an automatically generated wrapper",
53 | "for Fortran code made by 'fmodpy'. The original documentation",
54 | "for the Fortran source code follows.",
55 | "",
56 | self.docs,
57 | "'''",
58 | '',
59 | 'import os',
60 | 'import ctypes',
61 | 'import platform',
62 | 'import numpy',
63 | '',
64 | '# --------------------------------------------------------------------',
65 | '# CONFIGURATION',
66 | '# ',
67 | '_verbose = {verbose_module}',
68 | '_fort_compiler = "{f_compiler}"',
69 | '_shared_object_name = "{shared_object_name}." + platform.machine() + ".so"',
70 | '_this_directory = os.path.dirname(os.path.abspath(__file__))',
71 | '_path_to_lib = os.path.join(_this_directory, _shared_object_name)',
72 | '_compile_options = {f_compiler_args}',
73 | '_ordered_dependencies = {dependencies}',
74 | '_symbol_files = {symbol_files}'
75 | '# ',
76 | '# --------------------------------------------------------------------',
77 | '# AUTO-COMPILING',
78 | '#',
79 | '# Try to import the prerequisite symbols for the compiled code.',
80 | 'for _ in _symbol_files:',
81 | ' _ = ctypes.CDLL(os.path.join(_this_directory, _), mode=ctypes.RTLD_GLOBAL)',
82 | '# Try to import the existing object. If that fails, recompile and then try.',
83 | 'try:',
84 | ' # Check to see if the source files have been modified and a recompilation is needed.',
85 | ' if (max(max([0]+[os.path.getmtime(os.path.realpath(os.path.join(_this_directory,_))) for _ in _symbol_files]),',
86 | ' max([0]+[os.path.getmtime(os.path.realpath(os.path.join(_this_directory,_))) for _ in _ordered_dependencies]))',
87 | ' > os.path.getmtime(_path_to_lib)):',
88 | ' print()',
89 | ' print("WARNING: Recompiling because the modification time of a source file is newer than the library.", flush=True)',
90 | ' print()',
91 | ' if os.path.exists(_path_to_lib):',
92 | ' os.remove(_path_to_lib)',
93 | ' raise NotImplementedError(f"The newest library code has not been compiled.")',
94 | ' # Import the library.',
95 | ' clib = ctypes.CDLL(_path_to_lib)',
96 | 'except:',
97 | ' # Remove the shared object if it exists, because it is faulty.',
98 | ' if os.path.exists(_shared_object_name):',
99 | ' os.remove(_shared_object_name)',
100 | ' # Compile a new shared object.',
101 | ' _command = [_fort_compiler] + _ordered_dependencies + _compile_options + ["-o", _shared_object_name]',
102 | ' if _verbose:',
103 | ' print("Running system command with arguments")',
104 | ' print(" ", " ".join(_command))',
105 | ' # Run the compilation command.',
106 | ' import subprocess',
107 | ' subprocess.check_call(_command, cwd=_this_directory)',
108 | ' # Import the shared object file as a C library with ctypes.',
109 | ' clib = ctypes.CDLL(_path_to_lib)',
110 | '# --------------------------------------------------------------------',
111 | '']
112 | # Add all the Python lines for the children of this Code.
113 | lines += super().generate_python()
114 | # Return the Python file as a string.
115 | return "\n".join(lines + [''])
116 |
--------------------------------------------------------------------------------
/fmodpy/parsing/function.py:
--------------------------------------------------------------------------------
1 | from .subroutine import Subroutine
2 |
3 | # --------------------------------------------------------------------
4 | class Function(Subroutine):
5 | type = "FUNCTION"
6 | result = None
7 |
8 | # Produce a string representation of this Subroutine object.
9 | def __str__(self):
10 | # Print out header line.
11 | arg_names = [a.name for a in self.arguments if (a.name != self.result)]
12 | out = f"{self.type} {self.name}({', '.join(arg_names)})"
13 | if (self.result != self.name): out += f" RESULT({self.result})\n"
14 | else: out += "\n"
15 | # Use SUBROUTINE string method for the rest (strip SUBROUTINE).
16 | out += "\n".join(super().__str__().split("\n")[1:])
17 | return out
18 |
19 | # Given a list of lines (of a source Fortran file), parse out this
20 | # Function (assuming the first line is the first line *inside* of
21 | # this Function).
22 | def parse(self, list_of_lines):
23 | # Pre-fetch the "result" and modify the first line so that it
24 | # can be parsed correctly by the Subroutine.parse function.
25 | declaration_line = list_of_lines[0].strip().split()
26 | arg_start = declaration_line.index("(")
27 | arg_end = declaration_line.index(")")
28 | argument_names = declaration_line[arg_start+1:arg_end]
29 | while "," in argument_names: argument_names.remove(",")
30 | name = declaration_line[:arg_start][-1]
31 | rest_of_line = declaration_line[arg_end+1:]
32 | # Get the result from the end of the line.
33 | if ("RESULT" in rest_of_line):
34 | if ("(" not in rest_of_line) or (")" not in rest_of_line):
35 | from fmodpy.exceptions import ParseError
36 | raise(ParseError(f"Found 'RESULT' but no '()' on line.\n{list_of_lines[0].strip()}"))
37 | self.result = ''.join(rest_of_line[rest_of_line.index("(")+1:
38 | rest_of_line.index(")")])
39 | else: self.result = name
40 | argument_names.append( self.result )
41 | list_of_lines[0] = f"SUBROUTINE {name} ( {' , '.join(argument_names)} )"
42 | # ------------------------------------------------------------
43 | # Use Subroutine parse code.
44 | super().parse(list_of_lines)
45 | # ------------------------------------------------------------
46 | # Find the result in the identified arguments, make sure its
47 | # intent is not declared (it will make Fortran compilers angry).
48 | for arg in self.arguments:
49 | if (arg.name == self.result):
50 | arg.intent = "OUT"
51 | arg.show_intent = False
52 | break
53 | else:
54 | from fmodpy.config import fmodpy_print as print
55 | print()
56 | print("Did not find output argument in parsed list..")
57 | print(self.result)
58 | print()
59 | raise(NotImplementedError)
60 |
61 |
--------------------------------------------------------------------------------
/fmodpy/parsing/implicit_none.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tchlux/fmodpy/88002dc980ef368d200ed6c114aeae5f0aaab297/fmodpy/parsing/implicit_none.py
--------------------------------------------------------------------------------
/fmodpy/parsing/integer.py:
--------------------------------------------------------------------------------
1 | from .argument import Argument
2 |
3 | class Integer(Argument):
4 | type = "INTEGER"
5 | c_types = {"8":"ctypes.c_long", "4":"ctypes.c_int"}
6 | default_singleton = "1"
7 |
--------------------------------------------------------------------------------
/fmodpy/parsing/interface.py:
--------------------------------------------------------------------------------
1 | from . import parse_subroutine, parse_function
2 | from .code import Code
3 |
4 | # PROCEDURE cannot have "INTENT".
5 | #
6 | # All passed procedure arguments, will be defined as
7 | # 'ABSTRACT INTERFACE's and declared at the front of the
8 | # definitions for a routine. Since they are passed, they must
9 | # be procedures with the BIND(C) attribute enabled.
10 |
11 |
12 | # For parsing purposes, an Interface looks like a Module.
13 | class Interface(Code):
14 | type = "INTERFACE"
15 | named_end = False
16 | # prefixes = ["ABSTRACT"] # <- need another type for abstrac ones.
17 | can_contain = [(parse_subroutine, "subroutines"),
18 | (parse_function, "functions")
19 | ]
20 |
21 | # Produce a string representation of this Subroutine object.
22 | def __str__(self):
23 | # Print out header line.
24 | out = "INTERFACE\n"
25 | for f in self.subroutines:
26 | for line in str(f).split("\n"):
27 | out += " "+line+"\n"
28 | if (len(self.subroutines) > 0): out += "\n"
29 | for f in self.functions:
30 | for line in str(f).split("\n"):
31 | out += " "+line+"\n"
32 | out += "END INTERFACE"
33 | # Return the whole interface.
34 | return out
35 |
36 | # Given a list of lines (of a source Fortran file), parse out this
37 | # Function (assuming the first line is the first line *inside* of
38 | # this Function).
39 | def parse(self, list_of_lines):
40 | # Pop out the first line, it's not needed.
41 | list_of_lines.pop(0)
42 | # Use standard parsing code (looks for subroutines and functions).
43 | super().parse(list_of_lines)
44 |
--------------------------------------------------------------------------------
/fmodpy/parsing/logical.py:
--------------------------------------------------------------------------------
1 | from .argument import Argument
2 |
3 | class Logical(Argument):
4 | type = "LOGICAL"
5 | c_types = {"4":"ctypes.c_int", "1":"ctypes.c_bool"}
6 | default_singleton = "0"
7 |
8 | # For array inputs of logicals, change the C-type to be an "int".
9 | def __init__(self, *args, **kwargs):
10 | super().__init__(*args, **kwargs)
11 | if (self.dimension is not None):
12 | self.c_types = self.c_types.copy()
13 | self.c_types["4"] = "ctypes.c_int"
14 |
15 | # Add a warning about logical arrays.
16 | def py_declare(self):
17 | # Show a warning to the user for passing in logical array types.
18 | if ((self.dimension is not None) and (self._allowed_input())):
19 | from fmodpy.config import show_warnings
20 | if show_warnings:
21 | import warnings
22 | warnings.warn("Fortran LOGICAL arrays must be given as 32-bit integers.")
23 | # Return the usual function.
24 | return super().py_declare()
25 |
26 |
--------------------------------------------------------------------------------
/fmodpy/parsing/module.py:
--------------------------------------------------------------------------------
1 | from .code import Code
2 | from . import parse_use, parse_implicit, parse_public, parse_private, \
3 | parse_argument, parse_interface, parse_type, parse_subroutine, \
4 | parse_function
5 | from .subroutine import Subroutine
6 | from .function import Function
7 |
8 |
9 | # --------------------------------------------------------------------
10 | class Module(Code):
11 | type = "MODULE"
12 | status = "PUBLIC"
13 | can_contain = [(parse_use, "uses"),
14 | (parse_implicit, "implicit_none"),
15 | (parse_public, "public"),
16 | (parse_private, "private"),
17 | (parse_type, "types"),
18 | (parse_interface, "interfaces"),
19 | (parse_argument, "arguments"),
20 | (parse_subroutine, "subroutines"),
21 | (parse_function, "functions"),
22 | ]
23 |
24 |
25 | # Produce a string representation of this code object.
26 | def __str__(self):
27 | out = f"{self.type} {self.name}\n"
28 | # Add documentation.
29 | if (len(self.docs.strip()) > 0):
30 | doc_lines = self.docs.strip().split("\n")
31 | for line in doc_lines: out += f" {line}\n"
32 | # Add used modules.
33 | for line in sorted(self.uses): out += f" {line}\n"
34 | for line in sorted(self.implicit_none): out += f" {line}\n"
35 | # Add types.
36 | for t in self.types:
37 | for line in str(t).split("\n"):
38 | out += f" {line}\n"
39 | # Add interfaces.
40 | if (len(self.interfaces) > 0): out += "\n"
41 | for i in self.interfaces:
42 | for line in str(i).split("\n"):
43 | out += f" {line}\n"
44 | # Only add the space before arguments if types or
45 | # interfaces came before them.
46 | if ((max(len(self.interfaces),len(self.types)) > 0) and
47 | (len(self.arguments) > 0)): out += "\n"
48 | # Add arguments.
49 | for a in self.arguments:
50 | out += f" {a}\n"
51 | # Add subroutines.
52 | for obj in self.subroutines:
53 | out += "\n"
54 | for line in str(obj).split("\n"):
55 | out += f" {line}\n"
56 | # Add functions.
57 | for obj in self.functions:
58 | out += "\n"
59 | for line in str(obj).split("\n"):
60 | out += f" {line}\n"
61 | # End the module.
62 | out += f"END {self.type} {self.name}\n"
63 | return out
64 |
65 | # Generate Fortran wrapper code that can call this Module's source code.
66 | def generate_fortran(self):
67 | # Generate the input signature.
68 | fortran_arguments = []
69 | for arg in self.arguments: fortran_arguments += arg.fort_input()
70 | lines = ['',f'{self.type} C_{self.name}']
71 | # Add the "USE" line.
72 | lines += self.uses
73 | if any((a.allocatable and a._is_output()) for a in self.arguments):
74 | lines += [' USE ISO_FORTRAN_ENV, ONLY: INT64']
75 | if any(a._is_optional() for a in self.arguments):
76 | lines += [' USE ISO_C_BINDING, ONLY: C_BOOL']
77 | # Enforce no implicit typing (within this code).
78 | lines += [f' IMPLICIT NONE']
79 | lines += ['']
80 |
81 | # Declare all interfaces.
82 | for i in self.interfaces:
83 | if (len(i.subroutines) + len(i.functions) > 0):
84 | lines += ['']
85 | lines += [' '+l for l in str(i).split("\n")]
86 |
87 | lines += ['']
88 | lines += ['CONTAINS']
89 | lines += ['']
90 |
91 | # Define all "getter" and "setter" subroutines for accessing
92 | # internal module (PUBLIC) attributes.
93 | for arg in self.arguments:
94 | lines += ['',f' ! Getter and setter for {arg.name}.']
95 | lines += [' '+l for l in arg.fort_getter()]
96 | lines += [' '+l for l in arg.fort_setter()]
97 |
98 | # Declare all subroutines and functions.
99 | for code in self.subroutines + self.functions:
100 | lines += ['']
101 | lines += [' ' + l for l in code.generate_fortran()]
102 |
103 | # Add the END line.
104 | lines += [f"END {self.type} C_{self.name}",'']
105 |
106 | return lines
107 |
108 | # Generate Python code.
109 | def generate_python(self, *args):
110 | # Add access points for the contained subroutines and functions.
111 | lines = [ '',
112 | f'class {self.name.lower()}:',
113 | f" '''{self.docs}'''"]
114 | # Generate internal types.
115 | for t in self.types:
116 | lines += [ " "+l for l in t.py_declare()]
117 | # Generate getter and setter for arguments.
118 | for arg in self.arguments:
119 | py_name = arg.name.lower()
120 | lines += ['']
121 | lines += [f" # Declare '{py_name}'"]
122 | # Declare the "getter" for this module variable.
123 | lines += [ " "+l for l in arg.py_getter() ]
124 | # Declare the "setter" for this module variable.
125 | lines += [ " "+l for l in arg.py_setter() ]
126 | # Declare this as a property that has a getter and a setter.
127 | lines += [ " "+l for l in arg.py_property() ]
128 | # Generate static methods for all internal routines.
129 | for code in self.subroutines + self.functions:
130 | lines += ['']
131 | lines += [' '+l for l in code.generate_python(*args)]
132 | # Replace the class definition with a single instance of the class.
133 | lines += [ "",
134 | f"{self.name.lower()} = {self.name.lower()}()"]
135 | return lines
136 |
137 | # Given a list of lines (of a source Fortran file), parse out this
138 | # module (assuming the first line is the first line *inside* of
139 | # this module).
140 | def parse(self, list_of_lines):
141 | self.lines += 1
142 | line = list_of_lines.pop(0).strip().split()
143 | # Remove any comments on the line that may exist.
144 | if ("!" in line): line = line[:line.index("!")]
145 | # Catch a potential parsing error.
146 | if (len(line) <= 1):
147 | from fmodpy.exceptions import ParseError
148 | raise(ParseError(f"Expected 'MODULE ', but the line did not contain the name of module.\n {list_of_lines[0]}"))
149 | # Get the name of this module.
150 | self.name = line[1]
151 | # ------- Default parsing operations -------
152 | super().parse(list_of_lines)
153 | # ------------------------------------------
154 | # Get the set of things declared private.
155 | private = set(self.private)
156 | # Remove any instances of things that are declared private.
157 | if (len(private) > 0):
158 | for attr in ("arguments","interfaces","types","subroutines","functions"):
159 | codes = getattr(self,attr)
160 | to_remove = [i for (i,c) in enumerate(codes) if (c.name in private)]
161 | for i in reversed(to_remove): codes.pop(i)
162 | if (len(to_remove) > 0):
163 | from fmodpy.config import fmodpy_print as print
164 | print(f" removed {len(to_remove)} from '{attr}' declared PRIVATE..")
165 | # Remove any things not declared public if this MODULE is private.
166 | if (self.status == "PRIVATE"):
167 | public = set(self.public)
168 | for attr in ("interfaces","types","subroutines","functions","arguments"):
169 | codes = getattr(self,attr)
170 | to_remove = [i for (i,c) in enumerate(codes) if (c.name not in public)]
171 | for i in reversed(to_remove): codes.pop(i)
172 | if (len(to_remove) > 0):
173 | from fmodpy.config import fmodpy_print as print
174 | print(f" removed {len(to_remove)} from '{attr}' because this MODULE is PRIVATE..")
175 | # Make sure all "arguments" don't show intent.
176 | for a in self.arguments: a.show_intent = False
177 |
--------------------------------------------------------------------------------
/fmodpy/parsing/procedure.py:
--------------------------------------------------------------------------------
1 | from .argument import Argument
2 |
3 | class Procedure(Argument):
4 | type = "PROCEDURE"
5 | interface = None # Interface()
6 |
7 |
--------------------------------------------------------------------------------
/fmodpy/parsing/real.py:
--------------------------------------------------------------------------------
1 | from .argument import Argument
2 |
3 | class Real(Argument):
4 | type = "REAL"
5 | c_types = {"8":"ctypes.c_double", "4":"ctypes.c_float"}
6 | default_singleton = "1"
7 |
--------------------------------------------------------------------------------
/fmodpy/parsing/type.py:
--------------------------------------------------------------------------------
1 | from .code import Code
2 | from .argument import Argument
3 | from . import parse_argument
4 |
5 | STRUCT_DEFAULT_VALUE = 0
6 | CONTENTS_DECLARATION = '("{n}", {t})'
7 | FIELD_DECLARATION = 'self.{n} = kwargs.get("{n}", getattr(value, "{n}", value))'
8 | STRUCTURE_DEFINITION = '''
9 | # This defines a C structure that can be used to hold this defined type.
10 | class {name}(ctypes.Structure):
11 | # (name, ctype) fields for this structure.
12 | _fields_ = [{fields}]
13 | # Define an "__init__" that can take a class or keyword arguments as input.
14 | def __init__(self, value={default}, **kwargs):
15 | # From whatever object (or dictionary) was given, assign internal values.
16 | {field_declarations}
17 | # Define a "__str__" that produces a legible summary of this type.
18 | def __str__(self):
19 | s = []
20 | for (n, t) in self._fields_:
21 | s.append( n + "=" + str(getattr(self,n)) )
22 | return "{name}[" + ", ".join(s) + "]"
23 | # Define an "__eq__" method that checks equality of all fields.
24 | def __eq__(self, other):
25 | for (n, t) in self._fields_:
26 | if (getattr(self, n) != getattr(other, n, None)):
27 | return False
28 | return True
29 | '''
30 |
31 |
32 | class TypeDeclaration(Code):
33 | type = "TYPE"
34 | can_contain = [(parse_argument, "arguments")]
35 |
36 | # Given a list of lines (of a source Fortran file), parse out this
37 | # TYPE (assuming the first line is the line declaring this
38 | # Subroutine).
39 | def parse(self, list_of_lines):
40 | # If there is "TYPE (" then this doesn't declare a type, it is an argument.
41 | if ("(" == list_of_lines[0][5:6]): return
42 | # Otherwise, this declares a type.
43 | self.lines += 1
44 | if ('BIND ( C )' not in list_of_lines[0]):
45 | from fmodpy.exceptions import NotSupportedError
46 | raise NotSupportedError(f"\n Fortran derived types must be C compatible and include 'BIND(C)' to be wrapped.\n Encountered this issue when parsing the following line:\n {list_of_lines[0]}")
47 | # Parse the name of this subroutine out of the argument list.
48 | declaration_line = list_of_lines.pop(0).strip().split()
49 | assert (':' in declaration_line), "Expected ':' to exist in TYPE declaration line:\n {' '.join(declaration_line)}"
50 | colon_index = -declaration_line[::-1].index(':')-1
51 | declaration_line = declaration_line[colon_index+1:]
52 | # Extract any "arguments" to the type.
53 | argument_names = set()
54 | # Assume the format "TYPE :: name"
55 | # or "TYPE :: name(arg1, arg2, ...)"
56 | if ('(' in declaration_line):
57 | self.name = declaration_line.pop(0)
58 | argument_names = {
59 | token for token in declaration_line
60 | if token not in {'(', ',', ')'}
61 | }
62 | else:
63 | self.name = declaration_line[-1]
64 | # ------- Default parsing operations -------
65 | super().parse(list_of_lines)
66 | # ------------------------------------------
67 | # Disable showing "INTENT" on all arguments of this TYPE.
68 | for arg in self.arguments:
69 | arg.show_intent = False
70 | # Verify that all arguments are defined and permissable.
71 | if (len(argument_names) > 0):
72 | for arg in self.arguments:
73 | if (arg.name in argument_names):
74 | if (arg.type_kind):
75 | from fmodpy.exceptions import NotSupportedError
76 | raise(NotSupportedError(
77 | "\nDerived types with KIND parameters are not supported yet."+
78 | "\n Consider posting a simplified example with your motivation to"+
79 | "\n https://github.com/tchlux/fmodpy/issues"
80 | ))
81 | elif (not arg.type_len):
82 | raise(NotImplemented())
83 | argument_names.remove(arg.name)
84 | assert (len(argument_names) == 0), f'TYPE arguments {argument_names} were not defined within the type.'
85 |
86 |
87 | # Declare the lines that create the corresponding Struct for this type.
88 | # TODO: Treat the type like it is a c function being called, include
89 | # size parameters for arrays (when it is not C compatible).
90 | def py_declare(self):
91 | # Get the names and types necessary to declare a C-compatible struct.
92 | name = self.name
93 | names = [arg.name for arg in self.arguments]
94 | types = [arg.c_type for arg in self.arguments]
95 | struct = STRUCTURE_DEFINITION.format(
96 | name = name,
97 | default = STRUCT_DEFAULT_VALUE,
98 | fields = ", ".join([CONTENTS_DECLARATION.format(n=n.lower(), t=t)
99 | for (n,t) in zip(names, types)]),
100 | field_declarations = "\n ".join([FIELD_DECLARATION.format(n=n.lower())
101 | for n in names])
102 | )
103 | # Return the list of lines that defines this struct.
104 | return struct.split("\n")
105 |
106 | # Declare the lines that create this type in Fortran.
107 | def fort_declare(self):
108 | return str(self).split("\n")
109 |
110 | def __str__(self):
111 | # Define the arguments to the type (for constructing its internals).
112 | type_args = [a.name for a in self.arguments if a.type_len]
113 | if (len(type_args) > 0):
114 | args = f"(" + ", ".join(type_args) + ")"
115 | else:
116 | args = ""
117 | # Define the type.
118 | out = f"{self.type}, BIND(C) :: {self.name}{args}\n"
119 | # Add arguments.
120 | for a in self.arguments:
121 | temp = a.copy()
122 | temp.show_intent = False
123 | out += f" {temp}\n"
124 | # End type.
125 | out += f"END {self.type} {self.name}"
126 | return out
127 |
128 |
129 | class TypeArgument(Argument):
130 | type = "TYPE"
131 | c_types = {}
132 | default_singleton = 1
133 | kind_prefix = ""
134 |
135 | # For a type, the "c_type" in python is the type name (assuming
136 | # the struct was defined in an accessible scope).
137 | @property
138 | def c_type(self):
139 | return self.kind
140 |
141 | # TODO: Make both the type and a C compatible equivalent.
142 | # def fort_declare(self):
143 |
144 | # TODO: Copy the input into the true Fortran type.
145 | # def fort_before(self):
146 |
147 | # TODO: Copy the result from the call outputs into the C inputs.
148 | # def fort_after(self):
149 |
--------------------------------------------------------------------------------
/fmodpy/parsing/use.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tchlux/fmodpy/88002dc980ef368d200ed6c114aeae5f0aaab297/fmodpy/parsing/use.py
--------------------------------------------------------------------------------
/fmodpy/parsing/util.py:
--------------------------------------------------------------------------------
1 | from fmodpy.parsing import FORT_TEXT_REPLACEMENTS, FORT_TEXT_FIXES
2 |
3 | # Short functions.
4 | # identifying the strings before and after the last "."
5 | def before_dot(name): return name[:len(name) - 1 - name[::-1].find(".")];
6 | def after_dot(name): return name[min(-name[::-1].find("."),0):]
7 | # getting the class name of something, stripping module prefix
8 | def class_name(cls): return str(type(cls)).split(".")[-1].split("'")[0]
9 | # identifying legal python module names
10 | def legal_module_name(name):
11 | return (name.replace("_","")[0].isalpha() and
12 | name.replace("_","")[1:].isalnum() and
13 | (len(name) > 0))
14 |
15 | # Shorten all strings to be 132 characters at maximum given a list of str.
16 | def wrap_long_lines(list_of_lines, max_len=132):
17 | i = -1
18 | while (i+1 < len(list_of_lines)):
19 | i += 1
20 | # Get the line (strip off any comments for length checks).
21 | line = list_of_lines[i]
22 | if "!" in line: line = line[:line.index("!")]
23 | # Check the length of the (uncommented) line.
24 | if (len(line) > max_len-1):
25 | # Break up this line if it is too long.
26 | keep, rest = list_of_lines[i][:max_len-1], list_of_lines[i][max_len-1:]
27 | list_of_lines[i] = keep+"&"
28 | list_of_lines.insert(i+1, "&"+rest)
29 | return list_of_lines
30 |
31 | # Function for efficiently performing a series of replacements on a line of text
32 | def clean_text(text, replacements=FORT_TEXT_REPLACEMENTS, fixes=FORT_TEXT_FIXES):
33 | import re
34 | # Create a proper reg-exp dictionary of replacements
35 | replacements = {re.escape(k):v for (k,v) in replacements.items()}
36 | # Generate a regular expression pattern with that dictionary
37 | pattern = re.compile("|".join(replacements.keys()))
38 | # Perform the replacement using python reg-exp search
39 | cleaned_file = pattern.sub(lambda m: replacements[re.escape(m.group(0))], text)
40 | # Now repeat the above steps undoing any broken elements.
41 | fixes = {re.escape(k):v for (k,v) in fixes.items()}
42 | pattern = re.compile("|".join(fixes.keys()))
43 | fixed_file = pattern.sub(lambda m: fixes[re.escape(m.group(0))], cleaned_file)
44 | return fixed_file
45 |
46 | # Read a fortran file, store it as single lines
47 | # (without leading and trailing whitespace) and return
48 | def simplify_fortran_file(in_file, fixed_format=False):
49 | from fmodpy.parsing import ACCEPTABLE_LINE_STARTS, \
50 | LINE_STARTS_TO_REMOVE, IMMEDIATELY_EXCLUDE
51 | with open(in_file) as f:
52 | fort_file = []
53 | curr_line = ""
54 | for line in f.readlines():
55 | if (fixed_format) and (len(line) >0) and (line[0].upper() == "C"):
56 | line = "!" + line[1:]
57 | # Split out the comments from the line
58 | comment_start = line.find("!") if (line.find("!") != -1) else len(line)
59 | line, comment = line[:comment_start], line[comment_start:].strip()
60 | # Keep lines that are strictly comments (might be documentation)
61 | if len(line.strip()) == 0:
62 | fort_file.append(comment)
63 | continue
64 | # Make all fortran upper case
65 | line = line.upper()
66 | # Break the line by the colon character
67 | lines = line.split(";")
68 | for line in lines:
69 | if len(line.strip()) == 0: continue
70 | if (fixed_format):
71 | line = line[:72]
72 | # Line continuation character in column 5
73 | if (len(line) > 5) and (not line[5].isspace()):
74 | line = "&" + line[6:]
75 | # Retro-actively pop the previous line for continuation
76 | if len(curr_line) == 0: curr_line = fort_file.pop(-1)
77 | # Remove any numeric labels from the front of the line
78 | line = line.strip().split()
79 | if (len(line) > 0) and (line[0].isnumeric()): line = line[1:]
80 | line = " ".join(line)
81 | # After processing old fortran, properly strip the line of whitespace
82 | line = line.strip()
83 | # Remove a leading ampersand if necessary
84 | if (line[0] == "&"):
85 | line = line[1:]
86 | curr_line += line
87 | if ((len(line) > 0) and (line[-1] == "&")):
88 | curr_line = curr_line[:-1]
89 | else:
90 | # Process the line into a common format
91 | # (upper case, space separated, no commas)
92 | clean_line = clean_text(curr_line)
93 | line = [v.strip() for v in clean_line.split()]
94 | # Only take lines that are acceptable
95 | acceptable = (len(line) > 0) and (
96 | ("!" in line[0]) or (line[0] in ACCEPTABLE_LINE_STARTS))
97 | # Check for certain exclusions (like "PROGRAM").
98 | if (len(line) > 0) and (line[0] in IMMEDIATELY_EXCLUDE):
99 | from fmodpy.exceptions import FortranError
100 | raise(FortranError(("A valid fortran python module cannot"+
101 | " contain '%s'.")%(line[0])))
102 | # Store the line if it was acceptable.
103 | if acceptable:
104 | # Remove line starts that can be ignored safely.
105 | while ((len(line) > 0) and (line[0] in LINE_STARTS_TO_REMOVE)):
106 | line.pop(0)
107 | # Add the line to the simplified Fortran file.
108 | fort_file.append( " ".join(line) )
109 | curr_line = ""
110 | return fort_file
111 |
112 |
113 | # Given a list of strings, return the group of elements between
114 | # and . This is initially designed to extract
115 | # the elements within an open-close parenthesis while allowing for
116 | # nested parenthetical groups. Returns two lists, one containing the
117 | # group (if it starts at the beginning of , else empty), the
118 | # other containing the remainder of the list of strings.
119 | def pop_group(list_str, open_with="(", close_with=")"):
120 | group = []
121 | # Get the first element of the string (open the group, if matched).
122 | if ((len(list_str) > 0) and (list_str[0] == open_with)):
123 | list_str.pop(0)
124 | num_open = 1
125 | else: num_open = 0
126 | # Search until the started group is closed.
127 | while (num_open > 0):
128 | # TOOD: Might need to raise parsing error when this happens.
129 | if (len(list_str) == 0): raise(NotImplementedError)
130 | next_value = list_str.pop(0)
131 | if (next_value == open_with): num_open += 1
132 | elif (next_value == close_with): num_open -= 1
133 | if (num_open != 0): group.append(next_value)
134 | # Return the captured portion and the remaining.
135 | return group, list_str
136 |
137 |
--------------------------------------------------------------------------------
/fmodpy/test/.coverage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tchlux/fmodpy/88002dc980ef368d200ed6c114aeae5f0aaab297/fmodpy/test/.coverage
--------------------------------------------------------------------------------
/fmodpy/test/character/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name, verbose=False, wrap=True,
12 | rebuild=True, show_warnings=False)
13 | # ---------------------------------------------------------------
14 | # Begin specific testing code.
15 |
16 | import ctypes
17 | import numpy as np
18 | a = np.array(list(map(ord,['1', '0', '1', '0', '1', '0', '1', '0'])),
19 | dtype=ctypes.c_char, order='F')
20 | out = a.copy()
21 | # Check for successful copy.
22 | result = fort.test_simple_character(a, b=out, c=ord('1'))
23 | assert(all(result.view('uint8') == a.view('uint8')))
24 | # Check for successful overwrite.
25 | result = fort.test_simple_character(a, b=out, c=ord('0'))
26 | assert(all(result.view('uint8') == [1, 2, 0, 1, 2, 0, 1, 2]))
27 |
28 | # Check for string copy in and out correctly.
29 | a = "abc"
30 | b = fort.test_string(a)
31 | assert(a == b)
32 |
33 | # End specific testing code.
34 | # ---------------------------------------------------------------
35 | print("passed", flush=True)
36 | import shutil
37 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
38 |
39 | if __name__ == "__main__":
40 | test()
41 |
--------------------------------------------------------------------------------
/fmodpy/test/character/test_character.f03:
--------------------------------------------------------------------------------
1 |
2 | SUBROUTINE TEST_SIMPLE_CHARACTER(A,C,B)
3 | CHARACTER, INTENT(IN), DIMENSION(:) :: A
4 | CHARACTER, INTENT(IN), OPTIONAL :: C
5 | CHARACTER, INTENT(OUT), DIMENSION(SIZE(A)) :: B
6 | ! Local
7 | INTEGER :: I
8 | B(:) = 'A'
9 | DO I = 1, SIZE(B)
10 | IF (PRESENT(C) .AND. (C .EQ. '1')) THEN
11 | B(I) = A(I)
12 | ELSE
13 | B(I) = CHAR(MOD(I,3))
14 | END IF
15 | END DO
16 | END SUBROUTINE TEST_SIMPLE_CHARACTER
17 |
18 |
19 | SUBROUTINE TEST_STRING(A,B)
20 | CHARACTER(LEN=*), INTENT(IN) :: A
21 | CHARACTER(LEN=LEN(A)), INTENT(OUT) :: B
22 | INTEGER :: I
23 | DO I = 1, LEN(A)
24 | B(I:I) = A(I:I)
25 | END DO
26 | END SUBROUTINE TEST_STRING
27 |
28 |
29 | ! python3 -c "import fmodpy as f; f.fimport('simple_character.f03', build_dir='.', verbose=True)"
30 | ! python3 -c "import og_fmodpy as f; f.wrap('simple_character.f03', working_directory='og_fmodpy', verbose=True)"
31 |
--------------------------------------------------------------------------------
/fmodpy/test/complex128/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 |
15 | import numpy as np
16 | n = 10
17 | array_in = np.asarray(np.arange(n), dtype=np.complex128, order='F')
18 | array_out = np.zeros(n//2 - 1, dtype=np.complex128, order='F')
19 | sing_in = complex(7,0)
20 | out = fort.test_standard(sing_in, array_in, array_out)
21 | # Test the standard functionality.
22 | assert(out[0] == 8), out[0]
23 | assert(all(out[1] == [0,1,2,3]))
24 | assert(all(out[2] == [1,2,3,4]))
25 | assert(np.all(out[3] == [[2,3,4,5],
26 | [3,4,5,6],
27 | [4,5,6,7]]))
28 | assert(out[4] == None)
29 | # Test the extended functionality.
30 | out = fort.test_extended(4)
31 | assert(out[0] == None)
32 | assert(out[1] == None)
33 | assert(all(out[2] == [4, 3, 2, 1]))
34 | # WARNING: In the current code, the memory associated with out[2a]
35 | # will be freed by Fortran on subsequent calls to the
36 | # function "test_extended". Copy for object permanance.
37 | out2 = fort.test_extended(10, known_opt_array_out=True)
38 | assert(all(out2[0] == [1,2,3]))
39 | assert(out2[1] == None)
40 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
41 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
42 | # will be freed by Fortran on subsequent calls to
43 | # "test_extended". Copies should be made for permanence.
44 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
45 | assert(out3[0] == None)
46 | assert(all(out3[1] == [3,2,1]))
47 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
48 |
49 | # End specific testing code.
50 | # ---------------------------------------------------------------
51 | print("passed", flush=True)
52 | import shutil
53 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
54 |
55 | if __name__ == "__main__":
56 | test()
57 |
--------------------------------------------------------------------------------
/fmodpy/test/complex128/test_complex128.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran COMPLEX wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'COMPLEX' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | USE ISO_FORTRAN_ENV, ONLY: REAL64
10 | USE ISO_FORTRAN_ENV, ONLY: REAL64
11 | IMPLICIT NONE
12 | ! Argument definitions.
13 | COMPLEX(KIND=REAL64), INTENT(IN) :: SING_IN
14 | COMPLEX(KIND=REAL64), INTENT(OUT) :: SING_OUT
15 | COMPLEX(KIND=REAL64), DIMENSION(:), INTENT(IN) :: ARRAY_IN
16 | COMPLEX(KIND=REAL64), DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
17 | COMPLEX(KIND=REAL64), DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
18 | COMPLEX(KIND=REAL64), DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
19 | COMPLEX(KIND=REAL64), INTENT(IN), OPTIONAL :: OPT_SING_IN
20 | COMPLEX(KIND=REAL64), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
21 | ! Local variable.
22 | INTEGER :: I
23 | ! Copy the single input value to the single output value.
24 | SING_OUT = SING_IN + 1
25 | ! Copy as much of the input array as possible to the output array.
26 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
27 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
28 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
29 | ARRAY_OUT(I) = I
30 | END DO
31 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
32 | KNOWN_MATRIX_OUT(I,:) = I
33 | END DO
34 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
35 | DO I = 1,SIZE(ARRAY_OUT)
36 | KNOWN_ARRAY_OUT(I) = I
37 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
38 | END DO
39 | ! Do some operations on the optional inputs / outputs.
40 | IF (PRESENT(OPT_SING_OUT)) THEN
41 | IF (PRESENT(OPT_SING_IN)) THEN
42 | OPT_SING_OUT = OPT_SING_IN
43 | ELSE
44 | OPT_SING_OUT = SING_IN
45 | END IF
46 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
47 | SING_OUT = OPT_SING_IN
48 | END IF
49 | ! End of this subroutine.
50 | END SUBROUTINE TEST_STANDARD
51 |
52 |
53 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
54 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
55 | ! Test the extended functionaly of the 'COMPLEX' type and its
56 | ! interoperability with Python. This includes, optional array
57 | ! inputs, optional array outputs, and allocatable array outputs.
58 | USE ISO_FORTRAN_ENV, ONLY: REAL64
59 | IMPLICIT NONE
60 | COMPLEX(REAL64), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
61 | COMPLEX(REAL64), INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
62 | COMPLEX(REAL64), INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
63 | COMPLEX(REAL64), DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
64 | INTEGER, INTENT(IN) :: N
65 | ! Local variable.
66 | INTEGER :: I
67 |
68 | ! Assign the optional array output values.
69 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
70 | IF (PRESENT(OPT_ARRAY_IN)) THEN
71 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
72 | KNOWN_OPT_ARRAY_OUT(I) = I
73 | END DO
74 | ELSE
75 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
76 | KNOWN_OPT_ARRAY_OUT(I) = I
77 | END DO
78 | END IF
79 | END IF
80 |
81 | ! Allocate the optional array output and assign its values.
82 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
83 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
84 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
85 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
86 | END DO
87 | END IF
88 |
89 | ! Allocate the required array output to the specified size.
90 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
91 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
92 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
93 | END DO
94 |
95 | ! End of function.
96 | END FUNCTION TEST_EXTENDED
97 |
--------------------------------------------------------------------------------
/fmodpy/test/complex256/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | rebuild=True, wrap=False, verbose=True,
12 | output_dir=dir_name)
13 | # ---------------------------------------------------------------
14 | # Begin specific testing code.
15 |
16 | import numpy as np
17 | n = 10
18 | array_in = np.asarray(np.arange(n), dtype=np.complex256, order='F')
19 | array_out = np.zeros(n//2 - 1, dtype=np.complex256, order='F')
20 | sing_in = complex(7,0)
21 | out = fort.test_standard(sing_in, array_in, array_out)
22 | # Test the standard functionality.
23 | assert(out[0] == 8), out[0]
24 | assert(all(out[1] == [0,1,2,3]))
25 | assert(all(out[2] == [1,2,3,4]))
26 | assert(np.all(out[3] == [[2,3,4,5],
27 | [3,4,5,6],
28 | [4,5,6,7]]))
29 | assert(out[4] == None)
30 | # Test the extended functionality.
31 | out = fort.test_extended(4)
32 | assert(out[0] == None)
33 | assert(out[1] == None)
34 | assert(all(out[2] == [4, 3, 2, 1]))
35 | # WARNING: In the current code, the memory associated with out[2a]
36 | # will be freed by Fortran on subsequent calls to the
37 | # function "test_extended". Copy for object permanance.
38 | out2 = fort.test_extended(10, known_opt_array_out=True)
39 | assert(all(out2[0] == [1,2,3]))
40 | assert(out2[1] == None)
41 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
42 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
43 | # will be freed by Fortran on subsequent calls to
44 | # "test_extended". Copies should be made for permanence.
45 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
46 | assert(out3[0] == None)
47 | assert(all(out3[1] == [3,2,1]))
48 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
49 |
50 | # End specific testing code.
51 | # ---------------------------------------------------------------
52 | print("passed", flush=True)
53 | import shutil
54 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
55 |
56 | if __name__ == "__main__":
57 | test()
58 |
--------------------------------------------------------------------------------
/fmodpy/test/complex256/test_complex256.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran COMPLEX wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'COMPLEX' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | USE ISO_FORTRAN_ENV, ONLY: REAL128
10 | USE ISO_FORTRAN_ENV, ONLY: REAL128
11 | IMPLICIT NONE
12 | ! Argument definitions.
13 | COMPLEX(KIND=REAL128), INTENT(IN) :: SING_IN
14 | COMPLEX(KIND=REAL128), INTENT(OUT) :: SING_OUT
15 | COMPLEX(KIND=REAL128), DIMENSION(:), INTENT(IN) :: ARRAY_IN
16 | COMPLEX(KIND=REAL128), DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
17 | COMPLEX(KIND=REAL128), DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
18 | COMPLEX(KIND=REAL128), DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
19 | COMPLEX(KIND=REAL128), INTENT(IN), OPTIONAL :: OPT_SING_IN
20 | COMPLEX(KIND=REAL128), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
21 | ! Local variable.
22 | INTEGER :: I
23 | PRINT *, ''
24 | PRINT *, 'SING_IN ', SING_IN
25 | PRINT *, 'SING_OUT', SING_OUT
26 | PRINT *, 'ARRAY_IN ', ARRAY_IN
27 | PRINT *, 'ARRAY_OUT', ARRAY_OUT
28 | ! Copy the single input value to the single output value.
29 | SING_OUT = SING_IN + 1
30 | ! Copy as much of the input array as possible to the output array.
31 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
32 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
33 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
34 | ARRAY_OUT(I) = I
35 | END DO
36 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
37 | KNOWN_MATRIX_OUT(I,:) = I
38 | END DO
39 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
40 | DO I = 1,SIZE(ARRAY_OUT)
41 | KNOWN_ARRAY_OUT(I) = I
42 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
43 | END DO
44 | ! Do some operations on the optional inputs / outputs.
45 | IF (PRESENT(OPT_SING_OUT)) THEN
46 | IF (PRESENT(OPT_SING_IN)) THEN
47 | OPT_SING_OUT = OPT_SING_IN
48 | ELSE
49 | OPT_SING_OUT = SING_IN
50 | END IF
51 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
52 | SING_OUT = OPT_SING_IN
53 | END IF
54 | ! End of this subroutine.
55 | PRINT *, ''
56 | PRINT *, 'SING_IN ', SING_IN
57 | PRINT *, 'SING_OUT', SING_OUT
58 | PRINT *, 'ARRAY_IN ', ARRAY_IN
59 | PRINT *, 'ARRAY_OUT', ARRAY_OUT
60 | END SUBROUTINE TEST_STANDARD
61 |
62 |
63 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
64 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
65 | ! Test the extended functionaly of the 'COMPLEX' type and its
66 | ! interoperability with Python. This includes, optional array
67 | ! inputs, optional array outputs, and allocatable array outputs.
68 | USE ISO_FORTRAN_ENV, ONLY: REAL128
69 | IMPLICIT NONE
70 | COMPLEX(KIND=REAL128), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
71 | COMPLEX(KIND=REAL128), INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
72 | COMPLEX(KIND=REAL128), INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
73 | COMPLEX(KIND=REAL128), DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
74 | INTEGER, INTENT(IN) :: N
75 | ! Local variable.
76 | INTEGER :: I
77 |
78 | ! Assign the optional array output values.
79 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
80 | IF (PRESENT(OPT_ARRAY_IN)) THEN
81 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
82 | KNOWN_OPT_ARRAY_OUT(I) = I
83 | END DO
84 | ELSE
85 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
86 | KNOWN_OPT_ARRAY_OUT(I) = I
87 | END DO
88 | END IF
89 | END IF
90 |
91 | ! Allocate the optional array output and assign its values.
92 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
93 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
94 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
95 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
96 | END DO
97 | END IF
98 |
99 | ! Allocate the required array output to the specified size.
100 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
101 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
102 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
103 | END DO
104 |
105 | ! End of function.
106 | END FUNCTION TEST_EXTENDED
107 |
--------------------------------------------------------------------------------
/fmodpy/test/complex256/test_complex256/test_complex256_c_wrapper.f90:
--------------------------------------------------------------------------------
1 | ! This automatically generated Fortran wrapper file allows codes
2 | ! written in Fortran to be called directly from C and translates all
3 | ! C-style arguments into expected Fortran-style arguments (with
4 | ! assumed size, local type declarations, etc.).
5 |
6 |
7 | SUBROUTINE C_TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN_DIM_1, ARRAY_IN, ARRAY_OUT_DIM_1, ARRAY_OUT, KNOWN_ARRAY_OUT_DIM_1, KNOWN_AR&
8 | &RAY_OUT, KNOWN_MATRIX_OUT_DIM_1, KNOWN_MATRIX_OUT_DIM_2, KNOWN_MATRIX_OUT, OPT_SING_IN_PRESENT, OPT_SING_IN, OPT_SING_OUT_PRESENT,&
9 | & OPT_SING_OUT) BIND(C)
10 | USE ISO_FORTRAN_ENV , ONLY : REAL128
11 | USE ISO_FORTRAN_ENV , ONLY : REAL128
12 | IMPLICIT NONE
13 | COMPLEX(KIND=REAL128), INTENT(IN) :: SING_IN
14 | COMPLEX(KIND=REAL128), INTENT(OUT) :: SING_OUT
15 | INTEGER, INTENT(IN) :: ARRAY_IN_DIM_1
16 | COMPLEX(KIND=REAL128), INTENT(IN), DIMENSION(ARRAY_IN_DIM_1) :: ARRAY_IN
17 | INTEGER, INTENT(IN) :: ARRAY_OUT_DIM_1
18 | COMPLEX(KIND=REAL128), INTENT(OUT), DIMENSION(ARRAY_OUT_DIM_1) :: ARRAY_OUT
19 | INTEGER, INTENT(IN) :: KNOWN_ARRAY_OUT_DIM_1
20 | COMPLEX(KIND=REAL128), INTENT(OUT), DIMENSION(KNOWN_ARRAY_OUT_DIM_1) :: KNOWN_ARRAY_OUT
21 | INTEGER, INTENT(IN) :: KNOWN_MATRIX_OUT_DIM_1
22 | INTEGER, INTENT(IN) :: KNOWN_MATRIX_OUT_DIM_2
23 | COMPLEX(KIND=REAL128), INTENT(OUT), DIMENSION(KNOWN_MATRIX_OUT_DIM_1,KNOWN_MATRIX_OUT_DIM_2) :: KNOWN_MATRIX_OUT
24 | LOGICAL, INTENT(IN) :: OPT_SING_IN_PRESENT
25 | COMPLEX(KIND=REAL128), INTENT(IN) :: OPT_SING_IN
26 | LOGICAL, INTENT(IN) :: OPT_SING_OUT_PRESENT
27 | COMPLEX(KIND=REAL128), INTENT(OUT) :: OPT_SING_OUT
28 |
29 | INTERFACE
30 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT, KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
31 | ! Test the basic functionaly of the 'COMPLEX' type and its
32 | ! interoperability with Python. This includes, inputs, outputs,
33 | ! array inputs with known and unknown size, optional inputs, and
34 | ! optional outputs.
35 | USE ISO_FORTRAN_ENV , ONLY : REAL128
36 | USE ISO_FORTRAN_ENV , ONLY : REAL128
37 | IMPLICIT NONE
38 | COMPLEX(KIND=REAL128), INTENT(IN) :: SING_IN
39 | COMPLEX(KIND=REAL128), INTENT(OUT) :: SING_OUT
40 | COMPLEX(KIND=REAL128), INTENT(IN), DIMENSION(:) :: ARRAY_IN
41 | COMPLEX(KIND=REAL128), INTENT(OUT), DIMENSION(:) :: ARRAY_OUT
42 | COMPLEX(KIND=REAL128), INTENT(OUT), DIMENSION(SIZE(ARRAY_OUT)) :: KNOWN_ARRAY_OUT
43 | COMPLEX(KIND=REAL128), INTENT(OUT), DIMENSION(3,SIZE(ARRAY_OUT)) :: KNOWN_MATRIX_OUT
44 | COMPLEX(KIND=REAL128), INTENT(IN), OPTIONAL :: OPT_SING_IN
45 | COMPLEX(KIND=REAL128), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
46 | END SUBROUTINE TEST_STANDARD
47 | END INTERFACE
48 |
49 | IF (OPT_SING_IN_PRESENT) THEN
50 | IF (OPT_SING_OUT_PRESENT) THEN
51 | CALL TEST_STANDARD(SING_IN=SING_IN, SING_OUT=SING_OUT, ARRAY_IN=ARRAY_IN, ARRAY_OUT=ARRAY_OUT, KNOWN_ARRAY_OUT=KNOWN_ARRAY_OU&
52 | &T, KNOWN_MATRIX_OUT=KNOWN_MATRIX_OUT, OPT_SING_IN=OPT_SING_IN, OPT_SING_OUT=OPT_SING_OUT)
53 | ELSE
54 | CALL TEST_STANDARD(SING_IN=SING_IN, SING_OUT=SING_OUT, ARRAY_IN=ARRAY_IN, ARRAY_OUT=ARRAY_OUT, KNOWN_ARRAY_OUT=KNOWN_ARRAY_OU&
55 | &T, KNOWN_MATRIX_OUT=KNOWN_MATRIX_OUT, OPT_SING_IN=OPT_SING_IN)
56 | END IF
57 | ELSE
58 | IF (OPT_SING_OUT_PRESENT) THEN
59 | CALL TEST_STANDARD(SING_IN=SING_IN, SING_OUT=SING_OUT, ARRAY_IN=ARRAY_IN, ARRAY_OUT=ARRAY_OUT, KNOWN_ARRAY_OUT=KNOWN_ARRAY_OU&
60 | &T, KNOWN_MATRIX_OUT=KNOWN_MATRIX_OUT, OPT_SING_OUT=OPT_SING_OUT)
61 | ELSE
62 | CALL TEST_STANDARD(SING_IN=SING_IN, SING_OUT=SING_OUT, ARRAY_IN=ARRAY_IN, ARRAY_OUT=ARRAY_OUT, KNOWN_ARRAY_OUT=KNOWN_ARRAY_OU&
63 | &T, KNOWN_MATRIX_OUT=KNOWN_MATRIX_OUT)
64 | END IF
65 | END IF
66 | END SUBROUTINE C_TEST_STANDARD
67 |
68 |
69 | SUBROUTINE C_TEST_EXTENDED(OPT_ARRAY_IN_PRESENT, OPT_ARRAY_IN_DIM_1, OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT_PRESENT, KNOWN_OPT_ARRAY_OUT&
70 | &_DIM_1, KNOWN_OPT_ARRAY_OUT, OPT_ALLOC_ARRAY_OUT_PRESENT, OPT_ALLOC_ARRAY_OUT_DIM_1, OPT_ALLOC_ARRAY_OUT, N, ALLOC_ARRAY_OUT_DIM_1&
71 | &, ALLOC_ARRAY_OUT) BIND(C)
72 | USE ISO_FORTRAN_ENV , ONLY : REAL128
73 | USE ISO_FORTRAN_ENV, ONLY: INT64
74 | IMPLICIT NONE
75 | LOGICAL, INTENT(IN) :: OPT_ARRAY_IN_PRESENT
76 | INTEGER, INTENT(IN) :: OPT_ARRAY_IN_DIM_1
77 | COMPLEX(KIND=REAL128), INTENT(IN), DIMENSION(OPT_ARRAY_IN_DIM_1) :: OPT_ARRAY_IN
78 | LOGICAL, INTENT(IN) :: KNOWN_OPT_ARRAY_OUT_PRESENT
79 | INTEGER, INTENT(IN) :: KNOWN_OPT_ARRAY_OUT_DIM_1
80 | COMPLEX(KIND=REAL128), INTENT(OUT), DIMENSION(KNOWN_OPT_ARRAY_OUT_DIM_1) :: KNOWN_OPT_ARRAY_OUT
81 | LOGICAL, INTENT(IN) :: OPT_ALLOC_ARRAY_OUT_PRESENT
82 | INTEGER, INTENT(OUT) :: OPT_ALLOC_ARRAY_OUT_DIM_1
83 | COMPLEX(KIND=REAL128), ALLOCATABLE, SAVE, DIMENSION(:) :: OPT_ALLOC_ARRAY_OUT_LOCAL
84 | INTEGER(KIND=INT64), INTENT(OUT) :: OPT_ALLOC_ARRAY_OUT
85 | INTEGER, INTENT(IN) :: N
86 | INTEGER, INTENT(OUT) :: ALLOC_ARRAY_OUT_DIM_1
87 | COMPLEX(KIND=REAL128), ALLOCATABLE, SAVE, DIMENSION(:) :: ALLOC_ARRAY_OUT_LOCAL
88 | INTEGER(KIND=INT64), INTENT(OUT) :: ALLOC_ARRAY_OUT
89 |
90 | INTERFACE
91 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT, OPT_ALLOC_ARRAY_OUT, N) RESULT(ALLOC_ARRAY_OUT)
92 | ! Test the extended functionaly of the 'COMPLEX' type and its
93 | ! interoperability with Python. This includes, optional array
94 | ! inputs, optional array outputs, and allocatable array outputs.
95 | USE ISO_FORTRAN_ENV , ONLY : REAL128
96 | IMPLICIT NONE
97 | COMPLEX(KIND=REAL128), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
98 | COMPLEX(KIND=REAL128), INTENT(OUT), OPTIONAL, DIMENSION(3) :: KNOWN_OPT_ARRAY_OUT
99 | COMPLEX(KIND=REAL128), INTENT(OUT), OPTIONAL, ALLOCATABLE, DIMENSION(:) :: OPT_ALLOC_ARRAY_OUT
100 | INTEGER, INTENT(IN) :: N
101 | COMPLEX(KIND=REAL128), ALLOCATABLE, DIMENSION(:) :: ALLOC_ARRAY_OUT
102 | END FUNCTION TEST_EXTENDED
103 | END INTERFACE
104 |
105 | IF (OPT_ARRAY_IN_PRESENT) THEN
106 | IF (KNOWN_OPT_ARRAY_OUT_PRESENT) THEN
107 | IF (OPT_ALLOC_ARRAY_OUT_PRESENT) THEN
108 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N, OPT_ARRAY_IN=OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT=KNOWN_OPT_ARRAY_OUT, OPT_ALLOC_AR&
109 | &RAY_OUT=OPT_ALLOC_ARRAY_OUT_LOCAL)
110 | OPT_ALLOC_ARRAY_OUT_DIM_1 = SIZE(OPT_ALLOC_ARRAY_OUT_LOCAL,1)
111 | OPT_ALLOC_ARRAY_OUT = LOC(OPT_ALLOC_ARRAY_OUT_LOCAL(1))
112 | ELSE
113 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N, OPT_ARRAY_IN=OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT=KNOWN_OPT_ARRAY_OUT)
114 | OPT_ALLOC_ARRAY_OUT_DIM_1 = 0
115 | END IF
116 | ELSE
117 | IF (OPT_ALLOC_ARRAY_OUT_PRESENT) THEN
118 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N, OPT_ARRAY_IN=OPT_ARRAY_IN, OPT_ALLOC_ARRAY_OUT=OPT_ALLOC_ARRAY_OUT_LOCAL)
119 | OPT_ALLOC_ARRAY_OUT_DIM_1 = SIZE(OPT_ALLOC_ARRAY_OUT_LOCAL,1)
120 | OPT_ALLOC_ARRAY_OUT = LOC(OPT_ALLOC_ARRAY_OUT_LOCAL(1))
121 | ELSE
122 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N, OPT_ARRAY_IN=OPT_ARRAY_IN)
123 | OPT_ALLOC_ARRAY_OUT_DIM_1 = 0
124 | END IF
125 | END IF
126 | ELSE
127 | IF (KNOWN_OPT_ARRAY_OUT_PRESENT) THEN
128 | IF (OPT_ALLOC_ARRAY_OUT_PRESENT) THEN
129 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N, KNOWN_OPT_ARRAY_OUT=KNOWN_OPT_ARRAY_OUT, OPT_ALLOC_ARRAY_OUT=OPT_ALLOC_ARRAY_OUT&
130 | &_LOCAL)
131 | OPT_ALLOC_ARRAY_OUT_DIM_1 = SIZE(OPT_ALLOC_ARRAY_OUT_LOCAL,1)
132 | OPT_ALLOC_ARRAY_OUT = LOC(OPT_ALLOC_ARRAY_OUT_LOCAL(1))
133 | ELSE
134 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N, KNOWN_OPT_ARRAY_OUT=KNOWN_OPT_ARRAY_OUT)
135 | OPT_ALLOC_ARRAY_OUT_DIM_1 = 0
136 | END IF
137 | ELSE
138 | IF (OPT_ALLOC_ARRAY_OUT_PRESENT) THEN
139 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N, OPT_ALLOC_ARRAY_OUT=OPT_ALLOC_ARRAY_OUT_LOCAL)
140 | OPT_ALLOC_ARRAY_OUT_DIM_1 = SIZE(OPT_ALLOC_ARRAY_OUT_LOCAL,1)
141 | OPT_ALLOC_ARRAY_OUT = LOC(OPT_ALLOC_ARRAY_OUT_LOCAL(1))
142 | ELSE
143 | ALLOC_ARRAY_OUT_LOCAL = TEST_EXTENDED(N=N)
144 | OPT_ALLOC_ARRAY_OUT_DIM_1 = 0
145 | END IF
146 | END IF
147 | END IF
148 |
149 | ALLOC_ARRAY_OUT_DIM_1 = SIZE(ALLOC_ARRAY_OUT_LOCAL,1)
150 | ALLOC_ARRAY_OUT = LOC(ALLOC_ARRAY_OUT_LOCAL(1))
151 | END SUBROUTINE C_TEST_EXTENDED
152 |
153 |
--------------------------------------------------------------------------------
/fmodpy/test/complex256/test_complex256/test_complex256_python_wrapper.py:
--------------------------------------------------------------------------------
1 | '''This Python code is an automatically generated wrapper
2 | for Fortran code made by 'fmodpy'. The original documentation
3 | for the Fortran source code follows.
4 |
5 | ! Test Fortran COMPLEX wrapping and usage from Python with fmodpy.
6 | '''
7 |
8 | import os
9 | import ctypes
10 | import numpy
11 |
12 | # --------------------------------------------------------------------
13 | # CONFIGURATION
14 | #
15 | _verbose = True
16 | _fort_compiler = "gfortran"
17 | _shared_object_name = "test_complex256.so"
18 | _this_directory = os.path.dirname(os.path.abspath(__file__))
19 | _path_to_lib = os.path.join(_this_directory, _shared_object_name)
20 | _compile_options = ['-fPIC', '-shared', '-O3']
21 | _ordered_dependencies = ['test_complex256.f03', 'test_complex256_c_wrapper.f90']
22 | #
23 | # --------------------------------------------------------------------
24 | # AUTO-COMPILING
25 | #
26 | # Try to import the existing object. If that fails, recompile and then try.
27 | try:
28 | clib = ctypes.CDLL(_path_to_lib)
29 | except:
30 | # Remove the shared object if it exists, because it is faulty.
31 | if os.path.exists(_shared_object_name):
32 | os.remove(_shared_object_name)
33 | # Compile a new shared object.
34 | _command = " ".join([_fort_compiler] + _compile_options + ["-o", _shared_object_name] + _ordered_dependencies)
35 | if _verbose:
36 | print("Running system command with arguments")
37 | print(" ", _command)
38 | # Run the compilation command.
39 | import subprocess
40 | subprocess.run(_command, shell=True, cwd=_this_directory)
41 | # Import the shared object file as a C library with ctypes.
42 | clib = ctypes.CDLL(_path_to_lib)
43 | # --------------------------------------------------------------------
44 |
45 |
46 | # This defines a C structure that can be used to hold the complex C type.
47 | class c_complex_double(ctypes.Structure):
48 | _fields_ = [("real", ctypes.c_longdouble), ("imag", ctypes.c_longdouble)]
49 | # Define an "__init__" that can take a complex number as input.
50 | def __init__(self, real=0.0, imag=None):
51 | # If the provided first value was a complex number, expand it out.
52 | if (hasattr(real, "imag") and hasattr(real, "real") and (imag is None)):
53 | imag = real.imag
54 | real = real.real
55 | # Assign the internal fields.
56 | self.real = real
57 | self.imag = imag
58 | # Define a "value" so that it can be cleanly retreived as a single complex number.
59 | @property
60 | def value(self):
61 | return complex(self.real, self.imag)
62 |
63 |
64 |
65 | # ----------------------------------------------
66 | # Wrapper for the Fortran subroutine TEST_STANDARD
67 |
68 | def test_standard(sing_in, array_in, array_out, known_array_out=None, known_matrix_out=None, opt_sing_in=None, opt_sing_out=None):
69 | '''! Test the basic functionaly of the 'COMPLEX' type and its
70 | ! interoperability with Python. This includes, inputs, outputs,
71 | ! array inputs with known and unknown size, optional inputs, and
72 | ! optional outputs.'''
73 |
74 | # Setting up "sing_in"
75 | if (type(sing_in) is not c_complex_double): sing_in = c_complex_double(sing_in)
76 |
77 | # Setting up "sing_out"
78 | sing_out = c_complex_double()
79 |
80 | # Setting up "array_in"
81 | if ((not issubclass(type(array_in), numpy.ndarray)) or
82 | (not numpy.asarray(array_in).flags.f_contiguous) or
83 | (not (array_in.dtype == numpy.dtype(numpy.complex128)))):
84 | import warnings
85 | warnings.warn("The provided argument 'array_in' was not an f_contiguous NumPy array of type 'numpy.complex128' (or equivalent). Automatically converting (probably creating a full copy).")
86 | array_in = numpy.asarray(array_in, dtype=numpy.complex128, order='F')
87 | array_in_dim_1 = ctypes.c_int(array_in.shape[0])
88 |
89 | # Setting up "array_out"
90 | if ((not issubclass(type(array_out), numpy.ndarray)) or
91 | (not numpy.asarray(array_out).flags.f_contiguous) or
92 | (not (array_out.dtype == numpy.dtype(numpy.complex128)))):
93 | import warnings
94 | warnings.warn("The provided argument 'array_out' was not an f_contiguous NumPy array of type 'numpy.complex128' (or equivalent). Automatically converting (probably creating a full copy).")
95 | array_out = numpy.asarray(array_out, dtype=numpy.complex128, order='F')
96 | array_out_dim_1 = ctypes.c_int(array_out.shape[0])
97 |
98 | # Setting up "known_array_out"
99 | if (known_array_out is None):
100 | known_array_out = numpy.zeros(shape=(array_out.size), dtype=numpy.complex128, order='F')
101 | elif ((not issubclass(type(known_array_out), numpy.ndarray)) or
102 | (not numpy.asarray(known_array_out).flags.f_contiguous) or
103 | (not (known_array_out.dtype == numpy.dtype(numpy.complex128)))):
104 | import warnings
105 | warnings.warn("The provided argument 'known_array_out' was not an f_contiguous NumPy array of type 'numpy.complex128' (or equivalent). Automatically converting (probably creating a full copy).")
106 | known_array_out = numpy.asarray(known_array_out, dtype=numpy.complex128, order='F')
107 | known_array_out_dim_1 = ctypes.c_int(known_array_out.shape[0])
108 |
109 | # Setting up "known_matrix_out"
110 | if (known_matrix_out is None):
111 | known_matrix_out = numpy.zeros(shape=(3, array_out.size), dtype=numpy.complex128, order='F')
112 | elif ((not issubclass(type(known_matrix_out), numpy.ndarray)) or
113 | (not numpy.asarray(known_matrix_out).flags.f_contiguous) or
114 | (not (known_matrix_out.dtype == numpy.dtype(numpy.complex128)))):
115 | import warnings
116 | warnings.warn("The provided argument 'known_matrix_out' was not an f_contiguous NumPy array of type 'numpy.complex128' (or equivalent). Automatically converting (probably creating a full copy).")
117 | known_matrix_out = numpy.asarray(known_matrix_out, dtype=numpy.complex128, order='F')
118 | known_matrix_out_dim_1 = ctypes.c_int(known_matrix_out.shape[0])
119 | known_matrix_out_dim_2 = ctypes.c_int(known_matrix_out.shape[1])
120 |
121 | # Setting up "opt_sing_in"
122 | opt_sing_in_present = ctypes.c_bool(True)
123 | if (opt_sing_in is None):
124 | opt_sing_in_present = ctypes.c_bool(False)
125 | opt_sing_in = c_complex_double()
126 | if (type(opt_sing_in) is not c_complex_double): opt_sing_in = c_complex_double(opt_sing_in)
127 |
128 | # Setting up "opt_sing_out"
129 | opt_sing_out_present = ctypes.c_bool(True)
130 | if (opt_sing_out is None):
131 | opt_sing_out_present = ctypes.c_bool(False)
132 | opt_sing_out = c_complex_double()
133 |
134 | print()
135 | print("sing_in ", sing_in)
136 | print("sing_out", sing_out)
137 | print("array_in ", array_in)
138 | print("array_out", array_out)
139 |
140 | # Call C-accessible Fortran wrapper.
141 | clib.c_test_standard(ctypes.byref(sing_in), ctypes.byref(sing_out), ctypes.byref(array_in_dim_1), ctypes.c_void_p(array_in.ctypes.data), ctypes.byref(array_out_dim_1), ctypes.c_void_p(array_out.ctypes.data), ctypes.byref(known_array_out_dim_1), ctypes.c_void_p(known_array_out.ctypes.data), ctypes.byref(known_matrix_out_dim_1), ctypes.byref(known_matrix_out_dim_2), ctypes.c_void_p(known_matrix_out.ctypes.data), ctypes.byref(opt_sing_in_present), ctypes.byref(opt_sing_in), ctypes.byref(opt_sing_out_present), ctypes.byref(opt_sing_out))
142 |
143 | print()
144 | print("sing_in ", sing_in)
145 | print("sing_out", sing_out)
146 | print("array_in ", array_in)
147 | print("array_out", array_out)
148 |
149 | # Return final results, 'INTENT(OUT)' arguments only.
150 | return sing_out.value, array_out, known_array_out, known_matrix_out, (opt_sing_out.value if opt_sing_out_present else None)
151 |
152 |
153 | # ----------------------------------------------
154 | # Wrapper for the Fortran subroutine TEST_EXTENDED
155 |
156 | def test_extended(n, opt_array_in=None, known_opt_array_out=None, opt_alloc_array_out=None):
157 | '''! Test the extended functionaly of the 'COMPLEX' type and its
158 | ! interoperability with Python. This includes, optional array
159 | ! inputs, optional array outputs, and allocatable array outputs.'''
160 |
161 | # Setting up "opt_array_in"
162 | opt_array_in_present = ctypes.c_bool(True)
163 | if (opt_array_in is None):
164 | opt_array_in_present = ctypes.c_bool(False)
165 | opt_array_in = numpy.zeros(shape=(1), dtype=numpy.complex128, order='F')
166 | elif (type(opt_array_in) == bool) and (opt_array_in):
167 | opt_array_in = numpy.zeros(shape=(1), dtype=numpy.complex128, order='F')
168 | elif ((not issubclass(type(opt_array_in), numpy.ndarray)) or
169 | (not numpy.asarray(opt_array_in).flags.f_contiguous) or
170 | (not (opt_array_in.dtype == numpy.dtype(numpy.complex128)))):
171 | import warnings
172 | warnings.warn("The provided argument 'opt_array_in' was not an f_contiguous NumPy array of type 'numpy.complex128' (or equivalent). Automatically converting (probably creating a full copy).")
173 | opt_array_in = numpy.asarray(opt_array_in, dtype=numpy.complex128, order='F')
174 | if (opt_array_in_present):
175 | opt_array_in_dim_1 = ctypes.c_int(opt_array_in.shape[0])
176 | else:
177 | opt_array_in_dim_1 = ctypes.c_int()
178 |
179 | # Setting up "known_opt_array_out"
180 | known_opt_array_out_present = ctypes.c_bool(True)
181 | if (known_opt_array_out is None):
182 | known_opt_array_out_present = ctypes.c_bool(False)
183 | known_opt_array_out = numpy.zeros(shape=(1), dtype=numpy.complex128, order='F')
184 | elif (type(known_opt_array_out) == bool) and (known_opt_array_out):
185 | known_opt_array_out = numpy.zeros(shape=(3), dtype=numpy.complex128, order='F')
186 | elif ((not issubclass(type(known_opt_array_out), numpy.ndarray)) or
187 | (not numpy.asarray(known_opt_array_out).flags.f_contiguous) or
188 | (not (known_opt_array_out.dtype == numpy.dtype(numpy.complex128)))):
189 | import warnings
190 | warnings.warn("The provided argument 'known_opt_array_out' was not an f_contiguous NumPy array of type 'numpy.complex128' (or equivalent). Automatically converting (probably creating a full copy).")
191 | known_opt_array_out = numpy.asarray(known_opt_array_out, dtype=numpy.complex128, order='F')
192 | if (known_opt_array_out_present):
193 | known_opt_array_out_dim_1 = ctypes.c_int(known_opt_array_out.shape[0])
194 | else:
195 | known_opt_array_out_dim_1 = ctypes.c_int()
196 |
197 | # Setting up "opt_alloc_array_out"
198 | opt_alloc_array_out_present = ctypes.c_bool(True)
199 | if (opt_alloc_array_out is None):
200 | opt_alloc_array_out_present = ctypes.c_bool(False)
201 | opt_alloc_array_out = numpy.zeros(shape=(0), dtype=numpy.complex128, order='F')
202 | elif (type(opt_alloc_array_out) == bool) and (opt_alloc_array_out):
203 | opt_alloc_array_out = numpy.zeros(shape=(1), dtype=numpy.complex128, order='F')
204 | elif ((not issubclass(type(opt_alloc_array_out), numpy.ndarray)) or
205 | (not numpy.asarray(opt_alloc_array_out).flags.f_contiguous) or
206 | (not (opt_alloc_array_out.dtype == numpy.dtype(numpy.complex128)))):
207 | import warnings
208 | warnings.warn("The provided argument 'opt_alloc_array_out' was not an f_contiguous NumPy array of type 'numpy.complex128' (or equivalent). Automatically converting (probably creating a full copy).")
209 | opt_alloc_array_out = numpy.asarray(opt_alloc_array_out, dtype=numpy.complex128, order='F')
210 | if (opt_alloc_array_out_present):
211 | opt_alloc_array_out_dim_1 = ctypes.c_int(opt_alloc_array_out.shape[0])
212 | else:
213 | opt_alloc_array_out_dim_1 = ctypes.c_int()
214 | opt_alloc_array_out = ctypes.c_void_p(opt_alloc_array_out.ctypes.data)
215 |
216 | # Setting up "n"
217 | if (type(n) is not ctypes.c_int): n = ctypes.c_int(n)
218 |
219 | # Setting up "alloc_array_out"
220 | alloc_array_out = ctypes.c_void_p()
221 | alloc_array_out_dim_1 = ctypes.c_int()
222 |
223 | # Call C-accessible Fortran wrapper.
224 | clib.c_test_extended(ctypes.byref(opt_array_in_present), ctypes.byref(opt_array_in_dim_1), ctypes.c_void_p(opt_array_in.ctypes.data), ctypes.byref(known_opt_array_out_present), ctypes.byref(known_opt_array_out_dim_1), ctypes.c_void_p(known_opt_array_out.ctypes.data), ctypes.byref(opt_alloc_array_out_present), ctypes.byref(opt_alloc_array_out_dim_1), ctypes.byref(opt_alloc_array_out), ctypes.byref(n), ctypes.byref(alloc_array_out_dim_1), ctypes.byref(alloc_array_out))
225 |
226 | # Post-processing "opt_alloc_array_out"
227 | opt_alloc_array_out_size = (opt_alloc_array_out_dim_1.value)
228 | if (opt_alloc_array_out_present) and (opt_alloc_array_out_size > 0):
229 | opt_alloc_array_out = numpy.array(ctypes.cast(opt_alloc_array_out, ctypes.POINTER(c_complex_double*opt_alloc_array_out_size)).contents, copy=False).view(numpy.complex128)
230 | elif (opt_alloc_array_out_size == 0):
231 | opt_alloc_array_out = numpy.zeros(opt_alloc_array_out_dim_1.value, dtype=numpy.complex128, order='F')
232 | else:
233 | opt_alloc_array_out = None
234 |
235 | # Post-processing "alloc_array_out"
236 | alloc_array_out_size = (alloc_array_out_dim_1.value)
237 | if (alloc_array_out_size > 0):
238 | alloc_array_out = numpy.array(ctypes.cast(alloc_array_out, ctypes.POINTER(c_complex_double*alloc_array_out_size)).contents, copy=False).view(numpy.complex128)
239 | elif (alloc_array_out_size == 0):
240 | alloc_array_out = numpy.zeros(alloc_array_out_dim_1.value, dtype=numpy.complex128, order='F')
241 | else:
242 | alloc_array_out = None
243 |
244 | # Return final results, 'INTENT(OUT)' arguments only.
245 | return (known_opt_array_out if known_opt_array_out_present else None), (opt_alloc_array_out if opt_alloc_array_out_present else None), alloc_array_out
246 |
247 |
--------------------------------------------------------------------------------
/fmodpy/test/complex64/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 |
15 | import numpy as np
16 | n = 10
17 | array_in = np.asarray(np.arange(n), dtype=np.complex64, order='F')
18 | array_out = np.zeros(n//2 - 1, dtype=np.complex64, order='F')
19 | sing_in = complex(7,0)
20 | out = fort.test_standard(sing_in, array_in, array_out)
21 | # Test the standard functionality.
22 | assert(out[0] == 8), out[0]
23 | assert(all(out[1] == [0,1,2,3]))
24 | assert(all(out[2] == [1,2,3,4]))
25 | assert(np.all(out[3] == [[2,3,4,5],
26 | [3,4,5,6],
27 | [4,5,6,7]]))
28 | assert(out[4] == None)
29 | # Test the extended functionality.
30 | out = fort.test_extended(4)
31 | assert(out[0] == None)
32 | assert(out[1] == None)
33 | assert(all(out[2] == [4, 3, 2, 1]))
34 | # WARNING: In the current code, the memory associated with out[2a]
35 | # will be freed by Fortran on subsequent calls to the
36 | # function "test_extended". Copy for object permanance.
37 | out2 = fort.test_extended(10, known_opt_array_out=True)
38 | assert(all(out2[0] == [1,2,3]))
39 | assert(out2[1] == None)
40 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
41 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
42 | # will be freed by Fortran on subsequent calls to
43 | # "test_extended". Copies should be made for permanence.
44 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
45 | assert(out3[0] == None)
46 | assert(all(out3[1] == [3,2,1]))
47 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
48 |
49 | # End specific testing code.
50 | # ---------------------------------------------------------------
51 | print("passed", flush=True)
52 | import shutil
53 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
54 |
55 | if __name__ == "__main__":
56 | test()
57 |
--------------------------------------------------------------------------------
/fmodpy/test/complex64/test_complex64.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran COMPLEX wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'COMPLEX' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | USE ISO_FORTRAN_ENV, ONLY: REAL32
10 | USE ISO_FORTRAN_ENV, ONLY: REAL32
11 | IMPLICIT NONE
12 | ! Argument definitions.
13 | COMPLEX(KIND=REAL32), INTENT(IN) :: SING_IN
14 | COMPLEX(KIND=REAL32), INTENT(OUT) :: SING_OUT
15 | COMPLEX(KIND=REAL32), DIMENSION(:), INTENT(IN) :: ARRAY_IN
16 | COMPLEX(KIND=REAL32), DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
17 | COMPLEX(KIND=REAL32), DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
18 | COMPLEX(KIND=REAL32), DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
19 | COMPLEX(KIND=REAL32), INTENT(IN), OPTIONAL :: OPT_SING_IN
20 | COMPLEX(KIND=REAL32), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
21 | ! Local variable.
22 | INTEGER :: I
23 | ! Copy the single input value to the single output value.
24 | SING_OUT = SING_IN + 1
25 | ! Copy as much of the input array as possible to the output array.
26 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
27 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
28 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
29 | ARRAY_OUT(I) = I
30 | END DO
31 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
32 | KNOWN_MATRIX_OUT(I,:) = I
33 | END DO
34 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
35 | DO I = 1,SIZE(ARRAY_OUT)
36 | KNOWN_ARRAY_OUT(I) = I
37 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
38 | END DO
39 | ! Do some operations on the optional inputs / outputs.
40 | IF (PRESENT(OPT_SING_OUT)) THEN
41 | IF (PRESENT(OPT_SING_IN)) THEN
42 | OPT_SING_OUT = OPT_SING_IN
43 | ELSE
44 | OPT_SING_OUT = SING_IN
45 | END IF
46 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
47 | SING_OUT = OPT_SING_IN
48 | END IF
49 | ! End of this subroutine.
50 | END SUBROUTINE TEST_STANDARD
51 |
52 |
53 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
54 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
55 | ! Test the extended functionaly of the 'COMPLEX' type and its
56 | ! interoperability with Python. This includes, optional array
57 | ! inputs, optional array outputs, and allocatable array outputs.
58 | USE ISO_FORTRAN_ENV, ONLY: REAL32
59 | IMPLICIT NONE
60 | COMPLEX(REAL32), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
61 | COMPLEX(REAL32), INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
62 | COMPLEX(REAL32), INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
63 | COMPLEX(REAL32), DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
64 | INTEGER, INTENT(IN) :: N
65 | ! Local variable.
66 | INTEGER :: I
67 |
68 | ! Assign the optional array output values.
69 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
70 | IF (PRESENT(OPT_ARRAY_IN)) THEN
71 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
72 | KNOWN_OPT_ARRAY_OUT(I) = I
73 | END DO
74 | ELSE
75 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
76 | KNOWN_OPT_ARRAY_OUT(I) = I
77 | END DO
78 | END IF
79 | END IF
80 |
81 | ! Allocate the optional array output and assign its values.
82 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
83 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
84 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
85 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
86 | END DO
87 | END IF
88 |
89 | ! Allocate the required array output to the specified size.
90 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
91 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
92 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
93 | END DO
94 |
95 | ! End of function.
96 | END FUNCTION TEST_EXTENDED
97 |
--------------------------------------------------------------------------------
/fmodpy/test/double_precision/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 |
15 | import numpy as np
16 | n = 10
17 | array_in = np.asarray(np.arange(n), dtype=float, order='F')
18 | array_out = np.zeros(n//2 - 1, dtype=float, order='F')
19 | out = fort.test_standard(7, array_in, array_out)
20 | # Test the standard functionality.
21 | assert(out[0] == 8)
22 | assert(all(out[1] == [0,1,2,3]))
23 | assert(all(out[2] == [1,2,3,4]))
24 | assert(np.all(out[3] == [[2,3,4,5],
25 | [3,4,5,6],
26 | [4,5,6,7]]))
27 | assert(out[4] == None)
28 | # Test the extended functionality.
29 | out = fort.test_extended(4)
30 | assert(out[0] == None)
31 | assert(out[1] == None)
32 | assert(all(out[2] == [4, 3, 2, 1]))
33 | # WARNING: In the current code, the memory associated with out[2]
34 | # will be freed by Fortran on subsequent calls to the
35 | # function "test_extended". Copy for object permanance.
36 | out2 = fort.test_extended(10, known_opt_array_out=True)
37 | assert(all(out2[0] == [1,2,3]))
38 | assert(out2[1] == None)
39 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
40 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
41 | # will be freed by Fortran on subsequent calls to
42 | # "test_extended". Copies should be made for permanence.
43 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
44 | assert(out3[0] == None)
45 | assert(all(out3[1] == [3,2,1]))
46 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
47 |
48 | # End specific testing code.
49 | # ---------------------------------------------------------------
50 | print("passed", flush=True)
51 | import shutil
52 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
53 |
54 | if __name__ == "__main__":
55 | test()
56 |
--------------------------------------------------------------------------------
/fmodpy/test/double_precision/test_double_precision.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran REAL wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'REAL' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_DOUBLE
10 | IMPLICIT NONE
11 | ! Argument definitions.
12 | REAL(KIND=C_DOUBLE), INTENT(IN) :: SING_IN
13 | REAL(KIND=C_DOUBLE), INTENT(OUT) :: SING_OUT
14 | REAL(KIND=C_DOUBLE), DIMENSION(:), INTENT(IN) :: ARRAY_IN
15 | REAL(KIND=C_DOUBLE), DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
16 | REAL(KIND=C_DOUBLE), DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
17 | REAL(KIND=C_DOUBLE), DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
18 | REAL(KIND=C_DOUBLE), INTENT(IN), OPTIONAL :: OPT_SING_IN
19 | REAL(KIND=C_DOUBLE), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
20 | ! Local variable.
21 | INTEGER :: I
22 | ! Copy the single input value to the single output value.
23 | SING_OUT = SING_IN + 1
24 | ! Copy as much of the input array as possible to the output array.
25 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
26 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
27 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
28 | ARRAY_OUT(I) = I
29 | END DO
30 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
31 | KNOWN_MATRIX_OUT(I,:) = I
32 | END DO
33 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
34 | DO I = 1,SIZE(ARRAY_OUT)
35 | KNOWN_ARRAY_OUT(I) = I
36 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
37 | END DO
38 | ! Do some operations on the optional inputs / outputs.
39 | IF (PRESENT(OPT_SING_OUT)) THEN
40 | IF (PRESENT(OPT_SING_IN)) THEN
41 | OPT_SING_OUT = OPT_SING_IN
42 | ELSE
43 | OPT_SING_OUT = SING_IN
44 | END IF
45 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
46 | SING_OUT = OPT_SING_IN
47 | END IF
48 | ! End of this subroutine.
49 | END SUBROUTINE TEST_STANDARD
50 |
51 |
52 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
53 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
54 | ! Test the extended functionaly of the 'REAL' type and its
55 | ! interoperability with Python. This includes, optional array
56 | ! inputs, optional array outputs, and allocatable array outputs.
57 | USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_DOUBLE
58 | IMPLICIT NONE
59 | REAL(C_DOUBLE), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
60 | REAL(C_DOUBLE), INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
61 | REAL(C_DOUBLE), INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
62 | REAL(C_DOUBLE), DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
63 | INTEGER, INTENT(IN) :: N
64 | ! Local variable.
65 | INTEGER :: I
66 |
67 | ! Assign the optional array output values.
68 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
69 | IF (PRESENT(OPT_ARRAY_IN)) THEN
70 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
71 | KNOWN_OPT_ARRAY_OUT(I) = I
72 | END DO
73 | ELSE
74 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
75 | KNOWN_OPT_ARRAY_OUT(I) = I
76 | END DO
77 | END IF
78 | END IF
79 |
80 | ! Allocate the optional array output and assign its values.
81 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
82 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
83 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
84 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
85 | END DO
86 | END IF
87 |
88 | ! Allocate the required array output to the specified size.
89 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
90 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
91 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
92 | END DO
93 |
94 | ! End of function.
95 | END FUNCTION TEST_EXTENDED
96 |
--------------------------------------------------------------------------------
/fmodpy/test/fixed_format/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 | import numpy as np
15 | a = 1
16 | b = 2
17 | c = 3
18 | d = 4
19 | aa = np.zeros((2,10), dtype="int32", order="F")
20 | ab = np.zeros((3,11), dtype="int32", order="F")
21 | ac = np.zeros((12), dtype="int32", order="F")
22 | ad = np.zeros((13), dtype="int32", order="F")
23 | ba = np.zeros((c,d), dtype="int32", order="F")
24 | bb = np.zeros((6,14), dtype="int32", order="F")
25 | # Call the function.
26 | a, b, c, d, aa, ab, ac, ad, ba, bb = fort.test_standard(a, b, c, d, aa, ab, ac, ad, ba, bb)
27 | # Test the standard functionality.
28 | assert(a == 1)
29 | assert(b == 2)
30 | assert(c == 3)
31 | assert(d == 4)
32 | assert(aa[1,2] == 1)
33 | assert(ab[1,2] == 2)
34 | assert(ac[1] == 3)
35 | assert(ad[1] == 4)
36 | assert(ba[1,2] == 5)
37 | # End specific testing code.
38 | # ---------------------------------------------------------------
39 | print("passed", flush=True)
40 | import shutil
41 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
42 |
43 | if __name__ == "__main__":
44 | test()
45 |
--------------------------------------------------------------------------------
/fmodpy/test/fixed_format/test_fixed_format.f:
--------------------------------------------------------------------------------
1 | SUBROUTINE TEST_STANDARD(A,B,C,D,AA,AB,AC,AD,BA,BB)
2 | * Comments.
3 | * Parameters
4 | INTEGER PA,PB
5 | PARAMETER (PA=0,PB=6)
6 | * .. Scalar Arguments
7 | INTEGER A,B,C,D
8 | * Array Arguments ..
9 | INTEGER AA(2,*),AB(3,*),AC(*),AD(*),BA(C,D),
10 | + BB(PB,*)
11 | * Code.
12 | IF (A.NE.1) RETURN
13 | AA(2,3) = 1
14 | AB(2,3) = 2
15 | AC(2) = 3
16 | AD(2) = 4
17 | BA(2,3) = 5
18 | BB(2,3) = 6
19 | RETURN
20 | END
21 |
--------------------------------------------------------------------------------
/fmodpy/test/int32/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 |
15 | import numpy as np
16 | n = 10
17 | array_in = np.asarray(np.arange(n), dtype="int32", order='F')
18 | array_out = np.zeros(n//2 - 1, dtype="int32", order='F')
19 | out = fort.test_standard(7, array_in, array_out)
20 | # Test the standard functionality.
21 | assert(out[0] == 8)
22 | assert(all(out[1] == [0,1,2,3]))
23 | assert(all(out[2] == [1,2,3,4]))
24 | assert(np.all(out[3] == [[2,3,4,5],
25 | [3,4,5,6],
26 | [4,5,6,7]]))
27 | assert(out[4] == None)
28 | # Test the extended functionality.
29 | out = fort.test_extended(4)
30 | assert(out[0] == None)
31 | assert(out[1] == None)
32 | assert(all(out[2] == [4, 3, 2, 1]))
33 | # WARNING: In the current code, the memory associated with out[2]
34 | # will be freed by Fortran on subsequent calls to the
35 | # function "test_extended". Copy for object permanance.
36 | out2 = fort.test_extended(10, known_opt_array_out=True)
37 | assert(all(out2[0] == [1,2,3]))
38 | assert(out2[1] == None)
39 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
40 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
41 | # will be freed by Fortran on subsequent calls to
42 | # "test_extended". Copies should be made for permanence.
43 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
44 | assert(out3[0] == None)
45 | assert(all(out3[1] == [3,2,1]))
46 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
47 |
48 | # End specific testing code.
49 | # ---------------------------------------------------------------
50 | print("passed", flush=True)
51 | import shutil
52 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
53 |
54 | if __name__ == "__main__":
55 | test()
56 |
--------------------------------------------------------------------------------
/fmodpy/test/int32/test_int32.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran INTEGER wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'INTEGER' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | IMPLICIT NONE
10 | ! Argument definitions.
11 | INTEGER, INTENT(IN) :: SING_IN
12 | INTEGER, INTENT(OUT) :: SING_OUT
13 | INTEGER, DIMENSION(:), INTENT(IN) :: ARRAY_IN
14 | INTEGER, DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
15 | INTEGER, DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
16 | INTEGER, DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
17 | INTEGER, INTENT(IN), OPTIONAL :: OPT_SING_IN
18 | INTEGER, INTENT(OUT), OPTIONAL :: OPT_SING_OUT
19 | ! Local variable.
20 | INTEGER :: I
21 | ! Copy the single input value to the single output value.
22 | SING_OUT = SING_IN + 1
23 | ! Copy as much of the input array as possible to the output array.
24 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
25 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
26 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
27 | ARRAY_OUT(I) = I
28 | END DO
29 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
30 | KNOWN_MATRIX_OUT(I,:) = I
31 | END DO
32 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
33 | DO I = 1,SIZE(ARRAY_OUT)
34 | KNOWN_ARRAY_OUT(I) = I
35 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
36 | END DO
37 | ! Do some operations on the optional inputs / outputs.
38 | IF (PRESENT(OPT_SING_OUT)) THEN
39 | IF (PRESENT(OPT_SING_IN)) THEN
40 | OPT_SING_OUT = OPT_SING_IN
41 | ELSE
42 | OPT_SING_OUT = SING_IN
43 | END IF
44 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
45 | SING_OUT = OPT_SING_IN
46 | END IF
47 | ! End of this subroutine.
48 | END SUBROUTINE TEST_STANDARD
49 |
50 |
51 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
52 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
53 | ! Test the extended functionaly of the 'INTEGER' type and its
54 | ! interoperability with Python. This includes, optional array
55 | ! inputs, optional array outputs, and allocatable array outputs.
56 | IMPLICIT NONE
57 | INTEGER, INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
58 | INTEGER, INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
59 | INTEGER, INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
60 | INTEGER, DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
61 | INTEGER, INTENT(IN) :: N
62 | ! Local variable.
63 | INTEGER :: I
64 |
65 | ! Assign the optional array output values.
66 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
67 | IF (PRESENT(OPT_ARRAY_IN)) THEN
68 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
69 | KNOWN_OPT_ARRAY_OUT(I) = I
70 | END DO
71 | ELSE
72 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
73 | KNOWN_OPT_ARRAY_OUT(I) = I
74 | END DO
75 | END IF
76 | END IF
77 |
78 | ! Allocate the optional array output and assign its values.
79 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
80 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
81 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
82 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
83 | END DO
84 | END IF
85 |
86 | ! Allocate the required array output to the specified size.
87 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
88 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
89 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
90 | END DO
91 |
92 | ! End of function.
93 | END FUNCTION TEST_EXTENDED
94 |
--------------------------------------------------------------------------------
/fmodpy/test/int64/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 |
15 | import numpy as np
16 | n = 10
17 | array_in = np.asarray(np.arange(n), dtype=int, order='F')
18 | array_out = np.zeros(n//2 - 1, dtype=int, order='F')
19 | out = fort.test_standard(7, array_in, array_out)
20 | # Test the standard functionality.
21 | assert(out[0] == 8)
22 | assert(all(out[1] == [0,1,2,3]))
23 | assert(all(out[2] == [1,2,3,4]))
24 | assert(np.all(out[3] == [[2,3,4,5],
25 | [3,4,5,6],
26 | [4,5,6,7]]))
27 | assert(out[4] == None)
28 | # Test the extended functionality.
29 | out = fort.test_extended(4)
30 | assert(out[0] == None)
31 | assert(out[1] == None)
32 | assert(all(out[2] == [4, 3, 2, 1]))
33 | # WARNING: In the current code, the memory associated with out[2]
34 | # will be freed by Fortran on subsequent calls to the
35 | # function "test_extended". Copy for object permanance.
36 | out2 = fort.test_extended(10, known_opt_array_out=True)
37 | assert(all(out2[0] == [1,2,3]))
38 | assert(out2[1] == None)
39 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
40 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
41 | # will be freed by Fortran on subsequent calls to
42 | # "test_extended". Copies should be made for permanence.
43 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
44 | assert(out3[0] == None)
45 | assert(all(out3[1] == [3,2,1]))
46 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
47 |
48 | # Check that a function with an allocatable return works correctly.
49 | n = 13
50 | assert(tuple(fort.test_allocatable_return(n).tolist()) == tuple(reversed(list(range(n)))))
51 |
52 | # End specific testing code.
53 | # ---------------------------------------------------------------
54 | print("passed", flush=True)
55 | import shutil
56 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
57 |
58 | if __name__ == "__main__":
59 | test()
60 |
--------------------------------------------------------------------------------
/fmodpy/test/int64/test_int64.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran REAL wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'REAL' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | USE ISO_FORTRAN_ENV, ONLY: INT64
10 | IMPLICIT NONE
11 | ! Argument definitions.
12 | INTEGER(KIND=INT64), INTENT(IN) :: SING_IN
13 | INTEGER(KIND=INT64), INTENT(OUT) :: SING_OUT
14 | INTEGER(KIND=INT64), DIMENSION(:), INTENT(IN) :: ARRAY_IN
15 | INTEGER(KIND=INT64), DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
16 | INTEGER(KIND=INT64), DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
17 | INTEGER(KIND=INT64), DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
18 | INTEGER(KIND=INT64), INTENT(IN), OPTIONAL :: OPT_SING_IN
19 | INTEGER(KIND=INT64), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
20 | ! Local variable.
21 | INTEGER :: I
22 | ! Copy the single input value to the single output value.
23 | SING_OUT = SING_IN + 1
24 | ! Copy as much of the input array as possible to the output array.
25 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
26 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
27 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
28 | ARRAY_OUT(I) = I
29 | END DO
30 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
31 | KNOWN_MATRIX_OUT(I,:) = I
32 | END DO
33 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
34 | DO I = 1,SIZE(ARRAY_OUT)
35 | KNOWN_ARRAY_OUT(I) = I
36 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
37 | END DO
38 | ! Do some operations on the optional inputs / outputs.
39 | IF (PRESENT(OPT_SING_OUT)) THEN
40 | IF (PRESENT(OPT_SING_IN)) THEN
41 | OPT_SING_OUT = OPT_SING_IN
42 | ELSE
43 | OPT_SING_OUT = SING_IN
44 | END IF
45 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
46 | SING_OUT = OPT_SING_IN
47 | END IF
48 | ! End of this subroutine.
49 | END SUBROUTINE TEST_STANDARD
50 |
51 |
52 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
53 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
54 | ! Test the extended functionaly of the 'REAL' type and its
55 | ! interoperability with Python. This includes, optional array
56 | ! inputs, optional array outputs, and allocatable array outputs.
57 | USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_LONG
58 | IMPLICIT NONE
59 | INTEGER(C_LONG), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
60 | INTEGER(C_LONG), INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
61 | INTEGER(C_LONG), INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
62 | INTEGER(C_LONG), DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
63 | INTEGER, INTENT(IN) :: N
64 | ! Local variable.
65 | INTEGER :: I
66 |
67 | ! Assign the optional array output values.
68 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
69 | IF (PRESENT(OPT_ARRAY_IN)) THEN
70 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
71 | KNOWN_OPT_ARRAY_OUT(I) = REAL(I)
72 | END DO
73 | ELSE
74 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
75 | KNOWN_OPT_ARRAY_OUT(I) = REAL(I)
76 | END DO
77 | END IF
78 | END IF
79 |
80 | ! Allocate the optional array output and assign its values.
81 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
82 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
83 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
84 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
85 | END DO
86 | END IF
87 |
88 | ! Allocate the required array output to the specified size.
89 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
90 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
91 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
92 | END DO
93 |
94 | ! End of function.
95 | END FUNCTION TEST_EXTENDED
96 |
97 |
98 | ! Function returning allocatable.
99 | FUNCTION TEST_ALLOCATABLE_RETURN(N)
100 | USE ISO_FORTRAN_ENV, ONLY: INT64
101 | IMPLICIT NONE
102 | INTEGER(KIND=INT64), INTENT(IN) :: N
103 | INTEGER(KIND=INT64), ALLOCATABLE :: TEST_ALLOCATABLE_RETURN(:)
104 | INTEGER :: I
105 | ALLOCATE(TEST_ALLOCATABLE_RETURN(1:N))
106 | DO I = 1, N
107 | TEST_ALLOCATABLE_RETURN(I) = N - I
108 | END DO
109 | END FUNCTION TEST_ALLOCATABLE_RETURN
110 |
--------------------------------------------------------------------------------
/fmodpy/test/logical/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name, verbose=False, wrap=True,
12 | rebuild=True, show_warnings=False)
13 | # ---------------------------------------------------------------
14 | # Begin specific testing code.
15 |
16 | import ctypes
17 | import numpy as np
18 | a = np.array([True, False, True, False, True, False, True, False], dtype="int32")
19 | out = a.copy().astype(ctypes.c_bool)
20 | # Construct the answers (that are expected).
21 | b12 = np.array([(i+1)%3 == 0 for i in range(len(a))])
22 | b3 = np.array([(i+1)%2 == 0 for i in range(len(a))])
23 | assert(all(b12 == fort.test_simple_logical(a, b=out)))
24 | assert(all(b12 == fort.test_simple_logical(a, b=out, c=False)))
25 | assert(all(b3 == fort.test_simple_logical(a, b=out, c=True)))
26 | # End specific testing code.
27 | # ---------------------------------------------------------------
28 | print("passed", flush=True)
29 | import shutil
30 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
31 |
32 | if __name__ == "__main__":
33 | test()
34 |
--------------------------------------------------------------------------------
/fmodpy/test/logical/test_logical.f03:
--------------------------------------------------------------------------------
1 |
2 | SUBROUTINE TEST_SIMPLE_LOGICAL(A,C,B)
3 | USE ISO_C_BINDING, ONLY: C_BOOL
4 | LOGICAL, INTENT(IN), DIMENSION(:) :: A
5 | LOGICAL(KIND=C_BOOL), INTENT(IN), OPTIONAL :: C
6 | LOGICAL(KIND=C_BOOL), INTENT(OUT), DIMENSION(SIZE(A)) :: B
7 | ! Local
8 | INTEGER :: I
9 | B(:) = .TRUE.
10 | DO I = 1, SIZE(B)
11 | IF (PRESENT(C) .AND. (C)) THEN
12 | B(I) = (MOD(I,2) .EQ. 0)
13 | ELSE
14 | B(I) = (MOD(I,3) .EQ. 0)
15 | END IF
16 | END DO
17 | END SUBROUTINE TEST_SIMPLE_LOGICAL
18 |
19 | ! python3 -c "import fmodpy as f; f.fimport('simple_logical.f03', build_dir='.', verbose=True)"
20 | ! python3 -c "import og_fmodpy as f; f.wrap('simple_logical.f03', working_directory='og_fmodpy', verbose=True)"
21 |
--------------------------------------------------------------------------------
/fmodpy/test/misc/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | # Standardized testing interface.
3 | def test():
4 | import os
5 | import shutil
6 | dir_name = os.path.dirname(os.path.abspath(__file__))
7 | test_name = os.path.basename(dir_name)
8 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
9 | import fmodpy
10 | # ---------------------------------------------------------------
11 | # Begin specific testing code.
12 | fort_file = os.path.join(dir_name, f"subroutine_with_type.f90")
13 | print(f" {os.path.basename(fort_file)}..", end=" ", flush=True)
14 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
15 | output_dir=dir_name, rebuild=True,
16 | )
17 | assert (fort.one_up(1) == fort.FANCY(shirt=2.0, pants=3)), "Failed!"
18 | print("passed", flush=True)
19 | shutil.rmtree(os.path.join(dir_name,fort_file[:-4]))
20 | # End specific testing code.
21 | # ---------------------------------------------------------------
22 |
23 |
24 | if __name__ == "__main__":
25 | test()
26 |
--------------------------------------------------------------------------------
/fmodpy/test/misc/implicit_shape.f90:
--------------------------------------------------------------------------------
1 |
2 | PURE FUNCTION TEST_SHAPES(a) RESULT(b)
3 | REAL, INTENT(IN), DIMENSION(:,:) :: a
4 | INTEGER :: b(SIZE(a,1), SIZE(a(1,:)))
5 | ! INTEGER :: b(SIZE(a,1), SIZE(a(1,3:4)))
6 | b(:,:) = INT(a(:,:))
7 | END FUNCTION TEST_SHAPES
8 |
--------------------------------------------------------------------------------
/fmodpy/test/misc/simple.f03:
--------------------------------------------------------------------------------
1 | SUBROUTINE TEST(N, OUT)
2 | ! Simple allocation test, allocate a real array of size N.
3 | INTEGER, INTENT(IN) :: N
4 | REAL, INTENT(OUT), ALLOCATABLE :: OUT(:)
5 | ! Local variables.
6 | INTEGER :: I
7 | ALLOCATE(OUT(1:I))
8 | DO I = 1, N
9 | OUT(I) = I
10 | END DO
11 | END SUBROUTINE TEST
12 |
--------------------------------------------------------------------------------
/fmodpy/test/misc/subroutine_with_type.f90:
--------------------------------------------------------------------------------
1 |
2 | subroutine one_up(person) bind(c)
3 | use iso_c_binding, only: c_double, c_int
4 | type, bind(c) :: fancy
5 | real(c_double) :: shirt
6 | integer(c_int) :: pants
7 | end type fancy
8 | type(fancy), intent(inout) :: person
9 | person%shirt = person%shirt + 1
10 | person%pants = person%pants + 2
11 | end subroutine one_up
12 |
--------------------------------------------------------------------------------
/fmodpy/test/module/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name, rebuild=True,
12 | )
13 | # ---------------------------------------------------------------
14 | # Begin specific testing code.
15 |
16 | import numpy as np
17 | n = 10
18 | array_test = np.asarray(np.arange(n), dtype="float32", order='F')
19 |
20 | # Assign some internal variables.
21 | # a_pub (float)
22 | fort.addition.a_pub = 1.5
23 | assert(fort.addition.a_pub == 1.5)
24 | fort.addition.a_pub = 2.5
25 | assert(fort.addition.a_pub == 2.5)
26 |
27 | # b_pub (integer)
28 | fort.addition.b_pub = 2
29 | assert(fort.addition.b_pub == 2)
30 | fort.addition.b_pub = 1
31 | assert(fort.addition.b_pub == 1)
32 |
33 | # Verify that the public subtraction routine works correctly.
34 | assert(3.5 == fort.addition.add_ab())
35 |
36 | # a_vec_pub (real vector of size 10)
37 | import numpy as np
38 | a = np.asarray(np.random.random(size=(10,)), dtype=np.float32)
39 | # testing using the "get" before assignment
40 | fort.addition.a_vec_pub
41 | # test assignment.
42 | fort.addition.a_vec_pub = a
43 | assert(all(a == fort.addition.a_vec_pub))
44 | # try assigning with a vector that is too small..
45 | a = np.asarray(np.random.random(size=(8,)), dtype=np.float32)
46 | fort.addition.a_vec_pub = a
47 | assert(all(a == fort.addition.a_vec_pub[:len(a)]))
48 | # try assigning with a vector that is too big, might seg-fault..
49 | a = np.asarray(np.random.random(size=(80,)), dtype=np.float32)
50 | fort.addition.a_vec_pub = a
51 | assert(all(a[:len(fort.addition.a_vec_pub)] == fort.addition.a_vec_pub))
52 | # reassign a normal value.
53 | a = np.asarray(np.random.random(size=(10,)), dtype=np.float32)
54 | fort.addition.a_vec_pub = a
55 |
56 | # Make sure the vector is correctly negated internally and returned.
57 | result = fort.addition.sub_vecs()
58 | assert(all(result == -a))
59 |
60 | # b_vec_pub (real vector of size 10)
61 | b = np.asarray(np.arange(4), dtype=np.float32)
62 | # testing using the "get" before assignment
63 | # test assignment.
64 | fort.addition.b_vec_pub = b
65 | assert(all(b == fort.addition.b_vec_pub))
66 | # try assigning over top with a new vector
67 | b = np.asarray(np.arange(10), dtype=np.float32)
68 | fort.addition.b_vec_pub = b
69 | assert(all(b == fort.addition.b_vec_pub))
70 |
71 | # Make sure the vectors are correctly subtracted internally and returned.
72 | result = fort.addition.sub_vecs()
73 | assert(all(b == result + a))
74 |
75 | # TODO: check size of array, if large enough, warn about copy in SETTER
76 | # TODO: provide some protection against incorrectly sized array assignments
77 |
78 | # End specific testing code.
79 | # ---------------------------------------------------------------
80 | print("passed", flush=True)
81 | import shutil
82 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
83 |
84 | if __name__ == "__main__":
85 | test()
86 |
--------------------------------------------------------------------------------
/fmodpy/test/module/extra_module.f03:
--------------------------------------------------------------------------------
1 | MODULE EXTRA_CONSTANT
2 | USE CONSTANT, ONLY: C
3 | REAL, PARAMETER :: B = 1.0
4 | END MODULE EXTRA_CONSTANT
5 |
6 | MODULE REFERENCE_ADDITION
7 | USE ADDITION
8 | END MODULE REFERENCE_ADDITION
9 |
--------------------------------------------------------------------------------
/fmodpy/test/module/test_module.f03:
--------------------------------------------------------------------------------
1 | ! This file contains a few modules, only ADDITION is relevant.
2 |
3 | MODULE NOTHING
4 | END MODULE NOTHING
5 |
6 |
7 | ! This file contains a single module ADDITION.
8 | MODULE CONSTANT
9 | REAL, PARAMETER :: C = 1.0 ! Comment should not mess this up
10 | INTEGER, PARAMETER :: D = 2, E = 3 ! Comment should not mess this up
11 | END MODULE CONSTANT
12 |
13 |
14 | ! This module tests out private and public attributes and subroutines.
15 | MODULE ADDITION
16 | USE EXTRA_CONSTANT, ONLY: B
17 |
18 | PRIVATE :: A_PR
19 | PRIVATE B_PR, NOTHING_PR
20 | REAL :: A_PR
21 | INTEGER :: B_PR
22 |
23 | PUBLIC
24 | REAL :: A_PUB
25 | INTEGER :: B_PUB
26 | REAL, DIMENSION(10) :: A_VEC_PUB
27 | REAL, DIMENSION(:), ALLOCATABLE :: B_VEC_PUB
28 | REAL, DIMENSION(:,:), ALLOCATABLE :: C_MAT_PUB
29 |
30 | CONTAINS
31 |
32 | SUBROUTINE NOTHING_PR()
33 | ! Private subroutine that does nothing.
34 | END SUBROUTINE NOTHING_PR
35 |
36 | SUBROUTINE ADD_AB(C)
37 | ! Compute C = A_PUB + B_PUB.
38 | REAL, INTENT(OUT) :: C
39 | C = A_PUB + REAL(B_PUB)
40 | END SUBROUTINE ADD_AB
41 |
42 | SUBROUTINE SUB_VECS(C_VEC)
43 | ! Compute C_VEC = B_VEC_PUB - A_VEC_PUB.
44 | REAL, INTENT(OUT), DIMENSION(SIZE(A_VEC_PUB)) :: C_VEC
45 | IF (ALLOCATED(B_VEC_PUB)) THEN
46 | C_VEC(:) = B_VEC_PUB(:) - A_VEC_PUB(:)
47 | ELSE
48 | C_VEC(:) = -A_VEC_PUB(:)
49 | END IF
50 | END SUBROUTINE SUB_VECS
51 |
52 | SUBROUTINE RETURN_ALLOC(A)
53 | REAL, INTENT(OUT), DIMENSION(:), ALLOCATABLE :: A
54 | END SUBROUTINE RETURN_ALLOC
55 |
56 | END MODULE ADDITION
57 |
--------------------------------------------------------------------------------
/fmodpy/test/procedure/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Standardized testing interface.
4 | def test():
5 | import os
6 | dir_name = os.path.dirname(os.path.abspath(__file__))
7 | test_name = os.path.basename(dir_name)
8 | fort_file = os.path.join(dir_name, f"{test_name}.f03")
9 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
10 | print(f" {test_name}..", end=" ", flush=True)
11 | import fmodpy
12 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
13 | output_dir=dir_name, verbose=False, wrap=True,
14 | rebuild=True, show_warnings=False)
15 | # ---------------------------------------------------------------
16 | # Begin specific testing code.
17 |
18 | import numpy as np
19 | a = np.array([True, False, True, False, True, False, True, False], dtype=bool)
20 | temp = np.asarray(a, dtype='int32')
21 | out = temp.copy()
22 | assert(all(out == fort.test_simple_logical(temp, b=out, c=False)))
23 | assert(not any(fort.test_simple_logical(temp, b=out, c=True)))
24 |
25 | # End specific testing code.
26 | # ---------------------------------------------------------------
27 | print("passed", flush=True)
28 |
29 |
30 | if __name__ == "__main__":
31 | test()
32 |
33 |
--------------------------------------------------------------------------------
/fmodpy/test/procedure/og_procedure/procedure.f03:
--------------------------------------------------------------------------------
1 | SUBROUTINE TEST_STANDARD(QUADRATIC, STEPS, SOLUTION, MAX_WINDOW)
2 | IMPLICIT NONE
3 | ! Minimize a quadratic by taking random
4 | INTEGER, INTENT(IN) :: STEPS
5 | REAL, INTENT(INOUT) :: SOLUTION
6 | REAL, INTENT(IN), OPTIONAL :: MAX_WINDOW
7 | INTERFACE
8 | FUNCTION QUADRATIC(X) RESULT(Y)
9 | REAL, INTENT(IN) :: X
10 | REAL :: Y
11 | END FUNCTION QUADRATIC
12 | END INTERFACE
13 | ! Local variables.
14 | REAL :: DEFAULT_WINDOW
15 | REAL :: W, MOVEMENT, QVALUE, UVALUE
16 | INTEGER :: I
17 | ! Set the default window.
18 | IF (PRESENT(MAX_WINDOW)) THEN ; DEFAULT_WINDOW = MAX_WINDOW
19 | ELSE ; DEFAULT_WINDOW = 8.0
20 | END IF
21 | ! Initialize the value of the function.
22 | QVALUE = QUADRATIC(SOLUTION)
23 | W = DEFAULT_WINDOW
24 | ! Search randomly.
25 | DO I = 1, STEPS
26 | ! Generate a random movement.
27 | CALL RANDOM_NUMBER(MOVEMENT)
28 | MOVEMENT = W * (MOVEMENT - 0.5)
29 | UVALUE = QUADRATIC(SOLUTION+MOVEMENT)
30 | ! If this is better, take the step and reset the search window.
31 | IF (UVALUE < QVALUE) THEN
32 | SOLUTION = SOLUTION + MOVEMENT
33 | QVALUE = UVALUE
34 | W = DEFAULT_WINDOW
35 | ! This is not better, shrink the search window (increase likely of success).
36 | ELSE ; W = W / 2.0
37 | END IF
38 | END DO
39 | END SUBROUTINE TEST_STANDARD
40 |
--------------------------------------------------------------------------------
/fmodpy/test/procedure/og_procedure/procedure.pyx:
--------------------------------------------------------------------------------
1 | ''''''
2 |
3 |
4 | import cython
5 | import numpy
6 |
7 | class NotFortranCompatible(Exception): pass
8 |
9 | # Interface for fortran function quadratic
10 | # =================================================
11 |
12 | cdef void c_quadratic(float* x, float* y):
13 | # Recall the python function from global
14 | # (this was assigned by cython during python wrapper call)
15 | global quadratic_global
16 | # Define any variables needed to copy arrays
17 |
18 | # Create local variables that are python types
19 |
20 | # Call function with python variables in and out
21 | y[0] = quadratic_global(x[0])
22 | # Copy out from python variables back into c-typed (fortran) variables
23 |
24 |
25 | # Wrapper for fortran function test_standard
26 | # =================================================
27 |
28 | cdef extern:
29 | void c_test_standard( void (*quadratic)(float*, float*), int* steps, float* solution, float* max_window, bint* max_window_present )
30 |
31 | @cython.boundscheck(False)
32 | @cython.wraparound(False)
33 | def test_standard( quadratic, int steps, float solution, max_window=None ):
34 | '''! Minimize a quadratic by taking random'''
35 | # Prepare for fortran function call (initialize optionals)
36 | global quadratic_global
37 | quadratic_global = quadratic
38 | cdef bint max_window_present = True
39 | if (type(max_window) == type(None)):
40 | max_window_present = False
41 | max_window = 1
42 | cdef float local_max_window = max_window
43 |
44 |
45 | # Make fortran function call
46 | c_test_standard(&c_quadratic, &steps, &solution, &local_max_window, &max_window_present)
47 | # Return appropriate values based on "INTENT" from fortran code
48 |
49 | return solution
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/fmodpy/test/procedure/og_procedure/procedure_c_to_f.f90:
--------------------------------------------------------------------------------
1 | ! Module generated by fmodpy for fortran file named 'PROCEDURE'
2 | !=====================================================================
3 | MODULE PROCEDURE_WRAPPER
4 | ! Any necessary "USE" statements
5 |
6 | ! Make sure it is clear that no implicit types are used in this wrapper
7 | IMPLICIT NONE
8 | ! Any interfaces defined for "PROCEDURE" declarations
9 |
10 |
11 | ABSTRACT INTERFACE
12 | ! Fortran interface for declaring necessary "PROCEDURE" typed arguments
13 | SUBROUTINE QUADRATIC_INTERFACE( X, Y ) BIND(C)
14 |
15 | REAL, INTENT(IN) :: X
16 | REAL, INTENT(OUT) :: Y
17 | END SUBROUTINE QUADRATIC_INTERFACE
18 | END INTERFACE
19 |
20 |
21 |
22 | INTERFACE
23 | ! Fortran interface for passing assumed shapes to non-moduled functions.
24 | SUBROUTINE TEST_STANDARD( QUADRATIC, STEPS, SOLUTION, MAX_WINDOW &
25 | &)
26 |
27 | PROCEDURE(QUADRATIC_INTERFACE) :: QUADRATIC
28 | INTEGER, INTENT(IN) :: STEPS
29 | REAL, INTENT(INOUT) :: SOLUTION
30 | REAL, INTENT(IN), OPTIONAL :: MAX_WINDOW
31 | END SUBROUTINE TEST_STANDARD
32 | END INTERFACE
33 |
34 |
35 |
36 | CONTAINS
37 | ! Fortran wrapper for TEST_STANDARD, callable from C
38 | !========================================================
39 |
40 | SUBROUTINE c_TEST_STANDARD( QUADRATIC, STEPS, SOLUTION, MAX_WINDOW,&
41 | & MAX_WINDOW_PRESENT ) BIND(c)
42 | ! This subroutine's only purpose is to call the source fortran
43 | ! code while also being accessible from C.
44 | PROCEDURE(QUADRATIC_INTERFACE) :: QUADRATIC
45 | INTEGER, INTENT(IN) :: STEPS
46 | REAL, INTENT(INOUT) :: SOLUTION
47 | LOGICAL, INTENT(IN) :: MAX_WINDOW_PRESENT
48 | REAL, INTENT(IN), OPTIONAL :: MAX_WINDOW
49 | ! Preparation code before function call (copying character arrays)
50 |
51 | ! Calling source fortran function
52 | IF (MAX_WINDOW_PRESENT) THEN
53 | CALL TEST_STANDARD(QUADRATIC, STEPS, SOLUTION, MAX_WINDOW=MAX_W&
54 | &INDOW)
55 | ELSE
56 | CALL TEST_STANDARD(QUADRATIC, STEPS, SOLUTION)
57 | ENDIF
58 | ! Copying out any necessary values for return (character arrays)
59 |
60 | END SUBROUTINE c_TEST_STANDARD
61 |
62 | END MODULE PROCEDURE_WRAPPER
63 |
--------------------------------------------------------------------------------
/fmodpy/test/procedure/procedure.f03:
--------------------------------------------------------------------------------
1 | SUBROUTINE TEST_STANDARD(QUADRATIC, STEPS, SOLUTION, MAX_WINDOW)
2 | IMPLICIT NONE
3 | ! Minimize a quadratic by taking random
4 | INTEGER, INTENT(IN) :: STEPS
5 | REAL, INTENT(INOUT) :: SOLUTION
6 | REAL, INTENT(IN), OPTIONAL :: MAX_WINDOW
7 | INTERFACE
8 | FUNCTION QUADRATIC(X) RESULT(Y)
9 | REAL, INTENT(IN) :: X
10 | REAL :: Y
11 | END FUNCTION QUADRATIC
12 | END INTERFACE
13 | ! Local variables.
14 | REAL :: DEFAULT_WINDOW
15 | REAL :: W, MOVEMENT, QVALUE, UVALUE
16 | INTEGER :: I
17 | ! Set the default window.
18 | IF (PRESENT(MAX_WINDOW)) THEN ; DEFAULT_WINDOW = MAX_WINDOW
19 | ELSE ; DEFAULT_WINDOW = 8.0
20 | END IF
21 | ! Initialize the value of the function.
22 | QVALUE = QUADRATIC(SOLUTION)
23 | W = DEFAULT_WINDOW
24 | ! Search randomly.
25 | DO I = 1, STEPS
26 | ! Generate a random movement.
27 | CALL RANDOM_NUMBER(MOVEMENT)
28 | MOVEMENT = W * (MOVEMENT - 0.5)
29 | UVALUE = QUADRATIC(SOLUTION+MOVEMENT)
30 | ! If this is better, take the step and reset the search window.
31 | IF (UVALUE < QVALUE) THEN
32 | SOLUTION = SOLUTION + MOVEMENT
33 | QVALUE = UVALUE
34 | W = DEFAULT_WINDOW
35 | ! This is not better, shrink the search window (increase likely of success).
36 | ELSE ; W = W / 2.0
37 | END IF
38 | END DO
39 | END SUBROUTINE TEST_STANDARD
40 |
--------------------------------------------------------------------------------
/fmodpy/test/real32/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 |
15 | import numpy as np
16 | n = 10
17 | array_in = np.asarray(np.arange(n), dtype="float32", order='F')
18 | array_out = np.zeros(n//2 - 1, dtype="float32", order='F')
19 | out = fort.test_standard(7, array_in, array_out)
20 | # Test the standard functionality.
21 | assert(out[0] == 8)
22 | assert(all(out[1] == [0,1,2,3]))
23 | assert(all(out[2] == [1,2,3,4]))
24 | assert(np.all(out[3] == [[2,3,4,5],
25 | [3,4,5,6],
26 | [4,5,6,7]]))
27 | assert(out[4] == None)
28 | # Test the extended functionality.
29 | out = fort.test_extended(4)
30 | assert(out[0] == None)
31 | assert(out[1] == None)
32 | assert(all(out[2] == [4, 3, 2, 1]))
33 | # WARNING: In the current code, the memory associated with out[2]
34 | # will be freed by Fortran on subsequent calls to the
35 | # function "test_extended". Copy for object permanance.
36 | out2 = fort.test_extended(10, known_opt_array_out=True)
37 | assert(all(out2[0] == [1,2,3]))
38 | assert(out2[1] == None)
39 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
40 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
41 | # will be freed by Fortran on subsequent calls to
42 | # "test_extended". Copies should be made for permanence.
43 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
44 | assert(out3[0] == None)
45 | assert(all(out3[1] == [3,2,1]))
46 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
47 |
48 | # End specific testing code.
49 | # ---------------------------------------------------------------
50 | print("passed", flush=True)
51 | import shutil
52 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
53 |
54 | if __name__ == "__main__":
55 | test()
56 |
57 |
--------------------------------------------------------------------------------
/fmodpy/test/real32/test_real32.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran REAL wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'REAL' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | IMPLICIT NONE
10 | ! Argument definitions.
11 | REAL, INTENT(IN) :: SING_IN
12 | REAL, INTENT(OUT) :: SING_OUT
13 | REAL, DIMENSION(:), INTENT(IN) :: ARRAY_IN
14 | REAL, DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
15 | REAL, DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
16 | REAL, DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
17 | REAL, INTENT(IN), OPTIONAL :: OPT_SING_IN
18 | REAL, INTENT(OUT), OPTIONAL :: OPT_SING_OUT
19 | ! Local variable.
20 | INTEGER :: I
21 | ! Copy the single input value to the single output value.
22 | SING_OUT = SING_IN + 1
23 | ! Copy as much of the input array as possible to the output array.
24 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
25 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
26 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
27 | ARRAY_OUT(I) = I
28 | END DO
29 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
30 | KNOWN_MATRIX_OUT(I,:) = I
31 | END DO
32 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
33 | DO I = 1,SIZE(ARRAY_OUT)
34 | KNOWN_ARRAY_OUT(I) = I
35 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
36 | END DO
37 | ! Do some operations on the optional inputs / outputs.
38 | IF (PRESENT(OPT_SING_OUT)) THEN
39 | IF (PRESENT(OPT_SING_IN)) THEN
40 | OPT_SING_OUT = OPT_SING_IN
41 | ELSE
42 | OPT_SING_OUT = SING_IN
43 | END IF
44 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
45 | SING_OUT = OPT_SING_IN
46 | END IF
47 | ! End of this subroutine.
48 | END SUBROUTINE TEST_STANDARD
49 |
50 |
51 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
52 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
53 | ! Test the extended functionaly of the 'REAL' type and its
54 | ! interoperability with Python. This includes, optional array
55 | ! inputs, optional array outputs, and allocatable array outputs.
56 | IMPLICIT NONE
57 | REAL, INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
58 | REAL, INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
59 | REAL, INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
60 | REAL, DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
61 | INTEGER, INTENT(IN) :: N
62 | ! Local variable.
63 | INTEGER :: I
64 |
65 | ! Assign the optional array output values.
66 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
67 | IF (PRESENT(OPT_ARRAY_IN)) THEN
68 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
69 | KNOWN_OPT_ARRAY_OUT(I) = I
70 | END DO
71 | ELSE
72 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
73 | KNOWN_OPT_ARRAY_OUT(I) = I
74 | END DO
75 | END IF
76 |
77 | END IF
78 |
79 | ! Allocate the optional array output and assign its values.
80 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
81 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
82 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
83 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
84 | END DO
85 |
86 | END IF
87 |
88 | ! Allocate the required array output to the specified size.
89 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
90 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
91 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
92 | END DO
93 |
94 | ! End of function.
95 | END FUNCTION TEST_EXTENDED
96 |
--------------------------------------------------------------------------------
/fmodpy/test/real64/__init__.py:
--------------------------------------------------------------------------------
1 | # Standardized testing interface.
2 | def test():
3 | import os
4 | dir_name = os.path.dirname(os.path.abspath(__file__))
5 | test_name = os.path.basename(dir_name)
6 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
7 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
8 | print(f" {test_name}..", end=" ", flush=True)
9 | import fmodpy
10 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
11 | output_dir=dir_name)
12 | # ---------------------------------------------------------------
13 | # Begin specific testing code.
14 |
15 | import numpy as np
16 | n = 10
17 | array_in = np.asarray(np.arange(n), dtype=float, order='F')
18 | array_out = np.zeros(n//2 - 1, dtype=float, order='F')
19 | out = fort.test_standard(7, array_in, array_out)
20 | # Test the standard functionality.
21 | assert(out[0] == 8)
22 | assert(all(out[1] == [0,1,2,3]))
23 | assert(all(out[2] == [1,2,3,4]))
24 | assert(np.all(out[3] == [[2,3,4,5],
25 | [3,4,5,6],
26 | [4,5,6,7]]))
27 | assert(out[4] == None)
28 | # Test the extended functionality.
29 | out = fort.test_extended(4)
30 | assert(out[0] == None)
31 | assert(out[1] == None)
32 | assert(all(out[2] == [4, 3, 2, 1]))
33 | # WARNING: In the current code, the memory associated with out[2]
34 | # will be freed by Fortran on subsequent calls to the
35 | # function "test_extended". Copy for object permanance.
36 | out2 = fort.test_extended(10, known_opt_array_out=True)
37 | assert(all(out2[0] == [1,2,3]))
38 | assert(out2[1] == None)
39 | assert(all(out2[2] == list(reversed(range(11)[1:]))))
40 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
41 | # will be freed by Fortran on subsequent calls to
42 | # "test_extended". Copies should be made for permanence.
43 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
44 | assert(out3[0] == None)
45 | assert(all(out3[1] == [3,2,1]))
46 | assert(all(out3[2] == list(reversed(range(7)[1:]))))
47 |
48 | # Ensure that the output type of the subroutine is as expected.
49 | out = fort.test_allocated_output_type()
50 | assert(out.dtype.itemsize == 8)
51 | assert(out.dtype == "float64")
52 | assert(np.allclose(out / np.arange(1,out.size+1), 3.14))
53 |
54 | # End specific testing code.
55 | # ---------------------------------------------------------------
56 | print("passed", flush=True)
57 | import shutil
58 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
59 |
60 | if __name__ == "__main__":
61 | test()
62 |
--------------------------------------------------------------------------------
/fmodpy/test/real64/test_real64.f03:
--------------------------------------------------------------------------------
1 | ! Test Fortran REAL wrapping and usage from Python with fmodpy.
2 |
3 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
4 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
5 | ! Test the basic functionaly of the 'REAL' type and its
6 | ! interoperability with Python. This includes, inputs, outputs,
7 | ! array inputs with known and unknown size, optional inputs, and
8 | ! optional outputs.
9 | USE ISO_FORTRAN_ENV, ONLY: REAL64
10 | IMPLICIT NONE
11 | ! Argument definitions.
12 | REAL(KIND=REAL64), INTENT(IN) :: SING_IN
13 | REAL(KIND=REAL64), INTENT(OUT) :: SING_OUT
14 | REAL(KIND=REAL64), DIMENSION(:), INTENT(IN) :: ARRAY_IN
15 | REAL(KIND=REAL64), DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
16 | REAL(KIND=REAL64), DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
17 | REAL(KIND=REAL64), DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
18 | REAL(KIND=REAL64), INTENT(IN), OPTIONAL :: OPT_SING_IN
19 | REAL(KIND=REAL64), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
20 | ! Local variable.
21 | INTEGER :: I
22 | ! Copy the single input value to the single output value.
23 | SING_OUT = SING_IN + 1
24 | ! Copy as much of the input array as possible to the output array.
25 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
26 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
27 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
28 | ARRAY_OUT(I) = I
29 | END DO
30 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
31 | KNOWN_MATRIX_OUT(I,:) = I
32 | END DO
33 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
34 | DO I = 1,SIZE(ARRAY_OUT)
35 | KNOWN_ARRAY_OUT(I) = I
36 | KNOWN_MATRIX_OUT(:,I) = KNOWN_MATRIX_OUT(:,I) + I
37 | END DO
38 | ! Do some operations on the optional inputs / outputs.
39 | IF (PRESENT(OPT_SING_OUT)) THEN
40 | IF (PRESENT(OPT_SING_IN)) THEN
41 | OPT_SING_OUT = OPT_SING_IN
42 | ELSE
43 | OPT_SING_OUT = SING_IN
44 | END IF
45 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
46 | SING_OUT = OPT_SING_IN
47 | END IF
48 | ! End of this subroutine.
49 | END SUBROUTINE TEST_STANDARD
50 |
51 |
52 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
53 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
54 | ! Test the extended functionaly of the 'REAL' type and its
55 | ! interoperability with Python. This includes, optional array
56 | ! inputs, optional array outputs, and allocatable array outputs.
57 | USE ISO_FORTRAN_ENV, ONLY: REAL64
58 | IMPLICIT NONE
59 | REAL(REAL64), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
60 | REAL(REAL64), INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
61 | REAL(REAL64), INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
62 | REAL(REAL64), DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
63 | INTEGER, INTENT(IN) :: N
64 | ! Local variable.
65 | INTEGER :: I
66 |
67 | ! Assign the optional array output values.
68 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
69 | IF (PRESENT(OPT_ARRAY_IN)) THEN
70 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
71 | KNOWN_OPT_ARRAY_OUT(I) = I
72 | END DO
73 | ELSE
74 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
75 | KNOWN_OPT_ARRAY_OUT(I) = I
76 | END DO
77 | END IF
78 | END IF
79 |
80 | ! Allocate the optional array output and assign its values.
81 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
82 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
83 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
84 | OPT_ALLOC_ARRAY_OUT(I) = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
85 | END DO
86 | END IF
87 |
88 | ! Allocate the required array output to the specified size.
89 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
90 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
91 | ALLOC_ARRAY_OUT(I) = SIZE(ALLOC_ARRAY_OUT) - (I-1)
92 | END DO
93 |
94 | ! End of function.
95 | END FUNCTION TEST_EXTENDED
96 |
97 |
98 | ! Test added for ISSUE#9
99 | ! https://github.com/tchlux/fmodpy/issues/9
100 | SUBROUTINE TEST_ALLOCATED_OUTPUT_TYPE(ARRAY_REAL64)
101 | USE ISO_FORTRAN_ENV, ONLY: REAL64
102 | implicit none
103 | INTEGER :: I
104 | REAL(KIND=REAL64), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: ARRAY_REAL64
105 | ALLOCATE (ARRAY_REAL64(4))
106 | DO I = 1, SIZE(ARRAY_REAL64)
107 | ARRAY_REAL64(I) = 3.14*I
108 | END DO
109 | END SUBROUTINE TEST_ALLOCATED_OUTPUT_TYPE
110 |
--------------------------------------------------------------------------------
/fmodpy/test/test.py:
--------------------------------------------------------------------------------
1 | # - make immutable INTENT(OUT) objects not part of input.
2 | # - make test cases (and necessary code) for:
3 | # real
4 | # inputs
5 | # outputs
6 | # array inputs
7 | # array outputs (known size)
8 | # array outputs (unknown size)
9 | # optional inputs
10 | #
11 | # optional array inputs
12 | # optional array outputs (known size)
13 | # optional array outputs (unknown size)
14 | # allocatable outputs
15 | # optional allocatable outputs
16 | # double precision
17 | # ^^
18 | # integer
19 | # ^^
20 | # logical
21 | # ^^
22 | # character
23 | # ^^
24 | # type
25 | # ^^
26 | # undefined inputs
27 | # procedure inputs
28 | # POINTER attribute
29 | # modules with interfaces
30 | # modules with internal variables (define getter / setter functions)
31 | # modules with custom type declarations
32 | # type EXTENDS (copy the other type, add more definitions)
33 | # fixed format Fortran inputs (Fortran 77)
34 | #
35 | #
36 | # "Module" should be put inside a Python class. Then I can define
37 | # proper "getter" and "setter" behaviors for internal attributes.
38 | #
39 |
40 |
41 | def run_tests():
42 | from fmodpy.config import load_config
43 | load_config(verbose=False, verbose_module=False, wrap=True, rebuild=True)
44 |
45 | # Import all of the tests.
46 | from fmodpy.test.character import test as test_character
47 | from fmodpy.test.real32 import test as test_real32
48 | from fmodpy.test.real64 import test as test_real64
49 | from fmodpy.test.double_precision import test as test_double_precision
50 | from fmodpy.test.int32 import test as test_int32
51 | from fmodpy.test.int64 import test as test_int64
52 | from fmodpy.test.logical import test as test_logical
53 | from fmodpy.test.module import test as test_module
54 | from fmodpy.test.complex64 import test as test_complex64
55 | from fmodpy.test.complex128 import test as test_complex128
56 | from fmodpy.test.type import test as test_type
57 | from fmodpy.test.misc import test as test_misc
58 | from fmodpy.test.fixed_format import test as test_fixed_format
59 |
60 | # Run all of the tests.
61 | test_character()
62 | test_real32()
63 | test_real64()
64 | test_double_precision()
65 | test_int32()
66 | test_int64()
67 | test_logical()
68 | test_module()
69 | test_complex64()
70 | test_complex128()
71 | test_type()
72 | test_misc()
73 | test_fixed_format()
74 |
75 |
76 | if __name__ == "__main__":
77 | # /Users/thomaslux/Library/Python/3.7/bin/pprofile
78 | #
79 | # Activate a code coverage module.
80 | # import coverage, os
81 | # this_dir = os.path.dirname(os.path.abspath(__file__))
82 | # module_dir = os.path.dirname(this_dir)
83 | # omitted = ([this_dir] + [os.path.join(this_dir,f) for f in os.listdir(this_dir)]
84 | # + [os.path.join(this_dir,f)+"*" for f in os.listdir(this_dir)
85 | # if os.path.isdir(os.path.join(this_dir,f))])
86 | # print("Omitted:")
87 | # print(omitted)
88 | # cov = coverage.Coverage(source=[module_dir], omit=omitted)
89 | # cov.start()
90 |
91 | # Run the tests on `fmodpy`.
92 | run_tests()
93 |
94 | exit()
95 |
96 | print()
97 | print("Generating code coverage report..", flush=True)
98 | # Save data from the coverage tracking (for report).
99 | cov.stop()
100 | cov.save()
101 | print(" (will host local webserver for 10 minutes or until killed)", flush=True)
102 | # Create a temporary directory for holding test results.
103 | from tempfile import TemporaryDirectory
104 | temp_dir = TemporaryDirectory()
105 | results_dir = temp_dir.name
106 | cov.html_report(directory=results_dir)
107 | # Open the results file.
108 | import webbrowser
109 | webbrowser.open("file://"+os.path.join(results_dir, "index.html"))
110 | # Wait for load, then delete the temporary directory.
111 | # (user might decide to kill this process early).
112 | import time
113 | time.sleep(60*10) # <- Default to 10 minutes of waiting.
114 | temp_dir.cleanup()
115 | del temp_dir
116 |
--------------------------------------------------------------------------------
/fmodpy/test/type/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | # Standardized testing interface.
3 | def test():
4 | import os
5 | dir_name = os.path.dirname(os.path.abspath(__file__))
6 | test_name = os.path.basename(dir_name)
7 | fort_file = os.path.join(dir_name, f"test_{test_name}.f03")
8 | build_dir = os.path.join(dir_name, f"fmodpy_{test_name}")
9 | print(f" {test_name}..", end=" ", flush=True)
10 | import fmodpy
11 | fort = fmodpy.fimport(fort_file, build_dir=build_dir,
12 | dependencies=["test_type_aux.f03"],
13 | output_dir=dir_name, rebuild=True,
14 | verbose=False
15 | )
16 | # ---------------------------------------------------------------
17 | # Begin specific testing code.
18 |
19 | t1 = fort.ftypes.T1()
20 | t2 = fort.ftypes.copy_t1_to_t2(t1)
21 | assert((t1.a == t2.a) and (t1.b == t2.b))
22 |
23 | import numpy as np
24 | n = 10
25 | array_in = np.asarray(np.arange(n), dtype=fort.ftypes.T1, order='F')
26 | array_out = np.zeros(n//2 - 1, dtype=fort.T2, order='F')
27 | out = fort.test_standard(7, array_in, array_out)
28 | # Test the standard functionality.
29 | assert(out[0].a == out[0].b == 8)
30 | assert(all(out[1]["a"] == [0,1,2,3]))
31 | assert(all(out[1]["b"] == [0,1,2,3]))
32 | assert(all(out[2]["a"] == [1,2,3,4]))
33 | assert(all(out[2]["b"] == [1,2,3,4]))
34 | assert(np.all(out[3]["a"] == [[2,3,4,5],
35 | [3,4,5,6],
36 | [4,5,6,7]]))
37 | assert(np.all(out[3]["b"] == [[2,3,4,5],
38 | [3,4,5,6],
39 | [4,5,6,7]]))
40 | assert(out[4] == None)
41 | # Test the extended functionality.
42 | out = fort.test_extended(4)
43 | assert(out[0] == None)
44 | assert(out[1] == None)
45 | assert(all(out[2]["a"] == [4, 3, 2, 1]))
46 | assert(all(out[2]["b"] == [4, 3, 2, 1]))
47 | # WARNING: In the current code, the memory associated with out[2]
48 | # will be freed by Fortran on subsequent calls to the
49 | # function "test_extended". Copy for object permanance.
50 | out2 = fort.test_extended(10, known_opt_array_out=True)
51 | assert(all(out2[0]["a"] == [1,2,3]))
52 | assert(all(out2[0]["b"] == [1,2,3]))
53 | assert(out2[1] == None)
54 | assert(all(out2[2]["a"] == list(reversed(range(11)[1:]))))
55 | assert(all(out2[2]["b"] == list(reversed(range(11)[1:]))))
56 | # WARNING: Similar to before, the memory at out2[0] and out2[2]
57 | # will be freed by Fortran on subsequent calls to
58 | # "test_extended". Copies should be made for permanence.
59 | out3 = fort.test_extended(6, opt_alloc_array_out=True)
60 | assert(out3[0] == None)
61 | assert(all(out3[1]["a"] == [3,2,1]))
62 | assert(all(out3[1]["b"] == [3,2,1]))
63 | assert(all(out3[2]["a"] == list(reversed(range(7)[1:]))))
64 | assert(all(out3[2]["b"] == list(reversed(range(7)[1:]))))
65 |
66 |
67 | # Test a code that uses an auxilliary type (in another file).
68 | res = fort.test_return_aux()
69 | assert(res.a == 1)
70 | assert(res.b == 2.0)
71 | assert(res.c == '3'.encode())
72 |
73 | # End specific testing code.
74 | # ---------------------------------------------------------------
75 | print("passed", flush=True)
76 | import shutil
77 | shutil.rmtree(os.path.join(dir_name,f"test_{test_name}"))
78 |
79 |
80 | if __name__ == "__main__":
81 | test()
82 |
--------------------------------------------------------------------------------
/fmodpy/test/type/derived_type_error.f03:
--------------------------------------------------------------------------------
1 | ! Define a simple subroutine that copies a derived type from A to B.
2 | SUBROUTINE COPY_TYPE(N, A, B)
3 | IMPLICIT NONE
4 | TYPE :: TP(NX)
5 | INTEGER, LEN :: NX
6 | REAL, DIMENSION(NX) :: X
7 | END TYPE TP
8 | INTEGER, INTENT(IN) :: N
9 | TYPE(TP(N)), INTENT(IN) :: A
10 | TYPE(TP(N)), INTENT(OUT) :: B
11 | B = A
12 | END SUBROUTINE COPY_TYPE
13 |
14 | ! Run a program that tests derived type functionality.
15 | PROGRAM TEST_DERIVED_TYPES
16 | IMPLICIT NONE
17 | ! Configuration.
18 | TYPE :: TP(NX)
19 | INTEGER, LEN :: NX
20 | REAL, DIMENSION(NX) :: X
21 | END TYPE TP
22 | INTEGER, PARAMETER :: N = 10
23 | ! Locals for testing.
24 | INTEGER :: I
25 | TYPE(TP(N)) :: A
26 | TYPE(TP(N)) :: B
27 |
28 | ! -----------------------------------------------------------------
29 | ! ! Define an interface to the externally defined subroutine above.
30 | ! INTERFACE
31 | ! SUBROUTINE COPY_TYPE(N, A, B)
32 | ! IMPLICIT NONE
33 | ! TYPE :: TP(NX)
34 | ! INTEGER, LEN :: NX
35 | ! REAL, DIMENSION(NX) :: X
36 | ! END TYPE TP
37 | ! INTEGER, INTENT(IN) :: N
38 | ! TYPE(TP(N)), INTENT(IN) :: A
39 | ! TYPE(TP(N)), INTENT(OUT) :: B
40 | ! END SUBROUTINE COPY_TYPE
41 | ! END INTERFACE
42 |
43 | EXTERNAL :: COPY_TYPE
44 | ! -----------------------------------------------------------------
45 |
46 | ! Initialize A.
47 | DO I = 1, N
48 | A%X(I) = I
49 | END DO
50 | ! Call the test-running procedure.
51 | CALL COPY_TYPE(N, A, B)
52 |
53 | END PROGRAM TEST_DERIVED_TYPES
54 |
--------------------------------------------------------------------------------
/fmodpy/test/type/test_type.f03:
--------------------------------------------------------------------------------
1 | MODULE FTYPES
2 | TYPE, BIND(C) :: SIZE_TYPE
3 | INTEGER :: N
4 | END TYPE SIZE_TYPE
5 |
6 | TYPE, BIND(C) :: T1
7 | INTEGER :: A
8 | REAL :: B
9 | END TYPE T1
10 |
11 | TYPE, BIND(C) :: T2
12 | INTEGER :: A
13 | REAL :: B
14 | END TYPE T2
15 |
16 | CONTAINS
17 |
18 | SUBROUTINE COPY_T1_TO_T2(A, B)
19 | TYPE(T1), INTENT(IN) :: A
20 | TYPE(T2), INTENT(OUT) :: B
21 | B%A = A%A
22 | B%B = A%B
23 | END SUBROUTINE COPY_T1_TO_T2
24 |
25 | END MODULE FTYPES
26 |
27 | ! Test Fortran REAL wrapping and usage from Python with fmodpy.
28 |
29 | SUBROUTINE TEST_STANDARD(SING_IN, SING_OUT, ARRAY_IN, ARRAY_OUT,&
30 | KNOWN_ARRAY_OUT, KNOWN_MATRIX_OUT, OPT_SING_IN, OPT_SING_OUT)
31 | ! Test the basic functionaly of the 'TYPE(T1)' type and its
32 | ! interoperability with Python. This includes, inputs, outputs,
33 | ! array inputs with known and unknown size, optional inputs, and
34 | ! optional outputs.
35 | IMPLICIT NONE
36 | TYPE, BIND(C) :: T2
37 | INTEGER :: A
38 | REAL :: B
39 | END TYPE T2
40 | ! Argument definitions.
41 | TYPE(T2), INTENT(IN) :: SING_IN
42 | TYPE(T2), INTENT(OUT) :: SING_OUT
43 | TYPE(T2), DIMENSION(:), INTENT(IN) :: ARRAY_IN
44 | TYPE(T2), DIMENSION(:), INTENT(OUT) :: ARRAY_OUT
45 | TYPE(T2), DIMENSION(SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_ARRAY_OUT
46 | TYPE(T2), DIMENSION(3,SIZE(ARRAY_OUT)), INTENT(OUT) :: KNOWN_MATRIX_OUT
47 | TYPE(T2), INTENT(IN), OPTIONAL :: OPT_SING_IN
48 | TYPE(T2), INTENT(OUT), OPTIONAL :: OPT_SING_OUT
49 | ! Local variable.
50 | INTEGER :: I
51 | ! Copy the single input value to the single output value.
52 | SING_OUT%A = SING_IN%A + 1
53 | SING_OUT%B = SING_IN%B + 1.0
54 | ! Copy as much of the input array as possible to the output array.
55 | ARRAY_OUT(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))) = &
56 | &ARRAY_IN(1:MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT)))
57 | DO I = MIN(SIZE(ARRAY_IN),SIZE(ARRAY_OUT))+1, SIZE(ARRAY_OUT)
58 | ARRAY_OUT(I)%A = I
59 | ARRAY_OUT(I)%B = REAL(I)
60 | END DO
61 | DO I = 1, SIZE(KNOWN_MATRIX_OUT, 1)
62 | KNOWN_MATRIX_OUT(I,:)%A = I
63 | KNOWN_MATRIX_OUT(I,:)%B = REAL(I)
64 | END DO
65 | ! Set the KNOWN_ARRAY and the KNOWN_MATRIX values to be identifiabl.
66 | DO I = 1,SIZE(ARRAY_OUT)
67 | KNOWN_ARRAY_OUT(I)%A = I
68 | KNOWN_ARRAY_OUT(I)%B = REAL(I)
69 | KNOWN_MATRIX_OUT(:,I)%A = KNOWN_MATRIX_OUT(:,I)%A + I
70 | KNOWN_MATRIX_OUT(:,I)%B = KNOWN_MATRIX_OUT(:,I)%B + REAL(I)
71 | END DO
72 | ! Do some operations on the optional inputs / outputs.
73 | IF (PRESENT(OPT_SING_OUT)) THEN
74 | IF (PRESENT(OPT_SING_IN)) THEN
75 | OPT_SING_OUT = OPT_SING_IN
76 | ELSE
77 | OPT_SING_OUT = SING_IN
78 | END IF
79 | ELSE IF (PRESENT(OPT_SING_IN)) THEN
80 | SING_OUT = OPT_SING_IN
81 | END IF
82 | ! End of this subroutine.
83 | END SUBROUTINE TEST_STANDARD
84 |
85 |
86 | FUNCTION TEST_EXTENDED(OPT_ARRAY_IN, KNOWN_OPT_ARRAY_OUT,&
87 | & OPT_ALLOC_ARRAY_OUT, N ) RESULT(ALLOC_ARRAY_OUT)
88 | ! Test the extended functionaly of the 'TYPE(T1)' type and its
89 | ! interoperability with Python. This includes, optional array
90 | ! inputs, optional array outputs, and allocatable array outputs.
91 | USE FTYPES, ONLY: T1
92 | IMPLICIT NONE
93 | TYPE(T1), INTENT(IN), OPTIONAL, DIMENSION(:) :: OPT_ARRAY_IN
94 | TYPE(T1), INTENT(OUT), OPTIONAL :: KNOWN_OPT_ARRAY_OUT(3)
95 | TYPE(T1), INTENT(OUT), OPTIONAL, ALLOCATABLE :: OPT_ALLOC_ARRAY_OUT(:)
96 | TYPE(T1), DIMENSION(:), ALLOCATABLE :: ALLOC_ARRAY_OUT
97 | INTEGER, INTENT(IN) :: N
98 | ! Local variable.
99 | INTEGER :: I
100 |
101 | ! Assign the optional array output values.
102 | IF (PRESENT(KNOWN_OPT_ARRAY_OUT)) THEN
103 | IF (PRESENT(OPT_ARRAY_IN)) THEN
104 | DO I = 1, MIN(SIZE(OPT_ARRAY_IN), SIZE(KNOWN_OPT_ARRAY_OUT))
105 | KNOWN_OPT_ARRAY_OUT(I)%A = I
106 | KNOWN_OPT_ARRAY_OUT(I)%B = REAL(I)
107 | END DO
108 | ELSE
109 | DO I = 1, SIZE(KNOWN_OPT_ARRAY_OUT)
110 | KNOWN_OPT_ARRAY_OUT(I)%A = I
111 | KNOWN_OPT_ARRAY_OUT(I)%B = REAL(I)
112 | END DO
113 | END IF
114 |
115 | END IF
116 |
117 | ! Allocate the optional array output and assign its values.
118 | IF (PRESENT(OPT_ALLOC_ARRAY_OUT)) THEN
119 | ALLOCATE(OPT_ALLOC_ARRAY_OUT(1:N/2))
120 | DO I = 1, SIZE(OPT_ALLOC_ARRAY_OUT)
121 | OPT_ALLOC_ARRAY_OUT(I)%A = SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1)
122 | OPT_ALLOC_ARRAY_OUT(I)%B = REAL(SIZE(OPT_ALLOC_ARRAY_OUT) - (I-1))
123 | END DO
124 |
125 | END IF
126 |
127 | ! Allocate the required array output to the specified size.
128 | ALLOCATE(ALLOC_ARRAY_OUT(1:N))
129 | DO I = 1, SIZE(ALLOC_ARRAY_OUT)
130 | ALLOC_ARRAY_OUT(I)%A = SIZE(ALLOC_ARRAY_OUT) - (I-1)
131 | ALLOC_ARRAY_OUT(I)%B = REAL(SIZE(ALLOC_ARRAY_OUT) - (I-1))
132 | END DO
133 |
134 | ! End of function.
135 | END FUNCTION TEST_EXTENDED
136 |
137 |
138 | ! Create a test that pulls from a type defined in another file. This will
139 | ! require that the Fortran wrapper USE's the other module, and that
140 | ! that the Python wrapper has a definition of the type from the other file.
141 | FUNCTION TEST_RETURN_AUX() RESULT(ANSWER)
142 | TYPE, BIND(C) :: T3
143 | INTEGER :: A
144 | REAL :: B
145 | CHARACTER :: C
146 | END TYPE T3
147 | TYPE(T3) :: ANSWER
148 | ANSWER%A = 1
149 | ANSWER%B = 2.0
150 | ANSWER%C = '3'
151 | END FUNCTION TEST_RETURN_AUX
152 |
--------------------------------------------------------------------------------
/fmodpy/test/type/test_type_aux.f03:
--------------------------------------------------------------------------------
1 |
2 | MODULE EXTRA_TYPE
3 |
4 | TYPE, BIND(C) :: T3
5 | INTEGER :: A
6 | REAL :: B
7 | CHARACTER :: C
8 | END TYPE T3
9 |
10 | END MODULE EXTRA_TYPE
11 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
fmodpy
3 |
4 |
5 |
6 | An easy-to-use Fortran wrapper for Python.
7 |
8 | No source code changes, no manual specification files, just import like it's native.
9 |
10 |
11 | ```python
12 | # Automatically generate a wrapper, compile, link, and import into Python.
13 | fortran_module = fmodpy.fimport('MODULE.f08', dependencies=['LIB.f90', 'BLAS.f'])
14 | # - handles any Fortran source, modern, or fixed format F77
15 | # - reads the source to determine the interface specification
16 | # - includes Fortran documentation in the wrapped code
17 | # - produces a stand-alone and distrutable Python module with only numpy as dependency
18 | ```
19 |
20 |
21 | Modern Fortran is capable of being integrated with Python near
22 | seamlessly allowing for rapid transition between prototype and
23 | optimized production-ready code. This packages aims to make Fortran
24 | code as easy to import and use as native Python. This combines the
25 | performance of Fortran with the convenience and accessibility of
26 | Python, allowing for a productive and exciting development pipeline.
27 |
28 | This package is compiler independent. The generated wrappers are
29 | self contained, written purely in Python, and are immediately sharable
30 | to any other POSIX platform with a Fortran compiler installed. The only
31 | python dependency is `numpy` (no need to hassle with installing `cython`).
32 | After generating the python wrapper module, even `fmodpy` itself is no longer
33 | a dependency! This makes `fmodpy` particularly suitable to writing and sharing
34 | performant mathematical software with a broader audience, since
35 | complex dependencies can often be difficult to configure on cutting
36 | edge high performance computing platforms.
37 |
38 |
39 | ## INSTALLATION:
40 |
41 | python3 -m pip install fmodpy
42 |
43 | This code expects that you already have a Fortran compiler
44 | installed. By default most machines do not have a Fortran compiler
45 | installed, but most package managers support installation of
46 | `gfortran` (a GNU compiler). In addition, there are popular
47 | commercial Fortran compilers such as `pgifortran` (the [PGI
48 | compiler](https://www.pgroup.com/products/index.htm) that uniquely
49 | supports OpenACC), `ifort` (the [Intel
50 | compiler](https://software.intel.com/content/www/us/en/develop/tools/oneapi/components/fortran-compiler.html)),
51 | and `f90` (the [Oracle Sun
52 | compiler](https://www.oracle.com/application-development/technologies/developerstudio-features.html)).
53 |
54 | The easiest setup is to install `gfortran` with your preferred
55 | package manager, then the default behaviors of `fmodpy` will work
56 | correctly.
57 |
58 |
59 | ## USAGE:
60 |
61 | ### PYTHON:
62 |
63 | ```python
64 | import fmodpy
65 |
66 | # Compile and import the Fortran code. (This will automatically
67 | # recompile the module if the Fortran source has been saved
68 | # more recently than the last time the module was imported.)
69 | module = fmodpy.fimport("")
70 | ```
71 |
72 | For more details, see the `help(fmodpy.fimport)` documentation.
73 | Notably, global configurations (i.e., the default Fortran compiler)
74 | can be viewed and edited with `fmodpy.configure`.
75 |
76 | ### COMMAND LINE:
77 |
78 | Run `fmodpy` from the command line with:
79 |
80 | ```shell
81 | python3 -m fmodpy "" [setting1=""] [setting2=""] ...
82 | ```
83 |
84 | The result will be a directory containing a Python package that
85 | wraps and calls the underlying Fortran code.
86 |
87 | Execute with no arguments to get help documentation. For a list of
88 | the different configuration options, run the command:
89 | ```shell
90 | python3 -c "import fmodpy; fmodpy.configure()"
91 | ```
92 |
93 |
94 | ## SUPPORTED FORTRAN:
95 |
96 | - `INTEGER` 32 and 64 bit, (allocatable / assumed shape) arrays, optionals, pointers
97 | - `REAL` 32 and 64 bit, (allocatable / assumed shape) arrays, optionals, pointers
98 | - `CHARACTER` singletons and assumed-shape arrays, but *no* support for `LEN` behaviors
99 | - `COMPLEX` 64 and 128 bit, (allocatable / assumed shape) arrays, optionals, pointers
100 | - `LOGICAL` singletons, (allocatable / assumed shape) arrays, optionals, pointers
101 | - `TYPE` singletons, (allocatable / assumed shape) arrays, optionals, and pointers (type must have `BIND(C)` attribute)
102 | - `SUBROUTINE` standard behaviors (automatically drops `PURE` and `RECURSIVE` prefixes)
103 | - `FUNCTION` standard behaviors (wrapped with a standard subroutine call)
104 | - `MODULE` wrapper that behaves like an instantiated Python class with property-based accessors for internal attributes
105 |
106 | It is a goal to eventually allow for the following:
107 | - passing a `PROCEDURE` as an argument to Fortran code
108 |
109 | This code base is entirely driven by concrete examples and use-cases. If you want to see something supported that is not currently, please consider posting a minimum example of the Fortran code you'd like to wrap under the [Issues](https://github.com/tchlux/fmodpy/issues) page.
110 |
111 |
112 | ## HOW IT WORKS:
113 |
114 | Reads the fortran file, abstracting out the modules, subroutines,
115 | functions. Identifies the type-description of each argument for
116 | module variables, subroutines, and functions. Uses type-descriptors
117 | to generate a Fortran wrapper with `BIND(C)` enabled, as well as a
118 | matching Python wrapper using `ctypes` to pass data from Python into
119 | the Fortran wrapper. The constructed Python wrapper contains
120 | compilation settings that will automatically recompile a shared object
121 | file containing the underlying original Fortran source code.
122 | Overall, the call sequence at runtime looks like:
123 |
124 | Python code
125 | -> Python wrapper converting to C types
126 | -> Fortran wrapper bound to C (transferring characters, implicit shapes, etc.)
127 | -> Original Fortran code
128 |
129 |
130 | This uses the specifications from the fortran file to determine how
131 | the interface for each subroutine / function should behave. (I.e.,
132 | `INTENT(IN)` does not return, `INTENT(OUT)` is optional as input.)
133 |
134 |
135 | ## EXAMPLE CODE
136 |
137 | Here is a simple python code that compares a `fmodpy`-wrapped Fortran
138 | code with standard NumPy. This example performs a matrix multiply
139 | operation using Fortran.
140 |
141 | ```python
142 | # This is a Python file named whatever you like. It demonstrates
143 | # automatic `fmodpy` wrapping.
144 |
145 | import fmodpy
146 | import numpy as np
147 |
148 | code = fmodpy.fimport("code.f03")
149 |
150 | a = np.array([
151 | [1,2,3,4,5],
152 | [1,2,3,4,5]
153 | ], dtype=float, order='F')
154 |
155 | b = np.array([
156 | [1,2],
157 | [3,4],
158 | [5,6],
159 | [7,8],
160 | [9,10]
161 | ], dtype=float, order='F')
162 |
163 | print()
164 | print("a:")
165 | print(a)
166 |
167 | print()
168 | print("b:")
169 | print(b)
170 |
171 | print()
172 | print("Numpy result")
173 | print(np.matmul(a,b))
174 |
175 | print()
176 | print("Fortran result")
177 | print(code.matrix_multiply(a,b))
178 |
179 |
180 | print()
181 | help(code.matrix_multiply)
182 | ```
183 |
184 | Here is the associated Fortran file `code.f03` (no specific reason for
185 | the name, any of `.f95`, `.f03`, and `.f08` extensions could be used).
186 |
187 | ```fortran
188 | ! This module provides various testbed routines for demonstrating
189 | ! the simplicity of Fortran code usage with `fmodpy`.
190 | !
191 | ! Contains:
192 | !
193 | ! MATRIX_MULTIPLY -- A routine for multiplying two matrices of floats.
194 | !
195 |
196 | SUBROUTINE MATRIX_MULTIPLY(A,B,OUT)
197 | ! This subroutine multiplies the matrices A and B.
198 | !
199 | ! INPUT:
200 | ! A(M,N) -- A 2D matrix of 64 bit floats.
201 | ! B(N,P) -- A 2D matrix of 64 bit floats,
202 | !
203 | ! OUTPUT:
204 | ! OUT(M,P) -- The matrix that is the result of (AB).
205 | !
206 | USE ISO_FORTRAN_ENV, ONLY: REAL64 ! <- Get a float64 type.
207 | IMPLICIT NONE ! <- Make undefined variable usage raise errors.
208 | REAL(KIND=REAL64), INTENT(IN), DIMENSION(:,:) :: A, B
209 | REAL(KIND=REAL64), INTENT(OUT), DIMENSION(SIZE(A,1),SIZE(B,2)) :: OUT
210 |
211 | ! Compute the matrix multiplication of A and B.
212 | OUT(:,:) = MATMUL(A,B)
213 |
214 | END SUBROUTINE MATRIX_MULTIPLY
215 | ```
216 |
217 | Now run the python program and see the output!
218 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = readme.md
3 |
4 | [bdist_wheel]
5 | universal=1
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Custom error (in case user does not have setuptools)
2 | class DependencyError(Exception): pass
3 |
4 | # Try to import setuptools (if it fails, the user needs that package)
5 | try:
6 | from setuptools import setup, find_packages
7 | except:
8 | raise(DependencyError("Missing python package 'setuptools'.\n python3 -m pip install --user setuptools"))
9 |
10 | import os
11 | # Go to the "about" directory in the package directory
12 | PACKAGE_NAME = "fmodpy"
13 | DEFAULT_DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)),PACKAGE_NAME,"about")
14 | # Convenience function for reading information files
15 | def read(f_name, dir_name=DEFAULT_DIRECTORY, processed=True):
16 | text = []
17 | # Check for directory existence.
18 | if (not os.path.exists(dir_name)):
19 | print(f"ERROR: No directory found '{dir_name}'")
20 | while (not os.path.exists(dir_name)):
21 | dir_name = os.path.dirname(dir_name)
22 | print(f" only found {os.listdir(dir_name)}.")
23 | return [""] if processed else ""
24 | # Check for path existence.
25 | path = os.path.join(dir_name, f_name)
26 | if (not os.path.exists(path)):
27 | print(f"ERROR: No path found '{path}'")
28 | print(f" only found {os.listdir(dir_name)}.")
29 | return [""] if processed else ""
30 | # Process the contents and return.
31 | with open(path) as f:
32 | if processed:
33 | for line in f:
34 | line = line.strip()
35 | if (len(line) > 0) and (line[0] != "%"):
36 | text.append(line)
37 | else:
38 | text = f.read()
39 | # Return the text.
40 | return text
41 |
42 | if __name__ == "__main__":
43 | # Read in the package description files
44 | # ===============================================
45 | package = PACKAGE_NAME
46 | version =read("version.txt")[0]
47 | description = read("description.txt")[0]
48 | requirements = read("requirements.txt")
49 | keywords = read("keywords.txt")
50 | classifiers = read("classifiers.txt")
51 | name, email, git_username = read("author.txt")
52 |
53 | setup(
54 | author=name,
55 | author_email=email,
56 | name=package,
57 | packages=find_packages(exclude=[]),
58 | include_package_data=True,
59 | install_requires=requirements,
60 | version=version,
61 | url = f"https://github.com/{git_username}/{package}",
62 | download_url = f"https://github.com/{git_username}/{package}/archive/{version}.tar.gz",
63 | description = description,
64 | keywords = keywords,
65 | python_requires = ">=3.6",
66 | license="MIT",
67 | classifiers=classifiers
68 | )
69 |
70 | # Check to see if the user has a fortran compiler. Create a
71 | # warning if there is no Fortran compiler installed.
72 |
73 |
--------------------------------------------------------------------------------