Index: /issm/trunk-jpl/src/m/classes/clusters/cloud.m
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/cloud.m	(revision 26343)
+++ /issm/trunk-jpl/src/m/classes/clusters/cloud.m	(revision 26344)
@@ -20,6 +20,6 @@
 
 			%initialize cluster using user settings if provided
-			if (exist('cloud_settings')==2), 
-				eval('cloud_settings'); 
+			if (exist('cloud_settings')==2),
+				cloud_settings;
 			end
 
@@ -29,5 +29,5 @@
 		%}}}
 		function disp(cluster) % {{{
-			%  display the object
+			%display the object
 			disp(sprintf('class ''%s'' object ''%s'' = ',class(cluster),inputname(1)));
 			disp(sprintf('    name: %s',cluster.name));
@@ -53,13 +53,7 @@
 			fid=fopen([modelname '.queue'],'w');
 			fprintf(fid,'#!/bin/bash\n');
-			if cluster.interactive
-				fprintf(fid,'source %s%s\n',cluster.codepath,'/../etc/environment.sh');
-				fprintf(fid,'cd %s\n',[cluster.executionpath '/' dirname]);
-				fprintf(fid,'mpiexec -np %i -f /home/mpich2.hosts %s/issm.exe %s %s %s 2> %s.errlog > /dev/stdout | tee %s.outlog ',cluster.np,cluster.codepath,solution,[cluster.executionpath '/' dirname],modelname,modelname,modelname);
-			else
-				fprintf(fid,'source %s%s\n',cluster.codepath,'/../etc/environment.sh');
-				fprintf(fid,'cd %s\n',[cluster.executionpath '/' dirname]);
-				fprintf(fid,'mpiexec -np %i -f /home/mpich2.hosts %s/issm.exe %s %s %s 2> %s.errlog > /dev/stdout | tee %s.outlog ',cluster.np,cluster.codepath,solution,[cluster.executionpath '/' dirname],modelname,modelname,modelname);
-			end
+			fprintf(fid,'source %s%s\n',cluster.codepath,'/../etc/environment.sh');
+			fprintf(fid,'cd %s\n',[cluster.executionpath '/' dirname]);
+			fprintf(fid,'mpiexec -np %i -f /home/mpich2.hosts %s/issm.exe %s %s/%s %s 2> %s.errlog > /dev/stdout | tee %s.outlog',cluster.np,cluster.codepath,solution,cluster.executionpath,dirname,modelname,modelname,modelname);
 		end
 		%}}}
@@ -88,13 +82,13 @@
 				else
 					launchcommand=['cd ' cluster.executionpath ' && rm -rf ./' dirname ' && mkdir ' dirname ...
-						' && cd ' dirname ' && mv ../' dirname '.tar.gz ./ && tar -zxf ' dirname '.tar.gz '];
+						' && cd ' dirname ' && mv ../' dirname '.tar.gz ./ && tar -zxf ' dirname '.tar.gz'];
 				end
 			else
 				disp('launching solution sequence on remote cluster');
 				if ~isempty(restart)
-					launchcommand=['cd ' cluster.executionpath ' && cd ' dirname ' && qsub  ' modelname '.queue '];
+					launchcommand=['cd ' cluster.executionpath ' && cd ' dirname ' && qsub ' modelname '.queue'];
 				else
 					launchcommand=['cd ' cluster.executionpath ' && rm -rf ./' dirname ' && mkdir ' dirname ...
-						' && cd ' dirname ' && mv ../' dirname '.tar.gz ./ && tar -zxf ' dirname '.tar.gz  && qsub  ' modelname '.queue '];
+						' && cd ' dirname ' && mv ../' dirname '.tar.gz ./ && tar -zxf ' dirname '.tar.gz && qsub ' modelname '.queue'];
 				end
 			end
