<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
Step1:  Adding dynamic inputs
Let's update our previous input file with some dynamic behavior. Here we replace a couple of the variables, AMP and WID with placeholders. We'll also rename it with a .tpl extension so we're not confused out its purpose.
Step3:  Smarter wrapper templates
Our previous wrapper script was as simple as it gets, blindly calling any command it was given. Here we will add some logic restrict it to only run our funwave application with required variables, print out our runtime variables, and properly exit if anything goes wrong.
Step4:  Using Agave commands, we make a directory on the storage server an deploy our wrapper file there.
Step6:  All agave applications require a test file. The test file is a free form text file which allows you to specify what resources you might need to test your application.
Step8:  More descriptive apps
We are recycling our previous app description here with a few changes. First, we are updating the app id so a new app will be created. Second we have added three new parameters.  
compress_output is a boolean parameter that tells the wrapper whether to compress the output folder after running.   
AMP is a numeric parameter specifying the wave amplitude. Notice the validator requires this to be a decimal value. 
WID is a numeric parameter specifying the width. Notice the validator requires this to be a decimal value.  
We have also removed the data file input from the previous app description. This is because we uploaded the input template with our app description, so it will be copied in and dynamically crated based on the runtime variable values every time the app is run.
Step11:  Running Jobs
We have registered a new Agave app to run our funwave code. Let's update our job definition to test it out. For fun, let's post our notifications to Slack. If you do not have access to a Slack channel where you can create an incoming webhook, use the previous requestbin url from the job request file below.
Step13:  Because the setvar() command can evalute $() style bash shell substitutions, we will use it to submit our job. This will capture the output of the submit command, and allow us to parse it for the JOB_ID. We'll use the JOB_ID in several subsequent steps.
Step14:  <h2>Job Monitoring and Output</h2>
While the job is running, the requestbin you registered will receive webhooks from Agave every time a job event occurs. To monitor this in real time, evaluate the next cell an visit the printed url in your browser
Step15:  Of course, you can also monitor the job status by polling. Note that the notifications you receive via email and webhook are less wasteful of resources. However, we show you this for completeness.
Step16:  The jobs-history command provides you a record of the steps of what your job did. If your job fails for some reason, this is your best diagnostic.
Step17:  This command shows you the job id's and status of the last 5 jobs you ran.
Step18:  This next command provides you with a list of all the files generated by your job. You can use it to figure out which files you want to retrieve with jobs-output-get.
Step19:  Retrieve the standard output.
Step20:  Check our resolved wrapper template to verify the variables were replaced.
Step21:  Check the input.txt file resolved from the input template with our runtime values
Step22:  Retrieve the standard error output.
Step23:  In this next example, we run a job using an application created by the person sitting next to you. Simply edit the value for AGAVE_FRIEND
<ASSISTANT_TASK:>
Python Code:
writefile("funwave-input.tpl",
!INPUT FILE FOR FUNWAVE_TVD
  ! NOTE: all input parameter are capital sensitive
  ! --------------------TITLE-------------------------------------
  ! title only for log file
TITLE = VESSEL
  ! -------------------HOT START---------------------------------
HOT_START = F
FileNumber_HOTSTART = 1
  ! -------------------PARALLEL INFO-----------------------------
  ! 
  !    PX,PY - processor numbers in X and Y
  !    NOTE: make sure consistency with mpirun -np n (px*py)
  !    
PX = 2
PY = 1
  ! --------------------DEPTH-------------------------------------
  ! Depth types, DEPTH_TYPE=DATA: from depth file
  !              DEPTH_TYPE=FLAT: idealized flat, need depth_flat
  !              DEPTH_TYPE=SLOPE: idealized slope, 
  !                                 need slope,SLP starting point, Xslp
  !                                 and depth_flat
DEPTH_TYPE = FLAT
DEPTH_FLAT = 10.0
  ! -------------------PRINT---------------------------------
  ! PRINT*,
  ! result folder
RESULT_FOLDER = output/
  ! ------------------DIMENSION-----------------------------
  ! global grid dimension
Mglob = 500
Nglob = 100
  ! ----------------- TIME----------------------------------
  ! time: total computational time/ plot time / screen interval 
  ! all in seconds
TOTAL_TIME = 4.0
PLOT_INTV = 1.0
PLOT_INTV_STATION = 50000.0
SCREEN_INTV = 1.0
HOTSTART_INTV = 360000000000.0
WAVEMAKER = INI_GAU
AMP = %AMP%
Xc = 250.0
Yc = 50.0
WID = %WID%
  ! -----------------GRID----------------------------------
  ! if use spherical grid, in decimal degrees
  ! cartesian grid sizes
DX = 1.0
DY = 1.0
  ! ----------------SHIP WAKES ----------------------------
VESSEL_FOLDER = ./
NumVessel = 2
  ! -----------------OUTPUT-----------------------------
ETA = T
U = T
V = T
)
Explanation: Adding dynamic inputs
Let's update our previous input file with some dynamic behavior. Here we replace a couple of the variables, AMP and WID with placeholders. We'll also rename it with a .tpl extension so we're not confused out its purpose.
End of explanation
writefile("funwave-wrapper.txt",
#!/bin/bash
# This is a generic wrapper script for checking the runtime environment
# of a job and verifying the runtime template variable values
set +x
date
# print out the agave runtiem variables
echo "##################################################"
echo "# Agave Job Runtime Variables "
echo "##################################################"
echo "\n"
echo 'AGAVE_JOB_NAME="\${AGAVE_JOB_NAME}"'
echo 'AGAVE_JOB_ID="\${AGAVE_JOB_ID}"'
echo 'AGAVE_JOB_APP_ID="\${AGAVE_JOB_APP_ID}"'
echo 'AGAVE_JOB_EXECUTION_SYSTEM="\${AGAVE_JOB_EXECUTION_SYSTEM}"'
echo 'AGAVE_JOB_BATCH_QUEUE="\${AGAVE_JOB_BATCH_QUEUE}"'
echo 'AGAVE_JOB_SUBMIT_TIME="\${AGAVE_JOB_SUBMIT_TIME}"'
echo 'AGAVE_JOB_ARCHIVE_SYSTEM="\${AGAVE_JOB_ARCHIVE_SYSTEM}"'
echo 'AGAVE_JOB_ARCHIVE_PATH="\${AGAVE_JOB_ARCHIVE_PATH}"'
echo 'AGAVE_JOB_NODE_COUNT="\${AGAVE_JOB_NODE_COUNT}"'
echo 'AGAVE_JOB_PROCESSORS_PER_NODE="\${AGAVE_JOB_PROCESSORS_PER_NODE}"'
echo 'AGAVE_JOB_MEMORY_PER_NODE="\${AGAVE_JOB_MEMORY_PER_NODE}"'
echo 'AGAVE_JOB_ARCHIVE_URL="\${AGAVE_JOB_ARCHIVE_URL}"'
echo 'AGAVE_JOB_OWNER="\${AGAVE_JOB_OWNER}"'
echo 'AGAVE_JOB_TENANT="\${AGAVE_JOB_TENANT}"'
echo 'AGAVE_JOB_ARCHIVE="\${AGAVE_JOB_ARCHIVE}"'
echo 'AGAVE_JOB_MAX_RUNTIME="\${AGAVE_JOB_MAX_RUNTIME}"'
echo 'AGAVE_JOB_MAX_RUNTIME_SECONDS="\${AGAVE_JOB_MAX_RUNTIME_SECONDS}"'
echo 'AGAVE_JOB_MAX_RUNTIME_MILLISECONDS="\${AGAVE_JOB_MAX_RUNTIME_MILLISECONDS}"'
echo 'AGAVE_BASE_URL="\${AGAVE_BASE_URL}"'
echo 'AGAVE_JOB_ARCHIVE="\${AGAVE_JOB_ARCHIVE}"'
echo 'AGAVE_CACHE_DIR="\${AGAVE_CACHE_DIR}"'
echo 'AGAVE_JOB_ACCESS_TOKEN="\${AGAVE_JOB_ACCESS_TOKEN}"'
echo 'AGAVE_JOB_REFRESH_TOKEN="\${AGAVE_JOB_REFRESH_TOKEN}"'
echo 'AGAVE_JOB_PACKAGE_OUTPUT="\${AGAVE_JOB_PACKAGE_OUTPUT}"'
echo 'AGAVE_JOB_COMPRESS_OUTPUT="\${AGAVE_JOB_COMPRESS_OUTPUT}"'
echo "##################################################"
echo "# Job Runtime Environment "
echo "##################################################"
echo "\n"
LD_LIBRARY_PATH=/usr/local/lib 
# print environment
env
# copy to file for usage later on
env > ./environment.out
# resolve our job request parameters in funwave's input file
cp -f funwave-input.tpl  input.txt
sed -i 's#%WID%#\${WID}#' input.txt
sed -i 's#%AMP%#\${AMP}#' input.txt
# run our funwave command
mpirun -np 2 /home/jovyan/FUNWAVE-TVD/src/funwave_vessel 
# if the job request set the parameter compress_ouput to true
# the folder will be compressed
if [[ -z "\${compress_output}" ]]; then 
    tar czf output.tgz output
fi
)
Explanation: Smarter wrapper templates
Our previous wrapper script was as simple as it gets, blindly calling any command it was given. Here we will add some logic restrict it to only run our funwave application with required variables, print out our runtime variables, and properly exit if anything goes wrong.
End of explanation
!files-mkdir -S ${AGAVE_STORAGE_SYSTEM_ID} -N funwave-${AGAVE_APP_DEPLOYMENT_PATH}
!files-upload -F funwave-wrapper.txt -S ${AGAVE_STORAGE_SYSTEM_ID} funwave-${AGAVE_APP_DEPLOYMENT_PATH}/
!files-upload -F funwave-input.tpl -S ${AGAVE_STORAGE_SYSTEM_ID} funwave-${AGAVE_APP_DEPLOYMENT_PATH}/
Explanation: Using Agave commands, we make a directory on the storage server an deploy our wrapper file there.
End of explanation
writefile("funwave-test.txt",
compress_output=1
AMP=2.0
WID=24.0
funwave-wrapper.txt
)
!files-mkdir -S ${AGAVE_STORAGE_SYSTEM_ID} -N funwave-${AGAVE_APP_DEPLOYMENT_PATH}
!files-upload -F funwave-test.txt -S ${AGAVE_STORAGE_SYSTEM_ID} funwave-${AGAVE_APP_DEPLOYMENT_PATH}/
Explanation: All agave applications require a test file. The test file is a free form text file which allows you to specify what resources you might need to test your application.
End of explanation
writefile("funwave-app.txt",
{  
   "name":"${AGAVE_USERNAME}-${MACHINE_NAME}-funwave",
   "version":"1.0",
   "label":"Runs a command",
   "shortDescription":"Runs a command",
   "longDescription":"",
   "deploymentSystem":"${AGAVE_STORAGE_SYSTEM_ID}",
   "deploymentPath":"funwave-${AGAVE_APP_DEPLOYMENT_PATH}",
   "templatePath":"funwave-wrapper.txt",
   "testPath":"funwave-test.txt",
   "executionSystem":"${AGAVE_EXECUTION_SYSTEM_ID}",
   "executionType":"CLI",
   "parallelism":"SERIAL",
   "modules":[],
   "inputs":[],
   "parameters":[{
     "id" : "compress_output",
     "value" : {
       "visible":true,
       "required":true,
       "type":"bool",
       "order":0,
       "enquote":false,
       "default":true
     },
     "details":{
         "label": "Compress output folder",
         "description": "If true, output will be packed and compressed",
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     },
     "semantics":{
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     }
   },
   {
     "id" : "AMP",
     "value" : {
       "visible":true,
       "required":true,
       "type":"string",
       "order":0,
       "enquote":false,
       "default":"3.0",
       "validator": "\\\\d+\\\\.\\\\d+"
     },
     "details":{
         "label": "Wave amplitude",
         "description": "Wave amplitude expressed as a decimal value",
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     },
     "semantics":{
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     }
   },
   {
     "id" : "WID",
     "value" : {
       "visible":true,
       "required":true,
       "type":"string",
       "order":0,
       "enquote":false,
       "default":"25.0",
       "validator": "\\\\d+\\\\.\\\\d+"
     },
     "details":{
         "label": "Width",
         "description": "Width expressed as a decimal value",
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     },
     "semantics":{
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     }
   }],
   "outputs":[]
}
)
!apps-addupdate -F funwave-app.txt
Explanation: More descriptive apps
We are recycling our previous app description here with a few changes. First, we are updating the app id so a new app will be created. Second we have added three new parameters.  
compress_output is a boolean parameter that tells the wrapper whether to compress the output folder after running.   
AMP is a numeric parameter specifying the wave amplitude. Notice the validator requires this to be a decimal value. 
WID is a numeric parameter specifying the width. Notice the validator requires this to be a decimal value.  
We have also removed the data file input from the previous app description. This is because we uploaded the input template with our app description, so it will be copied in and dynamically crated based on the runtime variable values every time the app is run.
End of explanation
setvar(
EVENT=*
WEBHOOK_URL=https://hooks.slack.com/services/
WEBHOOK_URL=${REQUESTBIN_URL}?name=\\\${JOB_NAME}&event=\${EVENT}&jobid=\\\${JOB_ID}
)
writefile("funwave-job.txt",
 {
   "name":"funwave-1",
   "appId": "${AGAVE_USERNAME}-${MACHINE_NAME}-funwave-1.0",
   "executionSystem": "${AGAVE_EXECUTION_SYSTEM_ID}",
   "maxRunTime":"00:10:00",
   "archive": false,
   "notifications": [
    {
      "url":"${WEBHOOK_URL}",
      "event":"*",
      "persistent":"true"
    }
   ],
   "parameters": {
     "compress_output":true,
     "AMP":"4.0",
     "WID":"20.0"
   }
 }
)
Explanation: Running Jobs
We have registered a new Agave app to run our funwave code. Let's update our job definition to test it out. For fun, let's post our notifications to Slack. If you do not have access to a Slack channel where you can create an incoming webhook, use the previous requestbin url from the job request file below.
End of explanation
setvar(
# Capture the output of the job submit command
OUTPUT=$(jobs-submit -F funwave-job.txt)
# Parse out the job id from the output
JOB_ID=$(echo $OUTPUT | cut -d' ' -f4)
)
Explanation: Because the setvar() command can evalute $() style bash shell substitutions, we will use it to submit our job. This will capture the output of the submit command, and allow us to parse it for the JOB_ID. We'll use the JOB_ID in several subsequent steps.
End of explanation
!echo ${REQUESTBIN_URL}?inspect
Explanation: <h2>Job Monitoring and Output</h2>
While the job is running, the requestbin you registered will receive webhooks from Agave every time a job event occurs. To monitor this in real time, evaluate the next cell an visit the printed url in your browser:
End of explanation
for iter in range(20):
    setvar("STAT=$(jobs-status $JOB_ID)")
    stat = os.environ["STAT"]
    sleep(5.0)
    if stat == "FINISHED" or stat == "FAILED":
        break
