!-- thRead.f90 !-- 4 Mar 92, Richard Maine: Version 1.0. module th_read !-- Module for read access to time history files. !-- Does not support cmp2(elxsi) or enc1(sel) formats. !-- 19 Sep 90, Richard Maine. use precision use sysdep_io use string use th_read_gen use th_read_asc12 use th_read_unc2 use th_read_unc3 use th_read_cmp3 use th_read_net1 implicit none private !-- Public parameters from th_read_gen public thr_ok, thr_eoi, thr_eod, thr_error logical, save :: th_read_initialized = .false. integer, parameter :: n_formats = 7 integer, parameter :: usr1_code = 1, asc1_code = 2, asc2_code = 3, & unc2_code = 4, unc3_code = 5, cmp3_code = 6, net1_code = 7 character, save :: format_names(n_formats)*8 = ' none ' type fd_type !-- fd is short for file_descriptor integer :: format_code logical :: ok_to_read !-- True when an attempt to read is allowed. !-- If false, the file must be repositioned before the next read. integer :: n_signals, n_requested real(r_kind) :: start_time, stop_time, prev_time integer :: thin_factor logical :: first_read !-- True on the first read of a time interval. !-- Used in thinning. type(gen_type), pointer :: gen !-- Generic data for the format-specific modules. !-- None of the internal structure should be referenced here. end type !-- Handles are public with private components. type, public :: th_read_handle_type private type(fd_type), pointer :: fd end type type(fd_type), pointer :: fd !-- Scratch use only. Not saved. !-- Public procedures. public open_th_read, close_th_read, inquire_th, request_th_signals public seek_th, read_th !-- Forward reference for private procedures. private read_th_format, read_raw_file contains subroutine init_th_read !-- Initialize the th_read module. !-- Will automatically be called when needed. !-- 22 Jun 90, Richard Maine. !-------------------- executable code. !-- Return immediately if already done. if (th_read_initialized) return th_read_initialized = .true. format_names(asc1_code) = asc1_format_string format_names(asc2_code) = asc2_format_string format_names(unc2_code) = unc2_format_string format_names(unc3_code) = unc3_format_string format_names(cmp3_code) = cmp3_format_string format_names(net1_code) = net1_format_string return end subroutine init_th_read subroutine open_th_read (file_handle, file_name, file_format, & access_id, password, error) !-- Open a time history file for read access. !-- Note that, unlike in the old Fortran 77 routines, !-- this open does not establish a file position; !-- a call to seek_th is required before the first data access. !-- 19 Sept 90, Richard Maine. !-------------------- interface. type(th_read_handle_type), intent(out) :: file_handle !-- Identifies the file for subsequent reference. !-- Returns a null handle if the open fails. character*(*), intent(in) :: file_name character*(*), intent(in), optional :: file_format !-- Attempts automatic determination if this is blank or absent. character*(*), intent(in), optional :: access_id, password !-- Id and password for security authorization. !-- Some files may require specification of an acceptable !-- access_id and password to allow access. !-- Blank or omitted values imply that no special access !-- permissions are requested. logical, intent(out), optional :: error !-- Returns true if open fails. !-- Abort if open fails and this is not present. !-------------------- local. integer :: iostat, format_code character :: format_name*8, access_id_tmp*16, password_tmp*16, where*32 logical :: sub_error !-------------------- executable code. if (.not. th_read_initialized) call init_th_read !-- Allocate a file handle. where = 'allocate' nullify(fd) allocate(fd, stat=iostat) file_handle%fd => fd if (iostat /= 0) goto 8000 call allocate_th_read_gen(fd%gen, sub_error) if (sub_error) goto 8000 !-- Determine the format. fd%format_code = 0 format_name = '' if (present(file_format)) format_name = file_format if (format_name == '') call read_th_format(file_name, format_name) if (format_name == '') then where = 'format determination'; goto 8000; endif format_code = string_index(string=format_name, list=format_names) if (format_code==0) then where = 'unknown format: ' // format_name; goto 8000; endif !-- Handle defaults for security access. access_id_tmp = '' password_tmp = '' if (present(access_id)) access_id_tmp = access_id if (present(password)) password_tmp = password !-- Open the file according to the format. select case(format_code) case (asc1_code,asc2_code) call open_th_read_asc12(fd%gen, file_name, fd%n_signals, sub_error) case (unc2_code) call open_th_read_unc2(fd%gen, file_name, fd%n_signals, sub_error) case (unc3_code) call open_th_read_unc3(fd%gen, file_name, fd%n_signals, sub_error) case (cmp3_code) call open_th_read_cmp3(fd%gen, file_name, fd%n_signals, sub_error) case (net1_code) call open_th_read_net1(fd%gen, file_name, access_id_tmp, password_tmp, & fd%n_signals, sub_error) case default sub_error = .true. end select if (sub_error) then where = 'open format: ' // format_name; goto 8000; endif !-- Default to complete signal list. fd%n_requested = fd%n_signals !-- Normal return. fd%format_code = format_code fd%ok_to_read = .false. if (present(error)) error = .false. return !---------- error exit. 8000 continue if (iostat /= 0) call write_sys_error(iostat) call write_error_msg('Open_th_read failed at ' // where) call write_error_msg('File: ' // file_name) if (associated(fd)) call close_th_read(file_handle) if (.not.present(error)) call error_halt error = .true. return end subroutine open_th_read subroutine close_th_read (file_handle) !-- Close file opened by open_th_read. !-- All errors are currently fatal. !-- 13 Aug 90, Richard Maine. !-------------------- interface. type(th_read_handle_type), intent(inout) :: file_handle !-- Returns a null handle on exit. !-------------------- executable code. !-- Quietly return if the handle is null; otherwise validate it. !-- Ideally, we should not be called with a null handle. !-- but we will allow it. fd => file_handle%fd if (.not. associated(fd)) return if (associated(fd%gen)) then !-- Close according to the format. select case (fd%format_code) case (asc1_code,asc2_code) call close_th_read_asc12(fd%gen) case (unc2_code) call close_th_read_unc2(fd%gen) case (unc3_code) call close_th_read_unc3(fd%gen) case (cmp3_code) call close_th_read_cmp3(fd%gen) case (net1_code) call close_th_read_net1(fd%gen) case (0) !-- This may happen if called from open_th_read. continue end select !-- Free the handle. call free_th_read_gen(fd%gen) end if deallocate(file_handle%fd) return end subroutine close_th_read subroutine inquire_th (file_handle, n_signals, dt, file_title, & signal_names, eu_names, error) !-- Return information about a th_read file. !-- 26 Jun 90, Richard Maine. !-------------------- interface. type(th_read_handle_type), intent(in) :: file_handle integer, intent(out), optional :: n_signals !-- Number of signals available on the file. real(r_kind), intent(out), optional :: dt !-- Nominal sample interval. 0 if unknown. character*(*), intent(out), optional :: file_title !-- File title. Blank if none. character*(*), optional, intent(out) :: signal_names(:) !-- Names of the signals known to be available. !-- Note that this list may be incomplete in some cases !-- where the only way to find out if a signal is available !-- is to ask for it (sad, but true). character*(*), optional, intent(out) :: eu_names(:) !-- Engineering units names for the currently requested signals. !-- Note that this corresponds to signal_names only if all !-- the signals are currently requested. Blank if unknown. logical, intent(out), optional :: error !-- Returns true if an error occurs. !-- Abort if there is an error and this is not present. !-------------------- executable code. fd => file_handle%fd if (.not. associated(fd)) call error_halt('Closed th_read file_handle') if (present(n_signals)) n_signals = fd%n_signals if (present(dt)) dt = fd%gen%dt if (present(file_title)) file_title = fd%gen%file_title !-- Check array sizes. if (present(signal_names)) then if (size(signal_names) < fd%n_signals) & call error_halt('Signal_names vector too small in inquire_th.') end if if (present(eu_names)) then if (size(eu_names) < fd%n_requested) & call error_halt('Eu_names vector too small in inquire_th.') end if !-- Obtain signal names and eu names according to format if required. if (present(signal_names) .or. present(eu_names)) then select case (fd%format_code) case (asc1_code,asc2_code,unc2_code,unc3_code,cmp3_code,net1_code) call inquire_th_gen(fd%gen, signal_names, eu_names) end select end if !-- Normal exit. if (present(error)) error = .false. return end subroutine inquire_th subroutine request_th_signals (file_handle, requested_signals, & print_warning, signal_found, all_found, error) !-- Specify signals to be read from a time history file. !-- Terminates the current seek interval. !-- 19 Sept 90, Richard Maine. !-------------------- interface. type(th_read_handle_type), intent(in) :: file_handle character*(*), intent(in) :: requested_signals(:) !-- Requested signal names. logical, intent(in), optional :: print_warning !-- Should warning messages be printed for missing signals? !-- Default is .true. logical, intent(out), optional :: signal_found(:) !-- Indicates which requested signals were found. !-- If present, must be the same size as requested_signals. logical, intent(out), optional :: all_found !-- True if all requested non-blank signals were found. logical, intent(out), optional :: error !-- True if an error occurs. !-- Abort if an error occurs and this is not present. !-- This indicates a serious error (usually i/o). !-- Failure to find requested signals is not considered an error. !-- The signal_found argument and the resulting signal list !-- are undefined if this returns true. !-------------------- local. integer :: i, n_requested logical :: all_good, sub_error, warn logical :: found(size(requested_signals)) !-------------------- executable code. fd => file_handle%fd if (.not. associated(fd)) call error_halt('Closed th_read file_handle') n_requested = size(requested_signals) if (present(signal_found)) then if (size(signal_found) /= n_requested) & call error_halt('Inconsistent sizes in request_th_signals') end if fd%n_requested = -1 fd%ok_to_read = .false. all_good = .false. !-- Request signals according to format. sub_error = .false. select case (fd%format_code) case (asc1_code,asc2_code,unc2_code,unc3_code,cmp3_code) call request_th_gen(fd%gen, requested_signals, found) case (net1_code) call request_th_net1(fd%gen, requested_signals, found, sub_error) end select if (sub_error) goto 8000 !-- Print optional error messages. all_good = all(found .or. (requested_signals=='')) warn = .true. if (present(print_warning)) warn = print_warning if (warn .and. .not. all_good) then do i = 1 , n_requested if ((.not. found(i)) .and. (requested_signals(i) /= '')) & call write_error_msg('Signal ' // requested_signals(i) // & ' not found.') end do end if !-- Return optional arguments. fd%n_requested = n_requested if (present(signal_found)) signal_found = found !-- Both normal and error exits return through here. 8000 continue if (present(all_found)) all_found = all_good if (sub_error .and. .not.present(error)) & call error_halt('Error in request_th_signals.') if (present(error)) error = sub_error return end subroutine request_th_signals subroutine seek_th (file_handle, start_time, stop_time, thin, error) !-- Seek to a specified time interval on a time history file. !-- 19 Sept 90, Richard Maine. !-------------------- interface. type(th_read_handle_type), intent(in) :: file_handle real(r_kind), intent(in), optional :: start_time, stop_time !-- Default seek interval is 0-24 hours. integer, intent(in), optional :: thin !-- Thinning factor for data frames. Default is 1. !-- Must be >=1. Illegal values currently are ignored. logical, intent(out), optional :: error !-- True if an error occurs. It is not considered an error !-- to seek to an interval that has no data frames. !-------------------- local. logical :: sub_error !-------------------- executable code. fd => file_handle%fd if (.not. associated(fd)) call error_halt('Closed th_read file_handle') !-- Don't seek if the signal list is undefined (from an error). sub_error = fd%n_requested < 0 if (sub_error) goto 8000 !-- Save time interval specifications. if (present(start_time)) then fd%start_time = start_time else fd%start_time = 0. end if if (present(stop_time)) then fd%stop_time = stop_time else fd%stop_time = 86400._r_kind end if if (present(thin)) then fd%thin_factor = max(thin,1) else fd%thin_factor = 1 end if fd%prev_time = -big_real fd%first_read = .true. !-- Call lower level routines to do the seek. !-- Note that the low level routines are allowed to seek to !-- any earlier time (for instance to the first available frame). !-- The high level read will discard any data before the requested !-- start time. The decision of how much to do in the low !-- level routine is purely one of efficiency. !-- If the lower level routine handles thinning, then we must !-- set fd%thin_factor to 1 afterwards to avoid double thinning. sub_error = .true. select case (fd%format_code) case (asc1_code,asc2_code) call seek_th_asc12(fd%gen, fd%start_time, sub_error) case (unc2_code) call seek_th_unc2(fd%gen, fd%start_time, sub_error) case (unc3_code) call seek_th_unc3(fd%gen, fd%start_time, sub_error) case (cmp3_code) call seek_th_cmp3(fd%gen, fd%start_time, sub_error) case (net1_code) call seek_th_net1(fd%gen, fd%start_time, fd%stop_time, fd%thin_factor, & sub_error) end select !-- Normal or error exit. 8000 continue fd%ok_to_read = .not. sub_error if (sub_error .and. .not.present(error)) & call error_halt('Error in seek_th.') if (present(error)) error = sub_error return end subroutine seek_th subroutine read_th (file_handle, time, data, status) !-- Read a frame from a th file. !-- 19 Sept 90, Richard Maine. !-------------------- interface. type(th_read_handle_type), intent(in) :: file_handle real(r_kind), intent(out) :: time !-- Frame time. real(r_kind), intent(out) :: data(:) !-- Frame data. integer, intent(out) :: status !-- Returns 0 for valid data frame. !-- 1 = end of requested interval reached. !-- 2 = end of available data reached. !-- 3 = error. !-------------------- local. integer :: thin_count, thin_factor, n_req !-------------------- executable code. fd => file_handle%fd if (.not. associated(fd)) call error_halt('Closed th_read file_handle') status = 3 if (.not. fd%ok_to_read) return fd%ok_to_read = .false. n_req = fd%n_requested if (size(data) < n_req) call error_halt('data size wrong in read_th') !-- Read a frame according to format. !-- Error message for frames out of time order. !-- Skip frames prior to the requested interval. !-- Stop on frames after the requested interval. thin_factor = fd%thin_factor if (fd%first_read) thin_factor = 1 fd%first_read = .false. thin_loop: do thin_count = 1 , thin_factor seek_loop: do status = 3 select case (fd%format_code) case (asc1_code,asc2_code) call read_th_asc12(fd%gen, time, data(1:n_req), status) case (unc2_code) call read_th_unc2(fd%gen, time, data(1:n_req), status) case (unc3_code) call read_th_unc3(fd%gen, time, data(1:n_req), status) case (cmp3_code) call read_th_cmp3(fd%gen, time, data(1:n_req), status) case (net1_code) call read_th_net1(fd%gen, time, data(1:n_req), status) end select if (status /= 0) return if (time < fd%prev_time) then call write_error_msg('Time out of order in read_th.') status = 3 return end if fd%prev_time = time if (time >= fd%start_time) exit seek_loop end do seek_loop if (time > fd%stop_time) then status = 1 return end if end do thin_loop !-- Normal exit. fd%ok_to_read = .true. status = 0 return end subroutine read_th subroutine read_th_format (file_name, file_format) !-- Determine the format of a time history file for open_th_read. !-- 22 Jun 90, Richard Maine. !-------------------- interface. character*(*), intent(in) :: file_name character*(*), intent(out) :: file_format !-- Returns blank on failure. !-------------------- local. character :: data*40, key_string*8 logical :: error integer :: i, j !-------------------- executable code. file_format = '' call read_raw_file (file_name, data, error) if (error) return !-- Find the format field. !-- Explicitly down_casing protects against source code case changes. key_string = lower_case('format ') data = lower_case(data) i = index(string=data, substring=key_string) !-- xtract field, stripping embedded blanks. if (i > 0) then j = 0 do i = i+8 , min(i+15,len(data)) if (data(i:i) /= ' ') then j = j + 1 if (j > len(file_format)) exit file_format(j:j) = data(i:i) end if end do end if if (file_format == '') & call write_error_msg('No format field in th_read file.') return end subroutine read_th_format subroutine read_raw_file (file_name, data, error) !-- Read the first bytes from a file of unknown format. !-- System dependent. !-- This routine must open and read data from a file without first !-- knowing the file format, including such things as whether the !-- file is formatted, unformatted, sequential, or direct. !-- It is acceptable to include low-level system record-control !-- or block-control headers in the data returned; the calling code !-- will scan past such headers to find the desired data. !-- Generic version, insomuch as we can. !-- This version uses direct access formatted i/o. !-- Alternatives include non-advancing or direct access unformatted. !-- 22 Jun 90, Richard Maine. !-------------------- interface. character*(*), intent(in) :: file_name character*(*), intent(out) :: data logical, intent(out) :: error !-------------------- local. integer :: lun, iostat !-------------------- executable code. !-- Try to open the file. data = '' call assign_lun(lun) open(lun, file=file_name, status='old', action='read', iostat=iostat, & form='formatted', access='direct', recl=len(data)) error = iostat /= 0 if (iostat /= 0) then call write_sys_error(iostat) call write_error_msg('Open_th_read unable to open file.') call release_lun(lun) return end if read(lun, '(a)', rec=1, iostat=iostat) data !-- close file and exit. error = iostat /= 0 if (iostat /= 0) then call write_sys_error(iostat) call write_error_msg('Empty or bad file in open_th_read.') end if close(lun) call release_lun(lun) return end subroutine read_raw_file end module th_read