8000 Merge pull request #25226 from HaoZeke/gh25207 · numpy/numpy@003c846 · GitHub
[go: up one dir, main page]

Skip to content

Commit 003c846

Browse files
authored
Merge pull request #25226 from HaoZeke/gh25207
BUG: Handle `iso_c_type` mappings more consistently
2 parents d7d406f + 842e39f commit 003c846

File tree

6 files changed

+163
-74
lines changed

6 files changed

+163
-74
lines changed

numpy/f2py/_isocbind.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,53 @@
99
1010
NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
1111
"""
12+
# These map to keys in c2py_map, via forced casting for now, see gh-25229
1213
iso_c_binding_map = {
1314
'integer': {
1415
'c_int': 'int',
15-
'c_short': 'short int',
16-
'c_long': 'long int',
17-
'c_long_long': 'long long int',
18-
'c_signed_char': 'signed char',
19-
'c_size_t': 'size_t',
20-
'c_int8_t': 'int8_t',
21-
'c_int16_t': 'int16_t',
22-
'c_int32_t': 'int32_t',
23-
'c_int64_t': 'int64_t',
24-
'c_int_least8_t': 'int_least8_t',
25-
'c_int_least16_t': 'int_least16_t',
26-
'c_int_least32_t': 'int_least32_t',
27-
'c_int_least64_t': 'int_least64_t',
28-
'c_int_fast8_t': 'int_fast8_t',
29-
'c_int_fast16_t': 'int_fast16_t',
30-
'c_int_fast32_t': 'int_fast32_t',
31-
'c_int_fast64_t': 'int_fast64_t',
32-
'c_intmax_t': 'intmax_t',
33-
'c_intptr_t': 'intptr_t',
34-
'c_ptrdiff_t': 'intptr_t',
16+
'c_short': 'short', # 'short' <=> 'int' for now
17+
'c_long': 'long', # 'long' <=> 'int' for now
18+
'c_long_long': 'long_long',
19+
'c_signed_char': 'signed_char',
20+
'c_size_t': 'unsigned', # size_t <=> 'unsigned' for now
21+
'c_int8_t': 'signed_char', # int8_t <=> 'signed_char' for now
22+
'c_int16_t': 'short', # int16_t <=> 'short' for now
23+
'c_int32_t': 'int', # int32_t <=> 'int' for now
24+
'c_int64_t': 'long_long',
25+
'c_int_least8_t': 'signed_char', # int_least8_t <=> 'signed_char' for now
26+
'c_int_least16_t': 'short', # int_least16_t <=> 'short' for now
27+
'c_int_least32_t': 'int', # int_least32_t <=> 'int' for now
28+
'c_int_least64_t': 'long_long',
29+
'c_int_fast8_t': 'signed_char', # int_fast8_t <=> 'signed_char' for now
30+
'c_int_fast16_t': 'short', # int_fast16_t <=> 'short' for now
31+
'c_int_fast32_t': 'int', # int_fast32_t <=> 'int' for now
32+
'c_int_fast64_t': 'long_long',
33+
'c_intmax_t': 'long_long', # intmax_t <=> 'long_long' for now
34+
'c_intptr_t': 'long', # intptr_t <=> 'long' for now
35+
'c_ptrdiff_t': 'long', # ptrdiff_t <=> 'long' for now
3536
},
3637
'real': {
3738
'c_float': 'float',
3839
'c_double': 'double',
39-
'c_long_double': 'long double'
40+
'c_long_double': 'long_double'
4041
},
4142
'complex': {
42-
'c_float_complex': 'float _Complex',
43-
'c_double_complex': 'double _Complex',
44-
'c_long_double_complex': 'long double _Complex'
43+
'c_float_complex': 'complex_float',
44+
'c_double_complex': 'complex_double',
45+
'c_long_double_complex': 'complex_long_double'
4546
},
4647
'logical': {
47-
'c_bool': '_Bool'
48+
'c_bool': 'unsigned_char' # _Bool <=> 'unsigned_char' for now
4849
},
4950
'character': {
5051
'c_char': 'char'
5152
}
5253
}
5354

55+
# TODO: See gh-25229
56+
isoc_c2pycode_map = {}
57+
iso_c2py_map = {}
58+
5459
isoc_kindmap = {}
5560
for fortran_type, c_type_dict in iso_c_binding_map.items():
5661
for c_type in c_type_dict.keys():

numpy/f2py/auxfuncs.py

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
'isunsigned_long_longarray', 'isunsigned_short',
4545
'isunsigned_shortarray', 'l_and', 'l_not', 'l_or', 'outmess',
4646
'replace', 'show', 'stripcomma', 'throw_error', 'isattr_value',
47-
'deep_merge', 'getuseblocks'
47+
'getuseblocks', 'process_f2cmap_dict'
4848
]
4949

5050

@@ -893,28 +893,6 @@ def applyrules(rules, d, var={}):
893893
del ret[k]
894894
return ret
895895

896-
def deep_merge(dict1, dict2):
897-
"""Recursively merge two dictionaries into a new dictionary.
898-
899-
Parameters:
900-
- dict1: The base dictionary.
901-
- dict2: The dictionary to merge into a copy of dict1.
902-
If a key exists in both, the dict2 value will take precedence.
903-
904-
Returns:
905-
- A new merged dictionary.
906-
"""
907-
merged_dict = deepcopy(dict1)
908-
for key, value in dict2.items():
909-
if key in merged_dict:
910-
if isinstance(merged_dict[key], dict) and isinstance(value, dict):
911-
merged_dict[key] = deep_merge(merged_dict[key], value)
912-
else:
913-
merged_dict[key] = value
914-
else:
915-
merged_dict[key] = value
916-
return merged_dict
917-
918896
_f2py_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]+)',
919897
re.I).match
920898
_f2py_user_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]*?'
@@ -939,3 +917,72 @@ def getuseblocks(pymod):
939917
if modblock.get('use'):
940918
all_uses.extend([x for x in modblock.get("use").keys() if "__" not in x])
941919
return all_uses
920+
921+
def process_f2cmap_dict(f2cmap_all, new_map, c2py_map, verbose = False):
922+
"""
923+
Update the Fortran-to-C type mapping dictionary with new mappings and
924+
return a list of successfully mapped C types.
925+
926+
This function integrates a new mapping dictionary into an existing
927+
Fortran-to-C type mapping dictionary. It ensures that all keys are in
928+
lowercase and validates new entries against a given C-to-Python mapping
929+
dictionary. Redefinitions and invalid entries are reported with a warning.
930+
931+
Parameters
932+
----------
933+
f2cmap_all : dict
934+
The existing Fortran-to-C type mapping dictionary that will be updated.
935+
It should be a dictionary of dictionaries where the main keys represent
936+
Fortran types and the nested dictionaries map Fortran type specifiers
937+
to corresponding C types.
938+
939+
new_map : dict
940+
A dictionary containing new type mappings to be added to `f2cmap_all`.
941+
The structure should be similar to `f2cmap_all`, with keys representing
942+
Fortran types and values being dictionaries of type specifiers and their
943+
C type equivalents.
944+
945+
c2py_map : dict
946+
A dictionary used for validating the C types in `new_map`. It maps C
947+
types to corresponding Python types and is used to ensure that the C
948+
types specified in `new_map` are valid.
949+
950+
verbose : boolean
951+
A flag used to provide information about the types mapped
952+
953+
Returns
954+
-------
955+
tuple of (dict, list)
956+
The updated Fortran-to-C type mapping dictionary and a list of
957+
successfully mapped C types.
958+
"""
959+
f2cmap_mapped = []
960+
961+
new_map_lower = {}
962+
for k, d1 in new_map.items():
963+
d1_lower = {k1.lower(): v1 for k1, v1 in d1.items()}
964+
new_map_lower[k.lower()] = d1_lower
965+
966+
for k, d1 in new_map_lower.items():
967+
if k not in f2cmap_all:
968+
f2cmap_all[k] = {}
969+
970+
for k1, v1 in d1.items():
971+
if v1 in c2py_map:
972+
if k1 in f2cmap_all[k]:
973+
outmess(
974+
"\tWarning: redefinition of {'%s':{'%s':'%s'->'%s'}}\n"
975+
% (k, k1, f2cmap_all[k][k1], v1)
976+
)
977+
f2cmap_all[k][k1] = v1
978+
if verbose:
979+
outmess('\tMapping "%s(kind=%s)" to "%s"\n' % (k, k1, v1))
980+
f2cmap_mapped.append(v1)
981+
else:
982+
if verbose:
983+
errmess(
984+
"\tIgnoring map {'%s':{'%s':'%s'}}: '%s' must be in %s\n"
985+
% (k, k1, v1, v1, list(c2py_map.keys()))
986+
)
987+
988+
return f2cmap_all, f2cmap_mapped

numpy/f2py/capi_maps.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import os
1515
from .crackfortran import markoutercomma
1616
from . import cb_rules
17-
from ._isocbind import iso_c_binding_map
17+
from ._isocbind import iso_c_binding_map, isoc_c2pycode_map, iso_c2py_map
1818

1919
# The environment provided by auxfuncs.py is needed for some calls to eval.
2020
# As the needed functions cannot be determined by static inspection of the
@@ -24,7 +24,7 @@
2424
__all__ = [
2525
'getctype', 'getstrlength', 'getarrdims', 'getpydocsign',
2626
'getarrdocsign', 'getinit', 'sign2map', 'routsign2map', 'modsign2map',
27-
'cb_sign2map', 'cb_routsign2map', 'common_sign2map'
27+
'cb_sign2map', 'cb_routsign2map', 'common_sign2map', 'process_f2cmap_dict'
2828
]
2929

3030

@@ -126,13 +126,17 @@
126126
'byte': {'': 'char'},
127127
}
128128

129-
f2cmap_all = deep_merge(f2cmap_all, iso_c_binding_map)
129+
# Add ISO_C handling
130+
c2pycode_map.update(isoc_c2pycode_map)
131+
c2py_map.update(iso_c2py_map)
132+
f2cmap_all, _ = process_f2cmap_dict(f2cmap_all, iso_c_binding_map, c2py_map)
133+
# End ISO_C handling
130134
f2cmap_default = copy.deepcopy(f2cmap_all)
131135

132136
f2cmap_mapped = []
133137

134138
def load_f2cmap_file(f2cmap_file):
135-
global f2cmap_all
139+
global f2cmap_all, f2cmap_mapped
136140

137141
f2cmap_all = copy.deepcopy(f2cmap_default)
138142

@@ -151,29 +155,11 @@ def load_f2cmap_file(f2cmap_file):
151155
outmess('Reading f2cmap from {!r} ...\n'.format(f2cmap_file))
152156
with open(f2cmap_file) as f:
153157
d = eval(f.read().lower(), {}, {})
154-
for k, d1 in d.items():
155-
for k1 in d1.keys():
156-
d1[k1.lower()] = d1[k1]
157-
d[k.lower()] = d[k]
158-
for k in d.keys():
159-
if k not in f2cmap_all:
160-
f2cmap_all[k] = {}
161-
for k1 in d[k].keys():
162-
if d[k][k1] in c2py_map:
163-
if k1 in f2cmap_all[k]:
164-
outmess(
165-
"\tWarning: redefinition of {'%s':{'%s':'%s'->'%s'}}\n" % (k, k1, f2cmap_all[k][k1], d[k][k1]))
166-
f2cmap_all[k][k1] = d[k][k1]
167-
outmess('\tMapping "%s(kind=%s)" to "%s"\n' %
168-
(k, k1, d[k][k1]))
169-
f2cmap_mapped.append(d[k][k1])
170-
else:
171-
errmess("\tIgnoring map {'%s':{'%s':'%s'}}: '%s' must be in %s\n" % (
172-
k, k1, d[k][k1], d[k][k1], list(c2py_map.keys())))
158+
f2cmap_all, f2cmap_mapped = process_f2cmap_dict(f2cmap_all, d, c2py_map, True)
173159
outmess('Successfully applied user defined f2cmap changes\n')
174160
except Exception as msg:
175-
errmess(
176-
'Failed to apply user defined f2cmap changes: %s. Skipping.\n' % (msg))
161+
errmess('Failed to apply user defined f2cmap changes: %s. Skipping.\n' % (msg))
162+
177163

178164
cformat_map = {'double': '%g',
179165
'float': '%g',

numpy/f2py/cfuncs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,8 @@
827827
}
828828
"""
829829

