test_character.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. import pytest
  2. import textwrap
  3. from numpy.testing import assert_array_equal, assert_equal, assert_raises
  4. import numpy as np
  5. from numpy.f2py.tests import util
  6. class TestCharacterString(util.F2PyTest):
  7. # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py']
  8. suffix = '.f90'
  9. fprefix = 'test_character_string'
  10. length_list = ['1', '3', 'star']
  11. code = ''
  12. for length in length_list:
  13. fsuffix = length
  14. clength = dict(star='(*)').get(length, length)
  15. code += textwrap.dedent(f"""
  16. subroutine {fprefix}_input_{fsuffix}(c, o, n)
  17. character*{clength}, intent(in) :: c
  18. integer n
  19. !f2py integer, depend(c), intent(hide) :: n = slen(c)
  20. integer*1, dimension(n) :: o
  21. !f2py intent(out) o
  22. o = transfer(c, o)
  23. end subroutine {fprefix}_input_{fsuffix}
  24. subroutine {fprefix}_output_{fsuffix}(c, o, n)
  25. character*{clength}, intent(out) :: c
  26. integer n
  27. integer*1, dimension(n), intent(in) :: o
  28. !f2py integer, depend(o), intent(hide) :: n = len(o)
  29. c = transfer(o, c)
  30. end subroutine {fprefix}_output_{fsuffix}
  31. subroutine {fprefix}_array_input_{fsuffix}(c, o, m, n)
  32. integer m, i, n
  33. character*{clength}, intent(in), dimension(m) :: c
  34. !f2py integer, depend(c), intent(hide) :: m = len(c)
  35. !f2py integer, depend(c), intent(hide) :: n = f2py_itemsize(c)
  36. integer*1, dimension(m, n), intent(out) :: o
  37. do i=1,m
  38. o(i, :) = transfer(c(i), o(i, :))
  39. end do
  40. end subroutine {fprefix}_array_input_{fsuffix}
  41. subroutine {fprefix}_array_output_{fsuffix}(c, o, m, n)
  42. character*{clength}, intent(out), dimension(m) :: c
  43. integer n
  44. integer*1, dimension(m, n), intent(in) :: o
  45. !f2py character(f2py_len=n) :: c
  46. !f2py integer, depend(o), intent(hide) :: m = len(o)
  47. !f2py integer, depend(o), intent(hide) :: n = shape(o, 1)
  48. do i=1,m
  49. c(i) = transfer(o(i, :), c(i))
  50. end do
  51. end subroutine {fprefix}_array_output_{fsuffix}
  52. subroutine {fprefix}_2d_array_input_{fsuffix}(c, o, m1, m2, n)
  53. integer m1, m2, i, j, n
  54. character*{clength}, intent(in), dimension(m1, m2) :: c
  55. !f2py integer, depend(c), intent(hide) :: m1 = len(c)
  56. !f2py integer, depend(c), intent(hide) :: m2 = shape(c, 1)
  57. !f2py integer, depend(c), intent(hide) :: n = f2py_itemsize(c)
  58. integer*1, dimension(m1, m2, n), intent(out) :: o
  59. do i=1,m1
  60. do j=1,m2
  61. o(i, j, :) = transfer(c(i, j), o(i, j, :))
  62. end do
  63. end do
  64. end subroutine {fprefix}_2d_array_input_{fsuffix}
  65. """)
  66. @pytest.mark.parametrize("length", length_list)
  67. def test_input(self, length):
  68. fsuffix = {'(*)': 'star'}.get(length, length)
  69. f = getattr(self.module, self.fprefix + '_input_' + fsuffix)
  70. a = {'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length]
  71. assert_array_equal(f(a), np.array(list(map(ord, a)), dtype='u1'))
  72. @pytest.mark.parametrize("length", length_list[:-1])
  73. def test_output(self, length):
  74. fsuffix = length
  75. f = getattr(self.module, self.fprefix + '_output_' + fsuffix)
  76. a = {'1': 'a', '3': 'abc'}[length]
  77. assert_array_equal(f(np.array(list(map(ord, a)), dtype='u1')),
  78. a.encode())
  79. @pytest.mark.parametrize("length", length_list)
  80. def test_array_input(self, length):
  81. fsuffix = length
  82. f = getattr(self.module, self.fprefix + '_array_input_' + fsuffix)
  83. a = np.array([{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length],
  84. {'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length],
  85. ], dtype='S')
  86. expected = np.array([[c for c in s] for s in a], dtype='u1')
  87. assert_array_equal(f(a), expected)
  88. @pytest.mark.parametrize("length", length_list)
  89. def test_array_output(self, length):
  90. fsuffix = length
  91. f = getattr(self.module, self.fprefix + '_array_output_' + fsuffix)
  92. expected = np.array(
  93. [{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length],
  94. {'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length]], dtype='S')
  95. a = np.array([[c for c in s] for s in expected], dtype='u1')
  96. assert_array_equal(f(a), expected)
  97. @pytest.mark.parametrize("length", length_list)
  98. def test_2d_array_input(self, length):
  99. fsuffix = length
  100. f = getattr(self.module, self.fprefix + '_2d_array_input_' + fsuffix)
  101. a = np.array([[{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length],
  102. {'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length]],
  103. [{'1': 'f', '3': 'fgh', 'star': 'fghij' * 3}[length],
  104. {'1': 'F', '3': 'FGH', 'star': 'FGHIJ' * 3}[length]]],
  105. dtype='S')
  106. expected = np.array([[[c for c in item] for item in row] for row in a],
  107. dtype='u1', order='F')
  108. assert_array_equal(f(a), expected)
  109. class TestCharacter(util.F2PyTest):
  110. # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py']
  111. suffix = '.f90'
  112. fprefix = 'test_character'
  113. code = textwrap.dedent(f"""
  114. subroutine {fprefix}_input(c, o)
  115. character, intent(in) :: c
  116. integer*1 o
  117. !f2py intent(out) o
  118. o = transfer(c, o)
  119. end subroutine {fprefix}_input
  120. subroutine {fprefix}_output(c, o)
  121. character :: c
  122. integer*1, intent(in) :: o
  123. !f2py intent(out) c
  124. c = transfer(o, c)
  125. end subroutine {fprefix}_output
  126. subroutine {fprefix}_input_output(c, o)
  127. character, intent(in) :: c
  128. character o
  129. !f2py intent(out) o
  130. o = c
  131. end subroutine {fprefix}_input_output
  132. subroutine {fprefix}_inout(c, n)
  133. character :: c, n
  134. !f2py intent(in) n
  135. !f2py intent(inout) c
  136. c = n
  137. end subroutine {fprefix}_inout
  138. function {fprefix}_return(o) result (c)
  139. character :: c
  140. character, intent(in) :: o
  141. c = transfer(o, c)
  142. end function {fprefix}_return
  143. subroutine {fprefix}_array_input(c, o)
  144. character, intent(in) :: c(3)
  145. integer*1 o(3)
  146. !f2py intent(out) o
  147. integer i
  148. do i=1,3
  149. o(i) = transfer(c(i), o(i))
  150. end do
  151. end subroutine {fprefix}_array_input
  152. subroutine {fprefix}_2d_array_input(c, o)
  153. character, intent(in) :: c(2, 3)
  154. integer*1 o(2, 3)
  155. !f2py intent(out) o
  156. integer i, j
  157. do i=1,2
  158. do j=1,3
  159. o(i, j) = transfer(c(i, j), o(i, j))
  160. end do
  161. end do
  162. end subroutine {fprefix}_2d_array_input
  163. subroutine {fprefix}_array_output(c, o)
  164. character :: c(3)
  165. integer*1, intent(in) :: o(3)
  166. !f2py intent(out) c
  167. do i=1,3
  168. c(i) = transfer(o(i), c(i))
  169. end do
  170. end subroutine {fprefix}_array_output
  171. subroutine {fprefix}_array_inout(c, n)
  172. character :: c(3), n(3)
  173. !f2py intent(in) n(3)
  174. !f2py intent(inout) c(3)
  175. do i=1,3
  176. c(i) = n(i)
  177. end do
  178. end subroutine {fprefix}_array_inout
  179. subroutine {fprefix}_2d_array_inout(c, n)
  180. character :: c(2, 3), n(2, 3)
  181. !f2py intent(in) n(2, 3)
  182. !f2py intent(inout) c(2. 3)
  183. integer i, j
  184. do i=1,2
  185. do j=1,3
  186. c(i, j) = n(i, j)
  187. end do
  188. end do
  189. end subroutine {fprefix}_2d_array_inout
  190. function {fprefix}_array_return(o) result (c)
  191. character, dimension(3) :: c
  192. character, intent(in) :: o(3)
  193. do i=1,3
  194. c(i) = o(i)
  195. end do
  196. end function {fprefix}_array_return
  197. function {fprefix}_optional(o) result (c)
  198. character, intent(in) :: o
  199. !f2py character o = "a"
  200. character :: c
  201. c = o
  202. end function {fprefix}_optional
  203. """)
  204. @pytest.mark.parametrize("dtype", ['c', 'S1'])
  205. def test_input(self, dtype):
  206. f = getattr(self.module, self.fprefix + '_input')
  207. assert_equal(f(np.array('a', dtype=dtype)), ord('a'))
  208. assert_equal(f(np.array(b'a', dtype=dtype)), ord('a'))
  209. assert_equal(f(np.array(['a'], dtype=dtype)), ord('a'))
  210. assert_equal(f(np.array('abc', dtype=dtype)), ord('a'))
  211. assert_equal(f(np.array([['a']], dtype=dtype)), ord('a'))
  212. def test_input_varia(self):
  213. f = getattr(self.module, self.fprefix + '_input')
  214. assert_equal(f('a'), ord('a'))
  215. assert_equal(f(b'a'), ord(b'a'))
  216. assert_equal(f(''), 0)
  217. assert_equal(f(b''), 0)
  218. assert_equal(f(b'\0'), 0)
  219. assert_equal(f('ab'), ord('a'))
  220. assert_equal(f(b'ab'), ord('a'))
  221. assert_equal(f(['a']), ord('a'))
  222. assert_equal(f(np.array(b'a')), ord('a'))
  223. assert_equal(f(np.array([b'a'])), ord('a'))
  224. a = np.array('a')
  225. assert_equal(f(a), ord('a'))
  226. a = np.array(['a'])
  227. assert_equal(f(a), ord('a'))
  228. try:
  229. f([])
  230. except IndexError as msg:
  231. if not str(msg).endswith(' got 0-list'):
  232. raise
  233. else:
  234. raise SystemError(f'{f.__name__} should have failed on empty list')
  235. try:
  236. f(97)
  237. except TypeError as msg:
  238. if not str(msg).endswith(' got int instance'):
  239. raise
  240. else:
  241. raise SystemError(f'{f.__name__} should have failed on int value')
  242. @pytest.mark.parametrize("dtype", ['c', 'S1', 'U1'])
  243. def test_array_input(self, dtype):
  244. f = getattr(self.module, self.fprefix + '_array_input')
  245. assert_array_equal(f(np.array(['a', 'b', 'c'], dtype=dtype)),
  246. np.array(list(map(ord, 'abc')), dtype='i1'))
  247. assert_array_equal(f(np.array([b'a', b'b', b'c'], dtype=dtype)),
  248. np.array(list(map(ord, 'abc')), dtype='i1'))
  249. def test_array_input_varia(self):
  250. f = getattr(self.module, self.fprefix + '_array_input')
  251. assert_array_equal(f(['a', 'b', 'c']),
  252. np.array(list(map(ord, 'abc')), dtype='i1'))
  253. assert_array_equal(f([b'a', b'b', b'c']),
  254. np.array(list(map(ord, 'abc')), dtype='i1'))
  255. try:
  256. f(['a', 'b', 'c', 'd'])
  257. except ValueError as msg:
  258. if not str(msg).endswith(
  259. 'th dimension must be fixed to 3 but got 4'):
  260. raise
  261. else:
  262. raise SystemError(
  263. f'{f.__name__} should have failed on wrong input')
  264. @pytest.mark.parametrize("dtype", ['c', 'S1', 'U1'])
  265. def test_2d_array_input(self, dtype):
  266. f = getattr(self.module, self.fprefix + '_2d_array_input')
  267. a = np.array([['a', 'b', 'c'],
  268. ['d', 'e', 'f']], dtype=dtype, order='F')
  269. expected = a.view(np.uint32 if dtype == 'U1' else np.uint8)
  270. assert_array_equal(f(a), expected)
  271. def test_output(self):
  272. f = getattr(self.module, self.fprefix + '_output')
  273. assert_equal(f(ord(b'a')), b'a')
  274. assert_equal(f(0), b'\0')
  275. def test_array_output(self):
  276. f = getattr(self.module, self.fprefix + '_array_output')
  277. assert_array_equal(f(list(map(ord, 'abc'))),
  278. np.array(list('abc'), dtype='S1'))
  279. def test_input_output(self):
  280. f = getattr(self.module, self.fprefix + '_input_output')
  281. assert_equal(f(b'a'), b'a')
  282. assert_equal(f('a'), b'a')
  283. assert_equal(f(''), b'\0')
  284. @pytest.mark.parametrize("dtype", ['c', 'S1'])
  285. def test_inout(self, dtype):
  286. f = getattr(self.module, self.fprefix + '_inout')
  287. a = np.array(list('abc'), dtype=dtype)
  288. f(a, 'A')
  289. assert_array_equal(a, np.array(list('Abc'), dtype=a.dtype))
  290. f(a[1:], 'B')
  291. assert_array_equal(a, np.array(list('ABc'), dtype=a.dtype))
  292. a = np.array(['abc'], dtype=dtype)
  293. f(a, 'A')
  294. assert_array_equal(a, np.array(['Abc'], dtype=a.dtype))
  295. def test_inout_varia(self):
  296. f = getattr(self.module, self.fprefix + '_inout')
  297. a = np.array('abc', dtype='S3')
  298. f(a, 'A')
  299. assert_array_equal(a, np.array('Abc', dtype=a.dtype))
  300. a = np.array(['abc'], dtype='S3')
  301. f(a, 'A')
  302. assert_array_equal(a, np.array(['Abc'], dtype=a.dtype))
  303. try:
  304. f('abc', 'A')
  305. except ValueError as msg:
  306. if not str(msg).endswith(' got 3-str'):
  307. raise
  308. else:
  309. raise SystemError(f'{f.__name__} should have failed on str value')
  310. @pytest.mark.parametrize("dtype", ['c', 'S1'])
  311. def test_array_inout(self, dtype):
  312. f = getattr(self.module, self.fprefix + '_array_inout')
  313. n = np.array(['A', 'B', 'C'], dtype=dtype, order='F')
  314. a = np.array(['a', 'b', 'c'], dtype=dtype, order='F')
  315. f(a, n)
  316. assert_array_equal(a, n)
  317. a = np.array(['a', 'b', 'c', 'd'], dtype=dtype)
  318. f(a[1:], n)
  319. assert_array_equal(a, np.array(['a', 'A', 'B', 'C'], dtype=dtype))
  320. a = np.array([['a', 'b', 'c']], dtype=dtype, order='F')
  321. f(a, n)
  322. assert_array_equal(a, np.array([['A', 'B', 'C']], dtype=dtype))
  323. a = np.array(['a', 'b', 'c', 'd'], dtype=dtype, order='F')
  324. try:
  325. f(a, n)
  326. except ValueError as msg:
  327. if not str(msg).endswith(
  328. 'th dimension must be fixed to 3 but got 4'):
  329. raise
  330. else:
  331. raise SystemError(
  332. f'{f.__name__} should have failed on wrong input')
  333. @pytest.mark.parametrize("dtype", ['c', 'S1'])
  334. def test_2d_array_inout(self, dtype):
  335. f = getattr(self.module, self.fprefix + '_2d_array_inout')
  336. n = np.array([['A', 'B', 'C'],
  337. ['D', 'E', 'F']],
  338. dtype=dtype, order='F')
  339. a = np.array([['a', 'b', 'c'],
  340. ['d', 'e', 'f']],
  341. dtype=dtype, order='F')
  342. f(a, n)
  343. assert_array_equal(a, n)
  344. def test_return(self):
  345. f = getattr(self.module, self.fprefix + '_return')
  346. assert_equal(f('a'), b'a')
  347. @pytest.mark.skip('fortran function returning array segfaults')
  348. def test_array_return(self):
  349. f = getattr(self.module, self.fprefix + '_array_return')
  350. a = np.array(list('abc'), dtype='S1')
  351. assert_array_equal(f(a), a)
  352. def test_optional(self):
  353. f = getattr(self.module, self.fprefix + '_optional')
  354. assert_equal(f(), b"a")
  355. assert_equal(f(b'B'), b"B")
  356. class TestMiscCharacter(util.F2PyTest):
  357. # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py']
  358. suffix = '.f90'
  359. fprefix = 'test_misc_character'
  360. code = textwrap.dedent(f"""
  361. subroutine {fprefix}_gh18684(x, y, m)
  362. character(len=5), dimension(m), intent(in) :: x
  363. character*5, dimension(m), intent(out) :: y
  364. integer i, m
  365. !f2py integer, intent(hide), depend(x) :: m = f2py_len(x)
  366. do i=1,m
  367. y(i) = x(i)
  368. end do
  369. end subroutine {fprefix}_gh18684
  370. subroutine {fprefix}_gh6308(x, i)
  371. integer i
  372. !f2py check(i>=0 && i<12) i
  373. character*5 name, x
  374. common name(12)
  375. name(i + 1) = x
  376. end subroutine {fprefix}_gh6308
  377. subroutine {fprefix}_gh4519(x)
  378. character(len=*), intent(in) :: x(:)
  379. !f2py intent(out) x
  380. integer :: i
  381. do i=1, size(x)
  382. print*, "x(",i,")=", x(i)
  383. end do
  384. end subroutine {fprefix}_gh4519
  385. pure function {fprefix}_gh3425(x) result (y)
  386. character(len=*), intent(in) :: x
  387. character(len=len(x)) :: y
  388. integer :: i
  389. do i = 1, len(x)
  390. j = iachar(x(i:i))
  391. if (j>=iachar("a") .and. j<=iachar("z") ) then
  392. y(i:i) = achar(j-32)
  393. else
  394. y(i:i) = x(i:i)
  395. endif
  396. end do
  397. end function {fprefix}_gh3425
  398. subroutine {fprefix}_character_bc_new(x, y, z)
  399. character, intent(in) :: x
  400. character, intent(out) :: y
  401. !f2py character, depend(x) :: y = x
  402. !f2py character, dimension((x=='a'?1:2)), depend(x), intent(out) :: z
  403. character, dimension(*) :: z
  404. !f2py character, optional, check(x == 'a' || x == 'b') :: x = 'a'
  405. !f2py callstatement (*f2py_func)(&x, &y, z)
  406. !f2py callprotoargument character*, character*, character*
  407. if (y.eq.x) then
  408. y = x
  409. else
  410. y = 'e'
  411. endif
  412. z(1) = 'c'
  413. end subroutine {fprefix}_character_bc_new
  414. subroutine {fprefix}_character_bc_old(x, y, z)
  415. character, intent(in) :: x
  416. character, intent(out) :: y
  417. !f2py character, depend(x) :: y = x[0]
  418. !f2py character, dimension((*x=='a'?1:2)), depend(x), intent(out) :: z
  419. character, dimension(*) :: z
  420. !f2py character, optional, check(*x == 'a' || x[0] == 'b') :: x = 'a'
  421. !f2py callstatement (*f2py_func)(x, y, z)
  422. !f2py callprotoargument char*, char*, char*
  423. if (y.eq.x) then
  424. y = x
  425. else
  426. y = 'e'
  427. endif
  428. z(1) = 'c'
  429. end subroutine {fprefix}_character_bc_old
  430. """)
  431. def test_gh18684(self):
  432. # Test character(len=5) and character*5 usages
  433. f = getattr(self.module, self.fprefix + '_gh18684')
  434. x = np.array(["abcde", "fghij"], dtype='S5')
  435. y = f(x)
  436. assert_array_equal(x, y)
  437. def test_gh6308(self):
  438. # Test character string array in a common block
  439. f = getattr(self.module, self.fprefix + '_gh6308')
  440. assert_equal(self.module._BLNK_.name.dtype, np.dtype('S5'))
  441. assert_equal(len(self.module._BLNK_.name), 12)
  442. f("abcde", 0)
  443. assert_equal(self.module._BLNK_.name[0], b"abcde")
  444. f("12345", 5)
  445. assert_equal(self.module._BLNK_.name[5], b"12345")
  446. def test_gh4519(self):
  447. # Test array of assumed length strings
  448. f = getattr(self.module, self.fprefix + '_gh4519')
  449. for x, expected in [
  450. ('a', dict(shape=(), dtype=np.dtype('S1'))),
  451. ('text', dict(shape=(), dtype=np.dtype('S4'))),
  452. (np.array(['1', '2', '3'], dtype='S1'),
  453. dict(shape=(3,), dtype=np.dtype('S1'))),
  454. (['1', '2', '34'],
  455. dict(shape=(3,), dtype=np.dtype('S2'))),
  456. (['', ''], dict(shape=(2,), dtype=np.dtype('S1')))]:
  457. r = f(x)
  458. for k, v in expected.items():
  459. assert_equal(getattr(r, k), v)
  460. def test_gh3425(self):
  461. # Test returning a copy of assumed length string
  462. f = getattr(self.module, self.fprefix + '_gh3425')
  463. # f is equivalent to bytes.upper
  464. assert_equal(f('abC'), b'ABC')
  465. assert_equal(f(''), b'')
  466. assert_equal(f('abC12d'), b'ABC12D')
  467. @pytest.mark.parametrize("state", ['new', 'old'])
  468. def test_character_bc(self, state):
  469. f = getattr(self.module, self.fprefix + '_character_bc_' + state)
  470. c, a = f()
  471. assert_equal(c, b'a')
  472. assert_equal(len(a), 1)
  473. c, a = f(b'b')
  474. assert_equal(c, b'b')
  475. assert_equal(len(a), 2)
  476. assert_raises(Exception, lambda: f(b'c'))
  477. class TestStringScalarArr(util.F2PyTest):
  478. sources = [util.getpath("tests", "src", "string", "scalar_string.f90")]
  479. @pytest.mark.slow
  480. def test_char(self):
  481. for out in (self.module.string_test.string,
  482. self.module.string_test.string77):
  483. expected = ()
  484. assert out.shape == expected
  485. expected = '|S8'
  486. assert out.dtype == expected
  487. @pytest.mark.slow
  488. def test_char_arr(self):
  489. for out in (self.module.string_test.strarr,
  490. self.module.string_test.strarr77):
  491. expected = (5,7)
  492. assert out.shape == expected
  493. expected = '|S12'
  494. assert out.dtype == expected