Explanation: Of course, you can also monitor the job status by polling. Note that the notifications you receive via email and webhook are less wasteful of resources. However, we show you this for completeness.
End of explanation
!echo jobs-history ${JOB_ID}
!jobs-history ${JOB_ID}
Explanation: The jobs-history command provides you a record of the steps of what your job did. If your job fails for some reason, this is your best diagnostic.
End of explanation
!jobs-list -l 5
Explanation: This command shows you the job id's and status of the last 5 jobs you ran.
End of explanation
!jobs-output-list --rich --filter=type,length,name ${JOB_ID}
Explanation: This next command provides you with a list of all the files generated by your job. You can use it to figure out which files you want to retrieve with jobs-output-get.
End of explanation
!jobs-output-get ${JOB_ID} funwave-1.out
!cat funwave-1.out
Explanation: Retrieve the standard output.
End of explanation
!jobs-output-get ${JOB_ID} funwave-1.ipcexe
!cat funwave-1.ipcexe
Explanation: Check our resolved wrapper template to verify the variables were replaced.
End of explanation
!jobs-output-get ${JOB_ID} input.txt
!cat input.txt
Explanation: Check the input.txt file resolved from the input template with our runtime values
End of explanation
!jobs-output-get ${JOB_ID} funwave-1.err
!cat funwave-1.err
Explanation: Retrieve the standard error output.
End of explanation
setvar("AGAVE_FRIEND=training001")
!systems-roles-list ${AGAVE_EXECUTION_SYSTEM_ID}
!systems-roles-addupdate -u ${AGAVE_FRIEND} -r PUBLISHER ${AGAVE_EXECUTION_SYSTEM_ID}
!systems-roles-list ${AGAVE_EXECUTION_SYSTEM_ID}
Explanation: In this next example, we run a job using an application created by the person sitting next to you. Simply edit the value for AGAVE_FRIEND
End of explanation
<END_TASK>