#!/usr/bin/env python
# Cantilever simulator

import sys
import math
import datetime

requiredParams = set(("length",
        "width",
        "thickness",
        "youngs_modulus",
        "horizontal_load",
        "vertical_load",
        "density"))

outputHeader = """
     _____                 ___          _   __      __  _                   __
    / ___/____ _____  ____/ (_)___ _   / | / /___ _/ /_(_)___  ____  ____ _/ /
    \__ \/ __ `/ __ \/ __  / / __ `/  /  |/ / __ `/ __/ / __ \/ __ \/ __ `/ / 
   ___/ / /_/ / / / / /_/ / / /_/ /  / /|  / /_/ / /_/ / /_/ / / / / /_/ / /  
  /____/\__,_/_/ /_/\__,_/_/\__,_/  /_/ |_/\__,_/\__/_/\____/_/ /_/\__,_/_/   
                                                                              
           __          __                     __             _          
          / /   ____ _/ /_  ____  _________ _/ /_____  _____(_)__  _____
         / /   / __ `/ __ \/ __ \/ ___/ __ `/ __/ __ \/ ___/ / _ \/ ___/
        / /___/ /_/ / /_/ / /_/ / /  / /_/ / /_/ /_/ / /  / /  __(__  ) 
       /_____/\__,_/_.___/\____/_/   \__,_/\__/\____/_/  /_/\___/____/
-------------------------------------------------------------------------------- 
                      
                      Cantilever Physics, Release 12.1.4
                             All Rights Reserved
       This software released WITHOUT WARRANTY. User assumes ALL RISKS!

--------------------------------------------------------------------------------"""

def abort(message):
    """Print message to STDERR and quit with returncode 1"""
    sys.stdout.flush()
    print >> sys.stderr, "\n", message
    sys.exit(1)
# Write out header and invocation time
print outputHeader
print "Time at invocation:", str(datetime.datetime.now())
# Get user's input filename from command line args and read in
if len(sys.argv) != 2:
    abort("ERROR: Expected (only) name of input file on command " \
            "line.\nSyntax:   %s <filename>\n" % sys.argv[0])
try:
    print "Attempting to read input file................................................",
    with open(sys.argv[1],"r") as ifp:
        rawInput = ifp.readlines()
except IOError:
    abort("ERROR: Unable to open %s for reading." % sys.argv[1])
print "OK"
# Parse the input data
params = {} # will hold user's parameters, keyword:value 
errors = [] # Append parse errors to this list
for i, line in enumerate(rawInput):
    line = line.strip()
    # remove comment, if present
    try:
        commentIndex = line.index('#')
        line = line[:commentIndex]
    except ValueError:
        pass
    if not line: # line is empty
        continue
    # break line into tokens. Must result in two, non-zero-length strings
    tokens = [t.strip() for t in line.split('=')]
    if len(tokens) != 2 or not tokens[0] or not tokens[1]:
        errors.append("Line %3d: Syntax error in input file" % (i + 1,))
        continue
    key, value = tokens
    # Verify that keyword is one of the required parameters
    if key not in requiredParams:
        errors.append("Line %3d: Keyword '%s' not recognized." % (i+1, key))
        continue
    try:
        value = float(value)
    except ValueError:
        errors.append("Line %3d: Conversion of '%s' to float failed." %(i+1, value))
        continue
    # Do not permit repeated keywords
    if params.has_key(key):
        errors.append("Line %3d: Keyword '%s' appears more than once time." %(i+1, key))
        continue
    # All tests pass. Store keyword:value pair.
    params[key] = value

# Confirm that all required parameters were found
for k in requiredParams:
    try:
        params[k]
    except KeyError:
        errors.append("Required keyword '%s' not found." % k)

# Parsing complete. Output input file for reference, then list of errors
print "Echo user input file %s " % sys.argv[1]
print "--------------------------------------------------------------------------------"
for i, line in enumerate(rawInput):
    print "%3d: %s" %(i+1,line),
print "--------------------------------------------------------------------------------"
print "Parsing input file, checking for errors......................................",
if errors:
    print "\n",
    errors = "The following errors were encountered while parsing the " + \
            "input file:\n --" + "\n --".join(errors)
    abort(errors)
print "OK"

# Confirm that thickness, width, young's modulus, and length have physical values
print "Confirming feasability of inputs.............................................",
for k in ("length", "thickness", "width", "youngs_modulus","density","length"):
    if params[k] <= 0.0:
        abort("ERROR: Keyword '%s' must be > 0.0" % k)
print "OK"
print "Initializing simulation...................................................... OK"
print "Reticulating splines......................................................... OK"
print "Sequencing particles......................................................... OK"
print "Splatting transforms......................................................... OK"
print "Beginning simulation............................................................"
print "................................................................................"
print ".................Running........................................................"
print "..............................................Running..........................."
print "...............................Patience........................................."
print ".......................................................Almost there............."
print ".........Almost there..................................................... DONE!"
print """                          ____                  ____      
                         / __ \___  _______  __/ / /______
                        / /_/ / _ \/ ___/ / / / / __/ ___/
                       / _, _/  __(__  ) /_/ / / /_(__  ) 
                      /_/ |_|\___/____/\__,_/_/\__/____/ """
# Compute QoIs
area = params["thickness"] * params["width"] 
mass = area * params["density"] * params["length"] / 12**3.0
stress = 600.0/(area*params["thickness"])*params["vertical_load"] + \
        600.0/(area*params["width"])*params["horizontal_load"]
displacement = 4.0*params["length"]**3.0/(area*params["youngs_modulus"]) * \
        math.sqrt(
           (params["vertical_load"]/params["thickness"] ** 2.0)**2.0 + 
           (params["horizontal_load"]/params["width"] ** 2.0)**2.0
          )

# Output all results
print "--------------------------------------------------------------------------------"
print "Output Section: MASS"
print "Based on user inputs, estimated cantilever beam mass is:"
print "%30.8e (lb)" % mass
print "--------------------------------------------------------------------------------"
print "Output Section: STRESS"
print "Based on user inputs, estimated cantilever beam stress is:"
print "%30.8e (lb/in^2)" % stress
print "--------------------------------------------------------------------------------"
print "Output Section: DISPLACEMENT"
print "Based on user inputs, estimated cantilever beam displacement is:"
print "%30.8e (in)" % displacement
print "--------------------------------------------------------------------------------"
print "END OF RESULTS, Shutdown beginning"
print "Removing all temporary files................................................. OK"
print "Deallocating memory.......................................................... OK"
print "Deleting satellite telemetry................................................. OK"
print "DONE"

