For anyone interested in learning more about user-defined “constructors” via a generic interface that has the same name as the derived type, see below a simple example to experiment with using gfortran , Intel oneAPI IFORT, etc.
The example uses the topic at hand of linear regression. Note also with the question in the original post with derived type components being PRIVATE
, that notion has been extended to the basic OO concept of information hiding
and data encapsulation
in this example.
I’ll leave it to critics of OO to give commentary on how such a style can be unwieldy in some circumstances!
module linear_reg_m
private
type, public :: linear_reg_t
private ! All components private by default
real :: m_n = 0.0
real :: m_R_squared = 0.0 ! note default initialized to zero
real :: m_slope = 0.0
real :: m_intercept = 0.0
real :: m_Sx = 0.0
real :: m_Sy = 0.0
real :: m_Sxx = 0.0
real :: m_Syy = 0.0
real :: m_Sxy = 0.0
real, allocatable :: m_residuals(:)
contains
private
procedure, pass(this), public :: intercept => get_intercept
procedure, pass(this), public :: residuals => get_residuals
procedure, pass(this), public :: slope => get_slope
procedure, pass(this), public :: y => calc_y
end type
interface linear_reg_t ! Generic interface
module procedure construct_linear_reg_basic
module procedure construct_linear_reg_data
end interface
contains
function construct_linear_reg_basic( slope, intercept ) result(r)
real, intent(in) :: slope
real, intent(in) :: intercept
! Function result
type(linear_reg_t) :: r
r%m_slope = slope
r%m_intercept = intercept
end function
function construct_linear_reg_data( x, y ) result(r)
real, intent(in) :: x(:)
real, intent(in) :: y(:)
! Function result
type(linear_reg_t) :: r
! Elided are checks on data
r%m_n = real( size(x) )
r%m_Sx = sum( x )
r%m_Sy = sum( y )
r%m_Sxx = dot_product( x, x )
r%m_Syy = dot_product( y, y )
r%m_Sxy = dot_product( x, y )
r%m_slope = ( r%m_n*r%m_Sxy - r%m_Sx*r%m_Sy) / (r%m_n*r%m_Sxx - r%m_Sx**2)
r%m_intercept = ( r%m_Sy - r%m_slope*r%m_Sx ) / r%m_n
allocate( r%m_residuals(size(x)) )
r%m_residuals = calc_y( r, x ) - y
end function
elemental function get_intercept( this ) result(intercept)
class(linear_reg_t), intent(in) :: this
! Function result
real :: intercept
intercept = this%m_intercept
end function
elemental function get_slope( this ) result(slope)
class(linear_reg_t), intent(in) :: this
! Function result
real :: slope
slope = this%m_slope
end function
elemental function calc_y( this, x ) result(y)
class(linear_reg_t), intent(in) :: this
real, intent(in) :: x
! Function result
real :: y
y = this%m_slope*x + this%m_intercept
end function
function get_residuals(this) result(residuals)
class(linear_reg_t), intent(in) :: this
! Function result
real, allocatable :: residuals(:)
! Elided are checks on state
if ( allocated(this%m_residuals) ) then
residuals = this%m_residuals
else
! Return a zero-sized vector?
residuals = [ real :: ]
end if
end function
end module
use linear_reg_m, only : linear_reg_t
blk1: block
real, allocatable :: height(:)
real, allocatable :: weight(:)
type(linear_reg_t) :: model
print *, "Block 1: Linear Regression of some data"
! Arbitrary data
height = [ 1.47, 1.50, 1.52, 1.55, 1.57, 1.60 ]
weight = [ 52.21, 53.12, 54.48, 55.84, 57.20, 58.57 ]
model = linear_reg_t( x=height, y=weight ) !<< Construct model using raw data
print *, "Slope: ", model%slope()
print *, "Intercept: ", model%intercept()
print *, "Residuals: ", new_line(""), model%residuals()
end block blk1
print *
blk2: block
type(linear_reg_t) :: model
print *, "Block 2: Calculation using an arbitrary model"
model = linear_reg_t( slope=61.272, intercept=-39.062 ) !<< Construct model using reduced data
print *, "y(x) at x=1.5: ", model%y( x=1.5 )
end block blk2
end
Upon execution with gfortran, the output is as I expect:
Block 1: Linear Regression of some data
Slope: 50.7999115
Intercept: -22.7411957
Residuals:
-0.275321960 0.338672638 -5.32913208E-03 0.158664703 -0.185329437 -3.13339233E-02
Block 2: Calculation using an arbitrary model
y(x) at x=1.5: 52.8459969