Index: /issm/trunk-jpl/src/m/classes/clusters/cloud.py
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/cloud.py	(revision 26344)
+++ /issm/trunk-jpl/src/m/classes/clusters/cloud.py	(revision 26344)
@@ -0,0 +1,101 @@
+import subprocess
+
+try:
+    from cloud_settings import cloud_settings
+except ImportError:
+    print('You need cloud_settings.py to proceed, check presence and sys.path')
+
+class cloud(object):
+    """CLOUD cluster class definition
+
+    Usage:
+        cluster = cloud('name', 'astrid', 'np', 3)
+        cluster = cloud('name', oshostname(), 'np', 3, 'login', 'username')
+    """
+
+    def __init__(self, *args):  # {{{
+        self.name = ''
+        self.login = ''
+        self.np = 1
+        self.codepath = ''
+        self.executionpath = ''
+        self.interactive = 0
+
+        # Initialize cluster using user settings if provided
+        try:
+            self = cloud_settings(self)
+        except NameError:
+            print('cloud_settings.py not found, using default settings')
+
+        # OK get other fields
+        options = pairoptions(*args)
+        self = options.AssignObjectFields(self)
+    # }}}
+
+    def __repr__(self):  # {{{
+        # Display the object
+        s = 'class \'{}\' object \'{}\' = \n'.format(type(self), 'self')
+        s += '    name: {}\n'.format(self.name)
+        s += '    login: {}\n'.format(self.login)
+        s += '    np: {}\n'.format(self.np)
+        s += '    codepath: {}\n'.format(self.codepath)
+        s += '    executionpath: {}\n'.format(self.executionpath)
+        s += '    interactive: {}\n'.format(self.interactive)
+        return s
+    # }}}
+
+    def checkconsistency(self, md, solution, analyses):  # {{{
+        if self.np < 1:
+            md = md.checkmessage('number of processors should be at least 1')
+
+        if np.isnan(self.np):
+            md = md.checkmessage('number of processors should not be NaN!')
+
+        return self
+    # }}}
+
+    def BuildQueueScript(self, dirname, modelname, solution, io_gather, isvalgrind, isgprof, isdakota, isoceancoupling):  # {{{
+        # Write queuing script
+        fid = open(modelname + '.queue', 'w')
+
+        fid.write('#/bin/bash\n')
+        fid.write('source {}{}\n'.format(self.codepath, '/../etc/environment.sh'))
+        fid.write('cd {}/{}\n'.format(self.executionpath, dirname))
+        fid.write('mpiexec -np {} -f /home/mpich2.hosts {}/issm.exe {} {}/{} {} 2> {}.errlog > /dev/stdout | tee {}.outlog\n'.format(self.np, self.codepath, solution, self.executionpath, dirname, modelname, modelname, modelname))
+    # }}}
+
+    def UploadQueueJob(self, modelname, dirname, filelist):  # {{{
+        # Compress the files into one zip
+        compressstring = 'tar -zcf {}.tar.gz'.format(dirname)
+        for file in filelist:
+            compressstring += ' {}'.format(file)
+        subprocess.call(compressstring, shell=True)
+
+        if isempty(self.login):
+            raise Exception('cloud BuildQueueScript: login should be supplied!')
+
+        print('uploading input file and queueing script')
+        issmstscpout(self.name, self.executionpath, self.login, '{}.tar.gz'.format(dirname))
+    # }}}
+
+    def LaunchQueueJob(self, modelname, dirname, filelist, restart, batch):  # {{{
+        if self.interactive:
+            print('sending files to remote cluster. once done, please log into cluster and launch job')
+            if not isempty(restart):
+                launchcommand = 'cd {} && cd {}'.format(self.executionpath, dirname)
+            else:
+                launchcommand = 'cd {} && rm -rf ./{} && mkdir {} && cd {} && mv ../{}.tar.gz ./ && tar -zxf {}.tar.gz'.format(self.executionpath, dirname, dirname, dirname, dirname, dirname)
+        else:
+            print('launching solution sequence on remote cluster')
+            if not isempty(restart):
+                launchcommand = 'cd {} && cd {} && qsub {}.queue'.format(self.executionpath, dirname, modelname)
+            else:
+                launchcommand = 'cd {} && rm -rf ./{} && mkdir {} && cd {} && mv ../{}.tar.gz ./ && tar -zxf {}.tar.gz && qsub {}.queue'.format(self.executionpath, dirname, dirname, dirname, dirname, dirname, modelname)
+        issmstssh(self.name, self.login, launchcommand)
+    # }}}
+
+    def Download(self, dirname, filelist):  # {{{
+        # Copy files from cluster to current directory
+        directory = '{}/{}/'.format(self.executionpath, dirname)
+        issmstscpin(self.name, self.login, directory, filelist)
+    # }}}
Index: /issm/trunk-jpl/src/m/classes/clusters/cyclone.py
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/cyclone.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/classes/clusters/cyclone.py	(revision 26344)
@@ -1,12 +1,14 @@
 import subprocess