830+
# TODO: These should be dynamically generated, too many mapped to int things,
831+
# see note in _isocbind.py
830832
needs['char_from_pyobj'] = ['int_from_pyobj']
831833
cfuncs['char_from_pyobj'] = """
832834
static int

numpy/f2py/tests/src/isocintrin/isoCtests.f90

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module coddity
2-
use iso_c_binding, only: c_double, c_int
2+
use iso_c_binding, only: c_double, c_int, c_int64_t
33
implicit none
44
contains
55
subroutine c_add(a, b, c) bind(c, name="c_add")
@@ -14,4 +14,21 @@ function wat(x, y) result(z) bind(c)
1414

1515
z = x + 7
1616
end function wat
17+
! gh-25207
18+
subroutine c_add_int64(a, b, c) bind(c)
19+
integer(c_int64_t), intent(in) :: a, b
20+
integer(c_int64_t), intent(out) :: c
21+
c = a + b
22+
end subroutine c_add_int64
23+
! gh-25207
24+
subroutine add_arr(A, B, C)
25+
integer(c_int64_t), intent(in) :: A(3)
26+
integer(c_int64_t), intent(in) :: B(3)
27+
integer(c_int64_t), intent(out) :: C(3)
28+
integer :: j
29+
30+
do j = 1, 3
31+
C(j) = A(j)+B(j)
32+
end do
33+
end subroutine
1734
end module coddity

numpy/f2py/tests/test_isoc.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from . import util
22
import numpy as np
33
import pytest
4+
from numpy.testing import assert_allclose
45

56
class TestISOC(util.F2PyTest):
67
sources = [
@@ -19,3 +20,34 @@ def test_bindc_function(self):
1920
out = self.module.coddity.wat(1, 20)
2021
exp_out = 8
2122
assert out == exp_out
23+
24+
# gh-25207
25+
def test_bindc_kinds(self):
26+
out = self.module.coddity.c_add_int64(1, 20)
27+
exp_out = 21
28+
assert out == exp_out
29+
30+
# gh-25207
31+
def test_bindc_add_arr(self):
32+
a = np.array([1,2,3])
33+
b = np.array([1,2,3])
34+
out = self.module.coddity.add_arr(a, b)
35+
exp_out = a*2
36+
assert_allclose(out, exp_out)
37+
38+
39+
def test_process_f2cmap_dict():
40+
from numpy.f2py.auxfuncs import process_f2cmap_dict
41+
42+
f2cmap_all = {"integer": {"8": "rubbish_type"}}
43+
new_map = {"INTEGER": {"4": "int"}}
44+
c2py_map = {"int": "int", "rubbish_type": "long"}
45+
46+
exp_map, exp_maptyp = ({"integer": {"8": "rubbish_type", "4": "int"}}, ["int"])
47+
48+
# Call the function
49+
res_map, res_maptyp = process_f2cmap_dict(f2cmap_all, new_map, c2py_map)
50+
51+
# Assert the result is as expected
52+
assert res_map == exp_map
53+
assert res_maptyp == exp_maptyp

0 commit comments

Comments
 (0)
0