1#!/usr/local/bin/bash
2#
3# Copyright 2009-2021 The VOTCA Development Team (http://www.votca.org)
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18#-------------------defines----------------
19
20if [[ $1 = "--help" ]]; then
21  cat <<EOF
22${0##*/}, version %version%
23
24This file defines some commonly used functions:
25
26EOF
27sed -n 's/^\(.*\)([)] {[^#]*#\(.*\)$/* \1  -- \2/p' ${0}
28echo
29exit 0
30fi
31
32export BASH #need in CsgFunctions.pm
33
34shopt -s extglob
35
36msg() { #echos a msg on the screen and send it to the logfile if logging is enabled
37  local color colors="blue cyan cyann green red purp"
38  if [[ -z ${CSGNOCOLOR} ]]; then
39    local blue=""
40    local cyan=""
41    local cyann=""
42    local green=""
43    local red=""
44    local purp=""
45    local off=""
46  else
47    local blue cyan cyann green red purp off
48  fi
49  if [[ $1 = "--color" ]]; then
50    [[ -z $2 ]] && die "${FUNCNAME[0]}: missing argument after --color"
51    is_part "$2" "${colors}" || die "${FUNCNAME[0]}: Unknown color ($colors allowed)"
52    color="${!2}"
53    shift 2
54  else
55    off=""
56  fi
57  if [[ $1 = "--to-stderr" ]]; then
58    shift
59    [[ -z $* ]] && return
60    if [[ -n ${CSGLOG} ]]; then
61      echo -e "${color}$*${off}" >&4
62      echo -e "$*" >&2
63    else
64      echo -e "${color}$*${off}" >&2
65    fi
66  else
67    [[ -z $* ]] && return
68    if [[ -n ${CSGLOG} ]]; then
69      echo -e "${color}$*${off}" >&3
70      echo -e "$*"
71    else
72      echo -e "${color}$*${off}"
73    fi
74  fi
75}
76export -f msg
77
78show_callstack() { #show the current callstack
79  local space line
80  if [[ -n $CSG_CALLSTACK ]]; then
81    echo "$CSG_CALLSTACK"
82    space="$(echo "$CSG_CALLSTACK" | sed -n '$s/[^[:space:]].*$/    /p')"
83  else
84    space=""
85  fi
86  [[ $0 = *"bash" ]] || echo "${space}${0} - linenumber ${BASH_LINENO[ $(( ${#FUNCNAME[@]} -2 ))]}"
87  for ((c=${#FUNCNAME[*]}-1;c>0;c--)); do
88    [[ ${FUNCNAME[$c]} = main ]] && continue #main is useless as the info was printed 2 lines above
89    space+="    "
90    if [[ $0 = *csg_call || $0 = *inverse.sh ]]; then
91      echo "${space}${FUNCNAME[$c]} - linenumber ${BASH_LINENO[ $(( $c - 1 )) ]} in ${BASH_SOURCE[$c]}"
92    else
93      echo "${space}${FUNCNAME[$c]} - linenumber ${BASH_LINENO[ $(( $c - 1 )) ]} (see 'csg_call --cat function ${FUNCNAME[$c]}')"
94    fi
95  done
96  [[ $1 = "--extra" ]] || return 0
97  shift
98  for i in "$@"; do
99    space+="    "
100    echo "${space}${i} - linenumber ???"
101  done
102}
103export -f show_callstack
104
105unset -f die
106die () { #make the iterative frame work stopp
107  local pid pids c place
108  #Output callstack to stderr in case die was executed in $( )
109  echo -e "\nCallstack:" >&2
110  show_callstack >&2
111  [[ -z $CSGLOG ]] && place="Details can be found above" || place="For details see the logfile $CSGLOG"
112  [[ ${CSG_RUNTEST} && ${CSGLOG} ]] && tail -n 200 "${CSGLOG}" >&4
113  msg --color red --to-stderr "$(csg_banner "ERROR:" "$@" "$place")"
114  if [[ -n ${CSG_MASTER_PID} ]]; then
115    #grabbing the pid group would be easier, but it would not work on AIX
116    pid="$$"
117    pids="$$"
118    c=0
119    #find the parent of pid until we reach CSG_MASTER_PID
120    until [[ ${CSG_MASTER_PID} -eq $pid ]]; do
121      #get the parent pid using BSD style due to AIX
122      pid=$(ps -o ppid= -p "$pid" 2>/dev/null)
123      [[ -z $pid ]] && pids="0" && break
124      #store them in inverse order to kill parents before the child
125      pids="$pid $pids"
126      ((c++))
127      #at max 10000 iterations
128      if [[ $c -eq 10000 ]]; then
129        #failback to default, see comment below
130        pids="0"
131        break
132      fi
133    done
134    if [[ -n ${CSGLOG} ]]; then
135      echo "${FUNCNAME[0]}: (called from $$)  CSG_MASTER_PID is $CSG_MASTER_PID" >&2
136      echo "${FUNCNAME[0]}: pids to kill: $pids" >&2
137    fi
138    kill $pids
139  else
140    #send kill signal to all process within the process groups
141    kill 0
142  fi
143  exit 1
144}
145export -f die
146
147cat_external() { #takes a two tags and shows content of the according script
148  local script
149  script="$(source_wrapper $1 $2)" || die "${FUNCNAME[0]}: source_wrapper $1 $2 failed"
150  if [[ $1 = "function" ]]; then
151    type $2 | sed '1d'
152  else
153    cat "${script/ *}"
154  fi
155}
156export -f cat_external
157
158do_external() { #takes two tags, find the according script and excute it
159  local script tags quiet="no" ret
160  [[ $1 = "-q" ]] && quiet="yes" && shift
161  script="$(source_wrapper $1 $2)" || die "${FUNCNAME[0]}: source_wrapper $1 $2 failed"
162  tags="$1 $2"
163  [[ $1 != "function" && ! -x ${script/ *} ]] && die "${FUNCNAME[0]}: subscript '${script/ *}' (from tags $tags), is not executable! (Run chmod +x ${script/ *})"
164  #print this message to stderr to allow $(do_external ) and do_external XX >
165  [[ $quiet = "no" ]] && echo "Running subscript '${script##*/}${3:+ }${@:3}' (from tags $tags) dir ${script%/*}" >&2
166  # in debugmode we don't need to do anything special for $1 = function as set -x is already done
167  if [[ -n $CSGDEBUG ]] && [[ -n "$(sed -n '1s@bash@XXX@p' "${script/ *}")" ]]; then
168    CSG_CALLSTACK="$(show_callstack)" "${BASH}" -x $script "${@:3}"
169  elif [[ -n $CSGDEBUG && -n "$(sed -n '1s@perl@XXX@p' "${script/ *}")" ]]; then
170    local perl_debug="$(mktemp perl_debug.XXX)" ret
171    PERLDB_OPTS="NonStop=1 AutoTrace=1 frame=2 LineInfo=$perl_debug" perl -dS $script "${@:3}"
172    ret=$?
173    cat "$perl_debug" 2>&1
174    [[ $ret -eq 0 ]]
175  elif [[ $1 != "function" && -n "$(sed -n '1s@perl@XXX@p' "${script/ *}")" ]]; then
176    CSG_CALLSTACK="$(show_callstack --extra "${script/ *}")" $script "${@:3}"
177  else
178    CSG_CALLSTACK="$(show_callstack)" $script "${@:3}"
179  fi || die "${FUNCNAME[0]}: subscript" "$script ${*:3}" "(from tags $tags) failed"
180}
181export -f do_external
182
183critical() { #executes arguments as command and calls die if not succesful
184  local quiet="no"
185  [[ $1 = "-q" ]] && quiet="yes" && shift
186  [[ -z $1 ]] && die "${FUNCNAME[0]}: missing argument"
187  #print this message to stderr because $(critical something) is used very often
188  [[ $quiet = "no" ]] && echo "Running critical command '$*'" >&2
189   "$@" || die "${FUNCNAME[0]}: '$*' failed"
190}
191export -f critical
192
193for_all (){ #do something for all interactions (1st argument)
194  local bondtype ibondtype rbondtype bondtypes name interactions quiet="no"
195  [[ $1 = "-q" ]] && quiet="yes" && shift
196  [[ -z $1 || -z $2 ]] && die "${FUNCNAME[0]}: need at least two arguments"
197  bondtypes="$1"
198  shift
199  interactions=( $(csg_get_interaction_property --all name) )
200  min=( $(csg_get_interaction_property --all min) )
201  [[ ${#min[@]} -ne ${#interactions[@]} ]] && die "${FUNCNAME[0]}: one interaction has no name or min"
202  name=$(has_duplicate "${interactions[@]}") && die "${FUNCNAME[0]}: interaction name $name appears twice"
203  for bondtype in $bondtypes; do
204    #check that type is bonded or non-bonded
205    is_part "$bondtype" "non-bonded bonded angle bond dihedral" || die  "for_all: Argument 1 needs to be non-bonded, bonded, angle, bond or dihedral"
206    [[ $quiet = "no" ]] && echo "For all $bondtype" >&2
207    #internal bondtype
208    is_part "$bondtype" "angle bond dihedral bonded" && ibondtype="bonded" || ibondtype="non-bonded"
209    interactions=( $(csg_get_property --allow-empty cg.$ibondtype.name) ) #filter me away
210    for name in "${interactions[@]}"; do
211      #check if interaction is actually angle, bond or dihedral
212      if is_part "$bondtype" "angle bond dihedral"; then
213	rbondtype=$(bondtype="$ibondtype" bondname="$name" csg_get_interaction_property bondtype)
214	[[ $rbondtype = $bondtype ]] || continue
215      fi
216      #print this message to stderr to avoid problem with $(for_all something)
217      [[ $quiet = no ]] && echo "for_all: run '$*' for interaction named '$name'" >&2
218      #we need to use bash -c here to allow things like $(csg_get_interaction_property name) in arguments
219      #write variable defines in the front is better, that export
220      #no need to run unset afterwards
221      bondtype="$ibondtype" \
222      bondname="$name" \
223      CSG_CALLSTACK="$(show_callstack)" \
224      "${BASH}" -c "$*" || die "${FUNCNAME[0]}: ${BASH} -c '$*' failed for interaction named '$name'"
225    done
226  done
227}
228export -f for_all
229
230csg_get_interaction_property () { #gets an interaction property from the xml file, should only be called from inside a for_all loop or with --all option
231  local ret allow_empty="no" for_all="no" xmltype
232  while [[ $1 = --* ]]; do
233    case $1 in
234      --allow-empty)
235        allow_empty="yes";;
236      --all)
237        for_all="yes";;
238      *)
239	die "${FUNCNAME[0]}: Unknow option '$1'";;
240    esac
241    shift
242  done
243  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
244
245  if [[ $for_all = "yes" ]]; then
246    [[ $1 = "bondtype" ]] && die "${FUNCNAME[0]}: --all + bondtype not implemented yet"
247    local t
248    for t in non-bonded bonded; do
249      ret+=" $(csg_get_property --allow-empty "cg.$t.$1")" #filter me away
250    done
251    ret="$(echo "$ret" | trim_all)"
252    [[ -z $ret ]] && die "${FUNCNAME[0]}: Not a single interaction has a value for the property $1"
253    echo "$ret"
254    return 0
255  fi
256
257  #make these this case work even without name or type (called by csg_call)
258  if [[ $1 = "name" ]]; then
259    [[ -n $bondname ]] && echo "$bondname" && return 0
260    die "${FUNCNAME[0]}: bondname is undefined (when calling from csg_call set it by --ia-name option)"
261  fi
262  if [[ $1 = "bondtype" ]]; then
263    #bondtype is special -> dirty hack - removed whenever issue 13 is fixed
264    [[ -z "$bondtype" ]] && die "${FUNCNAME[0]}: bondtype is undefined (when calling from csg_call set it by --ia-type option)"
265    #for_all notation for any kind of bonded interaction, find the real type
266    if [[ $bondtype = "bonded" ]]; then
267      [[ -z ${bondname} ]] && die "${FUNCNAME[0]}: bondtype 'bonded' needs a bondname (when calling from csg_call set it by --ia-name option) or change type to angle, bond or dihedral"
268      [[ -n "$(type -p csg_property)" ]] || die "${FUNCNAME[0]}: Could not find csg_property"
269      mapping="$(csg_get_property --allow-empty cg.inverse.map)" #make error message more useful
270      [[ -z ${mapping} ]] && die "${FUNCNAME[0]}: bondtype 'bonded' needs a mapping file (cg.inverse.map in xml) to determine the actual bond type (when calling from csg_call better use --ia-type bond, angle or dihedral)"
271      local map names=() ret= ret2 dup
272      for map in ${mapping}; do
273        [[ -f "$(get_main_dir)/$map" ]] || die "${FUNCNAME[0]}: Mapping file '$map' for bonded interaction not found in maindir"
274	names+=( $(critical -q csg_property --file "$(get_main_dir)/$map" --path cg_molecule.topology.cg_bonded.*.name --print . --short) )
275	[[ -n ${names[@]} ]] && dup=$(has_duplicate "${names[@]}") && die "${FUNCNAME[0]}: cg_bonded name '$dup' appears twice in file(s) $mapping"
276        ret2="$(critical -q csg_property --file "$(get_main_dir)/$map" --path cg_molecule.topology.cg_bonded.* --filter name="$bondname" --print . --with-path | trim_all)"
277        ret2="$(echo "$ret2" | critical sed -n 's/.*cg_bonded\.\([^[:space:]]*\) .*/\1/p')"
278	if [[ -n $ret2 ]]; then
279	  [[ -n $ret ]] && die "${FUNCNAME[0]}: Found cg_bonded type for name '$bondname' twice"
280	  ret="${ret2}"
281	fi
282      done
283      [[ -z $ret ]] && die "${FUNCNAME[0]}: Could not find a bonded definition with name '$bondname' in the mapping file(s) '$mapping'. Make sure to use the same name in the settings file (or --ia-name when calling from csg_call) and the mapping file."
284      echo "$ret"
285    else
286      echo "$bondtype"
287    fi
288    return 0
289  fi
290
291  [[ -n "$CSGXMLFILE" ]] || die "${FUNCNAME[0]}: CSGXMLFILE is undefined (when calling from csg_call set it by --options option)"
292  [[ -n $bondtype ]] || die "${FUNCNAME[0]}: bondtype is undefined (when calling from csg_call set it by --ia-type option)"
293  [[ -n $bondname ]] || die "${FUNCNAME[0]}: bondname is undefined (when calling from csg_call set it by --ia-name option)"
294
295  #map bondtype back to tags in xml file (for csg_call)
296  case "$bondtype" in
297    "non-bonded")
298      xmltype="non-bonded";;
299    "bonded"|"bond"|"angle"|"dihedral")
300      xmltype="bonded";;
301    *)
302      msg "Unknown bondtype '$bondtype' - assume non-bonded"
303      xmltype="non-bonded";;
304  esac
305
306  [[ -n "$(type -p csg_property)" ]] || die "${FUNCNAME[0]}: Could not find csg_property"
307  #the --filter/--path(!=.) option will make csg_property fail if $1 does not exist
308  #so no critical here
309  ret="$(csg_property --file $CSGXMLFILE --short --path cg.${xmltype} --filter name=$bondname --print $1 | trim_all)"
310  #overwrite with function call value
311  [[ -z $ret && -n $2 ]] && ret="$2"
312  [[ -z $ret ]] && echo "${FUNCNAME[0]}: No value for '$1' found in $CSGXMLFILE, trying ${VOTCA_CSG_DEFAULTS}" >&2
313  # if still empty fetch it from defaults file
314  if [[ -z $ret && -f ${VOTCA_CSG_DEFAULTS} ]]; then
315    ret="$(critical -q csg_property --file "${VOTCA_CSG_DEFAULTS}" --short --path cg.${xmltype}.$1 --print . | trim_all)"
316    [[ $allow_empty = "yes" && -n "$res" ]] && msg "WARNING: '${FUNCNAME[0]} $1' was called with --allow-empty, but a default was found in '${VOTCA_CSG_DEFAULTS}'"
317    #from time to time the default is only given in the non-bonded section
318    [[ -z $ret ]] && ret="$(critical -q csg_property --file "${VOTCA_CSG_DEFAULTS}" --short --path cg.non-bonded.$1 --print . | trim_all)"
319    [[ -n $ret ]] && echo "${FUNCNAME[0]}: value for '$1' from ${VOTCA_CSG_DEFAULTS}: $ret" >&2
320  fi
321  [[ $allow_empty = "no" && -z $ret ]] && die "${FUNCNAME[0]}: Could not get '$1' for interaction with name '$bondname' from ${CSGXMLFILE} and no default was found in ${VOTCA_CSG_DEFAULTS}"
322  [[ -z $ret ]] && echo "${FUNCNAME[0]}: returning emtpy value for '$1'" >&2
323  echo "${ret}"
324}
325export -f csg_get_interaction_property
326
327csg_get_property () { #get an property from the xml file
328  local ret allow_empty
329  if [[ $1 = "--allow-empty" ]]; then
330    shift
331    allow_empty="yes"
332  else
333    allow_empty="no"
334  fi
335  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
336  [[ -n "$CSGXMLFILE" ]] || die "${FUNCNAME[0]}: CSGXMLFILE is undefined (when calling from csg_call set it by --options option)"
337  [[ -n "$(type -p csg_property)" ]] || die "${FUNCNAME[0]}: Could not find csg_property"
338  #csg_property only fails if xml file is bad otherwise result is empty
339  #leave the -q here to avoid flooding with messages
340  ret="$(critical -q csg_property --file "$CSGXMLFILE" --path ${1} --short --print . | trim_all)"
341  #overwrite with function call value
342  [[ -z $ret && -n $2 ]] && ret="$2"
343  [[ -z $ret ]] && echo "${FUNCNAME[0]}: No value for '$1' found in $CSGXMLFILE, trying ${VOTCA_CSG_DEFAULTS}" >&2
344  #if still empty fetch it from defaults file
345  if [[ -z $ret && -f ${VOTCA_CSG_DEFAULTS} ]]; then
346    ret="$(critical -q csg_property --file "${VOTCA_CSG_DEFAULTS}" --path "${1}" --short --print . | trim_all)"
347    [[ $allow_empty = "yes" && -n "$res" ]] && msg "WARNING: '${FUNCNAME[0]} $1' was called with --allow-empty, but a default was found in '${VOTCA_CSG_DEFAULTS}'"
348    #avoid endless recursion
349    [[ $1 = cg.inverse.program && -n $ret ]] && sim_prog="$ret" || \
350      sim_prog="$(csg_get_property cg.inverse.program)" #no problem to call recursively as sim_prog has a default
351    if [[ -z $ret ]] && [[ $1 = *${sim_prog}* ]]; then
352      local path=${1/${sim_prog}/sim_prog}
353      ret="$(critical -q csg_property --file "${VOTCA_CSG_DEFAULTS}" --path "${path}" --short --print . | trim_all)"
354    fi
355    [[ -n $ret ]] && echo "${FUNCNAME[0]}: value for '$1' from ${VOTCA_CSG_DEFAULTS}: $ret" >&2
356    [[ $allow_empty = "yes" && -n "$res" ]] && msg "WARNING: '${FUNCNAME[0]} $1' was called with --allow-empty, but a default was found in '${VOTCA_CSG_DEFAULTS}'"
357  fi
358  [[ $allow_empty = "no" && -z $ret ]] && die "${FUNCNAME[0]}: Could not get '$1' from ${CSGXMLFILE} and no default was found in ${VOTCA_CSG_DEFAULTS}"
359  [[ -z $ret ]] && echo "${FUNCNAME[0]}: returning emtpy value for '$1'" >&2
360  echo "${ret}"
361}
362export -f csg_get_property
363
364trim_all() { #make multiple lines into one and strip white space from beginning and the end, reads from stdin
365  [[ -n "$(type -p tr)" ]] || die "${FUNCNAME[0]}: Could not find tr"
366  tr '\n' ' ' | sed -e s'/^[[:space:]]*//' -e s'/[[:space:]]*$//' || die "${FUNCNAME[0]}: sed of argument $i failed"
367}
368export -f trim_all
369
370mark_done () { #mark a task (1st argument) as done in the restart file
371  local file
372  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
373  file="$(get_restart_file)"
374  is_done "$1" || echo "$1 done" >> "${file}"
375}
376export -f mark_done
377
378is_done () { #checks if something is already do in the restart file
379  local file
380  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
381  file="$(get_restart_file)"
382  [[ -f ${file} ]] || return 1
383  [[ -n "$(sed -n "/^$1 done\$/p" ${file})" ]] && return 0
384  return 1
385}
386export -f is_done
387
388is_int() { #checks if all arguments are integers
389  local i
390  [[ -z $1 ]] && die "${FUNCNAME[0]}: Missing argument"
391  for i in "$@"; do
392    [[ -n $i && -z ${i//[0-9]} ]] || return 1
393  done
394  return 0
395}
396export -f is_int
397
398to_int() { #convert all given numbers to int using awk's int function
399  local i
400  [[ -z $1 ]] && die "${FUNCNAME[0]}: Missing argument"
401  for i in "$@"; do
402    is_num "$i" || die "${FUNCNAME[0]}: $i is not a number"
403    awk -v x="$i" 'BEGIN{ print ( int(x) ) }' || die "${FUNCNAME[0]}: awk failed"
404  done
405  return 0
406}
407export -f to_int
408
409is_part() { #checks if 1st argument is part of the set given by other arguments
410  [[ -z $1 || -z $2 ]] && die "${FUNCNAME[0]}: Missing argument"
411  [[ " ${@:2} " = *" $1 "* ]]
412}
413export -f is_part
414
415has_duplicate() { #check if one of the arguments is double
416  local i j
417  [[ -z $1 ]] && die "${FUNCNAME[0]}: Missing argument"
418  for ((i=1;i<$#;i++)); do
419    for ((j=i+1;j<=$#;j++)); do
420      [[ ${!i} = ${!j} ]] && echo ${!i} && return 0
421    done
422  done
423  return 1
424}
425export -f has_duplicate
426
427remove_duplicate() { #remove duplicates list of arguments
428  local i j out=() c
429  [[ -z $1 ]] && die "${FUNCNAME[0]}: Missing argument"
430  for ((i=1;i<=$#;i++)); do
431    c=0
432    for ((j=0;j<${#out[@]};j++)); do
433      [[ ${!i} = ${out[j]} ]] && ((c++))
434    done
435    [[ $c -eq 0 ]] && out+=( "${!i}" )
436  done
437  echo "${out[@]}"
438}
439export -f remove_duplicate
440
441is_num() { #checks if all arguments are numbers
442  local i res
443  [[ -z $1 ]] && die "${FUNCNAME[0]}: Missing argument"
444  for i in "$@"; do
445    res=$(awk -v x="$i" 'BEGIN{ print ( x+0==x ) }') || die "${FUNCNAME[0]}: awk failed"
446    [[ $res -eq 1 ]] || return 1
447    unset res
448  done
449  return 0
450}
451export -f is_num
452
453get_stepname() { #get the dir name of a certain step number (1st argument)
454  local name
455  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
456  if [[ $1 = "--trunc" ]]; then
457    echo "step_"
458    return 0
459  fi
460  is_int "${1}" || die "${FUNCNAME[0]}: needs a int as argument, but got $1"
461  name="$(printf step_%03i "$1")"
462  [[ -z $name ]] && die "${FUNCNAME[0]}: Could not get stepname"
463  echo "$name"
464}
465export -f get_stepname
466
467update_stepnames(){ #updated the current working step to a certain number (1st argument)
468  local thisstep laststep nr
469  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
470  nr="$1"
471  is_int "$nr" || die "${FUNCNAME[0]}: needs a int as argument, but got $nr"
472  [[ -z $CSG_MAINDIR ]] && die "${FUNCNAME[0]}: CSG_MAINDIR is undefined"
473  [[ -d $CSG_MAINDIR ]] || die "${FUNCNAME[0]}: $CSG_MAINDIR is not dir"
474  thisstep="$(get_stepname $nr)"
475  export CSG_THISSTEP="$CSG_MAINDIR/$thisstep"
476  if [[ $nr -gt 0 ]]; then
477    laststep="$(get_stepname $((nr-1)) )"
478    export CSG_LASTSTEP="$CSG_MAINDIR/$laststep"
479  fi
480}
481export -f update_stepnames
482
483get_current_step_dir() { #print the directory of the current step
484  [[ -z $CSG_THISSTEP ]] && die "${FUNCNAME[0]}: \$CSG_THISSTEP is undefined (when calling from csg_call export it yourself)"
485  if [[ $1 = "--no-check" ]]; then
486    :
487  else
488    [[ -d $CSG_THISSTEP ]] || die "${FUNCNAME[0]}: $CSG_THISSTEP is not dir"
489  fi
490  echo "$CSG_THISSTEP"
491
492}
493export -f get_current_step_dir
494
495get_last_step_dir() { #print the directory of the last step
496  [[ -z $CSG_LASTSTEP ]] && die "${FUNCNAME[0]}: CSG_LASTSTEP is undefined  (when calling from csg_call export it yourself)"
497  [[ -d $CSG_LASTSTEP ]] || die "${FUNCNAME[0]}: $CSG_LASTSTEP is not dir"
498  echo "$CSG_LASTSTEP"
499}
500export -f get_last_step_dir
501
502get_main_dir() { #print the main directory
503  [[ -z $CSG_MAINDIR ]] && die "${FUNCNAME[0]}: CSG_MAINDIR is defined"
504  [[ -d $CSG_MAINDIR ]] || die "${FUNCNAME[0]}: $CSG_MAINDIR is not dir"
505  echo "$CSG_MAINDIR"
506}
507export -f get_main_dir
508
509get_current_step_nr() { #print the main directory
510  local name nr
511  name=$(get_current_step_dir)
512  nr=$(get_step_nr $name)
513  echo "$nr"
514}
515export -f get_current_step_nr
516
517get_step_nr() { #print the number of a certain step directory (1st argument)
518  local nr trunc
519  trunc=$(get_stepname --trunc)
520  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
521  nr=${1##*/}
522  nr=${nr#$trunc}
523  #convert to base 10 and cut leading zeros
524  nr=$((10#$nr))
525  is_int "$nr" || die "${FUNCNAME[0]}: Could not fetch step nr, got $nr"
526  echo "$nr"
527}
528export -f get_step_nr
529
530cp_from_main_dir() { #copy something from the main directory
531  critical pushd "$(get_main_dir)"
532  if [[ $1 = "--rename" ]]; then
533    shift
534    [[ $# -eq 2 && -n $1 && -n $2 ]] || die "${FUNCNAME[0]}: with --rename option has to be called with exactly 2 (non-empty) arguments"
535    echo "cp_from_main_dir: '$1' to '$2'"
536    critical cp $1 "$(dirs -l +1)/$2"
537  else
538    echo "cp_from_main_dir: '$@'"
539    critical cp $@ "$(dirs -l +1)"
540  fi
541  critical popd
542}
543export -f cp_from_main_dir
544
545cp_from_last_step() { #copy something from the last step
546  if [[ $1 = "--rename" ]]; then
547    shift
548    [[ $# -eq 2 && -n $1 && -n $2 ]] || die "${FUNCNAME[0]}: with --rename option has to be called with exactly 2 (non-empty) arguments"
549    echo "cp_from_last_step: '$1' to '$2'"
550    critical pushd "$(get_last_step_dir)"
551    critical cp $1 "$(dirs -l +1)/$2"
552    critical popd
553  else
554    echo "cp_from_last_step: '$@'"
555    critical pushd "$(get_last_step_dir)"
556    critical cp $@ "$(dirs -l +1)"
557    critical popd
558  fi
559}
560export -f cp_from_last_step
561
562get_time() { #gives back current time in sec from 1970
563  date +%s || die "${FUNCNAME[0]}:  date +%s failed"
564}
565export -f get_time
566
567get_number_tasks() { #get the number of possible tasks from the xml file or determine it automatically under some systems
568  local tasks
569  tasks="$(csg_get_property cg.inverse.simulation.tasks)"
570  [[ $tasks = "auto" ]] && tasks=0
571  is_int "$tasks" || die "${FUNCNAME[0]}: cg.inverse.simulation.tasks needs to be a number or 'auto', but I got $(csg_get_property cg.inverse.simulation.tasks)"
572  if [[ $tasks -eq 0 ]]; then #auto-detect
573    if [[ -r /proc/cpuinfo ]]; then #linux
574      tasks=$(sed -n '/processor/p' /proc/cpuinfo | sed -n '$=')
575    elif [[ -x /usr/sbin/sysctl ]]; then #mac os
576      tasks=$(/usr/sbin/sysctl -n hw.ncpu)
577    elif [[ -x /usr/sbin/lsdev ]]; then #AIX
578      tasks=$(/usr/sbin/lsdev | sed -n '/Processor/p' | sed -n '$=')
579    fi
580    is_int "${tasks}" || tasks=1 #failback in case we got non-int
581  fi
582  if [[ ${CSG_NUM_THREADS} ]]; then
583    is_int "${CSG_NUM_THREADS}" || die "${FUNCNAME[0]}: value of CSG_NUM_THREADS needs to be a number, but I got ${CSG_NUM_THREADS}"
584    msg --color blue --to-stderr "${FUNCNAME[0]}: Overwriting cg.inverse.simulation.tasks with '${CSG_NUM_THREADS}'"
585    tasks="${CSG_NUM_THREADS}"
586  fi
587  echo "$tasks"
588}
589export -f get_number_tasks
590
591get_table_comment() { #get comment lines from a table and add common information, which include the git id and other information
592  local version co
593  [[ -n "$(type -p csg_call)" ]] || die "${FUNCNAME[0]}: Could not find csg_call"
594  version="$(csg_call --version)" || die "${FUNCNAME[0]}: csg_call --version failed"
595  echo "Created on $(date) by $USER@$HOSTNAME"
596  echo "called from $version" | sed "s/csg_call/${0##*/}/"
597  [[ -n "${CSGXMLFILE}" ]] && echo "settings file: '$(globalize_file "${CSGXMLFILE}")'"
598  echo "working directory: $PWD"
599  if [[ -f $1 ]]; then
600    co=$(sed -n 's/^[#@][[:space:]]*//p' "$1") || die "${FUNCNAME[0]}: sed failed"
601    [[ -n $co ]] && echo "Comments from $(globalize_file $1):\n$co"
602  fi
603}
604export -f get_table_comment
605
606csg_inverse_clean() { #clean out the main directory
607  local i files log t
608  [[ -n $1 ]] && t="$1" || t="30"
609  log="$(csg_get_property cg.inverse.log_file 2>/dev/null)"
610  echo -e "So, you want to clean?\n"
611  echo "I will remove:"
612  files="$(ls -d done ${log} $(get_stepname --trunc)* *~ 2>/dev/null)"
613  if [[ -z $files ]]; then
614    echo "Nothing to clean"
615  else
616    msg --color red $files
617    msg --color blue "\nCTRL-C to stop it"
618    for ((i=$t;i>0;i--)); do
619      echo -n "$i "
620      sleep 1
621    done
622    rm -rf $files
623    msg --color green "\n\nDone, hope you are happy now"
624  fi
625}
626export -f csg_inverse_clean
627
628check_path_variable() { #check if a variable contains only valid paths
629  local old_IFS dir
630  [[ -z $1 ]] && die "${FUNCNAME[0]}: Missing argument"
631  for var in "$@"; do
632    [[ -z $var ]] && continue
633    old_IFS="$IFS"
634    IFS=":"
635    for dir in ${!var}; do
636      [[ -z $dir ]] && continue
637      [[ $dir = *votca* ]] || continue #to many error otherwise
638      [[ -d $dir ]] || die "${FUNCNAME[0]}: $dir from variable $var is not a directory"
639    done
640    IFS="$old_IFS"
641  done
642}
643export -f check_path_variable
644
645add_to_csgshare() { #added an directory to the csg internal search directories
646  local dir end="no"
647  [[ $1 = "--at-the-end" ]] && end="yes" && shift
648  [[ -z $1 ]] && die "${FUNCNAME[0]}: Missing argument"
649  for dirlist in "$@"; do
650    old_IFS="$IFS"
651    IFS=":"
652    for dir in $dirlist; do
653      #dir maybe contains $PWD or something
654      eval dir="$dir"
655      [[ -d $dir ]] || die "${FUNCNAME[0]}: Could not find scriptdir $dir"
656      dir="$(globalize_dir "$dir")"
657      if [[ $end = "yes" ]]; then
658        export CSGSHARE="${CSGSHARE}${CSGSHARE:+:}$dir"
659        export PERL5LIB="${PERL5LIB}${PERL5LIB:+:}$dir"
660      else
661        export CSGSHARE="$dir${CSGSHARE:+:}$CSGSHARE"
662        export PERL5LIB="$dir${PERL5LIB:+:}$PERL5LIB"
663      fi
664    done
665    IFS="$old_IFS"
666  done
667  check_path_variable CSGSHARE PERL5LIB
668}
669export -f add_to_csgshare
670
671globalize_dir() { #convert a local directory to a global one
672  [[ -z $1 ]] && die "${FUNCNAME[0]}: missing argument"
673  [[ -d $1 ]] || die "${FUNCNAME[0]}: '$1' is not a dir"
674  cd "$1"
675  pwd
676}
677export -f globalize_dir
678
679globalize_file() { #convert a local file name to a global one
680  [[ -z $1 ]] && die "${FUNCNAME[0]}: missing argument"
681  [[ -f $1 ]] || die "${FUNCNAME[0]}: '$1' is not a file"
682  local dir
683  [[ ${1%/*} = ${1} ]] && dir="." || dir="${1%/*}"
684  echo "$(globalize_dir "$dir")/${1##*/}"
685}
686export -f globalize_file
687
688source_function() { #source an extra function file
689  local function_file
690  [[ -n $1 ]] || die "${FUNCNAME[0]}: Missing argument"
691  function_file=$(source_wrapper functions $1) || die "${FUNCNAME[0]}: source_wrapper functions $1 failed"
692  source ${function_file} || die "${FUNCNAME[0]}: source ${function_file} failed"
693}
694export -f source_function
695
696csg_banner() { #print a big banner
697  local i l=0 list=()
698  [[ -z $1 ]] && return 0
699  for i in "$@"; do
700    while [[ -n $i && -z ${i/*\\n*} ]]; do
701      list[$l]="${i%%\\n*}"
702      ((l++))
703      i="${i#*\\n}"
704    done
705    list[$l]=$i
706    ((l++))
707  done
708
709  l="1"
710  for i in "${list[@]}"; do
711    [[ ${#l} -lt ${#i} ]] && l="${i}"
712  done
713
714  echo "####${l//?/#}"
715  echo "# ${l//?/ } #"
716  for i in "${list[@]}"; do
717    printf "# %-${#l}s #\n" "$i"
718  done
719  echo "# ${l//?/ } #"
720  echo "####${l//?/#}"
721}
722export -f csg_banner
723
724csg_calc() { #simple calculator, a + b, ...
725  local res ret=0 err="1e-2"
726  [[ -z $1 || -z $2 || -z $3 ]] && die "${FUNCNAME[0]}: Needs 3 arguments, but got '$*'"
727  is_num "$1" || die "${FUNCNAME[0]}: First argument of csg_calc should be a number, but got '$1'"
728  is_num "$3" || die "${FUNCNAME[0]}: Third argument of csg_calc should be a number, but got '$3'"
729  [[ -n "$(type -p awk)" ]] || die "${FUNCNAME[0]}: Could not find awk"
730  #we use awk -v because then " 1 " or "1\n" is equal to 1
731  case "$2" in
732    "+"|"-"|'*'|"/"|"^")
733       res="$(awk -v x="$1" -v y="$3" "BEGIN{print ( x $2 y ) }")" || die "${FUNCNAME[0]}: awk -v x='$1' -v y='$3' 'BEGIN{print ( x $2 y ) }' failed"
734       true;;
735    '>'|'<' )
736       res="$(awk -v x="$1" -v y="$3" "BEGIN{print ( x $2 y )}")" || die "${FUNCNAME[0]}: awk -v x='$1' -v y='$3' 'BEGIN{print ( x $2 y )}' failed"
737       #awk return 1 for true and 0 for false, shell exit codes are the other way around
738       ret="$((1-$res))"
739       #return value matters
740       res=""
741       true;;
742    "="|"==")
743       #this is really tricky... case x=0,y=0 is catched by (x==y) after that |x-y|/max(|x|,|y|) will work expect for x,y beginng close to zero
744       res="$(awk -v x="$1" -v y="$3" -v e="$err" \
745       'function max(x,y){return (x>y)?x:y;} function abs(x){return (x<0)?-x:x;} BEGIN{if (x==y){print 1;}else{if (abs(x-y)<e){print 1;}else{ print ( abs(x-y)/max(abs(x),abs(y)) < e );}}}')" \
746	 || die "${FUNCNAME[0]}: awk for =/== failed"
747       #awk return 1 for true and 0 for false, shell exit codes are the other way around
748       ret="$((1-$res))"
749       #return value matters
750       res=""
751       true;;
752    *)
753       die "${FUNCNAME[0]}: unknow operation"
754       true;;
755  esac
756  [[ -n $res ]] && echo "$res"
757  return $ret
758}
759export -f csg_calc
760
761show_csg_tables() { #show all concatinated csg tables
762  local old_IFS dir
763  old_IFS="$IFS"
764  IFS=":"
765  echo "#The order in which scripts get called"
766  echo "#CSGSHARE is $CSGSHARE"
767  for dir in ${CSGSHARE}; do
768    [[ -f $dir/csg_table ]] || continue
769    echo "#From: $dir/csg_table"
770    #remove comments and empty line, trim begin and end, tab to spaces
771    sed -e '/^#/d' -e '/^[[:space:]]*$/d' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/[[:space:]]\+/ /g' "$dir/csg_table"
772  done
773  IFS="$old_IFS"
774}
775export -f show_csg_tables
776
777get_command_from_csg_tables() { #print the name of script belonging to certain tags (1st, 2nd argument)
778  [[ -z $1 || -z $2 ]] && die "${FUNCNAME[0]}: Needs two tags"
779  show_csg_tables | \
780    sed -e '/^#/d' | \
781    sed -n "s/^$1 $2 \(.*\)$/\1/p" | \
782    sed -n '1p'
783}
784export -f get_command_from_csg_tables
785
786source_wrapper() { #print the full name of a script belonging to two tags (1st, 2nd argument)
787  [[ -z $1 || -z $2 ]] && die "${FUNCNAME[0]}: Needs two tags"
788  local cmd script
789  if [[ $1 = "function" ]]; then
790    [[ $(type -t "$2") = "function" ]] || die "${FUNCNAME[0]}: could not find any function called '$2' (when calling from csg_call you might need to add --simprog option or set cg.inverse.program in the xml file)"
791    echo "$2"
792  else
793    cmd=$(get_command_from_csg_tables "$1" "$2") || die "${FUNCNAME[0]}: get_command_from_csg_tables '$1' '$2' failed"
794    [[ -z $cmd ]] && die "${FUNCNAME[0]}: Could not get any script from tags '$1' '$2'"
795    #cmd might contain option after the script name
796    script="${cmd%% *}"
797    real_script="$(find_in_csgshare "$script")"
798    echo "${cmd/${script}/${real_script}}"
799  fi
800}
801export -f source_wrapper
802
803find_in_csgshare() { #find a script in csg script search path
804  [[ -z $1 ]] && die "${FUNCNAME[0]}: missing argument"
805  #global path
806  if [[ -z ${1##/*} ]]; then
807    [[ -f $1 ]] || die "${FUNCNAME[0]}: $1 is a script with global path, but was not found"
808    echo "$1" && return
809  fi
810  local old_IFS dir
811  old_IFS="$IFS"
812  IFS=":"
813  for dir in ${CSGSHARE}; do
814    [[ -f $dir/$1 ]] && break
815  done
816  IFS="$old_IFS"
817  [[ -f $dir/$1 ]] && echo "$dir/$1" && return
818  die "${FUNCNAME[0]}: Could not find script $1 in $CSGSHARE"
819}
820export -f find_in_csgshare
821
822if [ -z "$(type -p mktemp)" ]; then
823  #do not document this
824  mktemp() {
825    [[ $1 = "-u" ]] && shift
826    [[ -z $1 ]] && die "${FUNCNAME[0]}: missing argument"
827    [[ -z ${1##*X} ]] || die "${FUNCNAME[0]}: argument has to end at least with X"
828    local end trunc i l tmp newend
829    end=${1##*[^X]}
830    trunc=${1%${end}}
831    l=${end//[^X]}
832    l=${#l}
833    while true; do
834      newend="$end"
835      for ((i=0;i<$l;i++)); do
836        newend="${newend/X/${RANDOM:0:1}}"
837      done
838      tmp="${trunc}${newend}"
839      [[ -f $tmp ]] || break
840    done
841    echo "$tmp"
842  }
843  export -f mktemp
844fi
845
846enable_logging() { #enables the logging to a certain file (1st argument) or the logfile taken from the xml file
847  local log
848  if [[ -z $1 ]]; then
849    log="$(csg_get_property cg.inverse.log_file 2>/dev/null)"
850  else
851    log="$1"
852  fi
853  log="${PWD}/${log##*/}"
854  export CSGLOG="$log"
855  if [[ -f $CSGLOG ]]; then
856    exec 3>&1 4>&2 >> "$CSGLOG" 2>&1
857    echo -e "\n\n#################################"
858    echo "# Appending to existing logfile #"
859    echo -e "#################################\n\n"
860    msg --color blue "Appending to existing logfile ${CSGLOG##*/}"
861  else
862    exec 3>&1 4>&2 >> "$CSGLOG" 2>&1
863    msg "For a more verbose log see: ${CSGLOG##*/}"
864  fi
865}
866export -f enable_logging
867
868get_restart_file() { #print the name of the restart file to use
869  local file
870  file="$(csg_get_property cg.inverse.restart_file)"
871  [[ -z ${file/*\/*} ]] && die "${FUNCNAME[0]}: cg.inverse.restart_file has to be a local file with slash '/'"
872  echo "$file"
873}
874export -f get_restart_file
875
876check_for_obsolete_xml_options() { #check xml file for obsolete options
877  local i
878  for i in cg.inverse.mpi.tasks cg.inverse.mpi.cmd cg.inverse.parallel.tasks cg.inverse.parallel.cmd \
879    cg.inverse.gromacs.mdrun.bin cg.inverse.espresso.bin cg.inverse.scriptdir cg.inverse.gromacs.grompp.topol \
880    cg.inverse.gromacs.grompp.index cg.inverse.gromacs.g_rdf.topol cg.inverse.convergence_check \
881    cg.inverse.convergence_check_options.name_glob cg.inverse.convergence_check_options.limit \
882    cg.inverse.espresso.table_end cg.inverse.gromacs.traj_type cg.inverse.gromacs.topol_out \
883    cg.inverse.espresso.blockfile cg.inverse.espresso.blockfile_out cg.inverse.espresso.n_steps \
884    cg.inverse.espresso.exclusions cg.inverse.espresso.debug cg.inverse.espresso.n_snapshots \
885    cg.non-bonded.inverse.espresso.index1 cg.non-bonded.inverse.espresso.index2 cg.inverse.espresso.success \
886    cg.inverse.espresso.scriptdir cg.non-bonded.inverse.post_update_options.kbibi.type \
887    cg.inverse.imc.numpy.bin cg.inverse.imc.octave.bin cg.inverse.imc.matlab.bin cg.inverse.imc.solver \
888    cg.non-bonded.inverse.imc.reg \
889    ; do
890    [[ -z "$(csg_get_property --allow-empty $i)" ]] && continue #filter me away
891    new=""
892    case $i in
893      cg.inverse.mpi.tasks|cg.inverse.parallel.tasks)
894        new="cg.inverse.simulation.tasks";;
895      cg.inverse.gromacs.mdrun.bin|cg.inverse.espresso.bin)
896        new="${i/bin/command}";;
897      cg.inverse.scriptdir)
898        new="${i/dir/path}";;
899      cg.inverse.gromacs.grompp.index)
900        new="${i/.grompp}";;
901      cg.inverse.gromacs.grompp.topol)
902        new="cg.inverse.gromacs.topol_in";;
903      cg.inverse.gromacs.g_rdf.topol)
904        new="${i/g_}";;
905      cg.inverse.gromacs.topol_out)
906        new="${i/_out}";;
907      cg.inverse.gromacs.traj_type)
908        new="";;
909      cg.inverse.convergence_check)
910	new="${i}.type";;
911      cg.inverse.convergence_check_options.limit)
912        new="cg.inverse.convergence_check.limit";;
913      cg.non-bonded.inverse.imc.reg)
914        new="cg.inverse.imc.<group>.reg";;
915    esac
916    [[ -n $new ]] && new="has been renamed to $new" || new="has been removed"
917    die "${FUNCNAME[0]}: The xml option $i $new\nPlease remove the obsolete options from the xmlfile"
918  done
919}
920export -f check_for_obsolete_xml_options
921
922command_not_found_handle() { #print and error message if a command or a function was not found
923  die "Command/function $1 not found (when calling from csg_call you might need to add --simprog option or set cg.inverse.program in the xml file)"
924}
925export -f command_not_found_handle
926
927#in bash4 this is not needed, but for older bash we add add a failback from most important simulation functions
928for i in simulation_finish checkpoint_exist get_simulation_setting; do
929  eval $i\(\) { command_not_found_handle $i\; }
930  eval export -f $i
931done
932unset i
933
934