-from fielddisplay import fielddisplay
-from pairoptions import pairoptions
-from issmssh import issmssh
-from issmscpin import issmscpin
-from issmscpout import issmscpout
+
 try:
     from cyclone_settings import cyclone_settings
 except ImportError:
     print('You need cyclone_settings.py to proceed, check presence and sys.path')
+from fielddisplay import fielddisplay
+from helpers import *
+from pairoptions import pairoptions
+from issmscpin import issmscpin
+from issmscpout import issmscpout
+from issmssh import issmssh
 
 
@@ -96,5 +98,5 @@
     def LaunchQueueJob(self, modelname, dirname, filelist, restart, batch):  # {{{
         print('launching solution sequence on remote cluster')
-        if restart:
+        if not isempty(restart):
             launchcommand = 'cd %s && cd %s && qsub %s.queue' % (self.executionpath, dirname, modelname)
         else:
Index: /issm/trunk-jpl/src/m/classes/clusters/discover.py
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/discover.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/classes/clusters/discover.py	(revision 26344)
@@ -6,4 +6,5 @@
     print('You need discover_settings.py to proceed, check presence and sys.path')
 from fielddisplay import fielddisplay
+from helpers import *
 from IssmConfig import IssmConfig
 from issmscpin import issmscpin
@@ -11,6 +12,4 @@
 from issmssh import issmssh
 from MatlabFuncs import *
-import math
-import numpy as np
 from pairoptions import pairoptions
 from QueueRequirements import QueueRequirements
@@ -60,5 +59,5 @@
     def __repr__(self):  # {{{
         # Display the object
-        s = 'class pfe object\n'
+        s = 'class discover object\n'
         s += '    name: {}\n'.format(self.name)
         s += '    login: {}\n'.format(self.login)
@@ -87,7 +86,7 @@
 
     def checkconsistency(self, md, solution, analyses):  # {{{
-        queuedict = {'long': [24*60*60, 560],
-                     'allnccs': [12*60*60, 6000],
-                     'debug': [1*60*60, 532]}
+        queuedict = {'long': [24 * 60 * 60, 560],
+                     'allnccs': [12 * 60 * 60, 6000],
+                     'debug': [1 * 60 * 60, 532]}
         QueueRequirements(queuedict, self.queue, self.nprocs(), self.time)
 
@@ -136,5 +135,5 @@
         fid.write('#SBATCH -n {} \n'.format(self.nprocs()))
         fid.write('#SBATCH -N {} \n'.format(self.numnodes))
-        fid.write('#SBATCH -t {:02d}:{:02d}:00 \n'.format(int(math.floor(self.time / 3600)), int(math.floor(self.time % 3600) / 60)))
+        fid.write('#SBATCH -t {:02d}:{:02d}:00 \n'.format(floor(self.time / 3600), floor(self.time % 3600) / 60))
         fid.write('#SBATCH -A {} \n\n'.format(self.grouplist))
         if (self.email.find('@')>-1):
@@ -196,10 +195,10 @@
     def LaunchQueueJob(self, modelname, dirname, filelist, restart, batch):  # {{{
         if self.interactive:
-            if restart:
+            if not isempty(restart):
                 launchcommand = 'cd {}/Interactive{}'.format(self.executionpath, self.interactive)
             else:
                 launchcommand = 'cd {}/Interactive{} && tar -zxf {}.tar.gz'.format(self.executionpath, self.interactive, dirname)
         else:
-            if restart:
+            if not isempty(restart):
                 launchcommand = 'cd {} && cd {} && sbatch {}.queue'.format(self.executionpath, dirname, modelname)
             else:
Index: /issm/trunk-jpl/src/m/classes/clusters/fram.py
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/fram.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/classes/clusters/fram.py	(revision 26344)
@@ -1,15 +1,18 @@
 import subprocess
+
 import numpy as np
+
 from fielddisplay import fielddisplay
-from pairoptions import pairoptions
-from issmssh import issmssh
-from issmscpin import issmscpin
-from issmscpout import issmscpout
-from QueueRequirements import QueueRequirements
-from IssmConfig import IssmConfig
 try:
     from fram_settings import fram_settings
 except ImportError:
     print('You need fram_settings.py to proceed, check presence and sys.path')
