Skip to content

Commit

Permalink
Allocatable classes for factories
Browse files Browse the repository at this point in the history
  • Loading branch information
krystophny committed Dec 26, 2024
1 parent 1cd2210 commit d95e13d
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 74 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ build
*.mod
*.a
*.so
*.x
f90wrap*.f90
*.pyc
.pydevproject
Expand All @@ -20,3 +21,4 @@ src.*
.ipynb_checkpoints
.idea/
*.swp
itest/
5 changes: 4 additions & 1 deletion examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ EXAMPLES = arrayderivedtypes \
derivedtypes_procedure \
optional_string \
long_subroutine_name \
kind_map_default
kind_map_default \
issue206_subroutine_oldstyle \
issue227_allocatable \
issue235_allocatable_classes

PYTHON = python

Expand Down
65 changes: 26 additions & 39 deletions examples/issue227_allocatable/run.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,41 @@
#!/usr/bin/env python
import os
import unittest
import gc
import tracemalloc

import itest


def main():
test_type_output_is_wrapped()
test_intrinsic_output_is_not_wrapped()
test_array_output_is_not_wrapped()
test_type_output_wrapper()
test_memory_leak()


def test_type_output_is_wrapped():
assert hasattr(itest.alloc_output, 'alloc_output_type_func')


def test_intrinsic_output_is_not_wrapped():
assert (not hasattr(itest.alloc_output, 'alloc_output_intrinsic_func'))


def test_array_output_is_not_wrapped():
assert (not hasattr(itest.alloc_output, 'alloc_output_array_func'))


VAL = 10.0
TOL = 1e-13

class TestAllocOutput(unittest.TestCase):

def test_type_output_is_wrapped(self):
self.assertTrue(hasattr(itest.alloc_output, 'alloc_output_type_func'))

def test_type_output_wrapper():
t = itest.alloc_output.alloc_output_type_func(VAL)
assert(abs(t.a - VAL) < TOL)
def test_intrinsic_output_is_not_wrapped(self):
self.assertFalse(hasattr(itest.alloc_output, 'alloc_output_intrinsic_func'))

def test_array_output_is_not_wrapped(self):
self.assertFalse(hasattr(itest.alloc_output, 'alloc_output_array_func'))

def test_memory_leak():
gc.collect()
t = []
tracemalloc.start()
start_snapshot = tracemalloc.take_snapshot()
for i in range(2048):
t.append(itest.alloc_output.alloc_output_type_func(VAL))
del t
gc.collect()
end_snapshot = tracemalloc.take_snapshot()
tracemalloc.stop()
stats = end_snapshot.compare_to(start_snapshot, 'lineno')
assert sum(stat.size_diff for stat in stats) < 1024
def test_type_output_wrapper(self):
t = itest.alloc_output.alloc_output_type_func(VAL)
self.assertAlmostEqual(t.a, VAL, delta=TOL)

def test_memory_leak(self):
gc.collect()
t = []
tracemalloc.start()
start_snapshot = tracemalloc.take_snapshot()
for i in range(8192):
t.append(itest.alloc_output.alloc_output_type_func(VAL))
del t
gc.collect()
end_snapshot = tracemalloc.take_snapshot()
tracemalloc.stop()
stats = end_snapshot.compare_to(start_snapshot, 'lineno')
self.assertLess(sum(stat.size_diff for stat in stats), 4096)

if __name__ == '__main__':
main()
unittest.main()
25 changes: 25 additions & 0 deletions examples/issue235_allocatable_classes/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FC = gfortran
FCFLAGS = -fPIC
PYTHON = python

all: wrapper

test: wrapper
$(PYTHON) run.py

wrapper: f90wrapper mytype.o myclass.o myclass_factory.o
f2py-f90wrap --build-dir . -c -m _itest --opt="-O0 -g" \
f90wrap_mytype.f90 f90wrap_myclass.f90 f90wrap_myclass_factory.f90 \
mytype.o myclass.o myclass_factory.o

f90wrapper: mytype.f90 myclass.f90 myclass_factory.f90
f90wrap -m itest -P mytype.f90 myclass.f90 myclass_factory.f90 -v

%.o : %.f90
$(FC) $(FCFLAGS) -c -g -O0 $< -o $@

clean:
rm -f *.o f90wrap*.f90 *.so *.mod
rm -rf src.*/
rm -rf itest/
-rm -rf src.*/ .f2py_f2cmap .libs/ __pycache__/
6 changes: 6 additions & 0 deletions examples/issue235_allocatable_classes/Makefile.meson
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include ../make.meson.inc

