|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| module bob_phdae
|
| use, intrinsic :: iso_c_binding, only: c_int32_t, c_int64_t, c_double, &
|
| c_ptr, c_f_pointer, c_loc, c_associated
|
| use, intrinsic :: iso_fortran_env, only: int64, real64
|
| use bob_kinds
|
| use bob_errors
|
| use bob_worm, only: bob_worm_chain, blake3_hash_bytes
|
| implicit none
|
| private
|
|
|
|
|
|
|
|
|
| type, public :: skew_sym_matrix
|
| real(wp), allocatable :: data(:,:)
|
| integer(i4) :: n = 0
|
| logical(lk) :: is_valid = .false.
|
| contains
|
| procedure :: init => ssm_init
|
| procedure :: set => ssm_set
|
| procedure :: apply => ssm_apply
|
| procedure :: verify => ssm_verify
|
| procedure :: destroy => ssm_destroy
|
| end type skew_sym_matrix
|
|
|
|
|
|
|
|
|
| type, public :: psd_matrix
|
| real(wp), allocatable :: data(:,:)
|
| real(wp), allocatable :: chol(:,:)
|
| integer(i4) :: n = 0
|
| logical(lk) :: is_valid = .false.
|
| logical(lk) :: has_chol = .false.
|
| contains
|
| procedure :: init => psd_init
|
| procedure :: set_chol => psd_set_chol
|
| procedure :: apply => psd_apply
|
| procedure :: verify => psd_verify
|
| procedure :: destroy => psd_destroy
|
| end type psd_matrix
|
|
|
|
|
|
|
|
|
| type, public :: bob_phdae_t
|
| integer(i4) :: n = 0
|
| real(wp) :: time = ZERO
|
| real(wp) :: hamiltonian = ZERO
|
| real(wp) :: power_port = ZERO
|
| real(wp) :: power_diss = ZERO
|
| real(wp), allocatable :: state(:)
|
| real(wp), allocatable :: state_dot(:)
|
| real(wp), allocatable :: gradient(:)
|
| real(wp), allocatable :: input(:)
|
| real(wp), allocatable :: Q(:,:)
|
| real(wp), allocatable :: B(:,:)
|
| type(skew_sym_matrix) :: J
|
| type(psd_matrix) :: R
|
| type(bob_worm_chain) :: audit
|
| logical(lk) :: initialized = .false.
|
| contains
|
| procedure :: init => phdae_init
|
| procedure :: step => phdae_step
|
| procedure :: hamiltonian_val => phdae_H
|
| procedure :: power_balance => phdae_power
|
| procedure :: destroy => phdae_destroy
|
| end type bob_phdae_t
|
|
|
|
|
| type, public :: bob_step_receipt
|
| real(wp) :: time_in = ZERO
|
| real(wp) :: time_out = ZERO
|
| real(wp) :: h_in = ZERO
|
| real(wp) :: h_out = ZERO
|
| real(wp) :: dh = ZERO
|
| real(wp) :: p_port = ZERO
|
| real(wp) :: p_diss = ZERO
|
| real(wp) :: balance_err = ZERO
|
| logical(lk) :: balance_ok = .false.
|
| integer(i8) :: hash(32) = 0_i8
|
| end type bob_step_receipt
|
|
|
| public :: phdae_new
|
| public :: bob_phdae_new, bob_phdae_step, bob_phdae_free
|
|
|
| contains
|
|
|
|
|
|
|
|
|
|
|
| subroutine ssm_init(this, n)
|
| class(skew_sym_matrix), intent(inout) :: this
|
| integer(i4), intent(in) :: n
|
| if (allocated(this%data)) deallocate(this%data)
|
| allocate(this%data(n,n), source=ZERO)
|
| this%n = n; this%is_valid = .true.
|
| end subroutine ssm_init
|
|
|
|
|
| subroutine ssm_set(this, i, j, val)
|
| class(skew_sym_matrix), intent(inout) :: this
|
| integer(i4), intent(in) :: i, j
|
| real(wp), intent(in) :: val
|
| if (i == j) return
|
| this%data(i,j) = val
|
| this%data(j,i) = -val
|
| end subroutine ssm_set
|
|
|
|
|
| pure subroutine ssm_apply(this, x, y)
|
| class(skew_sym_matrix), intent(in) :: this
|
| real(wp), intent(in) :: x(this%n)
|
| real(wp), intent(out) :: y(this%n)
|
| integer(i4) :: i, j
|
| y = ZERO
|
| do i = 1, this%n
|
| do j = 1, this%n
|
| y(i) = y(i) + this%data(i,j) * x(j)
|
| end do
|
| end do
|
| end subroutine ssm_apply
|
|
|
|
|
| function ssm_verify(this) result(ok)
|
| class(skew_sym_matrix), intent(in) :: this
|
| logical :: ok
|
| real(wp) :: err
|
| integer(i4) :: i, j
|
| err = ZERO
|
| do i = 1, this%n; do j = 1, this%n
|
| err = err + abs(this%data(i,j) + this%data(j,i))**2
|
| end do; end do
|
| ok = sqrt(err) < TOL_NORM
|
| end function ssm_verify
|
|
|
| subroutine ssm_destroy(this)
|
| class(skew_sym_matrix), intent(inout) :: this
|
| if (allocated(this%data)) deallocate(this%data)
|
| this%n = 0; this%is_valid = .false.
|
| end subroutine ssm_destroy
|
|
|
|
|
|
|
|
|
|
|
| subroutine psd_init(this, n)
|
| class(psd_matrix), intent(inout) :: this
|
| integer(i4), intent(in) :: n
|
| if (allocated(this%data)) deallocate(this%data)
|
| if (allocated(this%chol)) deallocate(this%chol)
|
| allocate(this%data(n,n), source=ZERO)
|
| allocate(this%chol(n,n), source=ZERO)
|
| this%n = n; this%is_valid = .true.; this%has_chol = .false.
|
| end subroutine psd_init
|
|
|
|
|
| subroutine psd_set_chol(this, L)
|
| class(psd_matrix), intent(inout) :: this
|
| real(wp), intent(in) :: L(this%n, this%n)
|
| integer(i4) :: i, j, k
|
| this%chol = L
|
| this%has_chol = .true.
|
|
|
| this%data = ZERO
|
| do i = 1, this%n; do j = 1, this%n; do k = 1, this%n
|
| this%data(i,j) = this%data(i,j) + L(i,k) * L(j,k)
|
| end do; end do; end do
|
| end subroutine psd_set_chol
|
|
|
| pure subroutine psd_apply(this, x, y)
|
| class(psd_matrix), intent(in) :: this
|
| real(wp), intent(in) :: x(this%n)
|
| real(wp), intent(out) :: y(this%n)
|
| integer(i4) :: i, j
|
| y = ZERO
|
| do i = 1, this%n; do j = 1, this%n
|
| y(i) = y(i) + this%data(i,j) * x(j)
|
| end do; end do
|
| end subroutine psd_apply
|
|
|
| function psd_verify(this) result(ok)
|
| class(psd_matrix), intent(in) :: this
|
| logical :: ok
|
| real(wp) :: err, ev_min
|
| integer(i4) :: i, j
|
|
|
| err = ZERO
|
| do i = 1, this%n; do j = 1, this%n
|
| err = err + abs(this%data(i,j) - this%data(j,i))**2
|
| end do; end do
|
| ok = sqrt(err) < TOL_NORM
|
|
|
| if (ok) then
|
| do i = 1, this%n
|
| if (this%data(i,i) < -TOL_NORM) then; ok = .false.; exit; end if
|
| end do
|
| end if
|
| end function psd_verify
|
|
|
| subroutine psd_destroy(this)
|
| class(psd_matrix), intent(inout) :: this
|
| if (allocated(this%data)) deallocate(this%data)
|
| if (allocated(this%chol)) deallocate(this%chol)
|
| this%n = 0; this%is_valid = .false.
|
| end subroutine psd_destroy
|
|
|
|
|
|
|
|
|
|
|
| function phdae_new(n, num_inputs) result(sys)
|
| integer(i4), intent(in) :: n, num_inputs
|
| integer(i4) :: i
|
| type(bob_phdae_t) :: sys
|
| call sys%init(n, num_inputs)
|
| end function phdae_new
|
|
|
| subroutine phdae_init(this, n, num_inputs)
|
| class(bob_phdae_t), intent(inout) :: this
|
| integer(i4), intent(in) :: n, num_inputs
|
| integer(i4) :: i
|
| this%n = n; this%time = ZERO
|
| allocate(this%state(n), source=ZERO)
|
| allocate(this%state_dot(n), source=ZERO)
|
| allocate(this%gradient(n), source=ZERO)
|
| allocate(this%input(num_inputs), source=ZERO)
|
| allocate(this%Q(n,n), source=ZERO)
|
| allocate(this%B(n,num_inputs), source=ZERO)
|
|
|
| do i = 1, n; this%Q(i,i) = ONE; end do
|
| call this%J%init(n)
|
| call this%R%init(n)
|
| call this%audit%init()
|
| this%initialized = .true.
|
| end subroutine phdae_init
|
|
|
|
|
| function phdae_H(this) result(H)
|
| class(bob_phdae_t), intent(inout) :: this
|
| real(wp) :: H
|
| real(wp) :: Qz(this%n)
|
| integer(i4) :: i, j
|
|
|
| this%gradient = ZERO
|
| do i = 1, this%n; do j = 1, this%n
|
| this%gradient(i) = this%gradient(i) + this%Q(i,j) * this%state(j)
|
| end do; end do
|
|
|
| H = HALF * dot_product(this%gradient, this%gradient)
|
| end function phdae_H
|
|
|
|
|
|
|
|
|
| subroutine phdae_power(this)
|
| class(bob_phdae_t), intent(inout) :: this
|
| real(wp) :: Rg(this%n), Bu(this%n)
|
| integer(i4) :: i, j
|
|
|
| call this%R%apply(this%gradient, Rg)
|
| this%power_diss = dot_product(this%gradient, Rg)
|
|
|
| Bu = ZERO
|
| do i = 1, this%n; do j = 1, size(this%input)
|
| Bu(i) = Bu(i) + this%B(i,j) * this%input(j)
|
| end do; end do
|
| this%power_port = dot_product(Bu, this%gradient)
|
| end subroutine phdae_power
|
|
|
|
|
|
|
| subroutine phdae_step(this, dt, receipt)
|
| class(bob_phdae_t), intent(inout) :: this
|
| real(wp), intent(in) :: dt
|
| type(bob_step_receipt), intent(out) :: receipt
|
| real(wp) :: Jg(this%n), Rg(this%n), Bu(this%n), rhs(this%n)
|
| real(wp) :: h_before, h_after, balance_err
|
| integer(i4) :: i, j
|
| integer(i8) :: seal_payload(8)
|
|
|
| receipt%time_in = this%time
|
| h_before = this%hamiltonian_val()
|
| receipt%h_in = h_before
|
|
|
|
|
| this%gradient = ZERO
|
| do i = 1, this%n; do j = 1, this%n
|
| this%gradient(i) = this%gradient(i) + this%Q(i,j) * this%state(j)
|
| end do; end do
|
|
|
|
|
| call this%J%apply(this%gradient, Jg)
|
| call this%R%apply(this%gradient, Rg)
|
| Bu = ZERO
|
| do i = 1, this%n; do j = 1, size(this%input)
|
| Bu(i) = Bu(i) + this%B(i,j) * this%input(j)
|
| end do; end do
|
| rhs = Jg - Rg + Bu
|
|
|
|
|
| this%state_dot = rhs
|
| this%state = this%state + dt * rhs
|
| this%time = this%time + dt
|
|
|
|
|
| call this%power_balance()
|
| h_after = this%hamiltonian_val()
|
| this%hamiltonian = h_after
|
|
|
|
|
| balance_err = abs((h_after - h_before)/dt - this%power_port + this%power_diss)
|
|
|
| receipt%time_out = this%time
|
| receipt%h_out = h_after
|
| receipt%dh = h_after - h_before
|
| receipt%p_port = this%power_port
|
| receipt%p_diss = this%power_diss
|
| receipt%balance_err = balance_err
|
| receipt%balance_ok = balance_err < 1e-6_wp
|
|
|
|
|
| call this%audit%seal('PHDAE_STEP', 't='//achar(0), int(this%time*1000,i8))
|
| end subroutine phdae_step
|
|
|
| subroutine phdae_destroy(this)
|
| class(bob_phdae_t), intent(inout) :: this
|
| if (allocated(this%state)) deallocate(this%state)
|
| if (allocated(this%state_dot)) deallocate(this%state_dot)
|
| if (allocated(this%gradient)) deallocate(this%gradient)
|
| if (allocated(this%input)) deallocate(this%input)
|
| if (allocated(this%Q)) deallocate(this%Q)
|
| if (allocated(this%B)) deallocate(this%B)
|
| call this%J%destroy()
|
| call this%R%destroy()
|
| call this%audit%destroy()
|
| this%initialized = .false.
|
| end subroutine phdae_destroy
|
|
|
|
|
|
|
|
|
|
|
| function bob_phdae_new(n, num_inputs) result(ptr) bind(C, name="bob_phdae_new")
|
| integer(c_int32_t), value :: n, num_inputs
|
| type(c_ptr) :: ptr
|
| type(bob_phdae_t), pointer :: sys
|
| allocate(sys)
|
| call sys%init(int(n,i4), int(num_inputs,i4))
|
| ptr = c_loc(sys)
|
| end function bob_phdae_new
|
|
|
| function bob_phdae_step(sys_ptr, dt) result(err) bind(C, name="bob_phdae_step")
|
| type(c_ptr), value :: sys_ptr
|
| real(c_double), value :: dt
|
| real(c_double) :: err
|
| type(bob_phdae_t), pointer :: sys
|
| type(bob_step_receipt) :: receipt
|
| if (.not. c_associated(sys_ptr)) then; err = -1.0_wp; return; end if
|
| call c_f_pointer(sys_ptr, sys)
|
| call sys%step(real(dt,wp), receipt)
|
| err = real(receipt%balance_err, c_double)
|
| end function bob_phdae_step
|
|
|
| subroutine bob_phdae_free(sys_ptr) bind(C, name="bob_phdae_free")
|
| type(c_ptr), value :: sys_ptr
|
| type(bob_phdae_t), pointer :: sys
|
| if (.not. c_associated(sys_ptr)) return
|
| call c_f_pointer(sys_ptr, sys)
|
| call sys%destroy()
|
| deallocate(sys)
|
| end subroutine bob_phdae_free
|
|
|
| end module bob_phdae
|
|
|
|
|
|
|