+from helpers import *
+from pairoptions import pairoptions
+from IssmConfig import IssmConfig
+from issmscpin import issmscpin
+from issmscpout import issmscpout
+from issmssh import issmssh
+from QueueRequirements import QueueRequirements
 
 
@@ -147,5 +150,5 @@
     def LaunchQueueJob(self, modelname, dirname, filelist, restart, batch):  # {{{
         print('launching solution sequence on remote cluster')
-        if restart:
+        if not isempty(restart):
             launchcommand = 'cd %s && cd %s && sbatch %s.queue' % (self.executionpath, dirname, modelname)
         else:
Index: /issm/trunk-jpl/src/m/classes/clusters/pfe.m
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/pfe.m	(revision 26343)
+++ /issm/trunk-jpl/src/m/classes/clusters/pfe.m	(revision 26344)
@@ -138,5 +138,4 @@
 			if isempty(cluster.executionpath), md = checkmessage(md,'executionpath empty'); end
 			if isempty(cluster.grouplist), md = checkmessage(md,'grouplist empty'); end
-			if ~isempty(cluster.interactive), md = checkmessage(md,'interactive mode not implemented'); end
 
 		end
@@ -163,5 +162,5 @@
 			fprintf(fid,'#PBS -l select=%i:ncpus=%i:model=%s\n',cluster.numnodes,cluster.cpuspernode,cluster.processor);
 			fprintf(fid,'#PBS -l walltime=%i\n',cluster.time*60); %walltime is in seconds.
-			fprintf(fid,'#PBS -q %s \n',cluster.queue);
+			fprintf(fid,'#PBS -q %s\n',cluster.queue);
 			fprintf(fid,'#PBS -W group_list=%s\n',cluster.grouplist);
 			fprintf(fid,'#PBS -m e\n');
@@ -431,5 +430,5 @@
 				else
 					launchcommand=['cd ' cluster.executionpath ' && rm -rf ./' dirname ' && mkdir ' dirname ...
-						' && cd ' dirname ' && mv ../' dirname '.tar.gz ./ && tar -zxf ' dirname '.tar.gz  && qsub ' modelname '.queue '];
+						' && cd ' dirname ' && mv ../' dirname '.tar.gz ./ && tar -zxf ' dirname '.tar.gz && qsub ' modelname '.queue '];
 				end
 			end
Index: /issm/trunk-jpl/src/m/classes/clusters/pfe.py
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/pfe.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/classes/clusters/pfe.py	(revision 26344)
@@ -2,4 +2,5 @@
 
 from fielddisplay import fielddisplay
+from helpers import *
 from IssmConfig import IssmConfig
 from issmscpin import issmscpin
@@ -140,6 +141,4 @@
         if not self.grouplist:
             md = md.checkmessage('grouplist empty')
-        if self.interactive == 1:
-            md = md.checkmessage('interactive mode not implemented')
 
         return self
@@ -182,5 +181,4 @@
 
         fid.close()
-
     # }}}
 
@@ -200,5 +198,5 @@
         # Launch command, to be executed via ssh
         if self.interactive:
-            if restart:
+            if not isempty(restart):
                 launchcommand = 'cd {} /Interactive{}'.format(self.executionpath, self.interactive)
             else:
@@ -208,5 +206,5 @@
                     launchcommand = 'cd {} /Interactive{} && tar -zxf {}.tar.gz'.format(self.executionpath, self.interactive, dirname)
         else:
-            if restart:
+            if not isempty(restart):
                 launchcommand = 'cd {} && cd {} && qsub {}.queue'.format(self.executionpath, dirname, modelname)
             else:
Index: /issm/trunk-jpl/src/m/classes/clusters/saga.py
===================================================================
--- /issm/trunk-jpl/src/m/classes/clusters/saga.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/classes/clusters/saga.py	(revision 26344)
@@ -1,11 +1,13 @@
+import datetime
 import subprocess
+
 from fielddisplay import fielddisplay
-from pairoptions import pairoptions
-from issmssh import issmssh
+from helpers import *
+from IssmConfig import IssmConfig
 from issmscpin import issmscpin
 from issmscpout import issmscpout
+from issmssh import issmssh
+from pairoptions import pairoptions
 from QueueRequirements import QueueRequirements
-from IssmConfig import IssmConfig
-import datetime
 try:
     from saga_settings import saga_settings
@@ -157,5 +159,5 @@
     def LaunchQueueJob(self, modelname, dirname, filelist, restart, batch):  # {{{
         print('launching solution sequence on remote cluster')