NAME := itest

test: build
$(PYTHON) run.py
39 changes: 39 additions & 0 deletions examples/issue235_allocatable_classes/myclass.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module myclass

implicit none

integer :: create_count = 0
integer :: destroy_count = 0

type :: myclass_t
real :: val
contains
procedure :: get_val => myclass_get_val
procedure :: set_val => myclass_set_val
final :: myclass_destroy
end type myclass_t

contains

subroutine myclass_get_val(self, val)
class(myclass_t), intent(in) :: self
real, intent(out) :: val

val = self%val
end subroutine myclass_get_val

subroutine myclass_set_val(self, val)
class(myclass_t), intent(inout) :: self
real, intent(in) :: val

self%val = val
end subroutine myclass_set_val

subroutine myclass_destroy(self)
type(myclass_t), intent(inout) :: self

destroy_count = destroy_count + 1
print *, 'Destroying class_t with val = ', self%val
end subroutine myclass_destroy

end module myclass
18 changes: 18 additions & 0 deletions examples/issue235_allocatable_classes/myclass_factory.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module myclass_factory

use myclass, only: myclass_t, create_count
implicit none

contains

function myclass_create(val) result(myobject)
class(myclass_t), allocatable :: myobject
real, intent(in) :: val

allocate(myclass_t :: myobject)
call myobject%set_val(val)
create_count = create_count + 1

end function myclass_create

end module myclass_factory
31 changes: 31 additions & 0 deletions examples/issue235_allocatable_classes/mytype.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module mytype

implicit none

integer :: create_count = 0
integer :: destroy_count = 0

type :: mytype_t
real :: val
contains
final :: mytype_destroy
end type mytype_t

contains

function mytype_create(val) result(self)
type(mytype_t) :: self
real, intent(in) :: val

self%val = val
create_count = create_count + 1
end function mytype_create

subroutine mytype_destroy(self)
type(mytype_t), intent(inout) :: self

destroy_count = destroy_count + 1
print *, 'Destroying mytype_t with val = ', self%val
end subroutine mytype_destroy

end module mytype
83 changes: 83 additions & 0 deletions examples/issue235_allocatable_classes/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python
import unittest
from itest import mytype, myclass, myclass_factory

REF = 3.1415
TOL = 1.0e-6

class TestMyType(unittest.TestCase):

def test_create_destroy_type_object(self):
"""Object creation and destruction should happen only once."""
mytype.set_create_count(0)
mytype.set_destroy_count(0)

obj = mytype.mytype_create(REF)

self.assertEqual(mytype.get_create_count(), 1)

self.assertTrue(abs(obj.val - REF) < TOL)

del obj

self.assertEqual(mytype.get_create_count(), 1)
self.assertGreaterEqual(mytype.get_destroy_count(), 1)

def test_type_member_access(self):
"""Direct access of member variables."""
obj = mytype.mytype_create(REF)

self.assertTrue(abs(obj.val - REF) < TOL)

obj.val = 2.0 * REF

self.assertTrue(abs(obj.val - 2.0 * REF) < TOL)

del obj


class TestMyClass(unittest.TestCase):

def test_create_destroy_class_object(self):
"""Object creation and destruction should happen only once."""
myclass.set_create_count(0)
myclass.set_destroy_count(0)

obj = myclass_factory.myclass_create(REF)

self.assertEqual(myclass.get_create_count(), 1)

self.assertTrue(abs(obj.get_val() - REF) < TOL)

del obj

self.assertEqual(myclass.get_create_count(), 1)
self.assertGreaterEqual(myclass.get_destroy_count(), 1)

def test_class_getter_setter(self):
"""Getters and setters defined in Fortran should work."""
obj = myclass_factory.myclass_create(REF)

self.assertTrue(abs(obj.get_val() - REF) < TOL)

obj.set_val(2.0 * REF)

self.assertTrue(abs(obj.get_val() - 2.0 * REF) < TOL)

del obj

def test_class_member_access(self):
"""Direct access of member variables."""
obj = myclass_factory.myclass_create(REF)

self.assertTrue(abs(obj.val - REF) < TOL)

obj.val = 2.0 * REF

self.assertTrue(abs(obj.val - 2.0 * REF) < TOL)

del obj


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit d95e13d

Please sign in to comment.