From f47ae45596bbd6bcac5b8d708a4d47bb44d8b158 Mon Sep 17 00:00:00 2001 From: Chris Geroux Date: Thu, 18 Apr 2024 16:05:00 -0300 Subject: [PATCH] Various small fixes as I went through it --- _config.yml | 5 +- _episodes/break.md | 5 ++ _episodes/derived_types.md | 14 ++--- _episodes/destructors.md | 5 +- _episodes/extending_types.md | 8 ++- _episodes/interfaces.md | 2 +- _episodes/modules.md | 2 +- _episodes/type_bound_procedures.md | 6 +- code/destructor.f90 | 54 +++++++++--------- code/operators.f90 | 66 +++++++++++----------- code/type_bound_procedures_select_type.f90 | 40 ++++++------- 11 files changed, 107 insertions(+), 100 deletions(-) create mode 100644 _episodes/break.md diff --git a/_config.yml b/_config.yml index 750e42f..8ea22b2 100644 --- a/_config.yml +++ b/_config.yml @@ -12,7 +12,7 @@ # an: ACENET # cc: Compute Canada # alliance: Digital Research Alliance of Canada -# molmodsim: Compute Canada Molecular Simulation Team +# molmodsim: Compute Canada Molecular Simulation Team # bst: (compatibility) same as `molmodsim` carpentry: "an" @@ -119,8 +119,9 @@ episode_order: - derived_types - extending_types - interfaces + - break - type_bound_procedures - destructors - operators - wrapping_up - + diff --git a/_episodes/break.md b/_episodes/break.md new file mode 100644 index 0000000..f6e5e1d --- /dev/null +++ b/_episodes/break.md @@ -0,0 +1,5 @@ +--- +layout: break +title: "Break" +break: 10 +--- diff --git a/_episodes/derived_types.md b/_episodes/derived_types.md index e6ea44b..c590317 100644 --- a/_episodes/derived_types.md +++ b/_episodes/derived_types.md @@ -122,15 +122,15 @@ end module program main use m_vector implicit none - type(t_vector) numbers_none,numbers_some + type(t_vector)
numbers_none,numbers_some
- numbers_none=
create_empty_vector()
- print*, "numbers_none%num_elements=",numbers_none%num_elements +
numbers_none=create_empty_vector() + print*, "numbers_none%num_elements=",numbers_none%num_elements
- numbers_some=
create_sized_vector(4)
+
numbers_some=create_sized_vector(4) numbers_some%elements(1)=2 print*, "numbers_some%num_elements=",numbers_some%num_elements - print*, "numbers_some%elements(1)=",numbers_some%elements(1) + print*, "numbers_some%elements(1)=",numbers_some%elements(1)
end program @@ -159,7 +159,7 @@ Now we can use these functions to initialize and allocate memory for our vectors > It is a good idea to match allocations to deallocations. We will add this functionality later in the [Destructors episode](../destructors) once we learn a bit more about derived types. {: .callout} -> ## Access modifiers on derived type modifiers +> ## Access modifiers and derived types > Access modifiers can be applied to derived types in a similar way to modules. Here is an example. > > ~~~ @@ -174,7 +174,7 @@ Now we can use these functions to initialize and allocate memory for our vectors > end module > ~~~ > {: .fortran} -> In this case the member variable `num_elements` could no longer be accessed from outside the module, > > while the `elements` member variable can be. +> In this case the member variable `num_elements` could no longer be accessed from outside the module, while the `elements` member variable can be. {: .callout} > ## What is a derived type? diff --git a/_episodes/destructors.md b/_episodes/destructors.md index aca317e..7b30b6e 100644 --- a/_episodes/destructors.md +++ b/_episodes/destructors.md @@ -10,7 +10,7 @@ keypoints: - "A destructor is used to perform clean up when an object goes out of scope." - "To create a destructor use the **final** keyword when declaring at type bound procedure instead of the procedure keyword." --- -One aspect I have been ignoring until now is that the memory we allocate for our vectors is never explicitly freed by our program. So far our program has been simple enough that this is not a serious issue. We have only created a few objects that allocate memory within our main program. When the program execution has completed, that memory is returned to the operating system. However, if we had a long running loop inside our program that created new objects with allocated memory and we never deallocated that memory we would have a problem as our program would steadily increase its memory usage. This is referred to as a memory leak as was mentioned in the first half of this workshop. We can manually deallocate memory as we did with allocating memory before we created a function to create new `t_vector` and `t_vector_3` objects, however there is a way to create a new special type bound procedure that is automatically called with the object goes out of scope to deallocate this memory for us. To do this we use the **final** keyword within the type definition. +One aspect I have been ignoring until now is that the memory we allocate for our vectors is never explicitly freed by our program. So far our program has been simple enough that this is not a serious issue. We have only created a few objects that allocate memory within our main program. When the program execution has completed, that memory is returned to the operating system. However, if we had a long running loop inside our program that created new objects with allocated memory and we never deallocated that memory we would have a problem as our program would steadily increase its memory usage. This is referred to as a memory leak as was mentioned in the first half of this workshop. We can manually deallocate memory as we did with allocating memory before we created a function to create new `t_vector` and `t_vector_3` objects, however there is a way to create a new special type bound procedure that is automatically called when the object goes out of scope to deallocate this memory for us. To do this we use the **final** keyword within the type definition. ~~~ type @@ -126,7 +126,7 @@ end program ~~~ -$ gfortran destructor.f90 destructor +$ gfortran destructor.f90 -o destructor $ ./destructor ~~~ {: .bash} @@ -142,7 +142,6 @@ $ ./destructor 0.00000000 0.00000000 t_vector_3: - num_elements= 3 elements= 1.00000000 0.00000000 diff --git a/_episodes/extending_types.md b/_episodes/extending_types.md index 8f015f4..db569d3 100644 --- a/_episodes/extending_types.md +++ b/_episodes/extending_types.md @@ -4,10 +4,12 @@ teaching: 10 exercises: 0 questions: - "How do you extend a type?" +- "Why can it be useful to extend a type?" objectives: -- "Create an extend a type." +- "Create an extended a type." keypoints: -- "Type extension allows you to build upon an existing derived type to create a new derived type." +- "Type extension allows you to build upon an existing derived type to create a new derived type while adding new functionality or modifying existing functionality." +- "Allows reuse of common code between derived types." --- It is pretty common to use vectors to represent positions in 3D space. Lets create a new derived type which always has only three components. However, it would be really nice if we could reuse our more general vector type to represent one of these specific 3 component vectors. You can do this by using type extension. Type extension allows you to add new members (or not) to an existing type to create a new derived type. @@ -67,7 +69,7 @@ program main use m_vector implicit none type(t_vector) numbers_none,numbers_some - type(t_vector_3) location +
type(t_vector_3) location
numbers_none=create_empty_vector() print*, "numbers_none%num_elements=",numbers_none%num_elements diff --git a/_episodes/interfaces.md b/_episodes/interfaces.md index 363fe90..44fa4a6 100644 --- a/_episodes/interfaces.md +++ b/_episodes/interfaces.md @@ -12,7 +12,7 @@ keypoints: - "Procedures that are part of the same generic interface block must be distinguishable from each other based on the number, order, and type of arguments passed." --- -As mentioned previously it is a very common task to create new objects of a derived type and perform some initialization of the members of that derive type and we had created some functions to do this. One of the functions, `create_empty_vector` takes no arguments and creates a new empty vector, while another procedure, `create_sized_vector` creates a new vector of a specific size passed to the procedure. Both of these functions do the same thing, create a new `t_vector` object and initialize it. One might imagine many different such initialization routines, perhaps ones that take another `t_vectors` or `t_vector_3` objects to use to initialize a new `t_vector` object as a copy of the passed vector. All of these creation, or initialization, functions do basically the same thing but in a slightly different way depending on the arguments passed to them. It starts to get a bit tedious to have to remember all the names of these different initialization functions. If the compiler could somehow distinguish these functions automatically based on the number and type of arguments rather than the procedure name so that we could call the same generic procedure name and it would pick the correct procedure implementation based on the arguments we passed it. +As mentioned previously it is a very common task to create new objects of a derived type and perform some initialization of the members. We have already created some functions to do this. One of the functions, `create_empty_vector` takes no arguments and creates a new empty vector, while `create_sized_vector` creates a new vector of a specific size passed to the function. Both of these functions do the same thing, create a new `t_vector` object and initialize it. One might imagine many different such initialization routines, perhaps ones that take another `t_vectors` or `t_vector_3` objects to use to initialize a new `t_vector` object as a copy of the passed vector. All of these creation, or initialization, functions do basically the same thing but in a slightly different way depending on the arguments passed to them. It starts to get a bit tedious to have to remember all the names of these different initialization functions. If the compiler could somehow distinguish these functions automatically based on the number and type of arguments rather than the procedure name so that we could call the same generic procedure name and it would pick the correct procedure implementation based on the arguments we passed it. It turns out there was a feature added to Fortran 2003, called **interface blocks** which allow multiple procedures to be mapped to one name. The basic syntax of an interface block is as follows. ~~~ diff --git a/_episodes/modules.md b/_episodes/modules.md index 72df012..eb6f310 100644 --- a/_episodes/modules.md +++ b/_episodes/modules.md @@ -86,7 +86,7 @@ $ ./modules It is possible to control how variables and procedures declared in a module are accessed from outside the module. This can be done either on module wide basis or for specific procedures and variables. If you specify no access modifiers everything will be accessible from outside the module. There are two access modifiers: -* `private` indicates that that procedure or variable can only be accessed within the module +* `private` indicates that the procedure or variable can only be accessed within the module * `public` indicates it can be accessed from outside the module. Below is an example of using the `private` access modifier module wide. diff --git a/_episodes/type_bound_procedures.md b/_episodes/type_bound_procedures.md index 82ed08e..17a1f7e 100644 --- a/_episodes/type_bound_procedures.md +++ b/_episodes/type_bound_procedures.md @@ -4,6 +4,7 @@ teaching: 10 exercises: 5 questions: - "What is a type bound procedure?" +- "Can an extended type be passed to a type bound procedure?" objectives: - "How do you create a type bound procedure." keypoints: @@ -32,7 +33,7 @@ end program {: .fortran} However, in object oriented languages it is common to think of procedures as part of the object or type and have the procedure be called from the object. So instead of calling a procedure and explicitly passing in the object like `call display(vec)` as above the different syntax using the `%` operator `call vec%display()` is more in line with the object oriented way of thinking. -The `%` style of calling a subroutine works exactly the same as the usual way except that the first argument in the subroutine is automatically replaced by the object to the left of the `%` operator. This type of procedure is called a **type bound procedure**. In other languages this might be called a **member function** as it is a member of the type like the component variables are members. +The `%` style of calling a subroutine works exactly the same as the usual way, except that the first argument in the subroutine is automatically replaced by the object to the left of the `%` operator. This type of procedure is called a **type bound procedure**. In other languages this might be called a **member function** as it is a member of the type, like the component variables are members. To create a type bound procedure you must specify that the type *contains* that procedure. Lets add the `display` type bound procedure now. @@ -192,10 +193,10 @@ module m_vector
select type (vec) class is (t_vector) print*, "t_vector:" + print*, " num_elements=",vec%num_elements class is (t_vector_3) print*, "t_vector_3:" end select
- print*, " num_elements=",vec%num_elements print*, " elements=" do i=1,vec%num_elements print*, " ",vec%elements(i) @@ -262,7 +263,6 @@ $ ./type_bound_procedures_select_type 0.00000000 0.00000000 t_vector_3: - num_elements= 3 elements= 1.00000000 0.00000000 diff --git a/code/destructor.f90 b/code/destructor.f90 index b011c09..25f6ad0 100644 --- a/code/destructor.f90 +++ b/code/destructor.f90 @@ -1,90 +1,90 @@ module m_vector implicit none - + type t_vector integer:: num_elements real,dimension(:),allocatable:: elements - + contains - + procedure:: display final:: destructor_vector - + end type - + interface t_vector procedure:: create_empty_vector procedure:: create_sized_vector end interface - + type,extends(t_vector):: t_vector_3 - + contains - + final:: destructor_vector_3 - + end type - + interface t_vector_3 procedure:: create_size_3_vector end interface - + contains - + subroutine destructor_vector(self) implicit none type(t_vector):: self - + if (allocated(self%elements)) then deallocate(self%elements) endif end subroutine - + subroutine destructor_vector_3(self) implicit none type(t_vector_3):: self - + if (allocated(self%elements)) then deallocate(self%elements) endif end subroutine - + subroutine display(vec) implicit none class(t_vector),intent(in):: vec integer:: i - + select type (vec) class is (t_vector) print*, "t_vector:" + print*, " num_elements=",vec%num_elements class is (t_vector_3) print*, "t_vector_3:" end select - print*, " num_elements=",vec%num_elements print*, " elements=" do i=1,vec%num_elements print*, " ",vec%elements(i) end do end subroutine - + type(t_vector) function create_empty_vector() implicit none create_empty_vector%num_elements=0 end function - + type(t_vector) function create_sized_vector(vec_size) implicit none integer,intent(in):: vec_size create_sized_vector%num_elements=vec_size allocate(create_sized_vector%elements(vec_size)) end function - + type(t_vector_3) function create_size_3_vector() implicit none create_size_3_vector%num_elements=3 allocate(create_size_3_vector%elements(3)) end function - + end module program main @@ -92,16 +92,16 @@ program main implicit none type(t_vector) numbers_none,numbers_some type(t_vector_3) location - + numbers_none=t_vector() call numbers_none%display() - + numbers_some=t_vector(4) numbers_some%elements(1)=2 call numbers_some%display() - + location=t_vector_3() location%elements(1)=1.0 call location%display() - -end program \ No newline at end of file + +end program diff --git a/code/operators.f90 b/code/operators.f90 index 311f713..97c4a30 100644 --- a/code/operators.f90 +++ b/code/operators.f90 @@ -1,123 +1,123 @@ module m_vector implicit none - + type t_vector integer:: num_elements real,dimension(:),allocatable:: elements - + contains - + procedure:: display final:: destructor_vector - + end type - + interface t_vector procedure:: create_empty_vector procedure:: create_sized_vector end interface - + interface operator(+) procedure:: add_vectors end interface - + type,extends(t_vector):: t_vector_3 - + contains - + final:: destructor_vector_3 - + end type - + interface t_vector_3 procedure:: create_size_3_vector end interface - + contains - + type(t_vector) function add_vectors(left,right) implicit none type(t_vector),intent(in):: left, right integer:: i,total_size,result_i - + total_size=left%num_elements+right%num_elements add_vectors=create_sized_vector(total_size) - + !copy over left vector elements do i=1, left%num_elements add_vectors%elements(i)=left%elements(i) end do - + !copy over right vector elements result_i=left%num_elements+1 do i=1,right%num_elements add_vectors%elements(result_i)=right%elements(i) result_i=result_i+1 enddo - + end function - + subroutine destructor_vector(self) implicit none type(t_vector):: self - + if (allocated(self%elements)) then deallocate(self%elements) endif end subroutine - + subroutine destructor_vector_3(self) implicit none type(t_vector_3):: self - + if (allocated(self%elements)) then deallocate(self%elements) endif end subroutine - + subroutine display(vec) implicit none class(t_vector),intent(in):: vec integer:: i - + select type (vec) class is (t_vector) print*, "t_vector:" + print*, " num_elements=",vec%num_elements class is (t_vector_3) print*, "t_vector_3:" end select - print*, " num_elements=",vec%num_elements print*, " elements=" do i=1,vec%num_elements print*, " ",vec%elements(i) end do end subroutine - + type(t_vector) function create_empty_vector() implicit none create_empty_vector%num_elements=0 end function - + type(t_vector) function create_sized_vector(vec_size) implicit none integer,intent(in):: vec_size create_sized_vector%num_elements=vec_size allocate(create_sized_vector%elements(vec_size)) end function - + type(t_vector_3) function create_size_3_vector() implicit none create_size_3_vector%num_elements=3 allocate(create_size_3_vector%elements(3)) end function - + end module program main use m_vector implicit none type(t_vector) numbers_some,numbers_less,numbers_all - + numbers_some=t_vector(4) numbers_some%elements(1)=1 numbers_some%elements(2)=2 @@ -126,17 +126,17 @@ program main print*, "" print*, "numbers_some" call numbers_some%display() - + numbers_less=t_vector(2) numbers_less%elements(1)=5 numbers_less%elements(2)=6 print*, "" print*, "numbers_less" call numbers_less%display() - + numbers_all=numbers_some+numbers_less print*, "" print*, "numbers_all" call numbers_all%display() - -end program \ No newline at end of file + +end program diff --git a/code/type_bound_procedures_select_type.f90 b/code/type_bound_procedures_select_type.f90 index 13ac2be..0d8435c 100644 --- a/code/type_bound_procedures_select_type.f90 +++ b/code/type_bound_procedures_select_type.f90 @@ -1,66 +1,66 @@ module m_vector implicit none - + type t_vector integer:: num_elements real,dimension(:),allocatable:: elements - + contains - + procedure:: display - + end type - + interface t_vector procedure:: create_empty_vector procedure:: create_sized_vector end interface - + type,extends(t_vector):: t_vector_3 end type - + interface t_vector_3 procedure:: create_size_3_vector end interface - + contains - + subroutine display(vec) implicit none class(t_vector),intent(in):: vec integer:: i - + select type (vec) class is (t_vector) print*, "t_vector:" + print*, " num_elements=",vec%num_elements class is (t_vector_3) print*, "t_vector_3:" end select - print*, " num_elements=",vec%num_elements print*, " elements=" do i=1,vec%num_elements print*, " ",vec%elements(i) end do end subroutine - + type(t_vector) function create_empty_vector() implicit none create_empty_vector%num_elements=0 end function - + type(t_vector) function create_sized_vector(vec_size) implicit none integer,intent(in):: vec_size create_sized_vector%num_elements=vec_size allocate(create_sized_vector%elements(vec_size)) end function - + type(t_vector_3) function create_size_3_vector() implicit none create_size_3_vector%num_elements=3 allocate(create_size_3_vector%elements(3)) end function - + end module program main @@ -68,16 +68,16 @@ program main implicit none type(t_vector) numbers_none,numbers_some type(t_vector_3) location - + numbers_none=t_vector() call numbers_none%display() - + numbers_some=t_vector(4) numbers_some%elements(1)=2 call numbers_some%display() - + location=t_vector_3() location%elements(1)=1.0 call location%display() - -end program \ No newline at end of file + +end program