-        if restart:
+        if not isempty(restart):
             launchcommand = 'cd %s && cd %s && sbatch %s.queue' % (self.executionpath, dirname, modelname)
         else:
Index: /issm/trunk-jpl/src/m/mesh/planet/gmsh/gmshplanet.py
===================================================================
--- /issm/trunk-jpl/src/m/mesh/planet/gmsh/gmshplanet.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/mesh/planet/gmsh/gmshplanet.py	(revision 26344)
@@ -9,6 +9,5 @@
 
 def gmshplanet(*args):
-    '''
-    GMSHPLANET - mesh generation for a sphere. Very specific code for gmsh from $ISSM_DIR/src/demos/simple_geo/sphere.geo
+    """GMSHPLANET - mesh generation for a sphere. Very specific code for gmsh from $ISSM_DIR/src/demos/simple_geo/sphere.geo
 
     Available options (for more details see ISSM website http://issm.jpl.nasa.gov/):
@@ -18,10 +17,10 @@
     - refinemetric:       mesh quantity to specify resolution
 
-        Returns 'mesh3dsurface' type mesh
+    Returns 'mesh3dsurface' type mesh
 
-        Examples:
-            md.mesh = gmshplanet('radius', 6000, 'resolution', 100);
-            md.mesh = gmshplanet('radius', 6000, 'resolution', 100);
-    '''
+    Examples:
+        md.mesh = gmshplanet('radius', 6000, 'resolution', 100);
+        md.mesh = gmshplanet('radius', 6000, 'resolution', 100);
+    """
 
     # Get Gmsh version
@@ -35,15 +34,15 @@
         raise RuntimeError("gmshplanet: Gmsh major version {} not supported!".format(gmshmajorversion))
 
-    #process options
+    # Process options
     options = pairoptions(*args)
     #options = deleteduplicates(options, 1)
 
-    #recover parameters:
+    # Recover parameters
     radius = options.getfieldvalue('radius') * 1000
     resolution = options.getfieldvalue('resolution') * 1000
 
-    #initialize mesh:
+    # Initialize mesh
     mesh = mesh3dsurface()
-    #create .geo file:  {{{
+    # Create .geo file:  {{{
     fid = open('sphere.geo', 'w')
 
@@ -108,5 +107,5 @@
         metric = options.getfieldvalue('refinemetric')
 
-    #create .pos file with existing mesh and refining metric:  {{{
+        # Create .pos file with existing mesh and refining metric:  {{{
         fid = open('sphere.pos', 'w')
 
@@ -120,5 +119,5 @@
         fid.write('];\n')
         fid.close()
-    # }}}
+        #}}}
 
     # Call gmsh
@@ -133,8 +132,8 @@
         subprocess.call('gmsh -tol 1e-8 -2 -format msh2 sphere.geo', shell=True)
 
-    #import mesh:  {{{
+    # Import mesh  {{{
     fid = open('sphere.msh', 'r')
 
-    #Get Mesh format
+    # Get mesh format
     A = fid.readline().strip()
     if A != '$MeshFormat':
@@ -146,5 +145,5 @@
         raise RuntimeError(['Expecting $EndMeshFormat (', A, ')'])
 
-    #Nodes
+    # Nodes
     A = fid.readline().strip()
     if A != '$Nodes':
@@ -165,5 +164,5 @@
         raise RuntimeError(['Expecting $EndNodes (', A, ')'])
 
-    #Elements
+    # Elements
     A = fid.readline().strip()
     if A != '$Elements':
@@ -186,7 +185,7 @@
     mesh.long = np.arctan2(mesh.y, mesh.x) / np.pi * 180
 
-    #erase files:
+    # Erase files
     subprocess.call('rm -rf sphere.geo sphere.msh sphere.pos', shell=True)
 
-    #return mesh:
+    # Return mesh
     return mesh
Index: /issm/trunk-jpl/src/m/miscellaneous/MatlabFuncs.py
===================================================================
--- /issm/trunk-jpl/src/m/miscellaneous/MatlabFuncs.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/miscellaneous/MatlabFuncs.py	(revision 26344)
@@ -8,5 +8,5 @@
 """
 
-def acosd(X): #{{{
+def acosd(X):  #{{{
     """ function acosd - Inverse cosine in degrees
 
@@ -19,5 +19,5 @@
 #}}}
 
-def asind(X): #{{{
+def asind(X):  #{{{
     """ function asind - Inverse sine in degrees
 
@@ -30,5 +30,5 @@
 #}}}
 
-def atand(X): #{{{
+def atand(X):  #{{{
     """ function atand - Inverse tangent in degrees
 
@@ -42,6 +42,6 @@
 
 
-def atan2d(Y, X): #{{{
-    """ function atan2d - Four-quadrant inverse tangent in degrees
+def atan2d(Y, X):  #{{{
+    """function atan2d - Four-quadrant inverse tangent in degrees
 
     Usage:
@@ -53,5 +53,5 @@
 #}}}
 
-def det(a): #{{{
+def det(a):  #{{{
     if a.shape == (1, ):
         return a[0]
@@ -64,5 +64,13 @@
 #}}}
 
-def find(*args): #{{{
+def error(msg):  #{{{
+    raise Exception(msg)
+#}}}
+
+def etime(t2, t1):  #{{{
+    return t2 - t1
+#}}}
+
+def find(*args):  #{{{
     nargs = len(args)
     if nargs >= 1 or nargs <= 2:
@@ -80,5 +88,11 @@
 #}}}
 
-def heaviside(x): #{{{
+def floor(X):  #{{{
+    import math
+
+    return int(math.floor(X))
+#}}}
+
+def heaviside(x):  #{{{
     import numpy as np
 
@@ -90,5 +104,11 @@
 #}}}
 
-def ismac(): #{{{
+def isfile(fileName):  #{{{
+    import os
+
+    return os.path.exists(fileName)
+#}}}
+
+def ismac():  #{{{
     import platform
 
@@ -99,5 +119,5 @@
 #}}}
 
-def ismember(a, s): #{{{
+def ismember(a, s):  #{{{
     import numpy as np
 
@@ -121,5 +141,11 @@
 #}}}
 
-def ispc(): #{{{
+def isnan(A):  #{{{
+    import numpy as np
+
+    return np.isnan(A)
+#}}}
+
+def ispc():  #{{{
     import platform
 
@@ -130,9 +156,25 @@
 #}}}
 
-def mod(a, m): #{{{
+def isprop(obj, PropertyName):  #{{{
+    return hasattr(obj, PropertyName)
+#}}}
+
+def mod(a, m):  #{{{
     return a % m
 #}}}
 
-def oshostname(): #{{{
+def pause(n):  #{{{
+    import time
+
+    time.sleep(n)
+#}}}
+
+def pwd():  #{{{
+    import os
+
+    return os.getcwd()
+#}}}
+
+def oshostname():  #{{{
     import socket
 
@@ -140,5 +182,9 @@
 #}}}
 
-def sparse(ivec, jvec, svec, m=0, n=0, nzmax=0): #{{{
+def rem(a, b):  #{{{
+    return a % b
+#}}}
+
+def sparse(ivec, jvec, svec, m=0, n=0, nzmax=0):  #{{{
     import numpy as np
 
@@ -156,5 +202,5 @@
 #}}}
 
-def strcmp(s1, s2): #{{{
+def strcmp(s1, s2):  #{{{
     if s1 == s2:
         return True
@@ -163,5 +209,5 @@
 #}}}
 
-def strcmpi(s1, s2): #{{{
+def strcmpi(s1, s2):  #{{{
     if s1.lower() == s2.lower():
         return True
@@ -170,5 +216,5 @@
 #}}}
 
-def strjoin(*args): #{{{
+def strjoin(*args):  #{{{
     nargs = len(args)
     if nargs >= 1 or nargs <= 2:
@@ -181,5 +227,5 @@
 #}}}
 
-def strncmp(s1, s2, n): #{{{
+def strncmp(s1, s2, n):  #{{{
     if s1[0:n] == s2[0:n]:
         return True
@@ -188,5 +234,5 @@
 #}}}
 
-def strncmpi(s1, s2, n): #{{{
+def strncmpi(s1, s2, n):  #{{{
     if s1.lower()[0:n] == s2.lower()[0:n]:
         return True
Index: /issm/trunk-jpl/src/m/solve/solve.py
===================================================================
--- /issm/trunk-jpl/src/m/solve/solve.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/solve/solve.py	(revision 26344)
@@ -106,5 +106,5 @@
         pass  # do nothing
     else:
-        if restart:
+        if not isempty(restart):
             md.private.runtimename = restart
         else:
Index: /issm/trunk-jpl/src/m/solve/waitonlock.m
===================================================================
--- /issm/trunk-jpl/src/m/solve/waitonlock.m	(revision 26343)
+++ /issm/trunk-jpl/src/m/solve/waitonlock.m	(revision 26344)
@@ -37,5 +37,5 @@
 
 %initialize time and file presence test flag
-time=0; ispresent=0; time0=clock;
+elapsedtime=0; ispresent=0; starttime=clock;
 disp(['waiting for ' lockfilename ' hold on... (Ctrl+C to exit)'])
 
@@ -58,14 +58,14 @@
 
 %loop till file .lock exist or time is up
-while (ispresent==0 & time<timelimit)
+while (ispresent==0 & elapsedtime<timelimit)
 	if strcmpi(oshostname(),cluster.name),
 		pause(1);
 		ispresent=(exist(lockfilename,'file') & exist(logfilename,'file'));
-		time=etime(clock,time0)/60;
+		elapsedtime=etime(clock,starttime)/60;
 	else
 		pause(5);
-		time=etime(clock,time0);
-		fprintf('\rchecking for job completion (time: %i min %i sec)      ',floor(time/60),floor(rem(time,60)));
-		time=time/60; %converts time from sec to min
+		elapsedtime=etime(clock,starttime);
+		fprintf('\rchecking for job completion (time: %i min %i sec)      ',floor(elapsedtime/60),floor(rem(elapsedtime,60)));
+		elapsedtime=elapsedtime/60; %converts time from sec to min
 		ispresent=~system(command);
 		if ispresent, fprintf('\n'); end
@@ -74,5 +74,5 @@
 
 %build output
-if (time>timelimit),
+if (elapsedtime>timelimit),
 	disp('Time limit exceeded. Increase md.settings.waitonlock');
 	disp('The results must be loaded manually with md=loadresultsfromcluster(md).');
Index: /issm/trunk-jpl/src/m/solve/waitonlock.py
===================================================================
--- /issm/trunk-jpl/src/m/solve/waitonlock.py	(revision 26343)
+++ /issm/trunk-jpl/src/m/solve/waitonlock.py	(revision 26344)
@@ -1,3 +1,4 @@
-import os
+import subprocess
+import sys
 import time
 from MatlabFuncs import *
@@ -6,6 +7,9 @@
     """WAITONLOCK - wait for a file
 
-    This routine will return when a file named 'filename' is written to disk.
-    If the time limit given in input is exceeded, return 0
+    This routine will return when a file named 'lockfilename' is written to 
+    disk. Also check for outlog file because it might be written several 
+    seconds after the lock file.
+
+    If the time limit given in input is exceeded, return 0.
 
     Usage:
@@ -13,46 +17,71 @@
     """
 
-    #Get filename (lock file) and options
+    # Get lockfilename (lock file) and options
     executionpath = md.cluster.executionpath
-    cluster = md.cluster.name
     timelimit = md.settings.waitonlock
-    filename = os.path.join(executionpath, md.private.runtimename, md.miscellaneous.name + '.lock')
+    cluster = md.cluster
 
-    #waitonlock will work if the lock is on the same machine only:
-    if not strcmpi(oshostname(), cluster):
+    """
+    NOTE: We check cluster.name against string as cluster classes are not 
+          defined globally and we do not have to import them all
+    """
+    if cluster.name == 'pfe' and cluster.interactive > 1:
+        lockfilename = '{}/Interactive{}/{}.lock'.format(executionpath, cluster.interactive, md.miscellaneous.name)
+        logfilename = '{}/Interactive{}/{}.outlog'.format(executionpath, cluster.interactive, md.miscellaneous.name)
+    elif cluster.name == 'localpfe':
+        lockfilename = '{}/{}.lock'.format(executionpath, md.miscellaneous.name)
+        logfilename = '{}/{}.outlog'.format(executionpath, md.miscellaneous.name)
+    else:
+        lockfilename = '{}/{}/{}.lock'.format(executionpath, md.private.runtimename, md.miscellaneous.name)
+        logfilename = '{}/{}/{}.outlog'.format(executionpath, md.private.runtimename, md.miscellaneous.name)
 
-        print('solution launched on remote cluster. log in to detect job completion.')
-        choice = eval(input('Is the job successfully completed? (y / n) '))
-        if not strcmp(choice, 'y'):
-            print('Results not loaded... exiting')
-            flag = 0
+    # If we are using the generic cluster in interactive mode, job is already complete
+    if (cluster.name == 'generic' and cluster.interactive) or (cluster.name == 'generic_static'):
+        # We are in interactive mode, no need to check for job completion
+        return 1
+
+    # Initialize time and file presence test flag
+    elapsedtime = 0
+    ispresent = 0
+    starttime = time.time()
+    print('waiting for {} hold on... (Ctrl+C to exit)'.format(lockfilename))
+
+    # Prepare command if the job is not running on the local machine
+    if not strcmpi(oshostname(), cluster.name):
+        login = cluster.login
+        port = 0
+        if isprop(cluster, 'port'):
+            port = cluster.port
+        if port:
+            command = 'ssh -l {} -p {} localhost "[ -f {} ] && [ -f {} ]" 2>/dev/null'.format(login, port, lockfilename, logfilename)
+        elif cluster.name == 'cloud':
+            command = '[ -f {} ] && [ -f {} ] 2>/dev/null'.format(lockfilename, logfilename)
+            command = '{} sshmaster {} --user {} \'{}\''.format(starcluster(), cluster.name, cluster.login, command)
         else:
-            flag = 1
+            command = 'ssh -l {} {} "[ -f {} ] && [ -f {} ]" 2>/dev/null'.format(login, cluster.name, lockfilename, logfilename)
 
-    #job is running on the same machine
-    else:
-        if 'interactive' in vars(md.cluster) and md.cluster.interactive:
-            #We are in interactive mode, no need to check for job completion
-            flag = 1
-            return flag
-        #initialize time and file presence test flag
-        etime = 0
-        ispresent = 0
-        print(("waiting for '%s' hold on... (Ctrl + C to exit)" % filename))
+    while not ispresent and elapsedtime < timelimit:
+        if strcmpi(oshostname(), cluster.name):
+            pause(1)
+            ispresent = (isfile(lockfilename) and isfile(logfilename))
+            elapsedtime = etime(time.time(), starttime) / 60
+        else:
+            pause(5)
+            elapsedtime = etime(time.time(), starttime)
+            sys.stdout.write('\rchecking for job completion (time: {} min {} sec)      '.format(floor(elapsedtime / 60), floor(rem(elapsedtime, 60)))) # TODO: After Python 2 is deprecated, we can change this call to print([...], end='')
+            elapsedtime = elapsedtime / 60 # Converts time from sec to min
+            subproc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            outs, errs = subproc.communicate() # NOTE: Need to consume output before checking return code
+            if errs != '':
+                raise Exception("waitonlock: check for existence of files failed: {}".format(errs))
+            ispresent = not subproc.returncode
+            if ispresent:
+                print('')
 
-        #loop till file .lock exist or time is up
-        while ispresent == 0 and etime < timelimit:
-            ispresent = os.path.exists(filename)
-            time.sleep(1)
-            etime += 1 / 60
+    # Build output
+    if elapsedtime > timelimit:
+        print('Time limit exceeded. Increase md.settings.waitonlock')
+        print('The results must be loaded manually with md = loadresultsfromcluster(md).')
+        raise RuntimeError('waitonlock error message: time limit exceeded.')
 
-        #build output
-        if etime > timelimit:
-            print('Time limit exceeded. Increase md.settings.waitonlock')
-            print('The results must be loaded manually with md = loadresultsfromcluster(md).')
-            raise RuntimeError('waitonlock error message: time limit exceeded.')
-            flag = 0
-        else:
-            flag = 1
-
-    return flag
+    return ispresent
Index: /issm/trunk-jpl/src/wrappers/javascript/Makefile.am
===================================================================
--- /issm/trunk-jpl/src/wrappers/javascript/Makefile.am	(revision 26343)
+++ /issm/trunk-jpl/src/wrappers/javascript/Makefile.am	(revision 26344)
@@ -8,5 +8,5 @@
 
 js_scripts = ${ISSM_DIR}/src/wrappers/BamgMesher/BamgMesher.js \
-             ${ISSM_DIR}/src/wrappers/Triangle/Triangle.js  \
+			 ${ISSM_DIR}/src/wrappers/Triangle/Triangle.js \
 			 ${ISSM_DIR}/src/wrappers/NodeConnectivity/NodeConnectivity.js\
 			 ${ISSM_DIR}/src/wrappers/ContourToMesh/ContourToMesh.js\
