diff --git a/gflex/_version.py b/gflex/_version.py index b3ddbc4..a82b376 100644 --- a/gflex/_version.py +++ b/gflex/_version.py @@ -1 +1 @@ -__version__ = '1.1.1' +__version__ = "1.1.1" diff --git a/gflex/base.py b/gflex/base.py index 765e3ef..cb77363 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -30,1133 +30,1420 @@ class Utility: - """ - Generic utility functions - """ - def configGet(self, vartype, category, name, optional=False, specialReturnMessage=None): """ - Wraps a try / except and a check for self.filename around ConfigParser - as it talks to the configuration file. - Also, checks for existence of configuration file so this won't execute (and fail) - when no configuration file is provided (e.g., running in coupled mode with CSDMS - entirely with getters and setters) + Generic utility functions + """ - vartype can be 'float', 'str' or 'string' (str and string are the same), - or 'int' or 'integer' (also the same). + def configGet( + self, vartype, category, name, optional=False, specialReturnMessage=None + ): + """ + Wraps a try / except and a check for self.filename around ConfigParser + as it talks to the configuration file. + Also, checks for existence of configuration file so this won't execute (and fail) + when no configuration file is provided (e.g., running in coupled mode with CSDMS + entirely with getters and setters) - "Optional" determines whether or not the program will exit if the variable - fails to load. Set it to "True" if you don't want it to exit. In this case, - the variable will be set to "None". Otherwise, it defaults to "False". + vartype can be 'float', 'str' or 'string' (str and string are the same), + or 'int' or 'integer' (also the same). - "specialReturnMessage" is something that you would like to add at the end - of a failure to execute message. By default it does not print. - """ + "Optional" determines whether or not the program will exit if the variable + fails to load. Set it to "True" if you don't want it to exit. In this case, + the variable will be set to "None". Otherwise, it defaults to "False". - try: - if vartype == 'float': - var = self.config.getfloat(category, name) - elif vartype == 'string' or vartype == 'str': - var = self.config.get(category, name) - if var == "" and optional == False: - # but "" is acceptable for boundary conditions - if name[:17] != 'BoundaryCondition': - if self.Quiet != True: - print("An empty input string here is not an acceptable option.") - print(name, "is not optional.") - print("Program crash likely to occur.") - elif vartype == 'integer' or vartype == 'int': - var = self.config.getint(category, name) - elif vartype == 'boolean' or vartype == 'bool': - var = self.config.getboolean(category, name) - else: - print("Please enter 'float', 'string' (or 'str'), 'integer' (or 'int'), or 'boolean (or 'bool') for vartype") - sys.exit() # Won't exit, but will lead to exception - return var - except: - if optional: - # Carry on if the variable is optional - var = None - if self.Verbose or self.Debug: - if self.grass == False: - print("") - print('No value entered for optional parameter "' + name + '"') - print('in category "' + category + '" in configuration file.') - print("No action related to this optional parameter will be taken.") - print("") - else: - print('Problem loading ' + vartype + ' "' + name + '" in category "' + category + '" from configuration file.') - if specialReturnMessage: - print(specialReturnMessage) - sys.exit("Exiting.") - - def readyCoeff(self): - from scipy import sparse - if sparse.issparse(self.coeff_matrix): - pass # Good type - else: - try: - self.coeff_matrix = sparse.dia_matrix(self.coeff_matrix) - except: - sys.exit("Failed to make a sparse array or load a sparse matrix from the input.") - - def greatCircleDistance(self, lat1, long1, lat2, long2, radius): - """ - Returns the great circle distance between two points. - Useful when using the SAS_NG solution in lat/lon coordinates - Modified from http://www.johndcook.com/blog/python_longitude_latitude/ - It should be able to take numpy arrays. - """ + "specialReturnMessage" is something that you would like to add at the end + of a failure to execute message. By default it does not print. + """ - # Convert latitude and longitude to - # spherical coordinates in radians. - degrees_to_radians = np.pi/180.0 + try: + if vartype == "float": + var = self.config.getfloat(category, name) + elif vartype == "string" or vartype == "str": + var = self.config.get(category, name) + if var == "" and optional == False: + # but "" is acceptable for boundary conditions + if name[:17] != "BoundaryCondition": + if self.Quiet != True: + print( + "An empty input string here is not an acceptable option." + ) + print(name, "is not optional.") + print("Program crash likely to occur.") + elif vartype == "integer" or vartype == "int": + var = self.config.getint(category, name) + elif vartype == "boolean" or vartype == "bool": + var = self.config.getboolean(category, name) + else: + print( + "Please enter 'float', 'string' (or 'str'), 'integer' (or 'int'), or 'boolean (or 'bool') for vartype" + ) + sys.exit() # Won't exit, but will lead to exception + return var + except: + if optional: + # Carry on if the variable is optional + var = None + if self.Verbose or self.Debug: + if self.grass == False: + print("") + print('No value entered for optional parameter "' + name + '"') + print('in category "' + category + '" in configuration file.') + print( + "No action related to this optional parameter will be taken." + ) + print("") + else: + print( + "Problem loading " + + vartype + + ' "' + + name + + '" in category "' + + category + + '" from configuration file.' + ) + if specialReturnMessage: + print(specialReturnMessage) + sys.exit("Exiting.") + + def readyCoeff(self): + from scipy import sparse + + if sparse.issparse(self.coeff_matrix): + pass # Good type + else: + try: + self.coeff_matrix = sparse.dia_matrix(self.coeff_matrix) + except: + sys.exit( + "Failed to make a sparse array or load a sparse matrix from the input." + ) + + def greatCircleDistance(self, lat1, long1, lat2, long2, radius): + """ + Returns the great circle distance between two points. + Useful when using the SAS_NG solution in lat/lon coordinates + Modified from http://www.johndcook.com/blog/python_longitude_latitude/ + It should be able to take numpy arrays. + """ + + # Convert latitude and longitude to + # spherical coordinates in radians. + degrees_to_radians = np.pi / 180.0 + + # theta = colatitude = 90 - latitude + theta1rad = (90.0 - lat1) * degrees_to_radians + theta2rad = (90.0 - lat2) * degrees_to_radians + + # lambda = longitude + lambda1rad = long1 * degrees_to_radians + lambda2rad = long2 * degrees_to_radians + + # Compute spherical distance from spherical coordinates. + + # For two locations in spherical coordinates + # (1, theta, phi) and (1, theta, phi) + # cosine( arc length ) = + # sin(theta) * sin(theta') * cos(theta-theta') + cos(phi) * cos(phi') + # distance = radius * arc length + + cos_arc_length = np.sin(theta1rad) * np.sin(theta2rad) * np.cos( + lambda1rad - lambda2rad + ) + np.cos(theta1rad) * np.cos(theta2rad) + arc = np.arccos(cos_arc_length) + + great_circle_distance = radius * arc + + return great_circle_distance + + def define_points_grid(self): + """ + This is experimental code that could be used in the spatialDomainNoGrid + section to build a grid of points on which to generate the solution. + However, the current development plan (as of 27 Jan 2015) is to have the + end user supply the list of points where they want a solution (and/or for + it to be provided in a more automated way by GRASS GIS). But because this + (untested) code may still be useful, it will remain as its own function + here. + It used to be in f2d.py. + """ + # Grid making step + # In this case, an output at different (x,y), e.g., on a grid, is desired + # First, see if there is a need for a grid, and then make it + # latlon arrays must have a pre-set grid + if self.latlon == False: + # Warn that any existing grid will be overwritten + try: + self.dx + if self.Quiet == False: + print("dx and dy being overwritten -- supply a full grid") + except: + try: + self.dy + if self.Quiet == False: + print("dx and dy being overwritten -- supply a full grid") + except: + pass + # Boundaries + n = np.max(self.y) + self.alpha + s = np.min(self.y) - self.alpha + w = np.min(self.x) + self.alpha + e = np.max(self.x) - self.alpha + # Grid spacing + dxprelim = self.alpha / 50.0 # x or y + nx = np.ceil((e - w) / dxprelim) + ny = np.ceil((n - s) / dxprelim) + dx = (e - w) / nx + dy = (n - s) / ny + self.dx = self.dy = (dx + dy) / 2.0 # Average of these to create a + # square grid for more compatibility + self.xw = np.linspace(w, e, nx) + self.yw = np.linspace(s, n, ny) + else: + print("Lat/lon xw and yw must be pre-set: grid will not be square") + print("and may run into issues with poles, so to ensure the proper") + print("output points are chosen, the end user should do this.") + sys.exit() + + def loadFile(self, var, close_on_fail=True): + """ + A special function to replate a variable name that is a string file path + with the loaded file. + var is a string on input + output is a numpy array or a None-type object (success vs. failure) + """ + out = None + try: + # First see if it is a full path or directly links from the current + # working directory + out = np.load(var) + if self.Verbose: + print("Loading " + var + " from numpy binary") + except: + try: + out = np.loadtxt(var) + if self.Verbose: + print("Loading " + var + " ASCII") + except: + # Then see if it is relative to the location of the configuration file + try: + out = load(self.inpath + var) + if self.Verbose: + print("Loading " + var + " from numpy binary") + except: + try: + out = np.loadtxt(self.inpath + var) + if self.Verbose: + print("Loading " + var + " ASCII") + # If failure + except: + if close_on_fail: + print("Cannot find " + var + " file") + print("" + var + " path = " + var) + print("Looked relative to model python files.") + print("Also looked relative to configuration file path,") + print(" ", self.inpath) + print("Exiting.") + sys.exit() + else: + pass + return out - # theta = colatitude = 90 - latitude - theta1rad = (90.0 - lat1)*degrees_to_radians - theta2rad = (90.0 - lat2)*degrees_to_radians - # lambda = longitude - lambda1rad = long1*degrees_to_radians - lambda2rad = long2*degrees_to_radians +class Plotting: + # Plot, if desired + # 1D all here, 2D in functions + # Just because there is often more code in 2D plotting functions + # Also, yes, this portion of the code is NOT efficient or elegant in how it + # handles functions. But it's just a simple way to visualize results + # easily! And not too hard to improve with a bit of time. Anyway, the main + # goal here is the visualization, not the beauty of the code : ) + def plotting(self): + # try: + # self.plotChoice + # except: + # self.plotChoice = None + if self.plotChoice: + if self.Verbose: + print("Starting to plot " + self.plotChoice) + if self.dimension == 1: + if self.plotChoice == "q": + plt.figure(1) + if self.Method == "SAS_NG": + plt.plot(self.x / 1000.0, self.q / (self.rho_m * self.g), "ko-") + plt.ylabel( + "Load volume, mantle equivalent [m$^3$]", + fontsize=12, + fontweight="bold", + ) + else: + plt.plot(self.x / 1000.0, self.qs / (self.rho_m * self.g), "k-") + plt.ylabel( + "Load thickness, mantle equivalent [km]", + fontsize=12, + fontweight="bold", + ) + plt.xlabel( + "Distance along profile [km]", fontsize=12, fontweight="bold" + ) + plt.tight_layout() + plt.show() + elif self.plotChoice == "w": + plt.figure(1) + if self.Method == "SAS_NG": + plt.plot(self.xw / 1000.0, self.w, "k-") + else: + plt.plot(self.x / 1000.0, self.w, "k-") + plt.ylabel("Deflection [m]", fontsize=12, fontweight="bold") + plt.xlabel( + "Distance along profile [km]", fontsize=12, fontweight="bold" + ) + plt.tight_layout() + plt.show() + elif self.plotChoice == "both": + plt.figure(1, figsize=(6, 9)) + ax = plt.subplot(212) + if self.Method == "SAS_NG": + ax.plot(self.xw / 1000.0, self.w, "k-") + else: + ax.plot(self.x / 1000.0, self.w, "k-") + ax.set_ylabel("Deflection [m]", fontsize=12, fontweight="bold") + ax.set_xlabel( + "Distance along profile [m]", fontsize=12, fontweight="bold" + ) + plt.subplot(211) + plt.title("Loads and Lithospheric Deflections", fontsize=16) + if self.Method == "SAS_NG": + plt.plot(self.x / 1000.0, self.q / (self.rho_m * self.g), "ko-") + plt.ylabel( + "Load volume, mantle equivalent [m$^3$]", + fontsize=12, + fontweight="bold", + ) + plt.xlim(ax.get_xlim()) + else: + plt.plot(self.x / 1000.0, self.qs / (self.rho_m * self.g), "k-") + plt.ylabel( + "Load thickness, mantle equivalent [m]", + fontsize=12, + fontweight="bold", + ) + plt.xlabel( + "Distance along profile [km]", fontsize=12, fontweight="bold" + ) + plt.tight_layout() + plt.show() + elif self.plotChoice == "combo": + fig = plt.figure(1, figsize=(10, 6)) + titletext = "Loads and Lithospheric Deflections" + ax = fig.add_subplot(1, 1, 1) + # Plot undeflected load + if self.Method == "SAS_NG": + if self.Quiet == False: + print( + "Combo plot can't work with SAS_NG! Don't have mechanism in place\nto calculate load width." + ) + print( + "Big problem -- what is the area represented by the loads at the\nextreme ends of the array?" + ) + else: + ax.plot( + self.x / 1000.0, + self.qs / (self.rho_m * self.g), + "g--", + linewidth=2, + label="Load thickness [m mantle equivalent]", + ) + # Plot deflected load + if self.Method == "SAS_NG": + pass + # ax.plot(self.x/1000.,self.q/(self.rho_m*self.g) + self.w,'go-',linewidth=2,label="Load volume [m^3] mantle equivalent]") + else: + ax.plot( + self.x / 1000.0, + self.qs / (self.rho_m * self.g) + self.w, + "g-", + linewidth=2, + label="Deflection [m] + load thickness [m mantle equivalent]", + ) + # Plot deflection + if self.Method == "SAS_NG": + ax.plot( + self.xw / 1000.0, + self.w, + "ko-", + linewidth=2, + label="Deflection [m]", + ) + else: + ax.plot( + self.x / 1000.0, + self.w, + "k-", + linewidth=2, + label="Deflection [m]", + ) + # Set y min to equal to the absolute value maximum of y max and y min + # (and therefore show isostasy better) + yabsmax = max(abs(np.array(plt.ylim()))) + # Y axis label + plt.ylim((-yabsmax, yabsmax)) + # Plot title selector -- be infomrative + try: + self.Te + if self.Method == "FD": + if type(self.Te) is np.ndarray: + if (self.Te != (self.Te).mean()).any(): + plt.title(titletext, fontsize=16) + else: + plt.title( + titletext + + ", $T_e$ = " + + str((self.Te / 1000).mean()) + + " km", + fontsize=16, + ) + else: + plt.title( + titletext + + ", $T_e$ = " + + str(self.Te / 1000) + + " km", + fontsize=16, + ) + else: + plt.title( + titletext + ", $T_e$ = " + str(self.Te / 1000) + " km", + fontsize=16, + ) + except: + plt.title(titletext, fontsize=16) + # x and y labels + plt.ylabel("Loads and flexural response [m]", fontsize=16) + plt.xlabel("Distance along profile [km]", fontsize=16) + # legend -- based on lables + plt.legend(loc=0, numpoints=1, fancybox=True) + plt.tight_layout() + plt.show() + else: + if self.Quiet == False: + print( + 'Incorrect plotChoice input, "' + + self.plotChoice + + '" provided.' + ) + print( + "Possible input strings are: q, w, both, and (for 1D) combo" + ) + print("Unable to produce plot.") + elif self.dimension == 2: + if self.plotChoice == "q": + fig = plt.figure(1, figsize=(8, 6)) + if self.Method != "SAS_NG": + self.surfplot( + self.qs / (self.rho_m * self.g), + "Load thickness, mantle equivalent [m]", + ) + plt.show() + else: + self.xyzinterp( + self.x, + self.y, + self.q, + "Load volume, mantle equivalent [m$^3$]", + ) + plt.tight_layout() + plt.show() + elif self.plotChoice == "w": + fig = plt.figure(1, figsize=(8, 6)) + if self.Method != "SAS_NG": + self.surfplot(self.w, "Deflection [m]") + plt.show() + else: + self.xyzinterp(self.xw, self.yw, self.w, "Deflection [m]") + plt.tight_layout() + plt.show() + elif self.plotChoice == "both": + plt.figure(1, figsize=(6, 9)) + if self.Method != "SAS_NG": + self.twoSurfplots() + plt.show() + else: + plt.subplot(211) + self.xyzinterp( + self.x, + self.y, + self.q, + "Load volume, mantle equivalent [m$^3$]", + ) + plt.subplot(212) + self.xyzinterp(self.xw, self.yw, self.w, "Deflection [m]") + plt.tight_layout() + plt.show() + else: + if self.Quiet == False: + print( + 'Incorrect plotChoice input, "' + + self.plotChoice + + '" provided.' + ) + print( + "Possible input strings are: q, w, both, and (for 1D) combo" + ) + print("Unable to produce plot.") + + def surfplot(self, z, titletext): + """ + Plot if you want to - for troubleshooting - 1 figure + """ + if self.latlon: + plt.imshow( + z, extent=(0, self.dx * z.shape[0], self.dy * z.shape[1], 0) + ) # ,interpolation='nearest' + plt.xlabel("longitude [deg E]", fontsize=12, fontweight="bold") + plt.ylabel("latitude [deg N]", fontsize=12, fontweight="bold") + else: + plt.imshow( + z, + extent=( + 0, + self.dx / 1000.0 * z.shape[0], + self.dy / 1000.0 * z.shape[1], + 0, + ), + ) # ,interpolation='nearest' + plt.xlabel("x [km]", fontsize=12, fontweight="bold") + plt.ylabel("y [km]", fontsize=12, fontweight="bold") + plt.colorbar() + + plt.title(titletext, fontsize=16) + + def twoSurfplots(self): + """ + Plot multiple subplot figure for 2D array + """ + # Could more elegantly just call surfplot twice + # And also could include xyzinterp as an option inside surfplot. + # Noted here in case anyone wants to take that on in the future... + + plt.subplot(211) + plt.title("Load thickness, mantle equivalent [m]", fontsize=16) + if self.latlon: + plt.imshow( + self.qs / (self.rho_m * self.g), + extent=(0, self.dx * self.qs.shape[0], self.dy * self.qs.shape[1], 0), + ) + plt.xlabel("longitude [deg E]", fontsize=12, fontweight="bold") + plt.ylabel("latitude [deg N]", fontsize=12, fontweight="bold") + else: + plt.imshow( + self.qs / (self.rho_m * self.g), + extent=( + 0, + self.dx / 1000.0 * self.qs.shape[0], + self.dy / 1000.0 * self.qs.shape[1], + 0, + ), + ) + plt.xlabel("x [km]", fontsize=12, fontweight="bold") + plt.ylabel("y [km]", fontsize=12, fontweight="bold") + plt.colorbar() + + plt.subplot(212) + plt.title("Deflection [m]") + if self.latlon: + plt.imshow( + self.w, + extent=(0, self.dx * self.w.shape[0], self.dy * self.w.shape[1], 0), + ) + plt.xlabel("longitude [deg E]", fontsize=12, fontweight="bold") + plt.ylabel("latitude [deg N]", fontsize=12, fontweight="bold") + else: + plt.imshow( + self.w, + extent=( + 0, + self.dx / 1000.0 * self.w.shape[0], + self.dy / 1000.0 * self.w.shape[1], + 0, + ), + ) + plt.xlabel("x [km]", fontsize=12, fontweight="bold") + plt.ylabel("y [km]", fontsize=12, fontweight="bold") + plt.colorbar() + + def xyzinterp(self, x, y, z, titletext): + """ + Interpolates and plots ungridded model outputs from SAS_NG solution + """ + # Help from http://wiki.scipy.org/Cookbook/Matplotlib/Gridding_irregularly_spaced_data + + if self.Verbose: + print("Starting to interpolate grid for plotting -- can be a slow process!") + + import numpy.ma as ma + from scipy.interpolate import griddata + + # define grid. + xmin = np.min(self.xw) + xmean = np.mean(self.xw) # not used right now + xmax = np.max(self.xw) + ymin = np.min(self.yw) + ymean = np.mean(self.yw) # not used right now + ymax = np.max(self.yw) + x_range = xmax - xmin + y_range = ymax - ymin + + # x and y grids + # 100 cells on each side -- just for plotting, not so important + # to optimize with how many points are plotted + # xi = np.linspace(xmin-.05*x_range, xmax+.05*x_range, 200) + # yi = np.linspace(ymin-.05*y_range, ymax+.05*y_range, 200) + xi = np.linspace(xmin, xmax, 200) + yi = np.linspace(ymin, ymax, 200) + # grid the z-axis + zi = griddata((x, y), z, (xi[None, :], yi[:, None]), method="cubic") + # turn nan into 0 -- this will just be outside computation area for q + zi[np.isnan(zi)] = 0 + # contour the gridded outputs, plotting dots at the randomly spaced data points. + # CS = plt.contour(xi,yi,zi,15,linewidths=0.5,colors='k') -- don't need lines + if self.latlon: + CS = plt.contourf(xi, yi, zi, 100, cmap=plt.cm.jet) + else: + CS = plt.contourf(xi / 1000.0, yi / 1000.0, zi, 100, cmap=plt.cm.jet) + plt.colorbar() # draw colorbar + # plot model points. + # Computed at + if self.latlon: + plt.plot( + x, y, "o", markerfacecolor=".6", markeredgecolor=".6", markersize=1 + ) + plt.plot( + self.x, + self.y, + "o", + markerfacecolor=".2", + markeredgecolor=".2", + markersize=1, + ) + else: + plt.plot( + x / 1000.0, + y / 1000.0, + "o", + markerfacecolor=".6", + markeredgecolor=".6", + markersize=1, + ) + # Load sources (overlay computed at) + plt.plot( + self.x / 1000.0, + self.y / 1000.0, + "o", + markerfacecolor=".2", + markeredgecolor=".2", + markersize=1, + ) + # plt.hexbin(self.x, self.y, C=self.w) -- show colors on points -- harder to see + if self.latlon: + plt.xlabel("longitude [deg E]", fontsize=12, fontweight="bold") + plt.ylabel("latitude [deg N]", fontsize=12, fontweight="bold") + else: + plt.xlabel("x [km]", fontsize=12, fontweight="bold") + plt.ylabel("y [km]", fontsize=12, fontweight="bold") + # Limits -- to not get messed up by points (view wants to be wider so whole point visible) + if self.latlon: + plt.xlim((xi[0], xi[-1])) + plt.ylim((yi[0], yi[-1])) + else: + plt.xlim((xi[0] / 1000.0, xi[-1] / 1000.0)) + plt.ylim((yi[0] / 1000.0, yi[-1] / 1000.0)) + # Title + plt.title(titletext, fontsize=16) - # Compute spherical distance from spherical coordinates. - # For two locations in spherical coordinates - # (1, theta, phi) and (1, theta, phi) - # cosine( arc length ) = - # sin(theta) * sin(theta') * cos(theta-theta') + cos(phi) * cos(phi') - # distance = radius * arc length +class WhichModel(Utility): + def __init__(self, filename=None): + """ + WhichModel is a copy of initialization features inside the main class + """ + self.filename = filename + if self.filename: + try: + # only let this function imoprt things once + self.whichModel_AlreadyRun + except: + # Open parser and get what kind of model + _fileisvalid = self.config = configparser.ConfigParser() + _fileisvalid = len(_fileisvalid) + if _fileisvalid: + try: + self.config.read(filename) + # Need to change this and all slashes to be Windows compatible + self.inpath = os.path.dirname(os.path.realpath(filename)) + "/" + # Need to have these guys inside "try" to make sure it is set up OK + # (at least for them) + self.dimension = self.configGet("integer", "mode", "dimension") + self.whichModel_AlreadyRun = True + except: + sys.exit( + ">>>> Error: cannot locate specified configuration file. <<<<" + ) - cos_arc_length = np.sin(theta1rad) * np.sin(theta2rad) * \ - np.cos(lambda1rad - lambda2rad) + \ - np.cos(theta1rad) * np.cos(theta2rad) - arc = np.arccos( cos_arc_length ) - great_circle_distance = radius * arc +class Flexure(Utility, Plotting): + """ + Solves flexural isostasy both analytically (for constant flexural rigidity) + and numerically (for either variable or constant flexural rigidity). - return great_circle_distance + Analytical solutions are by superposition of analytical solutions + in the spatial domain (i.e. a sum of Green's functions) - def define_points_grid(self): - """ - This is experimental code that could be used in the spatialDomainNoGrid - section to build a grid of points on which to generate the solution. - However, the current development plan (as of 27 Jan 2015) is to have the - end user supply the list of points where they want a solution (and/or for - it to be provided in a more automated way by GRASS GIS). But because this - (untested) code may still be useful, it will remain as its own function - here. - It used to be in f2d.py. + Numerical solutions are finite difference by a direct sparse matrix solver. """ - # Grid making step - # In this case, an output at different (x,y), e.g., on a grid, is desired - # First, see if there is a need for a grid, and then make it - # latlon arrays must have a pre-set grid - if self.latlon == False: - # Warn that any existing grid will be overwritten - try: - self.dx - if self.Quiet == False: - print("dx and dy being overwritten -- supply a full grid") - except: + + def __init__(self, filename=None): + # 17 Nov 2014: Splitting out initialize from __init__ to allow space + # to use getters and setters to define values + + # Use standard routine to pull out values + # If no filename provided, will not initialize configuration file. + self.filename = filename + + # DEFAULT VERBOSITY + # Set default "quiet" to False, unless set by setter or overwritten by + # the configuration file. + self.Quiet = False + # And also set default verbosity + self.Verbose = True + self.Debug = False + + # x and y to None for checks + self.x = None + self.y = None + + # Set GRASS GIS usage flag: if GRASS is used, don't display error + # messages related to unset options. This sets it to False if it + # hasn't already been set (and it can be set after this too) + # (Though since this is __init__, would have to go through WhichModel + # for some reason to define self.grass before this try: - self.dy - if self.Quiet == False: - print("dx and dy being overwritten -- supply a full grid") + self.grass except: - pass - # Boundaries - n = np.max(self.y) + self.alpha - s = np.min(self.y) - self.alpha - w = np.min(self.x) + self.alpha - e = np.max(self.x) - self.alpha - # Grid spacing - dxprelim = self.alpha/50. # x or y - nx = np.ceil((e-w)/dxprelim) - ny = np.ceil((n-s)/dxprelim) - dx = (e-w) / nx - dy = (n-s) / ny - self.dx = self.dy = (dx+dy)/2. # Average of these to create a - # square grid for more compatibility - self.xw = np.linspace(w, e, nx) - self.yw = np.linspace(s, n, ny) - else: - print("Lat/lon xw and yw must be pre-set: grid will not be square") - print("and may run into issues with poles, so to ensure the proper") - print("output points are chosen, the end user should do this.") - sys.exit() - - - def loadFile(self, var, close_on_fail = True): - """ - A special function to replate a variable name that is a string file path - with the loaded file. - var is a string on input - output is a numpy array or a None-type object (success vs. failure) - """ - out = None - try: - # First see if it is a full path or directly links from the current - # working directory - out = np.load(var) - if self.Verbose: print("Loading "+var+" from numpy binary") - except: - try: - out = np.loadtxt(var) - if self.Verbose: print("Loading "+var+" ASCII") - except: - # Then see if it is relative to the location of the configuration file + self.grass = False + + # Default values for lat/lon usage -- defaulting not to use it try: - out = load(self.inpath + var) - if self.Verbose: print("Loading "+var+" from numpy binary") + self.latlon except: - try: - out = np.loadtxt(self.inpath + var) - if self.Verbose: print("Loading "+var+" ASCII") - # If failure - except: - if close_on_fail: - print("Cannot find "+var+" file") - print(""+var+" path = " + var) - print("Looked relative to model python files.") - print("Also looked relative to configuration file path,") - print(" ", self.inpath) - print("Exiting.") - sys.exit() - else: - pass - return out - -class Plotting: - # Plot, if desired - # 1D all here, 2D in functions - # Just because there is often more code in 2D plotting functions - # Also, yes, this portion of the code is NOT efficient or elegant in how it - # handles functions. But it's just a simple way to visualize results - # easily! And not too hard to improve with a bit of time. Anyway, the main - # goal here is the visualization, not the beauty of the code : ) - def plotting(self): - #try: - # self.plotChoice - #except: - # self.plotChoice = None - if self.plotChoice: - if self.Verbose: print("Starting to plot " + self.plotChoice) - if self.dimension == 1: - if self.plotChoice == 'q': - plt.figure(1) - if self.Method == 'SAS_NG': - plt.plot(self.x/1000., self.q/(self.rho_m*self.g), 'ko-') - plt.ylabel('Load volume, mantle equivalent [m$^3$]', fontsize=12, fontweight='bold') - else: - plt.plot(self.x/1000., self.qs/(self.rho_m*self.g), 'k-') - plt.ylabel('Load thickness, mantle equivalent [km]', fontsize=12, fontweight='bold') - plt.xlabel('Distance along profile [km]', fontsize=12, fontweight='bold') - plt.tight_layout() - plt.show() - elif self.plotChoice == 'w': - plt.figure(1) - if self.Method == 'SAS_NG': - plt.plot(self.xw/1000., self.w, 'k-') - else: - plt.plot(self.x/1000., self.w, 'k-') - plt.ylabel('Deflection [m]', fontsize=12, fontweight='bold') - plt.xlabel('Distance along profile [km]', fontsize=12, fontweight='bold') - plt.tight_layout() - plt.show() - elif self.plotChoice == 'both': - plt.figure(1,figsize=(6,9)) - ax = plt.subplot(212) - if self.Method == "SAS_NG": - ax.plot(self.xw/1000., self.w, 'k-') - else: - ax.plot(self.x/1000., self.w, 'k-') - ax.set_ylabel('Deflection [m]', fontsize=12, fontweight='bold') - ax.set_xlabel('Distance along profile [m]', fontsize=12, fontweight='bold') - plt.subplot(211) - plt.title('Loads and Lithospheric Deflections', fontsize=16) - if self.Method == 'SAS_NG': - plt.plot(self.x/1000., self.q/(self.rho_m*self.g), 'ko-') - plt.ylabel('Load volume, mantle equivalent [m$^3$]', fontsize=12, fontweight='bold') - plt.xlim(ax.get_xlim()) - else: - plt.plot(self.x/1000., self.qs/(self.rho_m*self.g), 'k-') - plt.ylabel('Load thickness, mantle equivalent [m]', fontsize=12, fontweight='bold') - plt.xlabel('Distance along profile [km]', fontsize=12, fontweight='bold') - plt.tight_layout() - plt.show() - elif self.plotChoice == 'combo': - fig = plt.figure(1,figsize=(10,6)) - titletext='Loads and Lithospheric Deflections' - ax = fig.add_subplot(1,1,1) - # Plot undeflected load - if self.Method == "SAS_NG": - if self.Quiet == False: - print("Combo plot can't work with SAS_NG! Don't have mechanism in place\nto calculate load width.") - print("Big problem -- what is the area represented by the loads at the\nextreme ends of the array?") - else: - ax.plot(self.x/1000., self.qs/(self.rho_m*self.g), 'g--', linewidth=2, label="Load thickness [m mantle equivalent]") - # Plot deflected load - if self.Method == "SAS_NG": - pass - #ax.plot(self.x/1000.,self.q/(self.rho_m*self.g) + self.w,'go-',linewidth=2,label="Load volume [m^3] mantle equivalent]") - else: - ax.plot(self.x/1000., self.qs/(self.rho_m*self.g) + self.w,'g-',linewidth=2,label="Deflection [m] + load thickness [m mantle equivalent]") - # Plot deflection - if self.Method == "SAS_NG": - ax.plot(self.xw/1000., self.w, 'ko-', linewidth=2, label="Deflection [m]") - else: - ax.plot(self.x/1000.,self.w, 'k-', linewidth=2, label="Deflection [m]") - # Set y min to equal to the absolute value maximum of y max and y min - # (and therefore show isostasy better) - yabsmax = max(abs(np.array(plt.ylim()))) - # Y axis label - plt.ylim((-yabsmax,yabsmax)) - # Plot title selector -- be infomrative - try: - self.Te - if self.Method == "FD": - if type(self.Te) is np.ndarray: - if (self.Te != (self.Te).mean()).any(): - plt.title(titletext,fontsize=16) - else: - plt.title(titletext + ', $T_e$ = ' + str((self.Te / 1000).mean()) + " km", fontsize=16) - else: - plt.title(titletext + ', $T_e$ = ' + str(self.Te / 1000) + " km", fontsize=16) + self.latlon = False + try: + self.PlanetaryRadius + except: + self.PlanetaryRadius = None + + def initialize(self, filename=None): + # Values from configuration file + + # If a filename is provided here, overwrite any prior value + if filename: + if self.filename: + pass # Don't overwrite if filename is None-type + # "Debug" not yet defined. + # if self.Debug: + # print("Overwriting filename from '__init__' step with that from\n"+\ + # "initialize step." else: - plt.title(titletext + ', $T_e$ = ' + str(self.Te / 1000) + " km", fontsize=16) - except: - plt.title(titletext,fontsize=16) - # x and y labels - plt.ylabel('Loads and flexural response [m]',fontsize=16) - plt.xlabel('Distance along profile [km]',fontsize=16) - # legend -- based on lables - plt.legend(loc=0,numpoints=1,fancybox=True) - plt.tight_layout() - plt.show() - else: - if self.Quiet == False: - print('Incorrect plotChoice input, "' + self.plotChoice + '" provided.') - print("Possible input strings are: q, w, both, and (for 1D) combo") - print("Unable to produce plot.") - elif self.dimension == 2: - if self.plotChoice == 'q': - fig = plt.figure(1, figsize=(8,6)) - if self.Method != 'SAS_NG': - self.surfplot(self.qs/(self.rho_m*self.g), 'Load thickness, mantle equivalent [m]') - plt.show() - else: - self.xyzinterp(self.x, self.y, self.q, 'Load volume, mantle equivalent [m$^3$]') - plt.tight_layout() - plt.show() - elif self.plotChoice == 'w': - fig = plt.figure(1, figsize=(8,6)) - if self.Method != 'SAS_NG': - self.surfplot(self.w, 'Deflection [m]') - plt.show() - else: - self.xyzinterp(self.xw, self.yw, self.w, 'Deflection [m]') - plt.tight_layout() - plt.show() - elif self.plotChoice == 'both': - plt.figure(1,figsize=(6,9)) - if self.Method != 'SAS_NG': - self.twoSurfplots() - plt.show() - else: - plt.subplot(211) - self.xyzinterp(self.x, self.y, self.q, 'Load volume, mantle equivalent [m$^3$]') - plt.subplot(212) - self.xyzinterp(self.xw, self.yw, self.w, 'Deflection [m]') - plt.tight_layout() - plt.show() - else: - if self.Quiet == False: - print('Incorrect plotChoice input, "' + self.plotChoice + '" provided.') - print("Possible input strings are: q, w, both, and (for 1D) combo") - print("Unable to produce plot.") - - def surfplot(self, z, titletext): - """ - Plot if you want to - for troubleshooting - 1 figure - """ - if self.latlon: - plt.imshow(z, extent=(0, self.dx*z.shape[0], self.dy*z.shape[1], 0)) #,interpolation='nearest' - plt.xlabel('longitude [deg E]', fontsize=12, fontweight='bold') - plt.ylabel('latitude [deg N]', fontsize=12, fontweight='bold') - else: - plt.imshow(z, extent=(0, self.dx/1000.*z.shape[0], self.dy/1000.*z.shape[1], 0)) #,interpolation='nearest' - plt.xlabel('x [km]', fontsize=12, fontweight='bold') - plt.ylabel('y [km]', fontsize=12, fontweight='bold') - plt.colorbar() - - plt.title(titletext,fontsize=16) - - def twoSurfplots(self): - """ - Plot multiple subplot figure for 2D array - """ - # Could more elegantly just call surfplot twice - # And also could include xyzinterp as an option inside surfplot. - # Noted here in case anyone wants to take that on in the future... - - plt.subplot(211) - plt.title('Load thickness, mantle equivalent [m]',fontsize=16) - if self.latlon: - plt.imshow(self.qs/(self.rho_m*self.g), extent=(0, self.dx*self.qs.shape[0], self.dy*self.qs.shape[1], 0)) - plt.xlabel('longitude [deg E]', fontsize=12, fontweight='bold') - plt.ylabel('latitude [deg N]', fontsize=12, fontweight='bold') - else: - plt.imshow(self.qs/(self.rho_m*self.g), extent=(0, self.dx/1000.*self.qs.shape[0], self.dy/1000.*self.qs.shape[1], 0)) - plt.xlabel('x [km]', fontsize=12, fontweight='bold') - plt.ylabel('y [km]', fontsize=12, fontweight='bold') - plt.colorbar() - - plt.subplot(212) - plt.title('Deflection [m]') - if self.latlon: - plt.imshow(self.w, extent=(0, self.dx*self.w.shape[0], self.dy*self.w.shape[1], 0)) - plt.xlabel('longitude [deg E]', fontsize=12, fontweight='bold') - plt.ylabel('latitude [deg N]', fontsize=12, fontweight='bold') - else: - plt.imshow(self.w, extent=(0, self.dx/1000.*self.w.shape[0], self.dy/1000.*self.w.shape[1], 0)) - plt.xlabel('x [km]', fontsize=12, fontweight='bold') - plt.ylabel('y [km]', fontsize=12, fontweight='bold') - plt.colorbar() - - def xyzinterp(self, x, y, z, titletext): - """ - Interpolates and plots ungridded model outputs from SAS_NG solution - """ - # Help from http://wiki.scipy.org/Cookbook/Matplotlib/Gridding_irregularly_spaced_data - - if self.Verbose: - print("Starting to interpolate grid for plotting -- can be a slow process!") - - import numpy.ma as ma - from scipy.interpolate import griddata - - # define grid. - xmin = np.min(self.xw) - xmean = np.mean(self.xw) # not used right now - xmax = np.max(self.xw) - ymin = np.min(self.yw) - ymean = np.mean(self.yw) # not used right now - ymax = np.max(self.yw) - x_range = xmax - xmin - y_range = ymax - ymin - - # x and y grids - # 100 cells on each side -- just for plotting, not so important - # to optimize with how many points are plotted - #xi = np.linspace(xmin-.05*x_range, xmax+.05*x_range, 200) - #yi = np.linspace(ymin-.05*y_range, ymax+.05*y_range, 200) - xi = np.linspace(xmin, xmax, 200) - yi = np.linspace(ymin, ymax, 200) - # grid the z-axis - zi = griddata((x, y), z, (xi[None,:], yi[:,None]), method='cubic') - # turn nan into 0 -- this will just be outside computation area for q - zi[np.isnan(zi)] = 0 - # contour the gridded outputs, plotting dots at the randomly spaced data points. - #CS = plt.contour(xi,yi,zi,15,linewidths=0.5,colors='k') -- don't need lines - if self.latlon: - CS = plt.contourf(xi, yi, zi, 100, cmap=plt.cm.jet) - else: - CS = plt.contourf(xi/1000., yi/1000., zi, 100, cmap=plt.cm.jet) - plt.colorbar() # draw colorbar - # plot model points. - # Computed at - if self.latlon: - plt.plot(x, y, 'o', markerfacecolor='.6', markeredgecolor='.6', markersize=1) - plt.plot(self.x, self.y, 'o', markerfacecolor='.2', markeredgecolor='.2', markersize=1) - else: - plt.plot(x/1000., y/1000., 'o', markerfacecolor='.6', markeredgecolor='.6', markersize=1) - # Load sources (overlay computed at) - plt.plot(self.x/1000., self.y/1000., 'o', markerfacecolor='.2', markeredgecolor='.2', markersize=1) - #plt.hexbin(self.x, self.y, C=self.w) -- show colors on points -- harder to see - if self.latlon: - plt.xlabel('longitude [deg E]', fontsize=12, fontweight='bold') - plt.ylabel('latitude [deg N]', fontsize=12, fontweight='bold') - else: - plt.xlabel('x [km]', fontsize=12, fontweight='bold') - plt.ylabel('y [km]', fontsize=12, fontweight='bold') - # Limits -- to not get messed up by points (view wants to be wider so whole point visible) - if self.latlon: - plt.xlim( (xi[0], xi[-1]) ) - plt.ylim( (yi[0], yi[-1]) ) - else: - plt.xlim( (xi[0]/1000., xi[-1]/1000.) ) - plt.ylim( (yi[0]/1000., yi[-1]/1000.) ) - # Title - plt.title(titletext, fontsize=16) + # Update to new filename + self.filename = filename + if self.filename: + # Set up ConfigParser + self.config = configparser.ConfigParser() + try: + self.config.read(self.filename) + self.inpath = os.path.dirname(os.path.realpath(self.filename)) + "/" + # Need to have these guys inside "try" to make sure it is set up OK + # (at least for them) + self.dimension = self.configGet("integer", "mode", "dimension") + self.whichModel_AlreadyRun = True + except: + sys.exit( + "No configuration file at specified path, or configuration file configured incorrectly" + ) -class WhichModel(Utility): - def __init__(self, filename=None): - """ - WhichModel is a copy of initialization features inside the main class - """ - self.filename = filename - if self.filename: - try: - # only let this function imoprt things once - self.whichModel_AlreadyRun - except: - # Open parser and get what kind of model - _fileisvalid = self.config = configparser.ConfigParser() - _fileisvalid = len(_fileisvalid) - if _fileisvalid: + # Set verbosity for model run + # Default is "verbose" with no debug or quiet + # Verbose + try: + self.Verbose = self.configGet( + "bool", "verbosity", "Verbose", optional=False + ) + except: + pass + # Deebug means that whole arrays, etc., can be printed + try: + self.Debug = self.configGet( + "bool", "verbosity", "Debug", optional=False + ) + except: + pass + # Deebug means that whole arrays, etc., can be printed try: - self.config.read(filename) - # Need to change this and all slashes to be Windows compatible - self.inpath = os.path.dirname(os.path.realpath(filename)) + '/' - # Need to have these guys inside "try" to make sure it is set up OK - # (at least for them) - self.dimension = self.configGet("integer", "mode", "dimension") - self.whichModel_AlreadyRun = True + self.Quiet = self.configGet( + "bool", "verbosity", "Quiet", optional=False + ) except: - sys.exit(">>>> Error: cannot locate specified configuration file. <<<<") + pass + # Quiet overrides all others + if self.Quiet: + self.Debug = False + self.Verbose = False + + # Introduce model + # After configuration file can define "Quiet", and getter/setter should be done + # by this point if we are going that way. + if self.Quiet == False: + print("") # Blank line at start of run + print("") + print("****************************" + "*" * len(__version__)) + print("*** Initializing gFlex v" + __version__ + " ***") + print("****************************" + "*" * len(__version__)) + print("") + print("Open-source licensed under GNU GPL v3") + print("") -class Flexure(Utility, Plotting): - """ - Solves flexural isostasy both analytically (for constant flexural rigidity) - and numerically (for either variable or constant flexural rigidity). - - Analytical solutions are by superposition of analytical solutions - in the spatial domain (i.e. a sum of Green's functions) - - Numerical solutions are finite difference by a direct sparse matrix solver. - """ - - def __init__(self, filename=None): - # 17 Nov 2014: Splitting out initialize from __init__ to allow space - # to use getters and setters to define values - - # Use standard routine to pull out values - # If no filename provided, will not initialize configuration file. - self.filename = filename - - # DEFAULT VERBOSITY - # Set default "quiet" to False, unless set by setter or overwritten by - # the configuration file. - self.Quiet = False - # And also set default verbosity - self.Verbose = True - self.Debug = False - - # x and y to None for checks - self.x = None - self.y = None - - # Set GRASS GIS usage flag: if GRASS is used, don't display error - # messages related to unset options. This sets it to False if it - # hasn't already been set (and it can be set after this too) - # (Though since this is __init__, would have to go through WhichModel - # for some reason to define self.grass before this - try: - self.grass - except: - self.grass = False - - # Default values for lat/lon usage -- defaulting not to use it - try: - self.latlon - except: - self.latlon = False - try: - self.PlanetaryRadius - except: - self.PlanetaryRadius = None - - def initialize(self, filename=None): - # Values from configuration file - - # If a filename is provided here, overwrite any prior value - if filename: - if self.filename: - pass # Don't overwrite if filename is None-type - # "Debug" not yet defined. - #if self.Debug: - # print("Overwriting filename from '__init__' step with that from\n"+\ - # "initialize step." - else: - # Update to new filename - self.filename = filename + if self.filename: + # Set clocks to None so if they are called by the getter before the + # calculation is performed, there won't be an error + self.coeff_creation_time = None + self.time_to_solve = None + + self.Method = self.configGet("string", "mode", "method") + # Boundary conditions + # This used to be nested inside an "if self.Method == 'FD'", but it seems + # better to define these to ensure there aren't mistaken impressions + # about what they do for the SAS case + # Not optional: flexural solutions can be very sensitive to b.c.'s + self.BC_E = self.configGet( + "string", "numerical", "BoundaryCondition_East", optional=False + ) + self.BC_W = self.configGet( + "string", "numerical", "BoundaryCondition_West", optional=False + ) + if self.dimension == 2: + self.BC_N = self.configGet( + "string", "numerical2D", "BoundaryCondition_North", optional=False + ) + self.BC_S = self.configGet( + "string", "numerical2D", "BoundaryCondition_South", optional=False + ) + + # Parameters + self.g = self.configGet("float", "parameter", "GravAccel") + self.rho_m = self.configGet("float", "parameter", "MantleDensity") + self.rho_fill = self.configGet( + "float", "parameter", "InfillMaterialDensity" + ) + + # Grid spacing + if self.Method != "SAS_NG": + # No meaning for ungridded superimposed analytical solutions + # From configuration file + self.dx = self.configGet("float", "numerical", "GridSpacing_x") + if self.dimension == 2: + self.dy = self.configGet("float", "numerical2D", "GridSpacing_y") + + # Mode: solution method and type of plate solution (if applicable) + if self.filename: + self.Method = self.configGet("string", "mode", "method") + if self.dimension == 2: + self.PlateSolutionType = self.configGet( + "string", "mode", "PlateSolutionType" + ) + + # Loading grid + # q0 is either a load array or an x,y,q array. + # Therefore q_0, initial q, before figuring out what it really is + # for grid, q0 could also be written as $q_\sigma$ or q/(dx*(dy)) + # it is a surface normal stress that is h_load * rho_load * g + # it later is combined with dx and (if 2D) dy for FD cases + # for point loads, need mass: q0 should be written as [x, (y), force]) + self.q0 = self.configGet("string", "input", "Loads") + + # Parameters -- rho_m and rho_fill defined, so this outside + # of if-statement (to work with getters/setters as well) + self.drho = self.rho_m - self.rho_fill + if self.filename: + self.E = self.configGet("float", "parameter", "YoungsModulus") + self.nu = self.configGet("float", "parameter", "PoissonsRatio") + + # Stop program if there is no q0 defined or if it is None-type + try: + self.q0 + # Stop program if q0 is None-type + if type(self.q0) == None: # if is None type, just be patient + sys.exit( + "Must define non-None-type q0 by this stage in the initialization step\n" + + "from either configuration file (string) or direct array import" + ) + except: + try: + self.q + except: + try: + self.qs + except: + sys.exit( + "Must define q0, q, or qs by this stage in the initialization step\n" + + "from either configuration file (string) or direct array import" + ) + + # Ignore this if no q0 set + try: + self.q0 + except: + self.q0 = None + if self.q0 == "": + self.q0 = None + if type(self.q0) == str: + self.q0 = self.loadFile(self.q0) # Won't do this if q0 is None + + # Check consistency of dimensions + if self.q0 is not None: + if self.Method != "SAS_NG": + if self.q0.ndim != self.dimension: + print("Number of dimensions in loads file is inconsistent with") + print("number of dimensions in solution technique.") + print("Loads", self.q0.ndim) + print("Dimensions", self.dimension) + print(self.q0) + print("Exiting.") + sys.exit() + + # Plotting selection + self.plotChoice = self.configGet("string", "output", "Plot", optional=True) + + # Ensure that Te is of floating-point type to avoid integer math + # and floor division + try: + self.Te = self.Te.astype(float) # array + except: + # Integer scalar Te does not seem to be a problem, but taking this step + # anyway for consistency + try: + self.Te = float(self.Te) # integer + except: + # If not already defined, then an input file is being used, and this + # code should bring the grid in as floating point type... just later. + pass - if self.filename: - # Set up ConfigParser - self.config = configparser.ConfigParser() - try: - self.config.read(self.filename) - self.inpath = os.path.dirname(os.path.realpath(self.filename)) + '/' - # Need to have these guys inside "try" to make sure it is set up OK - # (at least for them) - self.dimension = self.configGet("integer", "mode", "dimension") - self.whichModel_AlreadyRun = True - except: - sys.exit("No configuration file at specified path, or configuration file configured incorrectly") - - # Set verbosity for model run - # Default is "verbose" with no debug or quiet - # Verbose - try: - self.Verbose = self.configGet("bool", "verbosity", "Verbose", optional=False) - except: - pass - # Deebug means that whole arrays, etc., can be printed - try: - self.Debug = self.configGet("bool", "verbosity", "Debug", optional=False) - except: - pass - # Deebug means that whole arrays, etc., can be printed - try: - self.Quiet = self.configGet("bool", "verbosity", "Quiet", optional=False) - except: - pass - # Quiet overrides all others - if self.Quiet: - self.Debug = False - self.Verbose = False - - # Introduce model - # After configuration file can define "Quiet", and getter/setter should be done - # by this point if we are going that way. - if self.Quiet == False: - print("") # Blank line at start of run - print("") - print("****************************"+"*"*len(__version__)) - print("*** Initializing gFlex v"+__version__+" ***") - print("****************************"+"*"*len(__version__)) - print("") - print("Open-source licensed under GNU GPL v3") - print("") - - if self.filename: - # Set clocks to None so if they are called by the getter before the - # calculation is performed, there won't be an error - self.coeff_creation_time = None - self.time_to_solve = None - - self.Method = self.configGet("string", "mode", "method") - # Boundary conditions - # This used to be nested inside an "if self.Method == 'FD'", but it seems - # better to define these to ensure there aren't mistaken impressions - # about what they do for the SAS case - # Not optional: flexural solutions can be very sensitive to b.c.'s - self.BC_E = self.configGet('string', 'numerical', 'BoundaryCondition_East', optional=False) - self.BC_W = self.configGet('string', 'numerical', 'BoundaryCondition_West', optional=False) - if self.dimension == 2: - self.BC_N = self.configGet('string', 'numerical2D', 'BoundaryCondition_North', optional=False) - self.BC_S = self.configGet('string', 'numerical2D', 'BoundaryCondition_South', optional=False) - - # Parameters - self.g = self.configGet('float', "parameter", "GravAccel") - self.rho_m = self.configGet('float', "parameter", "MantleDensity") - self.rho_fill = self.configGet('float', "parameter", "InfillMaterialDensity") - - # Grid spacing - if self.Method != 'SAS_NG': - # No meaning for ungridded superimposed analytical solutions - # From configuration file - self.dx = self.configGet("float", "numerical", "GridSpacing_x") - if self.dimension == 2: - self.dy = self.configGet("float", "numerical2D", "GridSpacing_y") + # Check for end loads; otherwise set as 0 + # Do this for 2D; in the 1D case, xy and yy will just not be used + try: + self.sigma_xx + if self.Method != "FD": + warnings.warn( + category=RuntimeWarning, + message="End loads have been set but will not be implemented because the solution method is not finite difference", + ) + except: + self.sigma_xx = 0 + try: + self.sigma_xy + if self.Method != "FD": + warnings.warn( + category=RuntimeWarning, + message="End loads have been set but will not be implemented because the solution method is not finite difference", + ) + except: + self.sigma_xy = 0 + try: + self.sigma_yy + if self.Method != "FD": + warnings.warn( + category=RuntimeWarning, + message="End loads have been set but will not be implemented because the solution method is not finite difference", + ) + except: + self.sigma_yy = 0 - # Mode: solution method and type of plate solution (if applicable) - if self.filename: - self.Method = self.configGet("string", "mode", "method") - if self.dimension == 2: - self.PlateSolutionType = self.configGet("string", "mode", "PlateSolutionType") - - # Loading grid - # q0 is either a load array or an x,y,q array. - # Therefore q_0, initial q, before figuring out what it really is - # for grid, q0 could also be written as $q_\sigma$ or q/(dx*(dy)) - # it is a surface normal stress that is h_load * rho_load * g - # it later is combined with dx and (if 2D) dy for FD cases - # for point loads, need mass: q0 should be written as [x, (y), force]) - self.q0 = self.configGet('string', "input", "Loads") - - # Parameters -- rho_m and rho_fill defined, so this outside - # of if-statement (to work with getters/setters as well) - self.drho = self.rho_m - self.rho_fill - if self.filename: - self.E = self.configGet("float", "parameter", "YoungsModulus") - self.nu = self.configGet("float", "parameter", "PoissonsRatio") - - # Stop program if there is no q0 defined or if it is None-type - try: - self.q0 - # Stop program if q0 is None-type - if type(self.q0) == None: # if is None type, just be patient - sys.exit("Must define non-None-type q0 by this stage in the initialization step\n"+\ - "from either configuration file (string) or direct array import") - except: - try: - self.q - except: + # Finalize + def finalize(self): + # Can include an option for this later, but for the moment, this will + # clear the coefficient array so it doens't cause problems for model runs + # searching for the proper rigidity try: - self.qs + del self.coeff_matrix except: - sys.exit("Must define q0, q, or qs by this stage in the initialization step\n"+\ - "from either configuration file (string) or direct array import") - - # Ignore this if no q0 set - try: - self.q0 - except: - self.q0 = None - if self.q0 == '': - self.q0 = None - if type(self.q0) == str: - self.q0 = self.loadFile(self.q0) # Won't do this if q0 is None - - # Check consistency of dimensions - if self.q0 is not None: - if self.Method != 'SAS_NG': - if self.q0.ndim != self.dimension: - print("Number of dimensions in loads file is inconsistent with") - print("number of dimensions in solution technique.") - print("Loads", self.q0.ndim) - print("Dimensions", self.dimension) - print(self.q0) - print("Exiting.") - sys.exit() - - # Plotting selection - self.plotChoice = self.configGet("string", "output", "Plot", optional=True) - - # Ensure that Te is of floating-point type to avoid integer math - # and floor division - try: - self.Te = self.Te.astype(float) # array - except: - # Integer scalar Te does not seem to be a problem, but taking this step - # anyway for consistency - try: - self.Te = float(self.Te) # integer - except: - # If not already defined, then an input file is being used, and this - # code should bring the grid in as floating point type... just later. - pass + pass + if self.Quiet == False: + print("") - # Check for end loads; otherwise set as 0 - # Do this for 2D; in the 1D case, xy and yy will just not be used - try: - self.sigma_xx - if self.Method != 'FD': - warnings.warn(category=RuntimeWarning, message='End loads have been set but will not be implemented because the solution method is not finite difference') - except: - self.sigma_xx = 0 - try: - self.sigma_xy - if self.Method != 'FD': - warnings.warn(category=RuntimeWarning, message='End loads have been set but will not be implemented because the solution method is not finite difference') - except: - self.sigma_xy = 0 - try: - self.sigma_yy - if self.Method != 'FD': - warnings.warn(category=RuntimeWarning, message='End loads have been set but will not be implemented because the solution method is not finite difference') - except: - self.sigma_yy = 0 - - # Finalize - def finalize(self): - # Can include an option for this later, but for the moment, this will - # clear the coefficient array so it doens't cause problems for model runs - # searching for the proper rigidity - try: - del self.coeff_matrix - except: - pass - if self.Quiet==False: - print("") - - # SAVING TO FILE AND PLOTTING STEPS - - # Output: One of the functions run by isostasy.py; not part of IRF - # (for standalone model use) - def output(self): - if self.Verbose: print("Output step") - self.outputDeflections() - self.plotting() - - # Save output deflections to file, if desired - def outputDeflections(self): - """ - Outputs a grid of deflections if an output directory is defined in the - configuration file + # SAVING TO FILE AND PLOTTING STEPS - If the filename given in the configuration file ends in ".npy", then a binary - numpy grid will be exported. + # Output: One of the functions run by isostasy.py; not part of IRF + # (for standalone model use) + def output(self): + if self.Verbose: + print("Output step") + self.outputDeflections() + self.plotting() - Otherwise, an ASCII grid will be exported. - """ - try: - # If wOutFile exists, has already been set by a setter - self.wOutFile - if self.Verbose: - print("Output filename provided.") - # Otherwise, it needs to be set by an configuration file - except: - try: - self.wOutFile = self.configGet("string", "output", "DeflectionOut", optional=True) - except: - # if there is no parsable output string, do not generate output; - # this allows the user to leave the line blank and produce no output - if self.Debug: - print("No output filename provided:") - print(" not writing any deflection output to file") - if self.wOutFile: - if self.wOutFile[-4:] == '.npy': - from numpy import save - save(self.wOutFile,self.w) + # Save output deflections to file, if desired + def outputDeflections(self): + """ + Outputs a grid of deflections if an output directory is defined in the + configuration file + + If the filename given in the configuration file ends in ".npy", then a binary + numpy grid will be exported. + + Otherwise, an ASCII grid will be exported. + """ + try: + # If wOutFile exists, has already been set by a setter + self.wOutFile + if self.Verbose: + print("Output filename provided.") + # Otherwise, it needs to be set by an configuration file + except: + try: + self.wOutFile = self.configGet( + "string", "output", "DeflectionOut", optional=True + ) + except: + # if there is no parsable output string, do not generate output; + # this allows the user to leave the line blank and produce no output + if self.Debug: + print("No output filename provided:") + print(" not writing any deflection output to file") + if self.wOutFile: + if self.wOutFile[-4:] == ".npy": + from numpy import save + + save(self.wOutFile, self.w) + else: + from numpy import savetxt + + # Shouldn't need more than mm precision, at very most + savetxt(self.wOutFile, self.w, fmt="%.3f") + if self.Verbose: + print("Saving deflections --> " + self.wOutFile) + + def bc_check(self): + # Check that boundary conditions are acceptable with code implementation + # Acceptable b.c.'s + if self.Method == "FD": + # Check if a coefficient array has been defined + # It would only be by a getter or setter; + # no way to do I/O with this with present configuration files + # Define as None for use later. + try: + self.coeff_matrix + except: + self.coeff_matrix = None + # No need to create a coeff_matrix if one already exists + if self.coeff_matrix is None: + # Acceptable boundary conditions + self.bc1D = np.array( + [ + "0Displacement0Slope", + "Periodic", + "Mirror", + "0Moment0Shear", + "0Slope0Shear", + ] + ) + self.bc2D = np.array( + [ + "0Displacement0Slope", + "Periodic", + "Mirror", + "0Moment0Shear", + "0Slope0Shear", + ] + ) + # Boundary conditions should be defined by this point -- whether via + # the configuration file or the getters and setters + self.bclist = [self.BC_E, self.BC_W] + if self.dimension == 2: + self.bclist += [self.BC_N, self.BC_S] + # Now check that these are valid boundary conditions + for bc in self.bclist: + if self.dimension == 1: + if (bc == self.bc1D).any(): + pass + else: + sys.exit( + "'" + + bc + + "'" + + " is not an acceptable 1D finite difference boundary condition\n" + + "and/or is not yet implement in the code. Acceptable boundary conditions\n" + + "are:\n" + + str(self.bc1D) + + "\n" + + "Exiting." + ) + elif self.dimension == 2: + if (bc == self.bc2D).any(): + pass + else: + sys.exit( + "'" + + bc + + "'" + + " is not an acceptable 2D finite difference boundary condition\n" + + "and/or is not yet implement in the code. Acceptable boundary conditions\n" + + "are:\n" + + str(self.bc2D) + + "\n" + + "Exiting." + ) + else: + sys.exit( + "For a flexural solution, grid must be 1D or 2D. Exiting." + ) else: - from numpy import savetxt - - # Shouldn't need more than mm precision, at very most - savetxt(self.wOutFile,self.w,fmt='%.3f') - if self.Verbose: - print("Saving deflections --> " + self.wOutFile) - - def bc_check(self): - # Check that boundary conditions are acceptable with code implementation - # Acceptable b.c.'s - if self.Method == 'FD': - # Check if a coefficient array has been defined - # It would only be by a getter or setter; - # no way to do I/O with this with present configuration files - # Define as None for use later. - try: - self.coeff_matrix - except: - self.coeff_matrix = None - # No need to create a coeff_matrix if one already exists - if self.coeff_matrix is None: - # Acceptable boundary conditions - self.bc1D = np.array(['0Displacement0Slope', 'Periodic', 'Mirror', '0Moment0Shear', '0Slope0Shear']) - self.bc2D = np.array(['0Displacement0Slope', 'Periodic', 'Mirror', '0Moment0Shear', '0Slope0Shear']) - # Boundary conditions should be defined by this point -- whether via - # the configuration file or the getters and setters - self.bclist = [self.BC_E, self.BC_W] - if self.dimension == 2: - self.bclist += [self.BC_N, self.BC_S] - # Now check that these are valid boundary conditions - for bc in self.bclist: - if self.dimension == 1: - if (bc == self.bc1D).any(): - pass + # Analytical solution boundary conditions + # If they aren't set, it is because no input file has been used + # Just set them to an empty string (like input file would do) + try: + self.BC_E + except: + self.BC_E = "" + try: + self.BC_W + except: + self.BC_W = "" + if self.dimension == 2: + try: + self.BC_S + except: + self.BC_S = "" + try: + self.BC_N + except: + self.BC_N = "" + else: + # Simplifies flow control a few lines down to define these as None-type + self.BC_S = None + self.BC_N = None + if ( + self.BC_E == "NoOutsideLoads" + or self.BC_E == "" + and self.BC_W == "NoOutsideLoads" + or self.BC_W == "" + ) and ( + self.dimension != 2 + or ( + self.BC_E == "NoOutsideLoads" + or self.BC_E == "" + and self.BC_W == "NoOutsideLoads" + or self.BC_W == "" + ) + ): + if ( + self.BC_E == "" + or self.BC_W == "" + or self.BC_S == "" + or self.BC_N == "" + ): + if self.Verbose: + print( + "Assuming NoOutsideLoads boundary condition, as this is implicit in the " + ) + print(" superposition-based analytical solution") else: - sys.exit("'"+bc+"'"+ " is not an acceptable 1D finite difference boundary condition\n"\ - +"and/or is not yet implement in the code. Acceptable boundary conditions\n"\ - +"are:\n"\ - +str(self.bc1D)+"\n"\ - +"Exiting.") - elif self.dimension == 2: - if (bc == self.bc2D).any(): - pass + if self.Quiet == False: + print("") + print(">>> BOUNDARY CONDITIONS IMPROPERLY DEFINED <<<") + print("") + print("For analytical solutions the boundaries must be either:") + print("") + print("* NoOutsideLoads (explicitly)") + print("* ") + print("") + print( + "The latter is to implictly indicate a desire to use the only" + ) + print("boundary condition available for the superposition-based") + print("analytical solutions.") + print( + "This check is in place to ensure that the user does not apply" + ) + print("boundary conditions for finite difference solutions to the") + print("analytical solutions and expect them to work.") + print("") + sys.exit() + + def coeffArraySizeCheck(self): + """ + Make sure that q0 and coefficient array are the right size compared to + each other (for finite difference if loading a pre-build coefficient + array). Otherwise, exit. + """ + if prod(self.coeff_matrix.shape) != long( + prod(np.array(self.qs.shape, dtype=int64) + 2) ** 2 + ): + print("Inconsistent size of q0 array and coefficient mattrix") + print("Exiting.") + sys.exit() + + def TeArraySizeCheck(self): + """ + Checks that Te and q0 array sizes are compatible + For finite difference solution. + """ + # Only if they are both defined and are arrays + # Both being arrays is a possible bug in this check routine that I have + # intentionally introduced + if type(self.Te) == np.ndarray and type(self.qs) == np.ndarray: + # Doesn't touch non-arrays or 1D arrays + if type(self.Te) is np.ndarray: + if (np.array(self.Te.shape) != np.array(self.qs.shape)).any(): + sys.exit("q0 and Te arrays have incompatible shapes. Exiting.") else: - sys.exit("'"+bc+"'"+ " is not an acceptable 2D finite difference boundary condition\n"\ - +"and/or is not yet implement in the code. Acceptable boundary conditions\n"\ - +"are:\n"\ - +str(self.bc2D)+"\n"\ - +"Exiting.") - else: - sys.exit("For a flexural solution, grid must be 1D or 2D. Exiting.") - else: - # Analytical solution boundary conditions - # If they aren't set, it is because no input file has been used - # Just set them to an empty string (like input file would do) - try: - self.BC_E - except: - self.BC_E = '' - try: - self.BC_W - except: - self.BC_W = '' - if self.dimension == 2: + if self.Debug: + print("Te and qs array sizes pass consistency check") + + ### need to determine its interface, it is best to have a uniform interface + ### no matter it is 1D or 2D; but if it can't be that way, we can set up a + ### variable-length arguments, which is the way how Python overloads functions. + + def FD(self): + """ + Set-up for the finite difference solution method + """ + if self.Verbose: + print("Finite Difference Solution Technique") + # Used to check for coeff_matrix here, but now doing so in self.bc_check() + # called by f1d and f2d at the start + # + # Define a stress-based qs = q0 + # But only if the latter has not already been defined + # (e.g., by the getters and setters) try: - self.BC_S + self.qs except: - self.BC_S = '' + self.qs = self.q0.copy() + # Remove self.q0 to avoid issues with multiply-defined inputs + # q0 is the parsable input to either a qs grid or contains (x,(y),q) + del self.q0 + # Give it x and y dimensions for help with plotting tools + # (not implemented internally, but a help with external methods) + self.x = np.arange(self.dx / 2.0, self.dx * self.qs.shape[0], self.dx) + if self.dimension == 2: + self.y = np.arange(self.dy / 2.0, self.dy * self.qs.shape[1], self.dy) + # Is there a solver defined try: - self.BC_N + self.Solver # See if it exists already except: - self.BC_N = '' - else: - # Simplifies flow control a few lines down to define these as None-type - self.BC_S = None - self.BC_N = None - if ( self.BC_E == 'NoOutsideLoads' or self.BC_E == '' \ - and self.BC_W == 'NoOutsideLoads' or self.BC_W == '' ) \ - and ( self.dimension != 2 \ - or (self.BC_E == 'NoOutsideLoads' or self.BC_E == '' \ - and self.BC_W == 'NoOutsideLoads' or self.BC_W == '') ): - if self.BC_E == '' or self.BC_W == '' \ - or self.BC_S == '' or self.BC_N == '': - if self.Verbose: - print("Assuming NoOutsideLoads boundary condition, as this is implicit in the ") - print(" superposition-based analytical solution") - else: - if self.Quiet == False: - print("") - print(">>> BOUNDARY CONDITIONS IMPROPERLY DEFINED <<<") - print("") - print("For analytical solutions the boundaries must be either:") - print("") - print("* NoOutsideLoads (explicitly)") - print("* ") - print("") - print("The latter is to implictly indicate a desire to use the only") - print("boundary condition available for the superposition-based") - print("analytical solutions.") - print("This check is in place to ensure that the user does not apply") - print("boundary conditions for finite difference solutions to the") - print("analytical solutions and expect them to work.") - print("") - sys.exit() - - def coeffArraySizeCheck(self): - """ - Make sure that q0 and coefficient array are the right size compared to - each other (for finite difference if loading a pre-build coefficient - array). Otherwise, exit. - """ - if prod(self.coeff_matrix.shape) != long(prod(np.array(self.qs.shape,dtype=int64)+2)**2): - print("Inconsistent size of q0 array and coefficient mattrix") - print("Exiting.") - sys.exit() - - def TeArraySizeCheck(self): - """ - Checks that Te and q0 array sizes are compatible - For finite difference solution. - """ - # Only if they are both defined and are arrays - # Both being arrays is a possible bug in this check routine that I have - # intentionally introduced - if type(self.Te) == np.ndarray and type(self.qs) == np.ndarray: - # Doesn't touch non-arrays or 1D arrays - if type(self.Te) is np.ndarray: - if (np.array(self.Te.shape) != np.array(self.qs.shape)).any(): - sys.exit("q0 and Te arrays have incompatible shapes. Exiting.") - else: - if self.Debug: print("Te and qs array sizes pass consistency check") - - ### need to determine its interface, it is best to have a uniform interface - ### no matter it is 1D or 2D; but if it can't be that way, we can set up a - ### variable-length arguments, which is the way how Python overloads functions. - - def FD(self): - """ - Set-up for the finite difference solution method - """ - if self.Verbose: - print("Finite Difference Solution Technique") - # Used to check for coeff_matrix here, but now doing so in self.bc_check() - # called by f1d and f2d at the start - # - # Define a stress-based qs = q0 - # But only if the latter has not already been defined - # (e.g., by the getters and setters) - try: - self.qs - except: - self.qs = self.q0.copy() - # Remove self.q0 to avoid issues with multiply-defined inputs - # q0 is the parsable input to either a qs grid or contains (x,(y),q) - del self.q0 - # Give it x and y dimensions for help with plotting tools - # (not implemented internally, but a help with external methods) - self.x = np.arange(self.dx/2., self.dx * self.qs.shape[0], self.dx) - if self.dimension == 2: - self.y = np.arange(self.dy/2., self.dy * self.qs.shape[1], self.dy) - # Is there a solver defined - try: - self.Solver # See if it exists already - except: - # Well, will fail if it doesn't see this, maybe not the most reasonable - # error message. - if self.filename: - self.Solver = self.configGet("string", "numerical", "Solver") - else: - sys.exit("No solver defined!") - # Check consistency of size if coeff array was loaded - if self.filename: - # In the case that it is iterative, find the convergence criterion - self.iterative_ConvergenceTolerance = self.configGet("float", "numerical", "ConvergenceTolerance") - # Try to import Te grid or scalar for the finite difference solution - try: - self.Te = self.configGet("float", "input", "ElasticThickness", optional=False) - if self.Te is None: - Tepath = self.configGet("string", "input", "ElasticThickness", optional=False) - self.Te = Tepath - else: - Tepath = None - except: - Tepath = self.configGet("string", "input", "ElasticThickness", optional=False) - self.Te = Tepath - if self.Te is None: - if self.coeff_matrix is not None: - pass - else: - # Have to bring this out here in case it was discovered in the - # try statement that there is no value given - sys.exit("No input elastic thickness or coefficient matrix supplied.") - # or if getter/setter - if type(self.Te) == str: - # Try to import Te grid or scalar for the finite difference solution - Tepath = self.Te - else: - Tepath = None # in case no self.filename present (like for GRASS GIS) - # If there is a Tepath, import Te - # Assume that even if a coeff_matrix is defined - # That the user wants Te if they gave the path - if Tepath: - self.Te = self.loadFile(self.Te, close_on_fail = False) - if self.Te is None: - print("Requested Te file is provided but cannot be located.") - print("No scalar elastic thickness is provided in configuration file") - print("(Typo in path to input Te grid?)") - if self.coeff_matrix is not None: - print("But a coefficient matrix has been found.") - print("Calculations will be carried forward using it.") + # Well, will fail if it doesn't see this, maybe not the most reasonable + # error message. + if self.filename: + self.Solver = self.configGet("string", "numerical", "Solver") + else: + sys.exit("No solver defined!") + # Check consistency of size if coeff array was loaded + if self.filename: + # In the case that it is iterative, find the convergence criterion + self.iterative_ConvergenceTolerance = self.configGet( + "float", "numerical", "ConvergenceTolerance" + ) + # Try to import Te grid or scalar for the finite difference solution + try: + self.Te = self.configGet( + "float", "input", "ElasticThickness", optional=False + ) + if self.Te is None: + Tepath = self.configGet( + "string", "input", "ElasticThickness", optional=False + ) + self.Te = Tepath + else: + Tepath = None + except: + Tepath = self.configGet( + "string", "input", "ElasticThickness", optional=False + ) + self.Te = Tepath + if self.Te is None: + if self.coeff_matrix is not None: + pass + else: + # Have to bring this out here in case it was discovered in the + # try statement that there is no value given + sys.exit( + "No input elastic thickness or coefficient matrix supplied." + ) + # or if getter/setter + if type(self.Te) == str: + # Try to import Te grid or scalar for the finite difference solution + Tepath = self.Te else: - print("Exiting.") - sys.exit() - - # Check that Te is the proper size if it was loaded - # Will be array if it was loaded - if self.Te.any(): - self.TeArraySizeCheck() + Tepath = None # in case no self.filename present (like for GRASS GIS) + # If there is a Tepath, import Te + # Assume that even if a coeff_matrix is defined + # That the user wants Te if they gave the path + if Tepath: + self.Te = self.loadFile(self.Te, close_on_fail=False) + if self.Te is None: + print("Requested Te file is provided but cannot be located.") + print("No scalar elastic thickness is provided in configuration file") + print("(Typo in path to input Te grid?)") + if self.coeff_matrix is not None: + print("But a coefficient matrix has been found.") + print("Calculations will be carried forward using it.") + else: + print("Exiting.") + sys.exit() - ### need work - def FFT(self): - pass + # Check that Te is the proper size if it was loaded + # Will be array if it was loaded + if self.Te.any(): + self.TeArraySizeCheck() - # SAS and SAS_NG are the exact same here; leaving separate just for symmetry - # with other functions + ### need work + def FFT(self): + pass - def SAS(self): - """ - Set-up for the rectangularly-gridded superposition of analytical solutions - method for solving flexure - """ - if self.x is None: - self.x = np.arange(self.dx/2., self.dx * self.qs.shape[0], self.dx) - if self.filename: - # Define the (scalar) elastic thickness - self.Te = self.configGet("float", "input", "ElasticThickness") - # Define a stress-based qs = q0 - self.qs = self.q0.copy() - # Remove self.q0 to avoid issues with multiply-defined inputs - # q0 is the parsable input to either a qs grid or contains (x,(y),q) - del self.q0 - if self.dimension == 2: - if self.y is None: - self.y = np.arange(self.dy/2., self.dy * self.qs.shape[0], self.dy) - # Define a stress-based qs = q0 - # But only if the latter has not already been defined - # (e.g., by the getters and setters) - try: - self.qs - except: - self.qs = self.q0.copy() + # SAS and SAS_NG are the exact same here; leaving separate just for symmetry + # with other functions + + def SAS(self): + """ + Set-up for the rectangularly-gridded superposition of analytical solutions + method for solving flexure + """ + if self.x is None: + self.x = np.arange(self.dx / 2.0, self.dx * self.qs.shape[0], self.dx) + if self.filename: + # Define the (scalar) elastic thickness + self.Te = self.configGet("float", "input", "ElasticThickness") + # Define a stress-based qs = q0 + self.qs = self.q0.copy() + # Remove self.q0 to avoid issues with multiply-defined inputs + # q0 is the parsable input to either a qs grid or contains (x,(y),q) + del self.q0 + if self.dimension == 2: + if self.y is None: + self.y = np.arange(self.dy / 2.0, self.dy * self.qs.shape[0], self.dy) + # Define a stress-based qs = q0 + # But only if the latter has not already been defined + # (e.g., by the getters and setters) + try: + self.qs + except: + self.qs = self.q0.copy() + # Remove self.q0 to avoid issues with multiply-defined inputs + # q0 is the parsable input to either a qs grid or contains (x,(y),q) + del self.q0 + from scipy.special import kei + + def SAS_NG(self): + """ + Set-up for the ungridded superposition of analytical solutions + method for solving flexure + """ + if self.filename: + # Define the (scalar) elastic thickness + self.Te = self.configGet("float", "input", "ElasticThickness") + # See if it wants to be run in lat/lon + # Could put under in 2D if-statement, but could imagine an eventual desire + # to change this and have 1D lat/lon profiles as well. + # So while the options will be under "numerical2D", this place here will + # remain held for an eventual future. + self.latlon = self.configGet( + "string", "numerical2D", "latlon", optional=True + ) + self.PlanetaryRadius = self.configGet( + "float", "numerical2D", "PlanetaryRadius", optional=True + ) + if self.dimension == 2: + from scipy.special import kei + # Parse out input q0 into variables of imoprtance for solution + if self.dimension == 1: + try: + # If these have already been set, e.g., by getters/setters, great! + self.x + self.q + except: + # Using [x, y, w] configuration file + if self.q0.shape[1] == 2: + self.x = self.q0[:, 0] + self.q = self.q0[:, 1] + else: + sys.exit( + "For 1D (ungridded) SAS_NG configuration file, need [x,w] array. Your dimensions are: " + + str(self.q0.shape) + ) + else: + try: + # If these have already been set, e.g., by getters/setters, great! + self.x + self.u + self.q + except: + # Using [x, y, w] configuration file + if self.q0.shape[1] == 3: + self.x = self.q0[:, 0] + self.y = self.q0[:, 1] + self.q = self.q0[:, 2] + else: + sys.exit( + "For 2D (ungridded) SAS_NG configuration file, need [x,y,w] array. Your dimensions are: " + + str(self.q0.shape) + ) + # x, y are in absolute coordinates. Create a local grid reference to + # these. This local grid, which starts at (0,0), is defined just so that + # we have a way of running the model without defined real-world + # coordinates + self.x = self.x + if self.dimension == 2: + self.y = self.y # Remove self.q0 to avoid issues with multiply-defined inputs # q0 is the parsable input to either a qs grid or contains (x,(y),q) del self.q0 - from scipy.special import kei - def SAS_NG(self): - """ - Set-up for the ungridded superposition of analytical solutions - method for solving flexure - """ - if self.filename: - # Define the (scalar) elastic thickness - self.Te = self.configGet("float", "input", "ElasticThickness") - # See if it wants to be run in lat/lon - # Could put under in 2D if-statement, but could imagine an eventual desire - # to change this and have 1D lat/lon profiles as well. - # So while the options will be under "numerical2D", this place here will - # remain held for an eventual future. - self.latlon = self.configGet("string", "numerical2D", "latlon", optional=True) - self.PlanetaryRadius = self.configGet("float", "numerical2D", "PlanetaryRadius", optional=True) - if self.dimension == 2: - from scipy.special import kei - # Parse out input q0 into variables of imoprtance for solution - if self.dimension == 1: - try: - # If these have already been set, e.g., by getters/setters, great! - self.x - self.q - except: - # Using [x, y, w] configuration file - if self.q0.shape[1] == 2: - self.x = self.q0[:,0] - self.q = self.q0[:,1] - else: - sys.exit("For 1D (ungridded) SAS_NG configuration file, need [x,w] array. Your dimensions are: "+str(self.q0.shape)) - else: - try: - # If these have already been set, e.g., by getters/setters, great! - self.x - self.u - self.q - except: - # Using [x, y, w] configuration file - if self.q0.shape[1] == 3: - self.x = self.q0[:,0] - self.y = self.q0[:,1] - self.q = self.q0[:,2] - else: - sys.exit("For 2D (ungridded) SAS_NG configuration file, need [x,y,w] array. Your dimensions are: "+str(self.q0.shape)) - # x, y are in absolute coordinates. Create a local grid reference to - # these. This local grid, which starts at (0,0), is defined just so that - # we have a way of running the model without defined real-world - # coordinates - self.x = self.x - if self.dimension == 2: - self.y = self.y - # Remove self.q0 to avoid issues with multiply-defined inputs - # q0 is the parsable input to either a qs grid or contains (x,(y),q) - del self.q0 - - # Check if a seperate output set of x,y points has been defined - # otherwise, set those values to None - # First, try to load the arrays - try: - self.xw - except: - try: - self.xw = self.configGet('string', "input", "xw", optional=True) - if self.xw == '': - self.xw = None - except: - self.xw = None - # If strings, load arrays - if type(self.xw) == str: - self.xw = self.loadFile(self.xw) - if self.dimension == 2: - try: - # already set by setter? - self.yw - except: + # Check if a seperate output set of x,y points has been defined + # otherwise, set those values to None + # First, try to load the arrays try: - self.yw = self.configGet('string', "input", "yw", optional=True ) - if self.yw == '': - self.yw = None + self.xw except: - self.yw = None - # At this point, can check if we have both None or both defined - if (self.xw is not None and self.yw is None) \ - or (self.xw is None and self.yw is not None): - sys.exit("SAS_NG output at specified points requires both xw and yw to be defined") - # All right, now just finish defining - if type(self.yw) == str: - self.yw = self.loadFile(self.yw) - elif self.yw is None: - self.yw = self.y.copy() - if self.xw is None: - self.xw = self.x.copy() + try: + self.xw = self.configGet("string", "input", "xw", optional=True) + if self.xw == "": + self.xw = None + except: + self.xw = None + # If strings, load arrays + if type(self.xw) == str: + self.xw = self.loadFile(self.xw) + if self.dimension == 2: + try: + # already set by setter? + self.yw + except: + try: + self.yw = self.configGet("string", "input", "yw", optional=True) + if self.yw == "": + self.yw = None + except: + self.yw = None + # At this point, can check if we have both None or both defined + if (self.xw is not None and self.yw is None) or ( + self.xw is None and self.yw is not None + ): + sys.exit( + "SAS_NG output at specified points requires both xw and yw to be defined" + ) + # All right, now just finish defining + if type(self.yw) == str: + self.yw = self.loadFile(self.yw) + elif self.yw is None: + self.yw = self.y.copy() + if self.xw is None: + self.xw = self.x.copy() diff --git a/gflex/f1d.py b/gflex/f1d.py index 07498ff..328c4ae 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -24,436 +24,491 @@ class F1D(Flexure): - def initialize(self, filename=None): - self.dimension = 1 # Set it here in case it wasn't set for selection before - super().initialize() - if self.Verbose: print("F1D initialized") - - def run(self): - self.bc_check() - self.solver_start_time = time.time() - if self.Method == 'FD': - # Finite difference - super().FD() - self.method_func = self.FD - elif self.Method == 'FFT': - # Fast Fourier transform - super().FFT() - self.method_func = self.FFT - elif self.Method == "SAS": - # Superposition of analytical solutions - super().SAS() - self.method_func = self.SAS - elif self.Method == "SAS_NG": - # Superposition of analytical solutions, - # nonuniform points - super().SAS_NG() - self.method_func = self.SAS_NG - else: - sys.exit('Error: method must be "FD", "FFT", "SAS", or "SAS_NG"') - - if self.Verbose: print("F1D run") - self.method_func() - - self.time_to_solve = time.time() - self.solver_start_time - if self.Quiet == False: - print("Time to solve [s]:", self.time_to_solve) - - def finalize(self): - # If elastic thickness has been padded, return it to its original - # value, so this is not messed up for repeat operations in a - # model-coupling exercise - try: - self.Te = self.Te_unpadded - except: - pass - if self.Verbose: print("F1D finalized") - super().finalize() - - ######################################## - ## FUNCTIONS FOR EACH SOLUTION METHOD ## - ######################################## - - def FD(self): - self.gridded_x() - # Only generate coefficient matrix if it is not already provided - if self.coeff_matrix is not None: - pass - else: - self.elasprepFD() # define dx4 and D within self - self.BC_selector_and_coeff_matrix_creator() - self.fd_solve() # Get the deflection, "w" - - def FFT(self): - if self.plotChoice: - self.gridded_x() - sys.exit("The fast Fourier transform solution method is not yet implemented.") - - def SAS(self): - self.gridded_x() - self.spatialDomainVarsSAS() - self.spatialDomainGridded() - - def SAS_NG(self): - self.spatialDomainVarsSAS() - self.spatialDomainNoGrid() - - ###################################### - ## FUNCTIONS TO SOLVE THE EQUATIONS ## - ###################################### - - - ## UTILITY - ############ - - def gridded_x(self): - self.nx = self.qs.shape[0] - self._x_local = np.arange(0,self.dx*self.nx,self.dx) - - - ## SPATIAL DOMAIN SUPERPOSITION OF ANALYTICAL SOLUTIONS - ######################################################### - - # SETUP - - def spatialDomainVarsSAS(self): - # Check Te: - # * If scalar, okay. - # * If grid, convert to scalar if a singular value - # * Else, throw an error. - if np.isscalar(self.Te): - pass - elif np.all( self.Te == np.mean(self.Te) ): - self.Te = np.mean(self.Te) - else: - sys.exit("\nINPUT VARIABLE TYPE INCONSISTENT WITH SOLUTION TYPE.\n" - "The analytical solution requires a scalar Te.\n" - "(gFlex is smart enough to make this out of a uniform\n" - "array, but won't know what value you want with a spatially\n" - "varying array! Try finite difference instead in this case?\n" - "EXITING.") - - self.D = self.E*self.Te**3/(12*(1-self.nu**2)) # Flexural rigidity - self.alpha = (4*self.D/(self.drho*self.g))**.25 # 1D flexural parameter - self.coeff = self.alpha**3/(8*self.D) - - # UNIFORM DX ("GRIDDED"): LOADS PROVIDED AS AN ARRAY WITH KNOWN DX TO - # CONVERT LOAD MAGNITUDE AT A POINT INTO MASS INTEGRATED ACROSS DX - - def spatialDomainGridded(self): - - self.w = np.zeros(self.nx) # Deflection array - - for i in range(self.nx): - # Loop over locations that have loads, and sum - if self.qs[i]: - dist = abs(self._x_local[i]-self._x_local) - # -= b/c pos load leads to neg (downward) deflection - self.w -= self.qs[i] * self.coeff * self.dx * np.exp(-dist/self.alpha) * \ - (np.cos(dist/self.alpha) + np.sin(dist/self.alpha)) - # No need to return: w already belongs to "self" - - - # NONUNIFORM DX (NO GRID): ARBITRARILY-SPACED POINT LOADS - # So essentially a sum of Green's functions for flexural response - - def spatialDomainNoGrid(self): - """ - Superposition of analytical solutions without a gridded domain - """ - self.w = np.zeros(self.xw.shape) - - if self.Debug: - print("w = ") - print(self.w.shape) - - for i in range(len(self.q)): - # More efficient if we have created some 0-load points - # (e.g., for where we want output) - if self.q[i] != 0: - dist = np.abs(self.xw - self.x[i]) - self.w -= self.q[i] * self.coeff * np.exp(-dist/self.alpha) * \ - ( np.cos(dist/self.alpha) + np.sin(dist/self.alpha) ) - - ## FINITE DIFFERENCE - ###################### - - def elasprepFD(self): - """ - dx4, D = elasprepFD(dx,Te,E=1E11,nu=0.25) - - Defines the variables (except for the subset flexural rigidity) that are - needed to run "coeff_matrix_1d" - """ - self.dx4 = self.dx**4 - self.dx2 = self.dx**2 # Needed if horizontal (i.e., tectonic) stresses - self.D = self.E*self.Te**3/(12*(1-self.nu**2)) - - def BC_selector_and_coeff_matrix_creator(self): - """ - Selects the boundary conditions - Then calls the function to build the pentadiagonal matrix to solve - 1D flexure with variable (or constant) elsatic thickness - """ - - # Zeroth, start the timer and print the boundary conditions to the screen - self.coeff_start_time = time.time() - if self.Verbose: - print("Boundary condition, West:", self.BC_W, type(self.BC_W)) - print("Boundary condition, East:", self.BC_E, type(self.BC_E)) - - # First, set flexural rigidity boundary conditions to flesh out this padded - # array - self.BC_Rigidity() - - # Second, build the coefficient arrays -- with the rigidity b.c.'s - self.get_coeff_values() - - # Third, apply boundary conditions to the coeff_arrays to create the - # flexural solution - self.BC_Flexure() - - # Fourth, construct the sparse diagonal array - self.build_diagonals() - - # Finally, compute the total time this process took - self.coeff_creation_time = time.time() - self.coeff_start_time - if self.Quiet == False: - print("Time to construct coefficient (operator) array [s]:", self.coeff_creation_time) - - def BC_Rigidity(self): - """ - Utility function to help implement boundary conditions by specifying - them for and applying them to the elastic thickness grid - """ + def initialize(self, filename=None): + self.dimension = 1 # Set it here in case it wasn't set for selection before + super().initialize() + if self.Verbose: + print("F1D initialized") + + def run(self): + self.bc_check() + self.solver_start_time = time.time() + if self.Method == "FD": + # Finite difference + super().FD() + self.method_func = self.FD + elif self.Method == "FFT": + # Fast Fourier transform + super().FFT() + self.method_func = self.FFT + elif self.Method == "SAS": + # Superposition of analytical solutions + super().SAS() + self.method_func = self.SAS + elif self.Method == "SAS_NG": + # Superposition of analytical solutions, + # nonuniform points + super().SAS_NG() + self.method_func = self.SAS_NG + else: + sys.exit('Error: method must be "FD", "FFT", "SAS", or "SAS_NG"') + + if self.Verbose: + print("F1D run") + self.method_func() + + self.time_to_solve = time.time() - self.solver_start_time + if self.Quiet == False: + print("Time to solve [s]:", self.time_to_solve) + + def finalize(self): + # If elastic thickness has been padded, return it to its original + # value, so this is not messed up for repeat operations in a + # model-coupling exercise + try: + self.Te = self.Te_unpadded + except: + pass + if self.Verbose: + print("F1D finalized") + super().finalize() + + ######################################## + ## FUNCTIONS FOR EACH SOLUTION METHOD ## + ######################################## + + def FD(self): + self.gridded_x() + # Only generate coefficient matrix if it is not already provided + if self.coeff_matrix is not None: + pass + else: + self.elasprepFD() # define dx4 and D within self + self.BC_selector_and_coeff_matrix_creator() + self.fd_solve() # Get the deflection, "w" + + def FFT(self): + if self.plotChoice: + self.gridded_x() + sys.exit("The fast Fourier transform solution method is not yet implemented.") + + def SAS(self): + self.gridded_x() + self.spatialDomainVarsSAS() + self.spatialDomainGridded() + + def SAS_NG(self): + self.spatialDomainVarsSAS() + self.spatialDomainNoGrid() + + ###################################### + ## FUNCTIONS TO SOLVE THE EQUATIONS ## + ###################################### + + ## UTILITY + ############ + + def gridded_x(self): + self.nx = self.qs.shape[0] + self._x_local = np.arange(0, self.dx * self.nx, self.dx) + + ## SPATIAL DOMAIN SUPERPOSITION OF ANALYTICAL SOLUTIONS + ######################################################### + + # SETUP + + def spatialDomainVarsSAS(self): + # Check Te: + # * If scalar, okay. + # * If grid, convert to scalar if a singular value + # * Else, throw an error. + if np.isscalar(self.Te): + pass + elif np.all(self.Te == np.mean(self.Te)): + self.Te = np.mean(self.Te) + else: + sys.exit( + "\nINPUT VARIABLE TYPE INCONSISTENT WITH SOLUTION TYPE.\n" + "The analytical solution requires a scalar Te.\n" + "(gFlex is smart enough to make this out of a uniform\n" + "array, but won't know what value you want with a spatially\n" + "varying array! Try finite difference instead in this case?\n" + "EXITING." + ) + + self.D = self.E * self.Te**3 / (12 * (1 - self.nu**2)) # Flexural rigidity + self.alpha = ( + 4 * self.D / (self.drho * self.g) + ) ** 0.25 # 1D flexural parameter + self.coeff = self.alpha**3 / (8 * self.D) + + # UNIFORM DX ("GRIDDED"): LOADS PROVIDED AS AN ARRAY WITH KNOWN DX TO + # CONVERT LOAD MAGNITUDE AT A POINT INTO MASS INTEGRATED ACROSS DX + + def spatialDomainGridded(self): + self.w = np.zeros(self.nx) # Deflection array + + for i in range(self.nx): + # Loop over locations that have loads, and sum + if self.qs[i]: + dist = abs(self._x_local[i] - self._x_local) + # -= b/c pos load leads to neg (downward) deflection + self.w -= ( + self.qs[i] + * self.coeff + * self.dx + * np.exp(-dist / self.alpha) + * (np.cos(dist / self.alpha) + np.sin(dist / self.alpha)) + ) + # No need to return: w already belongs to "self" + + # NONUNIFORM DX (NO GRID): ARBITRARILY-SPACED POINT LOADS + # So essentially a sum of Green's functions for flexural response + + def spatialDomainNoGrid(self): + """ + Superposition of analytical solutions without a gridded domain + """ + self.w = np.zeros(self.xw.shape) - ######################################### - # FLEXURAL RIGIDITY BOUNDARY CONDITIONS # - ######################################### - # West - if self.BC_W == 'Periodic': - self.BC_Rigidity_W = 'periodic' - elif (self.BC_W == np.array(['0Displacement0Slope', '0Moment0Shear', '0Slope0Shear'])).any(): - self.BC_Rigidity_W = '0 curvature' - elif self.BC_W == 'Mirror': - self.BC_Rigidity_W = 'mirror symmetry' - else: - sys.exit("Invalid Te B.C. case") - # East - if self.BC_E == 'Periodic': - self.BC_Rigidity_E = 'periodic' - elif (self.BC_E == np.array(['0Displacement0Slope', '0Moment0Shear', '0Slope0Shear'])).any(): - self.BC_Rigidity_E = '0 curvature' - elif self.BC_E == 'Mirror': - self.BC_Rigidity_E = 'mirror symmetry' - else: - sys.exit("Invalid Te B.C. case") - - ############# - # PAD ARRAY # - ############# - if np.isscalar(self.Te): - self.D *= np.ones(self.qs.shape) # And leave Te as a scalar for checks - else: - self.Te_unpadded = self.Te.copy() - # F2D keeps this inside the "else" and handles this differently, - # largely because it has different ways of computing the flexural - # response with variable Te. We'll keep everything simpler here and - # just pad this array so it can be sent through the same process - # to create the coefficient arrays. - self.D = np.hstack([np.nan, self.D, np.nan]) - - ############################################################### - # APPLY FLEXURAL RIGIDITY BOUNDARY CONDITIONS TO PADDED ARRAY # - ############################################################### - if self.BC_Rigidity_W == "0 curvature": - self.D[0] = 2*self.D[1] - self.D[2] - if self.BC_Rigidity_E == "0 curvature": - self.D[-1] = 2*self.D[-2] - self.D[-3] - if self.BC_Rigidity_W == "mirror symmetry": - self.D[0] = self.D[2] - if self.BC_Rigidity_E == "mirror symmetry": - self.D[-1] = self.D[-3] - if self.BC_Rigidity_W == "periodic": - self.D[0] = self.D[-2] - if self.BC_Rigidity_E == "periodic": - self.D[-1] = self.D[-3] - - def get_coeff_values(self): - - ############################## - # BUILD GENERAL COEFFICIENTS # - ############################## - - # l2 corresponds to top value in solution vector, so to the left (-) side - # Good reference for how to determine central difference (and other) coefficients is: - # Fornberg, 1998: Generation of Finite Difference Formulas on Arbitrarily Spaced Grids - - ################################################### - # DEFINE SUB-ARRAYS FOR DERIVATIVE DISCRETIZATION # - ################################################### - Dm1 = self.D[:-2] - D0 = self.D[1:-1] - Dp1 = self.D[2:] - - ########################################################### - # DEFINE COEFFICIENTS TO W_-2 -- W_+2 WITH B.C.'S APPLIED # - ########################################################### - self.l2_coeff_i = ( Dm1/2. + D0 - Dp1/2. ) / self.dx4 - self.l1_coeff_i = ( -6.*D0 + 2.*Dp1 ) / self.dx4 - self.sigma_xx*self.Te/self.dx2 - self.c0_coeff_i = ( -2.*Dm1 + 10.*D0 - 2.*Dp1 ) / self.dx4 + 2*self.sigma_xx*self.Te/self.dx2 + self.drho*self.g - self.r1_coeff_i = ( 2.*Dm1 - 6.*D0 ) / self.dx4 - self.sigma_xx*self.Te/self.dx2 - self.r2_coeff_i = ( -Dm1/2. + D0 + Dp1/2. ) / self.dx4 - # These will be just the 1, -4, 6, -4, 1 for constant Te - - ################################################################### - # START DIAGONALS AS SIMPLY THE BASE COEFFICIENTS, WITH NO B.C.'S # - ################################################################### - self.l2 = self.l2_coeff_i.copy() - self.l1 = self.l1_coeff_i.copy() - self.c0 = self.c0_coeff_i.copy() - self.r1 = self.r1_coeff_i.copy() - self.r2 = self.r2_coeff_i.copy() - - # Number of columns; equals number of rows too - square coeff matrix - self.ncolsx = self.c0.shape[0] - - # Either way, the way that Scipy stacks is not the same way that I calculate - # the rows. It runs offsets down the column instead of across the row. So - # to simulate this, I need to re-zero everything. To do so, I use - # numpy.roll. (See self.build_diagonals.) - - def BC_Flexure(self): - - # Some links that helped me teach myself how to set up the boundary conditions - # in the matrix for the flexure problem: - # - # Good explanation of and examples of boundary conditions - # https://en.wikipedia.org/wiki/Euler%E2%80%93Bernoulli_beam_theory#Boundary_considerations - # - # Copy of Fornberg table: - # https://en.wikipedia.org/wiki/Finite_difference_coefficient - # - # Implementing b.c.'s: - # http://scicomp.stackexchange.com/questions/5355/writing-the-poisson-equation-finite-difference-matrix-with-neumann-boundary-cond - # http://scicomp.stackexchange.com/questions/7175/trouble-implementing-neumann-boundary-conditions-because-the-ghost-points-cannot - - if self.Verbose: - print("Boundary condition, West:", self.BC_W, type(self.BC_W)) - print("Boundary condition, East:", self.BC_E, type(self.BC_E)) - - # In 2D, these are handled inside the function; in 1D, there are separate - # defined functions. Keeping these due to inertia and fear of cut/paste - # mistakes - if self.BC_E == '0Displacement0Slope' or self.BC_W == '0Displacement0Slope': - self.BC_0Displacement0Slope() - if self.BC_E == '0Slope0Shear' or self.BC_W == '0Slope0Shear': - self.BC_0Slope0Shear() - if self.BC_E == '0Moment0Shear' or self.BC_W == '0Moment0Shear': - self.BC_0Moment0Shear() - if self.BC_E == 'Mirror' or self.BC_W == 'Mirror': - self.BC_Mirror() - if self.BC_E == 'Periodic' and self.BC_W == 'Periodic': - self.BC_Periodic() - if self.BC_E == 'Sandbox' or self.BC_W == 'Sandbox': - # Sandbox is the developer's testing ground - sys.exit("Sandbox Closed") - - def build_diagonals(self): - """ - Builds the diagonals for the coefficient array - """ - - ########################################################## - # INCORPORATE BOUNDARY CONDITIONS INTO COEFFICIENT ARRAY # - ########################################################## - - # Roll to keep the proper coefficients at the proper places in the - # arrays: Python will naturally just do vertical shifts instead of - # diagonal shifts, so this takes into account the horizontal compoent - # to ensure that boundary values are at the right place. - self.l2 = np.roll(self.l2, -2) - self.l1 = np.roll(self.l1, -1) - self.r1 = np.roll(self.r1, 1) - self.r2 = np.roll(self.r2, 2) - - # Then assemble these rows: this is where the periodic boundary condition - # can matter. - if self.coeff_matrix is not None: - pass - elif self.BC_E == 'Periodic' and self.BC_W == 'Periodic': - # In this case, the boundary-condition-related stacking has already - # happened inside b.c.-handling function. This is because periodic - # boundary conditions require extra diagonals to exist on the edges of - # the solution array - pass - else: - self.diags = np.vstack((self.l2,self.l1,self.c0,self.r1,self.r2)) - self.offsets = np.array([-2,-1,0,1,2]) - - # Everybody now (including periodic b.c. cases) - self.coeff_matrix = spdiags(self.diags, self.offsets, self.nx, self.nx, format='csr') - - def BC_Periodic(self): - """ - Periodic boundary conditions: wraparound to the other side. - """ - if self.BC_E == 'Periodic' and self.BC_W == 'Periodic': - # If both boundaries are periodic, we are good to go (and self-consistent) - pass # It is just a shift in the coeff. matrix creation. - else: - # If only one boundary is periodic and the other doesn't implicitly - # involve a periodic boundary, this is illegal! - # I could allow it, but would have to rewrite the Periodic b.c. case, - # which I don't want to do to allow something that doesn't make - # physical sense... so if anyone wants to do this for some unforeseen - # reason, they can just split my function into two pieces themselves.i - sys.exit("Having the boundary opposite a periodic boundary condition\n"+ - "be fixed and not include an implicit periodic boundary\n"+ - "condition makes no physical sense.\n"+ - "Please fix the input boundary conditions. Aborting.") - self.diags = np.vstack((self.r1,self.r2,self.l2,self.l1,self.c0,self.r1,self.r2,self.l2,self.l1)) - self.offsets = np.array([1-self.ncolsx,2-self.ncolsx,-2,-1,0,1,2,self.ncolsx-2,self.ncolsx-1]) - - def BC_0Displacement0Slope(self): - """ - 0Displacement0Slope boundary condition for 0 deflection. - This requires that nothing be done to the edges of the solution array, - because the lack of the off-grid terms implies that they go to 0 - Here we just turn the cells outside the array into nan, to ensure that - we are not accidentally including the wrong cells here (and for consistency - with the other solution types -- this takes negligible time) - """ - if self.BC_W == '0Displacement0Slope': - i=0 - self.l2[i] = np.nan - self.l1[i] = np.nan - self.c0[i] += 0 - self.r1[i] += 0 - self.r2[i] += 0 - i=1 - self.l2[i] = np.nan - self.l1[i] += 0 - self.c0[i] += 0 - self.r1[i] += 0 - self.r2[i] += 0 - if self.BC_E == '0Displacement0Slope': - i=-2 - self.l2[i] += 0 - self.l1[i] += 0 - self.c0[i] += 0 - self.r1[i] += 0 - self.r2[i] = np.nan - i=-1 - self.l2[i] += 0 - self.l1[i] += 0 - self.c0[i] += 0 - self.r1[i] = np.nan - self.r2[i] = np.nan - - def BC_0Slope0Shear(self): - i=0 - """ + if self.Debug: + print("w = ") + print(self.w.shape) + + for i in range(len(self.q)): + # More efficient if we have created some 0-load points + # (e.g., for where we want output) + if self.q[i] != 0: + dist = np.abs(self.xw - self.x[i]) + self.w -= ( + self.q[i] + * self.coeff + * np.exp(-dist / self.alpha) + * (np.cos(dist / self.alpha) + np.sin(dist / self.alpha)) + ) + + ## FINITE DIFFERENCE + ###################### + + def elasprepFD(self): + """ + dx4, D = elasprepFD(dx,Te,E=1E11,nu=0.25) + + Defines the variables (except for the subset flexural rigidity) that are + needed to run "coeff_matrix_1d" + """ + self.dx4 = self.dx**4 + self.dx2 = self.dx**2 # Needed if horizontal (i.e., tectonic) stresses + self.D = self.E * self.Te**3 / (12 * (1 - self.nu**2)) + + def BC_selector_and_coeff_matrix_creator(self): + """ + Selects the boundary conditions + Then calls the function to build the pentadiagonal matrix to solve + 1D flexure with variable (or constant) elsatic thickness + """ + + # Zeroth, start the timer and print the boundary conditions to the screen + self.coeff_start_time = time.time() + if self.Verbose: + print("Boundary condition, West:", self.BC_W, type(self.BC_W)) + print("Boundary condition, East:", self.BC_E, type(self.BC_E)) + + # First, set flexural rigidity boundary conditions to flesh out this padded + # array + self.BC_Rigidity() + + # Second, build the coefficient arrays -- with the rigidity b.c.'s + self.get_coeff_values() + + # Third, apply boundary conditions to the coeff_arrays to create the + # flexural solution + self.BC_Flexure() + + # Fourth, construct the sparse diagonal array + self.build_diagonals() + + # Finally, compute the total time this process took + self.coeff_creation_time = time.time() - self.coeff_start_time + if self.Quiet == False: + print( + "Time to construct coefficient (operator) array [s]:", + self.coeff_creation_time, + ) + + def BC_Rigidity(self): + """ + Utility function to help implement boundary conditions by specifying + them for and applying them to the elastic thickness grid + """ + + ######################################### + # FLEXURAL RIGIDITY BOUNDARY CONDITIONS # + ######################################### + # West + if self.BC_W == "Periodic": + self.BC_Rigidity_W = "periodic" + elif ( + self.BC_W + == np.array(["0Displacement0Slope", "0Moment0Shear", "0Slope0Shear"]) + ).any(): + self.BC_Rigidity_W = "0 curvature" + elif self.BC_W == "Mirror": + self.BC_Rigidity_W = "mirror symmetry" + else: + sys.exit("Invalid Te B.C. case") + # East + if self.BC_E == "Periodic": + self.BC_Rigidity_E = "periodic" + elif ( + self.BC_E + == np.array(["0Displacement0Slope", "0Moment0Shear", "0Slope0Shear"]) + ).any(): + self.BC_Rigidity_E = "0 curvature" + elif self.BC_E == "Mirror": + self.BC_Rigidity_E = "mirror symmetry" + else: + sys.exit("Invalid Te B.C. case") + + ############# + # PAD ARRAY # + ############# + if np.isscalar(self.Te): + self.D *= np.ones(self.qs.shape) # And leave Te as a scalar for checks + else: + self.Te_unpadded = self.Te.copy() + # F2D keeps this inside the "else" and handles this differently, + # largely because it has different ways of computing the flexural + # response with variable Te. We'll keep everything simpler here and + # just pad this array so it can be sent through the same process + # to create the coefficient arrays. + self.D = np.hstack([np.nan, self.D, np.nan]) + + ############################################################### + # APPLY FLEXURAL RIGIDITY BOUNDARY CONDITIONS TO PADDED ARRAY # + ############################################################### + if self.BC_Rigidity_W == "0 curvature": + self.D[0] = 2 * self.D[1] - self.D[2] + if self.BC_Rigidity_E == "0 curvature": + self.D[-1] = 2 * self.D[-2] - self.D[-3] + if self.BC_Rigidity_W == "mirror symmetry": + self.D[0] = self.D[2] + if self.BC_Rigidity_E == "mirror symmetry": + self.D[-1] = self.D[-3] + if self.BC_Rigidity_W == "periodic": + self.D[0] = self.D[-2] + if self.BC_Rigidity_E == "periodic": + self.D[-1] = self.D[-3] + + def get_coeff_values(self): + ############################## + # BUILD GENERAL COEFFICIENTS # + ############################## + + # l2 corresponds to top value in solution vector, so to the left (-) side + # Good reference for how to determine central difference (and other) coefficients is: + # Fornberg, 1998: Generation of Finite Difference Formulas on Arbitrarily Spaced Grids + + ################################################### + # DEFINE SUB-ARRAYS FOR DERIVATIVE DISCRETIZATION # + ################################################### + Dm1 = self.D[:-2] + D0 = self.D[1:-1] + Dp1 = self.D[2:] + + ########################################################### + # DEFINE COEFFICIENTS TO W_-2 -- W_+2 WITH B.C.'S APPLIED # + ########################################################### + self.l2_coeff_i = (Dm1 / 2.0 + D0 - Dp1 / 2.0) / self.dx4 + self.l1_coeff_i = ( + -6.0 * D0 + 2.0 * Dp1 + ) / self.dx4 - self.sigma_xx * self.Te / self.dx2 + self.c0_coeff_i = ( + (-2.0 * Dm1 + 10.0 * D0 - 2.0 * Dp1) / self.dx4 + + 2 * self.sigma_xx * self.Te / self.dx2 + + self.drho * self.g + ) + self.r1_coeff_i = ( + 2.0 * Dm1 - 6.0 * D0 + ) / self.dx4 - self.sigma_xx * self.Te / self.dx2 + self.r2_coeff_i = (-Dm1 / 2.0 + D0 + Dp1 / 2.0) / self.dx4 + # These will be just the 1, -4, 6, -4, 1 for constant Te + + ################################################################### + # START DIAGONALS AS SIMPLY THE BASE COEFFICIENTS, WITH NO B.C.'S # + ################################################################### + self.l2 = self.l2_coeff_i.copy() + self.l1 = self.l1_coeff_i.copy() + self.c0 = self.c0_coeff_i.copy() + self.r1 = self.r1_coeff_i.copy() + self.r2 = self.r2_coeff_i.copy() + + # Number of columns; equals number of rows too - square coeff matrix + self.ncolsx = self.c0.shape[0] + + # Either way, the way that Scipy stacks is not the same way that I calculate + # the rows. It runs offsets down the column instead of across the row. So + # to simulate this, I need to re-zero everything. To do so, I use + # numpy.roll. (See self.build_diagonals.) + + def BC_Flexure(self): + # Some links that helped me teach myself how to set up the boundary conditions + # in the matrix for the flexure problem: + # + # Good explanation of and examples of boundary conditions + # https://en.wikipedia.org/wiki/Euler%E2%80%93Bernoulli_beam_theory#Boundary_considerations + # + # Copy of Fornberg table: + # https://en.wikipedia.org/wiki/Finite_difference_coefficient + # + # Implementing b.c.'s: + # http://scicomp.stackexchange.com/questions/5355/writing-the-poisson-equation-finite-difference-matrix-with-neumann-boundary-cond + # http://scicomp.stackexchange.com/questions/7175/trouble-implementing-neumann-boundary-conditions-because-the-ghost-points-cannot + + if self.Verbose: + print("Boundary condition, West:", self.BC_W, type(self.BC_W)) + print("Boundary condition, East:", self.BC_E, type(self.BC_E)) + + # In 2D, these are handled inside the function; in 1D, there are separate + # defined functions. Keeping these due to inertia and fear of cut/paste + # mistakes + if self.BC_E == "0Displacement0Slope" or self.BC_W == "0Displacement0Slope": + self.BC_0Displacement0Slope() + if self.BC_E == "0Slope0Shear" or self.BC_W == "0Slope0Shear": + self.BC_0Slope0Shear() + if self.BC_E == "0Moment0Shear" or self.BC_W == "0Moment0Shear": + self.BC_0Moment0Shear() + if self.BC_E == "Mirror" or self.BC_W == "Mirror": + self.BC_Mirror() + if self.BC_E == "Periodic" and self.BC_W == "Periodic": + self.BC_Periodic() + if self.BC_E == "Sandbox" or self.BC_W == "Sandbox": + # Sandbox is the developer's testing ground + sys.exit("Sandbox Closed") + + def build_diagonals(self): + """ + Builds the diagonals for the coefficient array + """ + + ########################################################## + # INCORPORATE BOUNDARY CONDITIONS INTO COEFFICIENT ARRAY # + ########################################################## + + # Roll to keep the proper coefficients at the proper places in the + # arrays: Python will naturally just do vertical shifts instead of + # diagonal shifts, so this takes into account the horizontal compoent + # to ensure that boundary values are at the right place. + self.l2 = np.roll(self.l2, -2) + self.l1 = np.roll(self.l1, -1) + self.r1 = np.roll(self.r1, 1) + self.r2 = np.roll(self.r2, 2) + + # Then assemble these rows: this is where the periodic boundary condition + # can matter. + if self.coeff_matrix is not None: + pass + elif self.BC_E == "Periodic" and self.BC_W == "Periodic": + # In this case, the boundary-condition-related stacking has already + # happened inside b.c.-handling function. This is because periodic + # boundary conditions require extra diagonals to exist on the edges of + # the solution array + pass + else: + self.diags = np.vstack((self.l2, self.l1, self.c0, self.r1, self.r2)) + self.offsets = np.array([-2, -1, 0, 1, 2]) + + # Everybody now (including periodic b.c. cases) + self.coeff_matrix = spdiags( + self.diags, self.offsets, self.nx, self.nx, format="csr" + ) + + def BC_Periodic(self): + """ + Periodic boundary conditions: wraparound to the other side. + """ + if self.BC_E == "Periodic" and self.BC_W == "Periodic": + # If both boundaries are periodic, we are good to go (and self-consistent) + pass # It is just a shift in the coeff. matrix creation. + else: + # If only one boundary is periodic and the other doesn't implicitly + # involve a periodic boundary, this is illegal! + # I could allow it, but would have to rewrite the Periodic b.c. case, + # which I don't want to do to allow something that doesn't make + # physical sense... so if anyone wants to do this for some unforeseen + # reason, they can just split my function into two pieces themselves.i + sys.exit( + "Having the boundary opposite a periodic boundary condition\n" + + "be fixed and not include an implicit periodic boundary\n" + + "condition makes no physical sense.\n" + + "Please fix the input boundary conditions. Aborting." + ) + self.diags = np.vstack( + ( + self.r1, + self.r2, + self.l2, + self.l1, + self.c0, + self.r1, + self.r2, + self.l2, + self.l1, + ) + ) + self.offsets = np.array( + [ + 1 - self.ncolsx, + 2 - self.ncolsx, + -2, + -1, + 0, + 1, + 2, + self.ncolsx - 2, + self.ncolsx - 1, + ] + ) + + def BC_0Displacement0Slope(self): + """ + 0Displacement0Slope boundary condition for 0 deflection. + This requires that nothing be done to the edges of the solution array, + because the lack of the off-grid terms implies that they go to 0 + Here we just turn the cells outside the array into nan, to ensure that + we are not accidentally including the wrong cells here (and for consistency + with the other solution types -- this takes negligible time) + """ + if self.BC_W == "0Displacement0Slope": + i = 0 + self.l2[i] = np.nan + self.l1[i] = np.nan + self.c0[i] += 0 + self.r1[i] += 0 + self.r2[i] += 0 + i = 1 + self.l2[i] = np.nan + self.l1[i] += 0 + self.c0[i] += 0 + self.r1[i] += 0 + self.r2[i] += 0 + if self.BC_E == "0Displacement0Slope": + i = -2 + self.l2[i] += 0 + self.l1[i] += 0 + self.c0[i] += 0 + self.r1[i] += 0 + self.r2[i] = np.nan + i = -1 + self.l2[i] += 0 + self.l1[i] += 0 + self.c0[i] += 0 + self.r1[i] = np.nan + self.r2[i] = np.nan + + def BC_0Slope0Shear(self): + i = 0 + """ This boundary condition is esentially a Neumann 0-gradient boundary condition with that 0-gradient state extended over a longer part of the grid such that the third derivative also equals 0. @@ -467,173 +522,183 @@ def BC_0Slope0Shear(self): that extends outside of the computational domain. """ - if self.BC_W == '0Slope0Shear': - i=0 - self.l2[i] = np.nan - self.l1[i] = np.nan - self.c0[i] += 0 - self.r1[i] += self.l1_coeff_i[i] - self.r2[i] += self.l2_coeff_i[i] - i=1 - self.l2[i] = np.nan - self.l1[i] += 0 - self.c0[i] += 0 - self.r1[i] += 0 - self.r2[i] += self.l2_coeff_i[i] - if self.BC_E == '0Slope0Shear': - i=-2 - self.l2[i] += self.r2_coeff_i[i] - self.l1[i] += 0 - self.c0[i] += 0 - self.r1[i] += 0 - self.r2[i] = np.nan - i=-1 - self.l2[i] += self.r2_coeff_i[i] - self.l1[i] += self.r1_coeff_i[i] - self.c0[i] += 0 - self.r1[i] = np.nan - self.r2[i] = np.nan - - def BC_0Moment0Shear(self): - """ - d2w/dx2 = d3w/dx3 = 0 - (no moment or shear) - This simulates a free end (broken plate, end of a cantilevered beam: - think diving board tip) - It is *not* yet set up to have loads placed on the ends themselves: - (look up how to do this, thought Wikipdia has some info, but can't find - it... what I read said something about generalizing) - """ - - # First, just define coefficients for each of the positions in the array - # These will be added in code instead of being directly combined by - # the programmer (as I did above (now deleted) for constant Te), which might add - # rather negligibly to the compute time but save a bunch of possibility - # for unfortunate typos! - - # Also using 0-curvature boundary condition for D (i.e. Te) - if self.BC_W == '0Moment0Shear': - i=0 - self.l2[i] += np.nan - self.l1[i] += np.nan - self.c0[i] += 4*self.l2_coeff_i[i] + 2*self.l1_coeff_i[i] - self.r1[i] += -4*self.l2_coeff_i[i] - self.l1_coeff_i[i] - self.r2[i] += self.l2_coeff_i[i] - i=1 - self.l2[i] += np.nan - self.l1[i] += 2*self.l2_coeff_i[i] - self.c0[i] += 0 - self.r1[i] += -2*self.l2_coeff_i[i] - self.r2[i] += self.l2_coeff_i[i] - - if self.BC_E == '0Moment0Shear': - i=-2 - self.l2[i] += self.r2_coeff_i[i] - self.l1[i] += -2*self.r2_coeff_i[i] - self.c0[i] += 0 - self.r1[i] += 2*self.r2_coeff_i[i] - self.r2[i] += np.nan - i=-1 - self.l2[i] += self.r2_coeff_i[i] - self.l1[i] += -4*self.r2_coeff_i[i] - self.r1_coeff_i[i] - self.c0[i] += 4*self.r2_coeff_i[i] + + 2*self.r1_coeff_i[i] - self.r1[i] += np.nan - self.r2[i] += np.nan - - def BC_Mirror(self): - """ - Mirrors qs across the boundary on either the west (left) or east (right) - side, depending on the selections. - - This can, for example, produce a scenario in which you are observing - a mountain range up to the range crest (or, more correctly, the halfway - point across the mountain range). - """ - if self.BC_W == 'Mirror': - i=0 - #self.l2[i] += np.nan - #self.l1[i] += np.nan - self.c0[i] += 0 - self.r1[i] += self.l1_coeff_i[i] - self.r2[i] += self.l2_coeff_i[i] - i=1 - #self.l2[i] += np.nan - self.l1[i] += 0 - self.c0[i] += self.l2_coeff_i[i] - self.r1[i] += 0 - self.r2[i] += 0 - - if self.BC_E == 'Mirror': - i=-2 - self.l2[i] += 0 - self.l1[i] += 0 - self.c0[i] += self.r2_coeff_i[i] - self.r1[i] += 0 - #self.r2[i] += np.nan - i=-1 - self.l2[i] += self.r2_coeff_i[i] - self.l1[i] += self.r1_coeff_i[i] - self.c0[i] += 0 - #self.r1[i] += np.nan - #self.r2[i] += np.nan - - def calc_max_flexural_wavelength(self): - """ - Returns the approximate maximum flexural wavelength - This is important when padding of the grid is required: in Flexure (this - code), grids are padded out to one maximum flexural wavelength, but in any - case, the flexural wavelength is a good characteristic distance for any - truncation limit - """ - if np.isscalar(self.D): - Dmax = self.D - else: - Dmax = self.D.max() - # This is an approximation if there is fill that evolves with iterations - # (e.g., water), but should be good enough that this won't do much to it - alpha = (4*Dmax/(self.drho*self.g))**.25 # 2D flexural parameter - self.maxFlexuralWavelength = 2*np.pi*alpha - self.maxFlexuralWavelength_ncells = int(np.ceil(self.maxFlexuralWavelength / self.dx)) - - def fd_solve(self): - """ - w = fd_solve() - where coeff is the sparse coefficient matrix output from function - coeff_matrix and qs is the array of loads (stresses) + if self.BC_W == "0Slope0Shear": + i = 0 + self.l2[i] = np.nan + self.l1[i] = np.nan + self.c0[i] += 0 + self.r1[i] += self.l1_coeff_i[i] + self.r2[i] += self.l2_coeff_i[i] + i = 1 + self.l2[i] = np.nan + self.l1[i] += 0 + self.c0[i] += 0 + self.r1[i] += 0 + self.r2[i] += self.l2_coeff_i[i] + if self.BC_E == "0Slope0Shear": + i = -2 + self.l2[i] += self.r2_coeff_i[i] + self.l1[i] += 0 + self.c0[i] += 0 + self.r1[i] += 0 + self.r2[i] = np.nan + i = -1 + self.l2[i] += self.r2_coeff_i[i] + self.l1[i] += self.r1_coeff_i[i] + self.c0[i] += 0 + self.r1[i] = np.nan + self.r2[i] = np.nan + + def BC_0Moment0Shear(self): + """ + d2w/dx2 = d3w/dx3 = 0 + (no moment or shear) + This simulates a free end (broken plate, end of a cantilevered beam: + think diving board tip) + It is *not* yet set up to have loads placed on the ends themselves: + (look up how to do this, thought Wikipdia has some info, but can't find + it... what I read said something about generalizing) + """ + + # First, just define coefficients for each of the positions in the array + # These will be added in code instead of being directly combined by + # the programmer (as I did above (now deleted) for constant Te), which might add + # rather negligibly to the compute time but save a bunch of possibility + # for unfortunate typos! + + # Also using 0-curvature boundary condition for D (i.e. Te) + if self.BC_W == "0Moment0Shear": + i = 0 + self.l2[i] += np.nan + self.l1[i] += np.nan + self.c0[i] += 4 * self.l2_coeff_i[i] + 2 * self.l1_coeff_i[i] + self.r1[i] += -4 * self.l2_coeff_i[i] - self.l1_coeff_i[i] + self.r2[i] += self.l2_coeff_i[i] + i = 1 + self.l2[i] += np.nan + self.l1[i] += 2 * self.l2_coeff_i[i] + self.c0[i] += 0 + self.r1[i] += -2 * self.l2_coeff_i[i] + self.r2[i] += self.l2_coeff_i[i] + + if self.BC_E == "0Moment0Shear": + i = -2 + self.l2[i] += self.r2_coeff_i[i] + self.l1[i] += -2 * self.r2_coeff_i[i] + self.c0[i] += 0 + self.r1[i] += 2 * self.r2_coeff_i[i] + self.r2[i] += np.nan + i = -1 + self.l2[i] += self.r2_coeff_i[i] + self.l1[i] += -4 * self.r2_coeff_i[i] - self.r1_coeff_i[i] + self.c0[i] += 4 * self.r2_coeff_i[i] + +2 * self.r1_coeff_i[i] + self.r1[i] += np.nan + self.r2[i] += np.nan + + def BC_Mirror(self): + """ + Mirrors qs across the boundary on either the west (left) or east (right) + side, depending on the selections. + + This can, for example, produce a scenario in which you are observing + a mountain range up to the range crest (or, more correctly, the halfway + point across the mountain range). + """ + if self.BC_W == "Mirror": + i = 0 + # self.l2[i] += np.nan + # self.l1[i] += np.nan + self.c0[i] += 0 + self.r1[i] += self.l1_coeff_i[i] + self.r2[i] += self.l2_coeff_i[i] + i = 1 + # self.l2[i] += np.nan + self.l1[i] += 0 + self.c0[i] += self.l2_coeff_i[i] + self.r1[i] += 0 + self.r2[i] += 0 + + if self.BC_E == "Mirror": + i = -2 + self.l2[i] += 0 + self.l1[i] += 0 + self.c0[i] += self.r2_coeff_i[i] + self.r1[i] += 0 + # self.r2[i] += np.nan + i = -1 + self.l2[i] += self.r2_coeff_i[i] + self.l1[i] += self.r1_coeff_i[i] + self.c0[i] += 0 + # self.r1[i] += np.nan + # self.r2[i] += np.nan + + def calc_max_flexural_wavelength(self): + """ + Returns the approximate maximum flexural wavelength + This is important when padding of the grid is required: in Flexure (this + code), grids are padded out to one maximum flexural wavelength, but in any + case, the flexural wavelength is a good characteristic distance for any + truncation limit + """ + if np.isscalar(self.D): + Dmax = self.D + else: + Dmax = self.D.max() + # This is an approximation if there is fill that evolves with iterations + # (e.g., water), but should be good enough that this won't do much to it + alpha = (4 * Dmax / (self.drho * self.g)) ** 0.25 # 2D flexural parameter + self.maxFlexuralWavelength = 2 * np.pi * alpha + self.maxFlexuralWavelength_ncells = int( + np.ceil(self.maxFlexuralWavelength / self.dx) + ) + + def fd_solve(self): + """ + w = fd_solve() + where coeff is the sparse coefficient matrix output from function + coeff_matrix and qs is the array of loads (stresses) + + Sparse solver for one-dimensional flexure of an elastic plate + """ - Sparse solver for one-dimensional flexure of an elastic plate - """ + if self.Debug: + print("qs", self.qs.shape) + print("Te", self.Te.shape) + self.calc_max_flexural_wavelength() + print("maxFlexuralWavelength_ncells', self.maxFlexuralWavelength_ncells") + + if self.Solver == "iterative" or self.Solver == "Iterative": + if self.Debug: + print( + "Using generalized minimal residual method for iterative solution" + ) + if self.Verbose: + print( + "Converging to a tolerance of", + self.iterative_ConvergenceTolerance, + "m between iterations", + ) + # qs negative so bends down with positive load, bends up with neative load + # (i.e. material removed) + w = isolve.lgmres( + self.coeff_matrix, -self.qs, tol=self.iterative_ConvergenceTolerance + ) + self.w = w[0] # Reach into tuple to get my array back + else: + if self.Solver == "direct" or self.Solver == "Direct": + if self.Debug: + print("Using direct solution with UMFpack") + else: + print("Solution type not understood:") + print("Defaulting to direct solution with UMFpack") + # UMFpack is now the default, but setting true just to be sure in case + # anything changes + # qs negative so bends down with positive load, bends up with neative load + # (i.e. material removed) + self.w = spsolve(self.coeff_matrix, -self.qs, use_umfpack=True) - if self.Debug: - print("qs", self.qs.shape) - print("Te", self.Te.shape) - self.calc_max_flexural_wavelength() - print("maxFlexuralWavelength_ncells', self.maxFlexuralWavelength_ncells") - - if self.Solver == "iterative" or self.Solver == "Iterative": - if self.Debug: - print("Using generalized minimal residual method for iterative solution") - if self.Verbose: - print("Converging to a tolerance of", self.iterative_ConvergenceTolerance, "m between iterations") - # qs negative so bends down with positive load, bends up with neative load - # (i.e. material removed) - w = isolve.lgmres(self.coeff_matrix, -self.qs, tol=self.iterative_ConvergenceTolerance) - self.w = w[0] # Reach into tuple to get my array back - else: - if self.Solver == 'direct' or self.Solver == 'Direct': if self.Debug: - print("Using direct solution with UMFpack") - else: - print("Solution type not understood:") - print("Defaulting to direct solution with UMFpack") - # UMFpack is now the default, but setting true just to be sure in case - # anything changes - # qs negative so bends down with positive load, bends up with neative load - # (i.e. material removed) - self.w = spsolve(self.coeff_matrix, -self.qs, use_umfpack=True) - - if self.Debug: - print("w.shape:") - print(self.w.shape) - print("w:") - print(self.w) + print("w.shape:") + print(self.w.shape) + print("w:") + print(self.w) diff --git a/gflex/f2d.py b/gflex/f2d.py index 4e84bfb..d8da50d 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -27,1533 +27,1781 @@ # three parameters as class Isostasy; and it then sets up more parameters specific # to its own type of simulation. class F2D(Flexure): - def initialize(self, filename=None): - self.dimension = 2 # Set it here in case it wasn't set for selection before - super().initialize() - if self.Verbose: print("F2D initialized") - - def run(self): - self.bc_check() - self.solver_start_time = time.time() - - if self.Method == 'FD': - # Finite difference - super().FD() - self.method_func = self.FD - elif self.Method == 'FFT': - # Fast Fourier transform - super().FFT() - self.method_func = self.FFT - elif self.Method == "SAS": - # Superposition of analytical solutions - super().SAS() - self.method_func = self.SAS - elif self.Method == "SAS_NG": - # Superposition of analytical solutions, - # nonuniform points (no grid) - super().SAS_NG() - self.method_func = self.SAS_NG - else: - sys.exit('Error: method must be "FD", "FFT", "SAS", or "SAS_NG"') - - if self.Verbose: print("F2D run") - self.method_func() - - self.time_to_solve = time.time() - self.solver_start_time - if self.Quiet == False: - print("Time to solve [s]:", self.time_to_solve) - - def finalize(self): - # If elastic thickness has been padded, return it to its original - # value, so this is not messed up for repeat operations in a - # model-coupling exercise - try: - self.Te = self.Te_unpadded - except: - pass - if self.Verbose: print("F2D finalized") - super().finalize() - - ######################################## - ## FUNCTIONS FOR EACH SOLUTION METHOD ## - ######################################## - - def FD(self): - # Only generate coefficient matrix if it is not already provided - if self.coeff_matrix is not None: - pass - else: - self.elasprep() - self.BC_selector_and_coeff_matrix_creator() - self.fd_solve() - - def FFT(self): - sys.exit("The fast Fourier transform solution method is not yet implemented.") - - def SAS(self): - self.spatialDomainVarsSAS() - self.spatialDomainGridded() - - def SAS_NG(self): - self.spatialDomainVarsSAS() - self.spatialDomainNoGrid() - - - ###################################### - ## FUNCTIONS TO SOLVE THE EQUATIONS ## - ###################################### - - - ## SPATIAL DOMAIN SUPERPOSITION OF ANALYTICAL SOLUTIONS - ######################################################### - - # SETUP - - def spatialDomainVarsSAS(self): - - # Check Te: - # * If scalar, okay. - # * If grid, convert to scalar if a singular value - # * Else, throw an error. - if np.isscalar(self.Te): - pass - elif np.all( self.Te == np.mean(self.Te) ): - self.Te = np.mean(self.Te) - else: - sys.exit("\nINPUT VARIABLE TYPE INCONSISTENT WITH SOLUTION TYPE.\n" - "The analytical solution requires a scalar Te.\n" - "(gFlex is smart enough to make this out of a uniform\n" - "array, but won't know what value you want with a spatially\n" - "varying array! Try finite difference instead in this case?\n" - "EXITING.") - - self.D = self.E*self.Te**3/(12*(1-self.nu**2)) # Flexural rigidity - self.alpha = (self.D/(self.drho*self.g))**.25 # 2D flexural parameter - self.coeff = self.alpha**2/(2*np.pi*self.D) - - # GRIDDED - - def spatialDomainGridded(self): - - self.nx = self.qs.shape[1] - self.ny = self.qs.shape[0] - - # Prepare a large grid of solutions beforehand, so we don't have to - # keep calculating kei (time-intensive!) - # This pre-prepared solution will be for a unit load - bigshape = 2*self.ny+1,2*self.nx+1 # Tuple shape - - dist_ny = np.arange(bigshape[0]) - self.ny - dist_nx = np.arange(bigshape[1]) - self.nx - - dist_x,dist_y = np.meshgrid(dist_nx*self.dx,dist_ny*self.dy) - - bigdist = np.sqrt(dist_x**2 + dist_y**2) # Distances from center - # Center at [ny,nx] - - biggrid = self.coeff * kei(bigdist/self.alpha) # Kelvin fcn solution - - # Now compute the deflections - self.w = np.zeros((self.ny,self.nx)) # Deflection array - for i in range(self.nx): - for j in range(self.ny): - # Loop over locations that have loads, and sum - if self.qs[j,i]: - # Solve by summing portions of "biggrid" while moving origin - # to location of current cell - # Load must be multiplied by grid cell size - self.w += self.qs[j,i] * self.dx * self.dy \ - * biggrid[self.ny-j:2*self.ny-j,self.nx-i:2*self.nx-i] - # No need to return: w already belongs to "self" - - # NO GRID - - def spatialDomainNoGrid(self): - - self.w = np.zeros(self.xw.shape) - if self.Debug: - print("w = ") - print(self.w.shape) - - if self.latlon: - for i in range(len(self.x)): - # More efficient if we have created some 0-load points - # (e.g., for where we want output) - if self.q[i] != 0: - # Create array of distances from point of load - r = self.greatCircleDistance(lat1=self.y[i], long1=self.x[i], lat2=self.yw, long2=self.xw, radius=self.PlanetaryRadius) - self.w += self.q[i] * self.coeff * kei(r/self.alpha) - # Compute and sum deflection - self.w += self.q[i] * self.coeff * kei(r/self.alpha) - else: - for i in range(len(self.x)): - if self.q[i] != 0: - r = ( (self.xw - self.x[i])**2 + (self.yw - self.y[i])**2 )**.5 - self.w += self.q[i] * self.coeff * kei(r/self.alpha) - - ## FINITE DIFFERENCE - ###################### - - def elasprep(self): - """ - dx4, dy4, dx2dy2, D = elasprep(dx,dy,Te,E=1E11,nu=0.25) - - Defines the variables that are required to create the 2D finite - difference solution coefficient matrix - """ - - if self.Method != 'SAS_NG': - self.dx4 = self.dx**4 - self.dy4 = self.dy**4 - self.dx2dy2 = self.dx**2 * self.dy**2 - self.D = self.E*self.Te**3/(12*(1-self.nu**2)) - - def BC_selector_and_coeff_matrix_creator(self): - """ - Selects the boundary conditions - E-W is for inside each panel - N-S is for the block diagonal matrix ("with fringes") - Then calls the function to build the diagonal matrix - - The current method of coefficient matrix construction utilizes longer-range - symmetry in the coefficient matrix to build it block-wise, as opposed to - the much less computationally efficient row-by-row ("serial") method - that was previously employed. - - The method is spread across the subroutines here. - - Important to this is the use of np.roll() to properly offset the - diagonals that end up in the main matrix: spdiags() will put each vector - on the proper diagonal, but will align them such that their first cell is - along the first column, instead of using a 45 degrees to matrix corner - baseline that would stagger them appropriately for this solution method. - Therefore, np.roll() effectively does this staggering by having the - appropriate cell in the vector start at the first column. - """ - - # Zeroth, start the timer and print the boundary conditions to the screen - self.coeff_start_time = time.time() - if self.Verbose: - print("Boundary condition, West:", self.BC_W, type(self.BC_W)) - print("Boundary condition, East:", self.BC_E, type(self.BC_E)) - print("Boundary condition, North:", self.BC_N, type(self.BC_N)) - print("Boundary condition, South:", self.BC_S, type(self.BC_S)) - - # First, set flexural rigidity boundary conditions to flesh out this padded - # array - self.BC_Rigidity() - - # Second, build the coefficient arrays -- with the rigidity b.c.'s - self.get_coeff_values() - - # Third, apply boundary conditions to the coeff_arrays to create the - # flexural solution - self.BC_Flexure() - - # Fourth, construct the sparse diagonal array - self.build_diagonals() - - # Finally, compute the total time this process took - self.coeff_creation_time = time.time() - self.coeff_start_time - if self.Quiet == False: - print("Time to construct coefficient (operator) array [s]:", self.coeff_creation_time) - - def BC_Rigidity(self): - """ - Utility function to help implement boundary conditions by specifying - them for and applying them to the elastic thickness grid - """ - - ######################################### - # FLEXURAL RIGIDITY BOUNDARY CONDITIONS # - ######################################### - # West - if self.BC_W == 'Periodic': - self.BC_Rigidity_W = 'periodic' - elif (self.BC_W == np.array(['0Displacement0Slope', '0Moment0Shear', '0Slope0Shear'])).any(): - self.BC_Rigidity_W = '0 curvature' - elif self.BC_W == 'Mirror': - self.BC_Rigidity_W = 'mirror symmetry' - else: - sys.exit("Invalid Te B.C. case") - # East - if self.BC_E == 'Periodic': - self.BC_Rigidity_E = 'periodic' - elif (self.BC_E == np.array(['0Displacement0Slope', '0Moment0Shear', '0Slope0Shear'])).any(): - self.BC_Rigidity_E = '0 curvature' - elif self.BC_E == 'Mirror': - self.BC_Rigidity_E = 'mirror symmetry' - else: - sys.exit("Invalid Te B.C. case") - # North - if self.BC_N == 'Periodic': - self.BC_Rigidity_N = 'periodic' - elif (self.BC_N == np.array(['0Displacement0Slope', '0Moment0Shear', '0Slope0Shear'])).any(): - self.BC_Rigidity_N = '0 curvature' - elif self.BC_N == 'Mirror': - self.BC_Rigidity_N = 'mirror symmetry' - else: - sys.exit("Invalid Te B.C. case") - # South - if self.BC_S == 'Periodic': - self.BC_Rigidity_S = 'periodic' - elif (self.BC_S == np.array(['0Displacement0Slope', '0Moment0Shear', '0Slope0Shear'])).any(): - self.BC_Rigidity_S = '0 curvature' - elif self.BC_S == 'Mirror': - self.BC_Rigidity_S = 'mirror symmetry' - else: - sys.exit("Invalid Te B.C. case") - - ############# - # PAD ARRAY # - ############# - if np.isscalar(self.Te): - self.D *= np.ones(self.qs.shape) # And leave Te as a scalar for checks - else: - self.Te_unpadded = self.Te.copy() - self.Te = np.hstack(( np.nan*np.zeros((self.Te.shape[0], 1)), self.Te, np.nan*np.zeros((self.Te.shape[0], 1)) )) - self.Te = np.vstack(( np.nan*np.zeros(self.Te.shape[1]), self.Te, np.nan*np.zeros(self.Te.shape[1]) )) - self.D = np.hstack(( np.nan*np.zeros((self.D.shape[0], 1)), self.D, np.nan*np.zeros((self.D.shape[0], 1)) )) - self.D = np.vstack(( np.nan*np.zeros(self.D.shape[1]), self.D, np.nan*np.zeros(self.D.shape[1]) )) - - ############################################################### - # APPLY FLEXURAL RIGIDITY BOUNDARY CONDITIONS TO PADDED ARRAY # - ############################################################### - if self.BC_Rigidity_W == "0 curvature": - self.D[:,0] = 2*self.D[:,1] - self.D[:,2] - if self.BC_Rigidity_E == "0 curvature": - self.D[:,-1] = 2*self.D[:,-2] - self.D[:,-3] - if self.BC_Rigidity_N == "0 curvature": - self.D[0,:] = 2*self.D[1,:] - self.D[2,:] - if self.BC_Rigidity_S == "0 curvature": - self.D[-1,:] = 2*self.D[-2,:] - self.D[-3,:] - - if self.BC_Rigidity_W == "mirror symmetry": - self.D[:,0] = self.D[:,2] - if self.BC_Rigidity_E == "mirror symmetry": - self.D[:,-1] = self.D[:,-3] - if self.BC_Rigidity_N == "mirror symmetry": - self.D[0,:] = self.D[2,:] # Yes, will work on corners -- double-reflection - if self.BC_Rigidity_S == "mirror symmetry": - self.D[-1,:] = self.D[-3,:] - - if self.BC_Rigidity_W == "periodic": - self.D[:,0] = self.D[:,-2] - if self.BC_Rigidity_E == "periodic": - self.D[:,-1] = self.D[:,-3] - if self.BC_Rigidity_N == "periodic": - self.D[0,:] = self.D[-2,:] - if self.BC_Rigidity_S == "periodic": - self.D[-1,:] = self.D[-3,:] - - def get_coeff_values(self): - """ - Calculates the matrix of coefficients that is later used via sparse matrix - solution techniques (scipy.sparse.linalg.spsolve) to compute the flexural - response to the load. This step need only be performed once, and the - coefficient matrix can very rapidly compute flexural solutions to any load. - This makes this particularly good for probelms with time-variable loads or - that require iteration (e.g., water loading, in which additional water - causes subsidence, causes additional water detph, etc.). - - These must be linearly combined to solve the equation. - - 13 coefficients: 13 matrices of the same size as the load - - NOTATION FOR COEFFICIENT BIULDING MATRICES (e.g., "cj0i_2"): - c = "coefficient - j = columns = x-value - j0 = no offset: look at center of array - i = rows = y-value - i_2 = negative 2 offset (i2 = positive 2 offset) - """ - - # don't want to keep typing "self." everwhere! - D = self.D - drho = self.drho - dx4 = self.dx4 - dy4 = self.dy4 - dx2dy2 = self.dx2dy2 - nu = self.nu - g = self.g - - if np.isscalar(self.Te): - # So much simpler with constant D! And symmetrical stencil - self.cj2i0 = D/dy4 - self.cj1i_1 = 2*D/dx2dy2 + 2*self.sigma_xy*self.T_e - self.cj1i0 = -4*D/dy4 - 4*D/dx2dy2 - self.sigma_yy*self.T_e - self.cj1i1 = 2*D/dx2dy2 - 2*self.sigma_xy*self.T_e - self.cj0i_2 = D/dx4 - self.cj0i_1 = -4*D/dx4 - 4*D/dx2dy2 - self.sigma_xx*self.T_e - self.cj0i0 = 6*D/dx4 + 6*D/dy4 + 8*D/dx2dy2 + drho*g + 2*self.sigma_xx*self.T_e + 2*self.sigma_yy*self.T_e - self.cj0i1 = -4*D/dx4 - 4*D/dx2dy2 - self.sigma_xx*self.T_e # Symmetry - self.cj0i2 = D/dx4 # Symmetry - self.cj_1i_1 = 2*D/dx2dy2 - 2*self.sigma_xy*self.T_e # Symmetry - self.cj_1i0 = -4*D/dy4 - 4*D/dx2dy2 # Symmetry - self.cj_1i1 = 2*D/dx2dy2 + 2*self.sigma_xy*self.T_e # Symmetry - self.cj_2i0 = D/dy4 # Symmetry - # Bring up to size - self.cj2i0 *= np.ones(self.qs.shape) - self.cj1i_1 *= np.ones(self.qs.shape) - self.cj1i0 *= np.ones(self.qs.shape) - self.cj1i1 *= np.ones(self.qs.shape) - self.cj0i_2 *= np.ones(self.qs.shape) - self.cj0i_1 *= np.ones(self.qs.shape) - self.cj0i0 *= np.ones(self.qs.shape) - self.cj0i1 *= np.ones(self.qs.shape) - self.cj0i2 *= np.ones(self.qs.shape) - self.cj_1i_1 *= np.ones(self.qs.shape) - self.cj_1i0 *= np.ones(self.qs.shape) - self.cj_1i1 *= np.ones(self.qs.shape) - self.cj_2i0 *= np.ones(self.qs.shape) - # Create coefficient arrays to manage boundary conditions - self.cj2i0_coeff_ij = self.cj2i0.copy() - self.cj1i_1_coeff_ij = self.cj1i_1.copy() - self.cj1i0_coeff_ij = self.cj1i0.copy() - self.cj1i1_coeff_ij = self.cj1i1.copy() - self.cj0i_2_coeff_ij = self.cj0i_2.copy() - self.cj0i_1_coeff_ij = self.cj0i_1.copy() - self.cj0i0_coeff_ij = self.cj0i0.copy() - self.cj0i1_coeff_ij = self.cj0i1.copy() - self.cj0i2_coeff_ij = self.cj0i2.copy() - self.cj_1i_1_coeff_ij = self.cj_1i_1.copy() - self.cj_1i0_coeff_ij = self.cj_1i0.copy() - self.cj_1i1_coeff_ij = self.cj_1i1.copy() - self.cj_2i0_coeff_ij = self.cj_2i0.copy() - - elif type(self.Te) == np.ndarray: - - ####################################################### - # GENERATE COEFFICIENT VALUES FOR EACH SOLUTION TYPE. # - # "vWC1994" IS THE BEST: LOOSEST ASSUMPTIONS. # - # OTHERS HERE LARGELY FOR COMPARISON # - ####################################################### - - # All derivatives here, to make reading the equations below easier - D00 = D[1:-1,1:-1] - D10 = D[1:-1,2:] - D_10 = D[1:-1,:-2] - D01 = D[2:,1:-1] - D0_1 = D[:-2,1:-1] - D11 = D[2:,2:] - D_11 = D[2:,:-2] - D1_1 = D[:-2,2:] - D_1_1 = D[:-2,:-2] - # Derivatives of D -- not including /(dx^a dy^b) - D0 = D00 - Dx = (-D_10 + D10)/2. - Dy = (-D0_1 + D01)/2. - Dxx = (D_10 - 2.*D00 + D10) - Dyy = (D0_1 - 2.*D00 + D01) - Dxy = (D_1_1 - D_11 - D1_1 + D11)/4. - - if self.PlateSolutionType == 'vWC1994': - # van Wees and Cloetingh (1994) solution, re-discretized by me - # using a central difference approx. to 2nd order precision - # NEW STENCIL - # x = -2, y = 0 - self.cj_2i0_coeff_ij = (D0 - Dx) / dx4 - # x = 0, y = -2 - self.cj0i_2_coeff_ij = (D0 - Dy) / dy4 - # x = 0, y = 2 - self.cj0i2_coeff_ij = (D0 + Dy) / dy4 - # x = 2, y = 0 - self.cj2i0_coeff_ij = (D0 + Dx) / dx4 - # x = -1, y = -1 - self.cj_1i_1_coeff_ij = (2.*D0 - Dx - Dy + Dxy*(1-nu)/2.) / dx2dy2 - # x = -1, y = 1 - self.cj_1i1_coeff_ij = (2.*D0 - Dx + Dy - Dxy*(1-nu)/2.) / dx2dy2 - # x = 1, y = -1 - self.cj1i_1_coeff_ij = (2.*D0 + Dx - Dy - Dxy*(1-nu)/2.) / dx2dy2 - # x = 1, y = 1 - self.cj1i1_coeff_ij = (2.*D0 + Dx + Dy + Dxy*(1-nu)/2.) / dx2dy2 - # x = -1, y = 0 - self.cj_1i0_coeff_ij = (-4.*D0 + 2.*Dx + Dxx)/dx4 + (-4.*D0 + 2.*Dx + nu*Dyy)/dx2dy2 - # x = 0, y = -1 - self.cj0i_1_coeff_ij = (-4.*D0 + 2.*Dy + Dyy)/dy4 + (-4.*D0 + 2.*Dy + nu*Dxx)/dx2dy2 - # x = 0, y = 1 - self.cj0i1_coeff_ij = (-4.*D0 - 2.*Dy + Dyy)/dy4 + (-4.*D0 - 2.*Dy + nu*Dxx)/dx2dy2 - # x = 1, y = 0 - self.cj1i0_coeff_ij = (-4.*D0 - 2.*Dx + Dxx)/dx4 + (-4.*D0 - 2.*Dx + nu*Dyy)/dx2dy2 - # x = 0, y = 0 - self.cj0i0_coeff_ij = (6.*D0 - 2.*Dxx)/dx4 \ - + (6.*D0 - 2.*Dyy)/dy4 \ - + (8.*D0 - 2.*nu*Dxx - 2.*nu*Dyy)/dx2dy2 \ - + drho*g - - elif self.PlateSolutionType == 'G2009': - # STENCIL FROM GOVERS ET AL. 2009 -- first-order differences - # x is j and y is i b/c matrix row/column notation - # Note that this breaks down with b.c.'s that place too much control - # on the solution -- harmonic wavetrains - # x = -2, y = 0 - self.cj_2i0_coeff_ij = D_10/dx4 - # x = -1, y = -1 - self.cj_1i_1_coeff_ij = (D_10 + D0_1)/dx2dy2 - # x = -1, y = 0 - self.cj_1i0_coeff_ij = -2. * ( (D0_1 + D00)/dx2dy2 + (D00 + D_10)/dx4 ) - # x = -1, y = 1 - self.cj_1i1_coeff_ij = (D_10 + D01)/dx2dy2 - # x = 0, y = -2 - self.cj0i_2_coeff_ij = D0_1/dy4 - # x = 0, y = -1 - self.cj0i_1_coeff_ij = -2. * ( (D0_1 + D00)/dx2dy2 + (D00 + D0_1)/dy4) - # x = 0, y = 0 - self.cj0i0_coeff_ij = (D10 + 4.*D00 + D_10)/dx4 + (D01 + 4.*D00 + D0_1)/dy4 + (8.*D00/dx2dy2) + drho*g - # x = 0, y = 1 - self.cj0i1_coeff_ij = -2. * ( (D01 + D00)/dy4 + (D00 + D01)/dx2dy2 ) - # x = 0, y = 2 - self.cj0i2_coeff_ij = D0_1/dy4 - # x = 1, y = -1 - self.cj1i_1_coeff_ij = (D10+D0_1)/dx2dy2 - # x = 1, y = 0 - self.cj1i0_coeff_ij = -2. * ( (D10 + D00)/dx4 + (D10 + D00)/dx2dy2 ) - # x = 1, y = 1 - self.cj1i1_coeff_ij = (D10 + D01)/dx2dy2 - # x = 2, y = 0 - self.cj2i0_coeff_ij = D10/dx4 - else: - sys.exit("Not an acceptable plate solution type. Please choose from:\n"+ - "* vWC1994\n"+ - "* G2009\n"+ - "") - - ################################################################ - # CREATE COEFFICIENT ARRAYS: PLAIN, WITH NO B.C.'S YET APPLIED # - ################################################################ - # x = -2, y = 0 - self.cj_2i0 = self.cj_2i0_coeff_ij.copy() - # x = -1, y = -1 - self.cj_1i_1 = self.cj_1i_1_coeff_ij.copy() - # x = -1, y = 0 - self.cj_1i0 = self.cj_1i0_coeff_ij.copy() - # x = -1, y = 1 - self.cj_1i1 = self.cj_1i1_coeff_ij.copy() - # x = 0, y = -2 - self.cj0i_2 = self.cj0i_2_coeff_ij.copy() - # x = 0, y = -1 - self.cj0i_1 = self.cj0i_1_coeff_ij.copy() - # x = 0, y = 0 - self.cj0i0 = self.cj0i0_coeff_ij.copy() - # x = 0, y = 1 - self.cj0i1 = self.cj0i1_coeff_ij.copy() - # x = 0, y = 2 - self.cj0i2 = self.cj0i2_coeff_ij.copy() - # x = 1, y = -1 - self.cj1i_1 = self.cj1i_1_coeff_ij.copy() - # x = 1, y = 0 - self.cj1i0 = self.cj1i0_coeff_ij.copy() - # x = 1, y = 1 - self.cj1i1 = self.cj1i1_coeff_ij.copy() - # x = 2, y = 0 - self.cj2i0 = self.cj2i0_coeff_ij.copy() - - # Provide rows and columns in the 2D input to later functions - self.ncolsx = self.cj0i0.shape[1] - self.nrowsy = self.cj0i0.shape[0] - - def BC_Flexure(self): - - # The next section of code is split over several functions for the 1D - # case, but will be all in one function here, at least for now. - - # Inf for E-W to separate from nan for N-S. N-S will spill off ends - # of array (C order, in rows), while E-W will be internal, so I will - # later change np.inf to 0 to represent where internal boundaries - # occur. - - ####################################################################### - # DEFINE COEFFICIENTS TO W_j-2 -- W_j+2 WITH B.C.'S APPLIED (x: W, E) # - ####################################################################### - - # Infinitiy is used to flag places where coeff values should be 0, - # and would otherwise cause boundary condition nan's to appear in the - # cross-derivatives: infinity is changed into 0 later. - - if self.BC_W == 'Periodic': - if self.BC_E == 'Periodic': - # For each side, there will be two new diagonals (mostly zeros), and - # two sets of diagonals that will replace values in current diagonals. - # This is because of the pattern of fill in the periodic b.c.'s in the - # x-direction. - - # First, create arrays for the new values. - # One of the two values here, that from the y -/+ 1, x +/- 1 (E/W) - # boundary condition, will be in the same location that will be - # overwritten in the initiating grid by the next perioidic b.c. over - self.cj_1i1_Periodic_right = np.zeros(self.qs.shape) - self.cj_2i0_Periodic_right = np.zeros(self.qs.shape) - j = 0 - self.cj_1i1_Periodic_right[:,j] = self.cj_1i_1[:,j] - self.cj_2i0_Periodic_right[:,j] = self.cj_2i0[:,j] - j = 1 - self.cj_2i0_Periodic_right[:,j] = self.cj_2i0[:,j] - - # Then, replace existing values with what will be needed to make the - # periodic boundary condition work. - j = 0 - # ORDER IS IMPORTANT HERE! Don't change first before it changes other. - # (We are shuffling down the line) - self.cj_1i1[:,j] = self.cj_1i0[:,j] - self.cj_1i0[:,j] = self.cj_1i_1[:,j] - - # And then change remaning off-grid values to np.inf (i.e. those that - # were not altered to a real value - # These will be the +/- 2's and the j_1i_1 and the j1i1 - # These are the farthest-out pentadiagonals that can't be reached by - # the tridiagonals, and the tridiagonals that are farther away on the - # y (big grid) axis that can't be reached by the single diagonals - # that are farthest out - # So 4 diagonals. - # But ci1j1 is taken care of on -1 end before being rolled forwards - # (i.e. clockwise, if we are reading from the top of the tread of a - # tire) - j = 0 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += np.inf - j = 1 - self.cj_2i0[:,j] += np.inf - - else: - sys.exit("Not physical to have one wrap-around boundary but not its pair.") - elif self.BC_W == '0Displacement0Slope': - j = 0 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += np.inf - self.cj_1i0[:,j] += np.inf - self.cj_1i1[:,j] += np.inf - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 0 - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += 0 - j = 1 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 0 - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 0 - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += 0 - elif self.BC_W == '0Moment0Shear': - j = 0 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += np.inf - self.cj_1i0[:,j] += np.inf - self.cj_1i1[:,j] += np.inf - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 2*self.cj_1i_1_coeff_ij[:,j] - self.cj0i0[:,j] += 4*self.cj_2i0_coeff_ij[:,j] + 2*self.cj_1i0_coeff_ij[:,j] - self.cj0i1[:,j] += 2*self.cj_1i1_coeff_ij[:,j] - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += -self.cj_1i_1_coeff_ij[:,j] - self.cj1i0[:,j] += -4*self.cj_2i0_coeff_ij[:,j] - self.cj_1i0_coeff_ij[:,j] - self.cj1i1[:,j] += -self.cj_1i1_coeff_ij[:,j] - self.cj2i0[:,j] += self.cj_2i0_coeff_ij[:,j] - j = 1 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 2*self.cj_2i0_coeff_ij[:,j] - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += -2*self.cj_2i0_coeff_ij[:,j] - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += self.cj_2i0_coeff_ij[:,j] - elif self.BC_W == '0Slope0Shear': - j = 0 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += np.inf - self.cj_1i0[:,j] += np.inf - self.cj_1i1[:,j] += np.inf - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += self.cj_1i_1_coeff_ij[:,j] - self.cj1i0[:,j] += self.cj_1i0_coeff_ij[:,j] - self.cj1i1[:,j] += self.cj_1i1_coeff_ij[:,j] #Interference - self.cj2i0[:,j] += self.cj_2i0_coeff_ij[:,j] - j = 1 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 0 - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 0 - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += self.cj_2i0_coeff_ij[:,j] - elif self.BC_W == 'Mirror': - j = 0 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += np.inf - self.cj_1i0[:,j] += np.inf - self.cj_1i1[:,j] += np.inf - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += self.cj_1i_1_coeff_ij[:,j] - self.cj1i0[:,j] += self.cj_1i0_coeff_ij[:,j] - self.cj1i1[:,j] += self.cj_1i1_coeff_ij[:,j] - self.cj2i0[:,j] += self.cj_2i0_coeff_ij[:,j] - j = 1 - self.cj_2i0[:,j] += np.inf - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 0 - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += self.cj_2i0_coeff_ij[:,j] - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 0 - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += 0 - else: - # Possibly redundant safeguard - sys.exit("Invalid boundary condition") - - if self.BC_E == 'Periodic': - # See more extensive comments above (BC_W) - - if self.BC_W == 'Periodic': - # New arrays -- new diagonals, but mostly empty. Just corners of blocks - # (boxes) in block-diagonal matrix - self.cj1i_1_Periodic_left = np.zeros(self.qs.shape) - self.cj2i0_Periodic_left = np.zeros(self.qs.shape) - j = -1 - self.cj1i_1_Periodic_left[:,j] = self.cj1i_1[:,j] - self.cj2i0_Periodic_left[:,j] = self.cj2i0[:,j] - j=-2 - self.cj2i0_Periodic_left[:,j] = self.cj2i0[:,j] - - # Then, replace existing values with what will be needed to make the - # periodic boundary condition work. - j =-1 - self.cj1i_1[:,j] = self.cj1i0[:,j] - self.cj1i0[:,j] = self.cj1i1[:,j] - - # And then change remaning off-grid values to np.inf (i.e. those that - # were not altered to a real value - j = -1 - self.cj1i1[:,j] += np.inf - self.cj2i0[:,j] += np.inf - j = -2 - self.cj2i0[:,j] += np.inf - - else: - sys.exit("Not physical to have one wrap-around boundary but not its pair.") - - elif self.BC_E == '0Displacement0Slope': - j = -1 - self.cj_2i0[:,j] += 0 - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 0 - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += np.inf - self.cj1i0[:,j] += np.inf - self.cj1i1[:,j] += np.inf - self.cj2i0[:,j] += np.inf - j = -2 - self.cj_2i0[:,j] += 0 - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 0 - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 0 - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += np.inf - elif self.BC_E == '0Moment0Shear': - j = -1 - self.cj_2i0[:,j] += self.cj2i0_coeff_ij[:,j] - self.cj_1i_1[:,j] += -self.cj1i_1_coeff_ij[:,j] - self.cj_1i0[:,j] += -4*self.cj2i0_coeff_ij[:,j] - self.cj1i0_coeff_ij[:,j] - self.cj_1i1[:,j] += -self.cj1i1_coeff_ij[:,j] - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 2*self.cj1i_1_coeff_ij[:,j] - self.cj0i0[:,j] += 4*self.cj2i0_coeff_ij[:,j] + 2*self.cj1i0_coeff_ij[:,j] - self.cj0i1[:,j] += 2*self.cj1i1_coeff_ij[:,j] - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += np.inf - self.cj1i0[:,j] += np.inf - self.cj1i1[:,j] += np.inf - self.cj2i0[:,j] += np.inf - j = -2 - self.cj_2i0[:,j] += self.cj2i0_coeff_ij[:,j] - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += -2*self.cj2i0_coeff_ij[:,j] - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 2*self.cj2i0_coeff_ij[:,j] - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += np.inf - elif self.BC_E == '0Slope0Shear': - j = -1 - self.cj_2i0[:,j] += self.cj2i0_coeff_ij[:,j] - self.cj_1i_1[:,j] += self.cj1i_1_coeff_ij[:,j] - self.cj_1i0[:,j] += self.cj1i0_coeff_ij[:,j] - self.cj_1i1[:,j] += self.cj1i1_coeff_ij[:,j] - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += np.inf - self.cj1i0[:,j] += np.inf - self.cj1i1[:,j] += np.inf - self.cj2i0[:,j] += np.inf - j = -2 - self.cj_2i0[:,j] += self.cj2i0_coeff_ij[:,j] - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 0 - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 0 - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += np.inf - elif self.BC_E == 'Mirror': - j = -1 - self.cj_2i0[:,j] += self.cj2i0_coeff_ij[:,j] - self.cj_1i_1[:,j] += self.cj1i_1_coeff_ij[:,j] - self.cj_1i0[:,j] += self.cj1i0_coeff_ij[:,j] - self.cj_1i1[:,j] += self.cj1i1_coeff_ij[:,j] - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += 0 - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += np.inf - self.cj1i0[:,j] += np.inf - self.cj1i1[:,j] += np.inf - self.cj2i0[:,j] += np.inf - j = -2 - self.cj_2i0[:,j] += 0 - self.cj_1i_1[:,j] += 0 - self.cj_1i0[:,j] += 0 - self.cj_1i1[:,j] += 0 - self.cj0i_2[:,j] += 0 - self.cj0i_1[:,j] += 0 - self.cj0i0[:,j] += self.cj2i0_coeff_ij[:,j] - self.cj0i1[:,j] += 0 - self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += 0 - self.cj1i0[:,j] += 0 - self.cj1i1[:,j] += 0 - self.cj2i0[:,j] += np.inf - else: - # Possibly redundant safeguard - sys.exit("Invalid boundary condition") - - ####################################################################### - # DEFINE COEFFICIENTS TO W_i-2 -- W_i+2 WITH B.C.'S APPLIED (y: N, S) # - ####################################################################### - - if self.BC_N == 'Periodic': - if self.BC_S == 'Periodic': - pass # Will address the N-S (whole-matrix-involving) boundary condition - # inclusion below, when constructing sparse matrix diagonals - else: - sys.exit("Not physical to have one wrap-around boundary but not its pair.") - elif self.BC_N == '0Displacement0Slope': - i = 0 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:][self.cj_1i_1[i,:] != np.inf] = np.nan - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 0 #np.nan - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 - self.cj0i2[i,:] += 0 - self.cj1i_1[i,:][self.cj1i_1[i,:] != np.inf] += 0 #np.nan - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += 0 - self.cj2i0[i,:] += 0 - i = 1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 0 - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 - self.cj0i2[i,:] += 0 - self.cj1i_1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += 0 - self.cj2i0[i,:] += 0 - elif self.BC_N == '0Moment0Shear': - i = 0 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:][self.cj_1i_1[i,:] != np.inf] = np.nan - self.cj_1i0[i,:] += 2*self.cj_1i_1_coeff_ij[i,:] - self.cj_1i1[i,:] += -self.cj_1i_1_coeff_ij[i,:] - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 0 #np.nan - self.cj0i0[i,:] += 4*self.cj0i_2_coeff_ij[i,:] + 2*self.cj0i_1_coeff_ij[i,:] - self.cj0i1[i,:] += -4*self.cj0i_2_coeff_ij[i,:] - self.cj0i_1_coeff_ij[i,:] - self.cj0i2[i,:] += self.cj0i_2_coeff_ij[i,:] - self.cj1i_1[i,:][self.cj1i_1[i,:] != np.inf] += 0 #np.nan - self.cj1i0[i,:] += 2*self.cj1i_1_coeff_ij[i,:] - self.cj1i1[i,:] += -self.cj1i_1_coeff_ij[i,:] - self.cj2i0[i,:] += 0 - i = 1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 2*self.cj0i_2_coeff_ij[i,:] - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += -2*self.cj0i_2_coeff_ij[i,:] - self.cj0i2[i,:] += self.cj0i_2_coeff_ij[i,:] - self.cj1i_1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += 0 - self.cj2i0[i,:] += 0 - elif self.BC_N == '0Slope0Shear': - i = 0 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:][self.cj_1i_1[i,:] != np.inf] = np.nan - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += self.cj_1i_1_coeff_ij[i,:] - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 0 #np.nan - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += self.cj0i_1_coeff_ij[i,:] - self.cj0i2[i,:] += self.cj0i_2_coeff_ij[i,:] - self.cj1i_1[i,:][self.cj1i_1[i,:] != np.inf] += 0 #np.nan - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += self.cj1i_1_coeff_ij[i,:] - self.cj2i0[i,:] += 0 - i = 1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 0 - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 - self.cj0i2[i,:] += self.cj0i_2_coeff_ij[i,:] - self.cj1i_1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += 0 - self.cj2i0[i,:] += 0 - elif self.BC_N == 'Mirror': - i = 0 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:][self.cj_1i_1[i,:] != np.inf] = np.nan - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += self.cj_1i_1_coeff_ij[i,:] - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 0 #np.nan - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += self.cj0i_1_coeff_ij[i,:] - self.cj0i2[i,:] += self.cj0i_2_coeff_ij[i,:] - self.cj1i_1[i,:][self.cj1i_1[i,:] != np.inf] += 0 #np.nan - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += self.cj1i_1_coeff_ij[i,:] - self.cj2i0[i,:] += 0 - i = 1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += 0 #np.nan - self.cj0i_1[i,:] += 0 - self.cj0i0[i,:] += self.cj0i_2_coeff_ij[i,:] - self.cj0i1[i,:] += 0 - self.cj0i2[i,:] += 0 - self.cj1i_1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += 0 - self.cj2i0[i,:] += 0 - else: - # Possibly redundant safeguard - sys.exit("Invalid boundary condition") - - if self.BC_S == 'Periodic': - if self.BC_N == 'Periodic': - pass # Will address the N-S (whole-matrix-involving) boundary condition - # inclusion below, when constructing sparse matrix diagonals - else: - sys.exit("Not physical to have one wrap-around boundary but not its pair.") - elif self.BC_S == '0Displacement0Slope': - i = -2 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += 0 - self.cj0i_1[i,:] += 0 - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 - self.cj0i2[i,:] += 0 #np.nan - self.cj1i1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i_1[i,:] += 0 - self.cj2i0[i,:] += 0 - i = -1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:][self.cj_1i1[i,:] != np.inf] += 0 #np.nan - self.cj0i_2[i,:] += 0 - self.cj0i_1[i,:] += 0 - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 #np.nan - self.cj0i2[i,:] += 0 #np.nan - self.cj1i_1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i1[i,:][self.cj1i1[i,:] != np.inf] += 0 #np.nan - self.cj2i0[i,:] += 0 - elif self.BC_S == '0Moment0Shear': - i = -2 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += self.cj0i2_coeff_ij[i,:] - self.cj0i_1[i,:] += -2*self.cj0i2_coeff_ij[i,:] - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 2*self.cj0i2_coeff_ij[i,:] - self.cj0i2[i,:] += 0 #np.nan - self.cj1i1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i_1[i,:] += 0 - self.cj2i0[i,:] += 0 - i = -1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += -self.cj1i1_coeff_ij[i,:] - self.cj_1i0[i,:] += 2*self.cj1i1_coeff_ij[i,:] - self.cj_1i1[i,:][self.cj_1i1[i,:] != np.inf] += 0 #np.nan - self.cj0i_2[i,:] += self.cj0i2_coeff_ij[i,:] - self.cj0i_1[i,:] += -4*self.cj0i2_coeff_ij[i,:] - self.cj0i1_coeff_ij[i,:] - self.cj0i0[i,:] += 4*self.cj0i2_coeff_ij[i,:] + 2*self.cj0i1_coeff_ij[i,:] - self.cj0i1[i,:] += 0 #np.nan - self.cj0i2[i,:] += 0 #np.nan - self.cj1i_1[i,:] += -self.cj_1i1_coeff_ij[i,:] - self.cj1i0[i,:] += 2*self.cj_1i1_coeff_ij[i,:] - self.cj1i1[i,:][self.cj1i1[i,:] != np.inf] += 0 #np.nan - self.cj2i0[i,:] += 0 - elif self.BC_S == '0Slope0Shear': - i = -2 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += self.cj0i2_coeff_ij[i,:] - self.cj0i_1[i,:] += 0 - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 - self.cj0i2[i,:] += 0 #np.nan - self.cj1i_1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += 0 - self.cj2i0[i,:] += 0 - i = -1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += self.cj_1i1_coeff_ij[i,:] - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:][self.cj_1i1[i,:] != np.inf] += 0 #np.nan - self.cj0i_2[i,:] += self.cj0i2_coeff_ij[i,:] - self.cj0i_1[i,:] += self.cj0i1_coeff_ij[i,:] - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 #np.nan - self.cj0i2[i,:] += 0 #np.nan - self.cj1i_1[i,:] += self.cj1i1_coeff_ij[i,:] - self.cj1i0[i,:] += 0 - self.cj1i1[i,:][self.cj1i1[i,:] != np.inf] += 0 #np.nan - self.cj2i0[i,:] += 0 - elif self.BC_S == 'Mirror': - i = -2 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += 0 - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:] += 0 - self.cj0i_2[i,:] += 0 - self.cj0i_1[i,:] += 0 - self.cj0i0[i,:] += self.cj0i2_coeff_ij[i,:] - self.cj0i1[i,:] += 0 - self.cj0i2[i,:] += 0 #np.nan - self.cj1i_1[i,:] += 0 - self.cj1i0[i,:] += 0 - self.cj1i1[i,:] += 0 - self.cj2i0[i,:] += 0 - i = -1 - self.cj_2i0[i,:] += 0 - self.cj_1i_1[i,:] += self.cj_1i1_coeff_ij[i,:] - self.cj_1i0[i,:] += 0 - self.cj_1i1[i,:][self.cj_1i1[i,:] != np.inf] += 0 #np.nan - self.cj0i_2[i,:] += self.cj0i2_coeff_ij[i,:] - self.cj0i_1[i,:] += self.cj0i1_coeff_ij[i,:] - self.cj0i0[i,:] += 0 - self.cj0i1[i,:] += 0 #np.nan - self.cj0i2[i,:] += 0 #np.nan - self.cj1i_1[i,:] += self.cj1i1_coeff_ij[i,:] - self.cj1i0[i,:] += 0 - self.cj1i1[i,:][self.cj1i1[i,:] != np.inf] += 0 #np.nan - self.cj2i0[i,:] += 0 - else: - # Possibly redundant safeguard - sys.exit("Invalid boundary condition") - - ##################################################### - # CORNERS: INTERFERENCE BETWEEN BOUNDARY CONDITIONS # - ##################################################### - - # In 2D, have to consider diagonals and interference (additive) among - # boundary conditions - - ############################ - # DIRICHLET -- DO NOTHING. # - ############################ - - # Do nothing. - # What about combinations? - # This will mean that dirichlet boundary conditions will implicitly - # control the corners, so, for examplel, they would be locked all of the - # way to the edge of the domain instead of becoming free to deflect at the - # ends. - # Indeed it is much easier to envision this case than one in which - # the stationary clamp is released. - - ################# - # 0MOMENT0SHEAR # - ################# - if self.BC_N == '0Moment0Shear' and self.BC_W == '0Moment0Shear': - self.cj0i0[0,0] += 2*self.cj_1i_1_coeff_ij[0,0] - self.cj1i1[0,0] -= self.cj_1i_1_coeff_ij[0,0] - if self.BC_N == '0Moment0Shear' and self.BC_E == '0Moment0Shear': - self.cj0i0[0,-1] += 2*self.cj_1i_1_coeff_ij[0,-1] - self.cj_1i1[0,-1] -= self.cj1i_1_coeff_ij[0,-1] - if self.BC_S == '0Moment0Shear' and self.BC_W == '0Moment0Shear': - self.cj0i0[-1,0] += 2*self.cj_1i_1_coeff_ij[-1,0] - self.cj1i_1[-1,0] -= self.cj_1i1_coeff_ij[-1,0] - if self.BC_S == '0Moment0Shear' and self.BC_E == '0Moment0Shear': - self.cj0i0[-1,-1] += 2*self.cj_1i_1_coeff_ij[-1,-1] - self.cj_1i_1[-1,-1] -= self.cj1i1_coeff_ij[-1,-1] - - ############ - # PERIODIC # - ############ - - # I think that nothing will be needed here. - # Periodic should just take care of all repeating in all directions by - # its very nature. (I.e. it is embedded in the sparse array structure - # of diagonals) - - - ################ - # COMBINATIONS # - ################ - - ############################## - # 0SLOPE0SHEAR AND/OR MIRROR # - ############################## - # (both end up being the same) - if (self.BC_N == '0Slope0Shear' or self.BC_N == 'Mirror') \ - and (self.BC_W == '0Slope0Shear' or self.BC_W == 'Mirror'): - self.cj1i1[0,0] += self.cj_1i_1_coeff_ij[0,0] - if (self.BC_N == '0Slope0Shear' or self.BC_N == 'Mirror') \ - and (self.BC_E == '0Slope0Shear' or self.BC_E == 'Mirror'): - self.cj_1i1[0,-1] += self.cj1i_1_coeff_ij[0,-1] - if (self.BC_S == '0Slope0Shear' or self.BC_S == 'Mirror') \ - and (self.BC_W == '0Slope0Shear' or self.BC_W == 'Mirror'): - self.cj1i_1[-1,0] += self.cj_1i1_coeff_ij[-1,0] - if (self.BC_S == '0Slope0Shear' or self.BC_S == 'Mirror') \ - and (self.BC_E == '0Slope0Shear' or self.BC_E == 'Mirror'): - self.cj_1i_1[-1,-1] += self.cj1i1_coeff_ij[-1,-1] - - ################################ - # 0MOMENT0SHEAR - AND - MIRROR # - ################################ - # How do multiple types of b.c.'s interfere - # 0Moment0Shear must determine corner conditions in order to be mirrored - # by the "mirror" b.c. - if (self.BC_N == 'Mirror' and self.BC_W == '0Moment0Shear') \ - or (self.BC_W == 'Mirror' and self.BC_N == '0Moment0Shear'): - self.cj0i0[0,0] += 2*self.cj_1i_1_coeff_ij[0,0] - self.cj1i1[0,0] -= self.cj_1i_1_coeff_ij[0,0] - if (self.BC_N == 'Mirror' and self.BC_E == '0Moment0Shear') \ - or (self.BC_E == 'Mirror' and self.BC_N == '0Moment0Shear'): - self.cj0i0[0,-1] += 2*self.cj_1i_1_coeff_ij[0,-1] - self.cj1i1[0,-1] -= self.cj_1i_1_coeff_ij[0,-1] - if (self.BC_S == 'Mirror' and self.BC_W == '0Moment0Shear') \ - or (self.BC_W == 'Mirror' and self.BC_S == '0Moment0Shear'): - self.cj0i0[-1,0] += 2*self.cj_1i_1_coeff_ij[-1,0] - self.cj1i_1[-1,0] -= self.cj_1i1_coeff_ij[-1,0] - if (self.BC_S == 'Mirror' and self.BC_E == '0Moment0Shear') \ - or (self.BC_E == 'Mirror' and self.BC_S == '0Moment0Shear'): - self.cj0i0[-1,-1] += 2*self.cj_1i_1_coeff_ij[-1,-1] - self.cj_1i_1[-1,-1] -= self.cj1i1_coeff_ij[-1,-1] + def initialize(self, filename=None): + self.dimension = 2 # Set it here in case it wasn't set for selection before + super().initialize() + if self.Verbose: + print("F2D initialized") + + def run(self): + self.bc_check() + self.solver_start_time = time.time() + + if self.Method == "FD": + # Finite difference + super().FD() + self.method_func = self.FD + elif self.Method == "FFT": + # Fast Fourier transform + super().FFT() + self.method_func = self.FFT + elif self.Method == "SAS": + # Superposition of analytical solutions + super().SAS() + self.method_func = self.SAS + elif self.Method == "SAS_NG": + # Superposition of analytical solutions, + # nonuniform points (no grid) + super().SAS_NG() + self.method_func = self.SAS_NG + else: + sys.exit('Error: method must be "FD", "FFT", "SAS", or "SAS_NG"') + + if self.Verbose: + print("F2D run") + self.method_func() + + self.time_to_solve = time.time() - self.solver_start_time + if self.Quiet == False: + print("Time to solve [s]:", self.time_to_solve) + + def finalize(self): + # If elastic thickness has been padded, return it to its original + # value, so this is not messed up for repeat operations in a + # model-coupling exercise + try: + self.Te = self.Te_unpadded + except: + pass + if self.Verbose: + print("F2D finalized") + super().finalize() + + ######################################## + ## FUNCTIONS FOR EACH SOLUTION METHOD ## + ######################################## + + def FD(self): + # Only generate coefficient matrix if it is not already provided + if self.coeff_matrix is not None: + pass + else: + self.elasprep() + self.BC_selector_and_coeff_matrix_creator() + self.fd_solve() + + def FFT(self): + sys.exit("The fast Fourier transform solution method is not yet implemented.") + + def SAS(self): + self.spatialDomainVarsSAS() + self.spatialDomainGridded() + + def SAS_NG(self): + self.spatialDomainVarsSAS() + self.spatialDomainNoGrid() ###################################### - # 0MOMENT0SHEAR - AND - 0SLOPE0SHEAR # + ## FUNCTIONS TO SOLVE THE EQUATIONS ## ###################################### - # Just use 0Moment0Shear-style b.c.'s at corners: letting this dominate - # because it seems to be the more geologically likely b.c. - if (self.BC_N == '0Slope0Shear' and self.BC_W == '0Moment0Shear') \ - or (self.BC_W == '0Slope0Shear' and self.BC_N == '0Moment0Shear'): - self.cj0i0[0,0] += 2*self.cj_1i_1_coeff_ij[0,0] - self.cj1i1[0,0] -= self.cj_1i_1_coeff_ij[0,0] - if (self.BC_N == '0Slope0Shear' and self.BC_E == '0Moment0Shear') \ - or (self.BC_E == '0Slope0Shear' and self.BC_N == '0Moment0Shear'): - self.cj0i0[0,-1] += 2*self.cj_1i_1_coeff_ij[0,-1] - self.cj1i1[0,-1] -= self.cj_1i_1_coeff_ij[0,-1] - if (self.BC_S == '0Slope0Shear' and self.BC_W == '0Moment0Shear') \ - or (self.BC_W == '0Slope0Shear' and self.BC_S == '0Moment0Shear'): - self.cj0i0[-1,0] += 2*self.cj_1i_1_coeff_ij[-1,0] - self.cj1i_1[-1,0] -= self.cj_1i1_coeff_ij[-1,0] - if (self.BC_S == '0Slope0Shear' and self.BC_E == '0Moment0Shear') \ - or (self.BC_E == '0Slope0Shear' and self.BC_S == '0Moment0Shear'): - self.cj0i0[-1,-1] += 2*self.cj_1i_1_coeff_ij[-1,-1] - self.cj_1i_1[-1,-1] -= self.cj1i1_coeff_ij[-1,-1] - # What about 0Moment0SHear on N/S part? - - ############################## - # PERIODIC B.C.'S AND OTHERS # - ############################## - - # The Periodic boundary natively continues the other boundary conditions - # Nothing to be done here. - - def build_diagonals(self): - - ########################################################## - # INCORPORATE BOUNDARY CONDITIONS INTO COEFFICIENT ARRAY # - ########################################################## - - # Roll to keep the proper coefficients at the proper places in the - # arrays: Python will naturally just do vertical shifts instead of - # diagonal shifts, so this takes into account the horizontal compoent - # to ensure that boundary values are at the right place. - - # Roll x -# ASYMMETRIC RESPONSE HERE -- THIS GETS TOWARDS SOURCE OF PROBLEM! - self.cj_2i0 = np.roll(self.cj_2i0, -2, 1) - self.cj_1i0 = np.roll(self.cj_1i0, -1, 1) - self.cj1i0 = np.roll(self.cj1i0, 1, 1) - self.cj2i0 = np.roll(self.cj2i0, 2, 1) - # Roll y - self.cj0i_2 = np.roll(self.cj0i_2, -2, 0) - self.cj0i_1 = np.roll(self.cj0i_1, -1, 0) - self.cj0i1 = np.roll(self.cj0i1, 1, 0) - self.cj0i2 = np.roll(self.cj0i2, 2, 0) - # Roll x and y - self.cj_1i_1 = np.roll(self.cj_1i_1, -1, 1) - self.cj_1i_1 = np.roll(self.cj_1i_1, -1, 0) - self.cj_1i1 = np.roll(self.cj_1i1, -1, 1) - self.cj_1i1 = np.roll(self.cj_1i1, 1, 0) - self.cj1i_1 = np.roll(self.cj1i_1, 1, 1) - self.cj1i_1 = np.roll(self.cj1i_1, -1, 0) - self.cj1i1 = np.roll(self.cj1i1, 1, 1) - self.cj1i1 = np.roll(self.cj1i1, 1, 0) - - coeff_array_list = [self.cj_2i0, self.cj_1i0, self.cj1i0, self.cj2i0, self.cj0i_2, self.cj0i_1, self.cj0i1, self.cj0i2, self.cj_1i_1, self.cj_1i1, self.cj1i_1, self.cj1i1, self.cj0i0] - for array in coeff_array_list: - array[np.isinf(array)] = 0 - #array[np.isnan(array)] = 0 # had been used for testing - - # Reshape to put in solver - vec_cj_2i0 = np.reshape(self.cj_2i0, -1, order='C') - vec_cj_1i_1 = np.reshape(self.cj_1i_1, -1, order='C') - vec_cj_1i0 = np.reshape(self.cj_1i0, -1, order='C') - vec_cj_1i1 = np.reshape(self.cj_1i1, -1, order='C') - vec_cj0i_2 = np.reshape(self.cj0i_2, -1, order='C') - vec_cj0i_1 = np.reshape(self.cj0i_1, -1, order='C') - vec_cj0i0 = np.reshape(self.cj0i0, -1, order='C') - vec_cj0i1 = np.reshape(self.cj0i1, -1, order='C') - vec_cj0i2 = np.reshape(self.cj0i2, -1, order='C') - vec_cj1i_1 = np.reshape(self.cj1i_1, -1, order='C') - vec_cj1i0 = np.reshape(self.cj1i0, -1, order='C') - vec_cj1i1 = np.reshape(self.cj1i1, -1, order='C') - vec_cj2i0 = np.reshape(self.cj2i0, -1, order='C') - - # Changed this 6 Nov. 2014 in betahaus Berlin to be x-based - Up2 = vec_cj0i2 - Up1 = np.vstack(( vec_cj_1i1, vec_cj0i1, vec_cj1i1 )) - Mid = np.vstack(( vec_cj_2i0, vec_cj_1i0, vec_cj0i0, vec_cj1i0, vec_cj2i0 )) - Dn1 = np.vstack(( vec_cj_1i_1, vec_cj0i_1, vec_cj1i_1 )) - Dn2 = vec_cj0i_2 - - # Number of rows and columns for array size and offsets - self.ny = self.nrowsy - self.nx = self.ncolsx - - if (self.BC_N == 'Periodic' and self.BC_S == 'Periodic' and \ - self.BC_W == 'Periodic' and self.BC_E == 'Periodic' ): - # Additional vector creation - # West - # Roll - self.cj_2i0_Periodic_right = np.roll(self.cj_2i0_Periodic_right, -2, 1) - self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, -1, 1) - self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, 1, 0) - # Reshape - vec_cj_2i0_Periodic_right = np.reshape(self.cj_2i0_Periodic_right, -1, order='C') - vec_cj_1i1_Periodic_right = np.reshape(self.cj_1i1_Periodic_right, -1, order='C') - # East - # Roll - self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, 1, 1) - self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, -1, 0) - self.cj2i0_Periodic_left = np.roll(self.cj2i0_Periodic_left, 2, 1) - # Reshape - vec_cj1i_1_Periodic_left = np.reshape(self.cj1i_1_Periodic_left, -1, order='C') - vec_cj2i0_Periodic_left = np.reshape(self.cj2i0_Periodic_left, -1, order='C') - - # Build diagonals with additional entries - # I think the fact that everything is rolled will make this work all right - # without any additional rolling. - # Checked -- and indeed, what would be in my mind the last value for - # Mid[3] is the first value in its array. Hooray, patterns! - self.diags = np.vstack(( vec_cj1i_1_Periodic_left, - Up1, - vec_cj_1i1_Periodic_right, - Up2, - Dn2, - vec_cj1i_1_Periodic_left, - Dn1, - vec_cj2i0_Periodic_left, - Mid, - vec_cj_2i0_Periodic_right, - Up1, - vec_cj_1i1_Periodic_right, - Up2, - Dn2, - vec_cj1i_1_Periodic_left, - Dn1, - vec_cj_1i1_Periodic_right )) - # Getting too complicated to have everything together - self.offsets = [ - # New: LL corner of LL box - -self.ny*self.nx+1, - # Periodic b.c. tridiag - self.nx-self.ny*self.nx-1, self.nx-self.ny*self.nx, self.nx-self.ny*self.nx+1, - # New: UR corner of LL box - 2*self.nx-self.ny*self.nx-1, - # Periodic b.c. single diag - 2*self.nx-self.ny*self.nx, - -2*self.nx, - # New: - -2*self.nx+1, - # Right term here (-self.nx+1) modified: - -self.nx-1, -self.nx, -self.nx+1, - # New: - -self.nx+2, - # -1 and 1 terms here modified: - -2, -1, 0, 1, 2, - # New: - self.nx-2, - # Left term here (self.nx-1) modified: - self.nx-1, self.nx, self.nx+1, - # New: - 2*self.nx-1, - 2*self.nx, - # Periodic b.c. single diag - self.ny*self.nx-2*self.nx, - # New: LL corner of UR box - self.ny*self.nx-2*self.nx+1, - # Periodic b.c. tridiag - self.ny*self.nx-self.nx-1, self.ny*self.nx-self.nx, self.ny*self.nx-self.nx+1, - # New: UR corner of UR box - self.ny*self.nx-1 - ] - - # create banded sparse matrix - self.coeff_matrix = scipy.sparse.spdiags(self.diags, self.offsets, - self.ny*self.nx, self.ny*self.nx, format='csr') - - elif (self.BC_W == 'Periodic' and self.BC_E == 'Periodic'): - # Additional vector creation - # West - # Roll - self.cj_2i0_Periodic_right = np.roll(self.cj_2i0_Periodic_right, -2, 1) - self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, -1, 1) - self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, 1, 0) - # Reshape - vec_cj_2i0_Periodic_right = np.reshape(self.cj_2i0_Periodic_right, -1, order='C') - vec_cj_1i1_Periodic_right = np.reshape(self.cj_1i1_Periodic_right, -1, order='C') - # East - # Roll - self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, 1, 1) - self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, -1, 0) - self.cj2i0_Periodic_left = np.roll(self.cj2i0_Periodic_left, 2, 1) - # Reshape - vec_cj1i_1_Periodic_left = np.reshape(self.cj1i_1_Periodic_left, -1, order='C') - vec_cj2i0_Periodic_left = np.reshape(self.cj2i0_Periodic_left, -1, order='C') - - # Build diagonals with additional entries - self.diags = np.vstack(( Dn2, - vec_cj1i_1_Periodic_left, - Dn1, - vec_cj2i0_Periodic_left, - Mid, - vec_cj_2i0_Periodic_right, - Up1, - vec_cj_1i1_Periodic_right, - Up2 )) - # Getting too complicated to have everything together - self.offsets = [-2*self.nx, - # New: - -2*self.nx+1, - # Right term here (-self.nx+1) modified: - -self.nx-1, -self.nx, -self.nx+1, - # New: - -self.nx+2, - # -1 and 1 terms here modified: - -2, -1, 0, 1, 2, - # New: - self.nx-2, - # Left term here (self.nx-1) modified: - self.nx-1, self.nx, self.nx+1, - # New: - 2*self.nx-1, - 2*self.nx] - - # create banded sparse matrix - self.coeff_matrix = scipy.sparse.spdiags(self.diags, self.offsets, - self.ny*self.nx, self.ny*self.nx, format='csr') - - elif (self.BC_N == 'Periodic' and self.BC_S == 'Periodic'): - # Periodic. - # If these are periodic, we need to wrap around the ends of the - # large-scale diagonal structure - self.diags = np.vstack(( Up1, - Up2, - Dn2, - Dn1, - Mid, - Up1, - Up2, - Dn2, - Dn1 )) - # Create banded sparse matrix - # Rows: - # Lower left - # Middle - # Upper right - self.coeff_matrix = scipy.sparse.spdiags(self.diags, [self.nx-self.ny*self.nx-1, self.nx-self.ny*self.nx, self.nx-self.ny*self.nx+1, 2*self.nx-self.ny*self.nx, - -2*self.nx, -self.nx-1, -self.nx, -self.nx+1, -2, -1, 0, 1, 2, self.nx-1, self.nx, self.nx+1, 2*self.nx, - self.ny*self.nx-2*self.nx, self.ny*self.nx-self.nx-1, self.ny*self.nx-self.nx, self.ny*self.nx-self.nx+1], - self.ny*self.nx, self.ny*self.nx, format='csr') - - else: - # No periodic boundary conditions -- original form of coeff_matrix - # creator. - # Arrange in solver - self.diags = np.vstack(( Dn2, - Dn1, - Mid, - Up1, - Up2 )) - # Create banded sparse matrix - self.coeff_matrix = scipy.sparse.spdiags(self.diags, [-2*self.nx, -self.nx-1, -self.nx, -self.nx+1, -2, -1, 0, 1, 2, self.nx-1, self.nx, self.nx+1, 2*self.nx], self.ny*self.nx, self.ny*self.nx, format='csr') # create banded sparse matrix - - def calc_max_flexural_wavelength(self): - """ - Returns the approximate maximum flexural wavelength - This is important when padding of the grid is required: in Flexure (this - code), grids are padded out to one maximum flexural wavelength, but in any - case, the flexural wavelength is a good characteristic distance for any - truncation limit - """ - if np.isscalar(self.D): - Dmax = self.D - else: - Dmax = self.D.max() - # This is an approximation if there is fill that evolves with iterations - # (e.g., water), but should be good enough that this won't do much to it - alpha = (4*Dmax/(self.drho*self.g))**.25 # 2D flexural parameter - self.maxFlexuralWavelength = 2*np.pi*alpha - self.maxFlexuralWavelength_ncells_x = int(np.ceil(self.maxFlexuralWavelength / self.dx)) - self.maxFlexuralWavelength_ncells_y = int(np.ceil(self.maxFlexuralWavelength / self.dy)) - - def fd_solve(self): - """ - w = fd_solve() - Sparse flexural response calculation. - Can be performed by direct factorization with UMFpack (defuault) - or by an iterative minimum residual technique - These are both the fastest of the standard Scipy builtin techniques in - their respective classes - Requires the coefficient matrix from "2D.coeff_matrix" - """ - - if self.Debug: - try: - # Will fail if scalar - print("self.Te", self.Te.shape) - except: - pass - print("self.qs", self.qs.shape) - self.calc_max_flexural_wavelength() - print("maxFlexuralWavelength_ncells: (x, y):", self.maxFlexuralWavelength_ncells_x, self.maxFlexuralWavelength_ncells_y) - - q0vector = self.qs.reshape(-1, order='C') - if self.Solver == "iterative" or self.Solver == "Iterative": - if self.Debug: - print("Using generalized minimal residual method for iterative solution") - if self.Verbose: - print("Converging to a tolerance of", self.iterative_ConvergenceTolerance, "m between iterations") - wvector = scipy.sparse.linalg.isolve.lgmres(self.coeff_matrix, q0vector)#, tol=1E-10)#,x0=woldvector)#,x0=wvector,tol=1E-15) - wvector = wvector[0] # Reach into tuple to get my array back - else: - if self.Solver == "direct" or self.Solver == "Direct": + + ## SPATIAL DOMAIN SUPERPOSITION OF ANALYTICAL SOLUTIONS + ######################################################### + + # SETUP + + def spatialDomainVarsSAS(self): + # Check Te: + # * If scalar, okay. + # * If grid, convert to scalar if a singular value + # * Else, throw an error. + if np.isscalar(self.Te): + pass + elif np.all(self.Te == np.mean(self.Te)): + self.Te = np.mean(self.Te) + else: + sys.exit( + "\nINPUT VARIABLE TYPE INCONSISTENT WITH SOLUTION TYPE.\n" + "The analytical solution requires a scalar Te.\n" + "(gFlex is smart enough to make this out of a uniform\n" + "array, but won't know what value you want with a spatially\n" + "varying array! Try finite difference instead in this case?\n" + "EXITING." + ) + + self.D = self.E * self.Te**3 / (12 * (1 - self.nu**2)) # Flexural rigidity + self.alpha = (self.D / (self.drho * self.g)) ** 0.25 # 2D flexural parameter + self.coeff = self.alpha**2 / (2 * np.pi * self.D) + + # GRIDDED + + def spatialDomainGridded(self): + self.nx = self.qs.shape[1] + self.ny = self.qs.shape[0] + + # Prepare a large grid of solutions beforehand, so we don't have to + # keep calculating kei (time-intensive!) + # This pre-prepared solution will be for a unit load + bigshape = 2 * self.ny + 1, 2 * self.nx + 1 # Tuple shape + + dist_ny = np.arange(bigshape[0]) - self.ny + dist_nx = np.arange(bigshape[1]) - self.nx + + dist_x, dist_y = np.meshgrid(dist_nx * self.dx, dist_ny * self.dy) + + bigdist = np.sqrt(dist_x**2 + dist_y**2) # Distances from center + # Center at [ny,nx] + + biggrid = self.coeff * kei(bigdist / self.alpha) # Kelvin fcn solution + + # Now compute the deflections + self.w = np.zeros((self.ny, self.nx)) # Deflection array + for i in range(self.nx): + for j in range(self.ny): + # Loop over locations that have loads, and sum + if self.qs[j, i]: + # Solve by summing portions of "biggrid" while moving origin + # to location of current cell + # Load must be multiplied by grid cell size + self.w += ( + self.qs[j, i] + * self.dx + * self.dy + * biggrid[ + self.ny - j : 2 * self.ny - j, self.nx - i : 2 * self.nx - i + ] + ) + # No need to return: w already belongs to "self" + + # NO GRID + + def spatialDomainNoGrid(self): + self.w = np.zeros(self.xw.shape) if self.Debug: - print("Using direct solution with UMFpack") - else: + print("w = ") + print(self.w.shape) + + if self.latlon: + for i in range(len(self.x)): + # More efficient if we have created some 0-load points + # (e.g., for where we want output) + if self.q[i] != 0: + # Create array of distances from point of load + r = self.greatCircleDistance( + lat1=self.y[i], + long1=self.x[i], + lat2=self.yw, + long2=self.xw, + radius=self.PlanetaryRadius, + ) + self.w += self.q[i] * self.coeff * kei(r / self.alpha) + # Compute and sum deflection + self.w += self.q[i] * self.coeff * kei(r / self.alpha) + else: + for i in range(len(self.x)): + if self.q[i] != 0: + r = ((self.xw - self.x[i]) ** 2 + (self.yw - self.y[i]) ** 2) ** 0.5 + self.w += self.q[i] * self.coeff * kei(r / self.alpha) + + ## FINITE DIFFERENCE + ###################### + + def elasprep(self): + """ + dx4, dy4, dx2dy2, D = elasprep(dx,dy,Te,E=1E11,nu=0.25) + + Defines the variables that are required to create the 2D finite + difference solution coefficient matrix + """ + + if self.Method != "SAS_NG": + self.dx4 = self.dx**4 + self.dy4 = self.dy**4 + self.dx2dy2 = self.dx**2 * self.dy**2 + self.D = self.E * self.Te**3 / (12 * (1 - self.nu**2)) + + def BC_selector_and_coeff_matrix_creator(self): + """ + Selects the boundary conditions + E-W is for inside each panel + N-S is for the block diagonal matrix ("with fringes") + Then calls the function to build the diagonal matrix + + The current method of coefficient matrix construction utilizes longer-range + symmetry in the coefficient matrix to build it block-wise, as opposed to + the much less computationally efficient row-by-row ("serial") method + that was previously employed. + + The method is spread across the subroutines here. + + Important to this is the use of np.roll() to properly offset the + diagonals that end up in the main matrix: spdiags() will put each vector + on the proper diagonal, but will align them such that their first cell is + along the first column, instead of using a 45 degrees to matrix corner + baseline that would stagger them appropriately for this solution method. + Therefore, np.roll() effectively does this staggering by having the + appropriate cell in the vector start at the first column. + """ + + # Zeroth, start the timer and print the boundary conditions to the screen + self.coeff_start_time = time.time() + if self.Verbose: + print("Boundary condition, West:", self.BC_W, type(self.BC_W)) + print("Boundary condition, East:", self.BC_E, type(self.BC_E)) + print("Boundary condition, North:", self.BC_N, type(self.BC_N)) + print("Boundary condition, South:", self.BC_S, type(self.BC_S)) + + # First, set flexural rigidity boundary conditions to flesh out this padded + # array + self.BC_Rigidity() + + # Second, build the coefficient arrays -- with the rigidity b.c.'s + self.get_coeff_values() + + # Third, apply boundary conditions to the coeff_arrays to create the + # flexural solution + self.BC_Flexure() + + # Fourth, construct the sparse diagonal array + self.build_diagonals() + + # Finally, compute the total time this process took + self.coeff_creation_time = time.time() - self.coeff_start_time if self.Quiet == False: - print("Solution type not understood:") - print("Defaulting to direct solution with UMFpack") - wvector = scipy.sparse.linalg.spsolve(self.coeff_matrix, q0vector, use_umfpack=True) + print( + "Time to construct coefficient (operator) array [s]:", + self.coeff_creation_time, + ) + + def BC_Rigidity(self): + """ + Utility function to help implement boundary conditions by specifying + them for and applying them to the elastic thickness grid + """ + + ######################################### + # FLEXURAL RIGIDITY BOUNDARY CONDITIONS # + ######################################### + # West + if self.BC_W == "Periodic": + self.BC_Rigidity_W = "periodic" + elif ( + self.BC_W + == np.array(["0Displacement0Slope", "0Moment0Shear", "0Slope0Shear"]) + ).any(): + self.BC_Rigidity_W = "0 curvature" + elif self.BC_W == "Mirror": + self.BC_Rigidity_W = "mirror symmetry" + else: + sys.exit("Invalid Te B.C. case") + # East + if self.BC_E == "Periodic": + self.BC_Rigidity_E = "periodic" + elif ( + self.BC_E + == np.array(["0Displacement0Slope", "0Moment0Shear", "0Slope0Shear"]) + ).any(): + self.BC_Rigidity_E = "0 curvature" + elif self.BC_E == "Mirror": + self.BC_Rigidity_E = "mirror symmetry" + else: + sys.exit("Invalid Te B.C. case") + # North + if self.BC_N == "Periodic": + self.BC_Rigidity_N = "periodic" + elif ( + self.BC_N + == np.array(["0Displacement0Slope", "0Moment0Shear", "0Slope0Shear"]) + ).any(): + self.BC_Rigidity_N = "0 curvature" + elif self.BC_N == "Mirror": + self.BC_Rigidity_N = "mirror symmetry" + else: + sys.exit("Invalid Te B.C. case") + # South + if self.BC_S == "Periodic": + self.BC_Rigidity_S = "periodic" + elif ( + self.BC_S + == np.array(["0Displacement0Slope", "0Moment0Shear", "0Slope0Shear"]) + ).any(): + self.BC_Rigidity_S = "0 curvature" + elif self.BC_S == "Mirror": + self.BC_Rigidity_S = "mirror symmetry" + else: + sys.exit("Invalid Te B.C. case") + + ############# + # PAD ARRAY # + ############# + if np.isscalar(self.Te): + self.D *= np.ones(self.qs.shape) # And leave Te as a scalar for checks + else: + self.Te_unpadded = self.Te.copy() + self.Te = np.hstack( + ( + np.nan * np.zeros((self.Te.shape[0], 1)), + self.Te, + np.nan * np.zeros((self.Te.shape[0], 1)), + ) + ) + self.Te = np.vstack( + ( + np.nan * np.zeros(self.Te.shape[1]), + self.Te, + np.nan * np.zeros(self.Te.shape[1]), + ) + ) + self.D = np.hstack( + ( + np.nan * np.zeros((self.D.shape[0], 1)), + self.D, + np.nan * np.zeros((self.D.shape[0], 1)), + ) + ) + self.D = np.vstack( + ( + np.nan * np.zeros(self.D.shape[1]), + self.D, + np.nan * np.zeros(self.D.shape[1]), + ) + ) + + ############################################################### + # APPLY FLEXURAL RIGIDITY BOUNDARY CONDITIONS TO PADDED ARRAY # + ############################################################### + if self.BC_Rigidity_W == "0 curvature": + self.D[:, 0] = 2 * self.D[:, 1] - self.D[:, 2] + if self.BC_Rigidity_E == "0 curvature": + self.D[:, -1] = 2 * self.D[:, -2] - self.D[:, -3] + if self.BC_Rigidity_N == "0 curvature": + self.D[0, :] = 2 * self.D[1, :] - self.D[2, :] + if self.BC_Rigidity_S == "0 curvature": + self.D[-1, :] = 2 * self.D[-2, :] - self.D[-3, :] + + if self.BC_Rigidity_W == "mirror symmetry": + self.D[:, 0] = self.D[:, 2] + if self.BC_Rigidity_E == "mirror symmetry": + self.D[:, -1] = self.D[:, -3] + if self.BC_Rigidity_N == "mirror symmetry": + self.D[0, :] = self.D[ + 2, : + ] # Yes, will work on corners -- double-reflection + if self.BC_Rigidity_S == "mirror symmetry": + self.D[-1, :] = self.D[-3, :] + + if self.BC_Rigidity_W == "periodic": + self.D[:, 0] = self.D[:, -2] + if self.BC_Rigidity_E == "periodic": + self.D[:, -1] = self.D[:, -3] + if self.BC_Rigidity_N == "periodic": + self.D[0, :] = self.D[-2, :] + if self.BC_Rigidity_S == "periodic": + self.D[-1, :] = self.D[-3, :] + + def get_coeff_values(self): + """ + Calculates the matrix of coefficients that is later used via sparse matrix + solution techniques (scipy.sparse.linalg.spsolve) to compute the flexural + response to the load. This step need only be performed once, and the + coefficient matrix can very rapidly compute flexural solutions to any load. + This makes this particularly good for probelms with time-variable loads or + that require iteration (e.g., water loading, in which additional water + causes subsidence, causes additional water detph, etc.). + + These must be linearly combined to solve the equation. + + 13 coefficients: 13 matrices of the same size as the load + + NOTATION FOR COEFFICIENT BIULDING MATRICES (e.g., "cj0i_2"): + c = "coefficient + j = columns = x-value + j0 = no offset: look at center of array + i = rows = y-value + i_2 = negative 2 offset (i2 = positive 2 offset) + """ + + # don't want to keep typing "self." everwhere! + D = self.D + drho = self.drho + dx4 = self.dx4 + dy4 = self.dy4 + dx2dy2 = self.dx2dy2 + nu = self.nu + g = self.g + + if np.isscalar(self.Te): + # So much simpler with constant D! And symmetrical stencil + self.cj2i0 = D / dy4 + self.cj1i_1 = 2 * D / dx2dy2 + 2 * self.sigma_xy * self.T_e + self.cj1i0 = -4 * D / dy4 - 4 * D / dx2dy2 - self.sigma_yy * self.T_e + self.cj1i1 = 2 * D / dx2dy2 - 2 * self.sigma_xy * self.T_e + self.cj0i_2 = D / dx4 + self.cj0i_1 = -4 * D / dx4 - 4 * D / dx2dy2 - self.sigma_xx * self.T_e + self.cj0i0 = ( + 6 * D / dx4 + + 6 * D / dy4 + + 8 * D / dx2dy2 + + drho * g + + 2 * self.sigma_xx * self.T_e + + 2 * self.sigma_yy * self.T_e + ) + self.cj0i1 = ( + -4 * D / dx4 - 4 * D / dx2dy2 - self.sigma_xx * self.T_e + ) # Symmetry + self.cj0i2 = D / dx4 # Symmetry + self.cj_1i_1 = 2 * D / dx2dy2 - 2 * self.sigma_xy * self.T_e # Symmetry + self.cj_1i0 = -4 * D / dy4 - 4 * D / dx2dy2 # Symmetry + self.cj_1i1 = 2 * D / dx2dy2 + 2 * self.sigma_xy * self.T_e # Symmetry + self.cj_2i0 = D / dy4 # Symmetry + # Bring up to size + self.cj2i0 *= np.ones(self.qs.shape) + self.cj1i_1 *= np.ones(self.qs.shape) + self.cj1i0 *= np.ones(self.qs.shape) + self.cj1i1 *= np.ones(self.qs.shape) + self.cj0i_2 *= np.ones(self.qs.shape) + self.cj0i_1 *= np.ones(self.qs.shape) + self.cj0i0 *= np.ones(self.qs.shape) + self.cj0i1 *= np.ones(self.qs.shape) + self.cj0i2 *= np.ones(self.qs.shape) + self.cj_1i_1 *= np.ones(self.qs.shape) + self.cj_1i0 *= np.ones(self.qs.shape) + self.cj_1i1 *= np.ones(self.qs.shape) + self.cj_2i0 *= np.ones(self.qs.shape) + # Create coefficient arrays to manage boundary conditions + self.cj2i0_coeff_ij = self.cj2i0.copy() + self.cj1i_1_coeff_ij = self.cj1i_1.copy() + self.cj1i0_coeff_ij = self.cj1i0.copy() + self.cj1i1_coeff_ij = self.cj1i1.copy() + self.cj0i_2_coeff_ij = self.cj0i_2.copy() + self.cj0i_1_coeff_ij = self.cj0i_1.copy() + self.cj0i0_coeff_ij = self.cj0i0.copy() + self.cj0i1_coeff_ij = self.cj0i1.copy() + self.cj0i2_coeff_ij = self.cj0i2.copy() + self.cj_1i_1_coeff_ij = self.cj_1i_1.copy() + self.cj_1i0_coeff_ij = self.cj_1i0.copy() + self.cj_1i1_coeff_ij = self.cj_1i1.copy() + self.cj_2i0_coeff_ij = self.cj_2i0.copy() + + elif type(self.Te) == np.ndarray: + ####################################################### + # GENERATE COEFFICIENT VALUES FOR EACH SOLUTION TYPE. # + # "vWC1994" IS THE BEST: LOOSEST ASSUMPTIONS. # + # OTHERS HERE LARGELY FOR COMPARISON # + ####################################################### + + # All derivatives here, to make reading the equations below easier + D00 = D[1:-1, 1:-1] + D10 = D[1:-1, 2:] + D_10 = D[1:-1, :-2] + D01 = D[2:, 1:-1] + D0_1 = D[:-2, 1:-1] + D11 = D[2:, 2:] + D_11 = D[2:, :-2] + D1_1 = D[:-2, 2:] + D_1_1 = D[:-2, :-2] + # Derivatives of D -- not including /(dx^a dy^b) + D0 = D00 + Dx = (-D_10 + D10) / 2.0 + Dy = (-D0_1 + D01) / 2.0 + Dxx = D_10 - 2.0 * D00 + D10 + Dyy = D0_1 - 2.0 * D00 + D01 + Dxy = (D_1_1 - D_11 - D1_1 + D11) / 4.0 + + if self.PlateSolutionType == "vWC1994": + # van Wees and Cloetingh (1994) solution, re-discretized by me + # using a central difference approx. to 2nd order precision + # NEW STENCIL + # x = -2, y = 0 + self.cj_2i0_coeff_ij = (D0 - Dx) / dx4 + # x = 0, y = -2 + self.cj0i_2_coeff_ij = (D0 - Dy) / dy4 + # x = 0, y = 2 + self.cj0i2_coeff_ij = (D0 + Dy) / dy4 + # x = 2, y = 0 + self.cj2i0_coeff_ij = (D0 + Dx) / dx4 + # x = -1, y = -1 + self.cj_1i_1_coeff_ij = ( + 2.0 * D0 - Dx - Dy + Dxy * (1 - nu) / 2.0 + ) / dx2dy2 + # x = -1, y = 1 + self.cj_1i1_coeff_ij = ( + 2.0 * D0 - Dx + Dy - Dxy * (1 - nu) / 2.0 + ) / dx2dy2 + # x = 1, y = -1 + self.cj1i_1_coeff_ij = ( + 2.0 * D0 + Dx - Dy - Dxy * (1 - nu) / 2.0 + ) / dx2dy2 + # x = 1, y = 1 + self.cj1i1_coeff_ij = ( + 2.0 * D0 + Dx + Dy + Dxy * (1 - nu) / 2.0 + ) / dx2dy2 + # x = -1, y = 0 + self.cj_1i0_coeff_ij = (-4.0 * D0 + 2.0 * Dx + Dxx) / dx4 + ( + -4.0 * D0 + 2.0 * Dx + nu * Dyy + ) / dx2dy2 + # x = 0, y = -1 + self.cj0i_1_coeff_ij = (-4.0 * D0 + 2.0 * Dy + Dyy) / dy4 + ( + -4.0 * D0 + 2.0 * Dy + nu * Dxx + ) / dx2dy2 + # x = 0, y = 1 + self.cj0i1_coeff_ij = (-4.0 * D0 - 2.0 * Dy + Dyy) / dy4 + ( + -4.0 * D0 - 2.0 * Dy + nu * Dxx + ) / dx2dy2 + # x = 1, y = 0 + self.cj1i0_coeff_ij = (-4.0 * D0 - 2.0 * Dx + Dxx) / dx4 + ( + -4.0 * D0 - 2.0 * Dx + nu * Dyy + ) / dx2dy2 + # x = 0, y = 0 + self.cj0i0_coeff_ij = ( + (6.0 * D0 - 2.0 * Dxx) / dx4 + + (6.0 * D0 - 2.0 * Dyy) / dy4 + + (8.0 * D0 - 2.0 * nu * Dxx - 2.0 * nu * Dyy) / dx2dy2 + + drho * g + ) + + elif self.PlateSolutionType == "G2009": + # STENCIL FROM GOVERS ET AL. 2009 -- first-order differences + # x is j and y is i b/c matrix row/column notation + # Note that this breaks down with b.c.'s that place too much control + # on the solution -- harmonic wavetrains + # x = -2, y = 0 + self.cj_2i0_coeff_ij = D_10 / dx4 + # x = -1, y = -1 + self.cj_1i_1_coeff_ij = (D_10 + D0_1) / dx2dy2 + # x = -1, y = 0 + self.cj_1i0_coeff_ij = -2.0 * ( + (D0_1 + D00) / dx2dy2 + (D00 + D_10) / dx4 + ) + # x = -1, y = 1 + self.cj_1i1_coeff_ij = (D_10 + D01) / dx2dy2 + # x = 0, y = -2 + self.cj0i_2_coeff_ij = D0_1 / dy4 + # x = 0, y = -1 + self.cj0i_1_coeff_ij = -2.0 * ( + (D0_1 + D00) / dx2dy2 + (D00 + D0_1) / dy4 + ) + # x = 0, y = 0 + self.cj0i0_coeff_ij = ( + (D10 + 4.0 * D00 + D_10) / dx4 + + (D01 + 4.0 * D00 + D0_1) / dy4 + + (8.0 * D00 / dx2dy2) + + drho * g + ) + # x = 0, y = 1 + self.cj0i1_coeff_ij = -2.0 * ((D01 + D00) / dy4 + (D00 + D01) / dx2dy2) + # x = 0, y = 2 + self.cj0i2_coeff_ij = D0_1 / dy4 + # x = 1, y = -1 + self.cj1i_1_coeff_ij = (D10 + D0_1) / dx2dy2 + # x = 1, y = 0 + self.cj1i0_coeff_ij = -2.0 * ((D10 + D00) / dx4 + (D10 + D00) / dx2dy2) + # x = 1, y = 1 + self.cj1i1_coeff_ij = (D10 + D01) / dx2dy2 + # x = 2, y = 0 + self.cj2i0_coeff_ij = D10 / dx4 + else: + sys.exit( + "Not an acceptable plate solution type. Please choose from:\n" + + "* vWC1994\n" + + "* G2009\n" + + "" + ) + + ################################################################ + # CREATE COEFFICIENT ARRAYS: PLAIN, WITH NO B.C.'S YET APPLIED # + ################################################################ + # x = -2, y = 0 + self.cj_2i0 = self.cj_2i0_coeff_ij.copy() + # x = -1, y = -1 + self.cj_1i_1 = self.cj_1i_1_coeff_ij.copy() + # x = -1, y = 0 + self.cj_1i0 = self.cj_1i0_coeff_ij.copy() + # x = -1, y = 1 + self.cj_1i1 = self.cj_1i1_coeff_ij.copy() + # x = 0, y = -2 + self.cj0i_2 = self.cj0i_2_coeff_ij.copy() + # x = 0, y = -1 + self.cj0i_1 = self.cj0i_1_coeff_ij.copy() + # x = 0, y = 0 + self.cj0i0 = self.cj0i0_coeff_ij.copy() + # x = 0, y = 1 + self.cj0i1 = self.cj0i1_coeff_ij.copy() + # x = 0, y = 2 + self.cj0i2 = self.cj0i2_coeff_ij.copy() + # x = 1, y = -1 + self.cj1i_1 = self.cj1i_1_coeff_ij.copy() + # x = 1, y = 0 + self.cj1i0 = self.cj1i0_coeff_ij.copy() + # x = 1, y = 1 + self.cj1i1 = self.cj1i1_coeff_ij.copy() + # x = 2, y = 0 + self.cj2i0 = self.cj2i0_coeff_ij.copy() + + # Provide rows and columns in the 2D input to later functions + self.ncolsx = self.cj0i0.shape[1] + self.nrowsy = self.cj0i0.shape[0] + + def BC_Flexure(self): + # The next section of code is split over several functions for the 1D + # case, but will be all in one function here, at least for now. + + # Inf for E-W to separate from nan for N-S. N-S will spill off ends + # of array (C order, in rows), while E-W will be internal, so I will + # later change np.inf to 0 to represent where internal boundaries + # occur. + + ####################################################################### + # DEFINE COEFFICIENTS TO W_j-2 -- W_j+2 WITH B.C.'S APPLIED (x: W, E) # + ####################################################################### + + # Infinitiy is used to flag places where coeff values should be 0, + # and would otherwise cause boundary condition nan's to appear in the + # cross-derivatives: infinity is changed into 0 later. + + if self.BC_W == "Periodic": + if self.BC_E == "Periodic": + # For each side, there will be two new diagonals (mostly zeros), and + # two sets of diagonals that will replace values in current diagonals. + # This is because of the pattern of fill in the periodic b.c.'s in the + # x-direction. + + # First, create arrays for the new values. + # One of the two values here, that from the y -/+ 1, x +/- 1 (E/W) + # boundary condition, will be in the same location that will be + # overwritten in the initiating grid by the next perioidic b.c. over + self.cj_1i1_Periodic_right = np.zeros(self.qs.shape) + self.cj_2i0_Periodic_right = np.zeros(self.qs.shape) + j = 0 + self.cj_1i1_Periodic_right[:, j] = self.cj_1i_1[:, j] + self.cj_2i0_Periodic_right[:, j] = self.cj_2i0[:, j] + j = 1 + self.cj_2i0_Periodic_right[:, j] = self.cj_2i0[:, j] + + # Then, replace existing values with what will be needed to make the + # periodic boundary condition work. + j = 0 + # ORDER IS IMPORTANT HERE! Don't change first before it changes other. + # (We are shuffling down the line) + self.cj_1i1[:, j] = self.cj_1i0[:, j] + self.cj_1i0[:, j] = self.cj_1i_1[:, j] + + # And then change remaning off-grid values to np.inf (i.e. those that + # were not altered to a real value + # These will be the +/- 2's and the j_1i_1 and the j1i1 + # These are the farthest-out pentadiagonals that can't be reached by + # the tridiagonals, and the tridiagonals that are farther away on the + # y (big grid) axis that can't be reached by the single diagonals + # that are farthest out + # So 4 diagonals. + # But ci1j1 is taken care of on -1 end before being rolled forwards + # (i.e. clockwise, if we are reading from the top of the tread of a + # tire) + j = 0 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += np.inf + j = 1 + self.cj_2i0[:, j] += np.inf + + else: + sys.exit( + "Not physical to have one wrap-around boundary but not its pair." + ) + elif self.BC_W == "0Displacement0Slope": + j = 0 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += np.inf + self.cj_1i0[:, j] += np.inf + self.cj_1i1[:, j] += np.inf + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 0 + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += 0 + j = 1 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 0 + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 0 + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += 0 + elif self.BC_W == "0Moment0Shear": + j = 0 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += np.inf + self.cj_1i0[:, j] += np.inf + self.cj_1i1[:, j] += np.inf + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 2 * self.cj_1i_1_coeff_ij[:, j] + self.cj0i0[:, j] += ( + 4 * self.cj_2i0_coeff_ij[:, j] + 2 * self.cj_1i0_coeff_ij[:, j] + ) + self.cj0i1[:, j] += 2 * self.cj_1i1_coeff_ij[:, j] + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += -self.cj_1i_1_coeff_ij[:, j] + self.cj1i0[:, j] += ( + -4 * self.cj_2i0_coeff_ij[:, j] - self.cj_1i0_coeff_ij[:, j] + ) + self.cj1i1[:, j] += -self.cj_1i1_coeff_ij[:, j] + self.cj2i0[:, j] += self.cj_2i0_coeff_ij[:, j] + j = 1 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 2 * self.cj_2i0_coeff_ij[:, j] + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += -2 * self.cj_2i0_coeff_ij[:, j] + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += self.cj_2i0_coeff_ij[:, j] + elif self.BC_W == "0Slope0Shear": + j = 0 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += np.inf + self.cj_1i0[:, j] += np.inf + self.cj_1i1[:, j] += np.inf + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += self.cj_1i_1_coeff_ij[:, j] + self.cj1i0[:, j] += self.cj_1i0_coeff_ij[:, j] + self.cj1i1[:, j] += self.cj_1i1_coeff_ij[:, j] # Interference + self.cj2i0[:, j] += self.cj_2i0_coeff_ij[:, j] + j = 1 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 0 + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 0 + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += self.cj_2i0_coeff_ij[:, j] + elif self.BC_W == "Mirror": + j = 0 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += np.inf + self.cj_1i0[:, j] += np.inf + self.cj_1i1[:, j] += np.inf + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += self.cj_1i_1_coeff_ij[:, j] + self.cj1i0[:, j] += self.cj_1i0_coeff_ij[:, j] + self.cj1i1[:, j] += self.cj_1i1_coeff_ij[:, j] + self.cj2i0[:, j] += self.cj_2i0_coeff_ij[:, j] + j = 1 + self.cj_2i0[:, j] += np.inf + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 0 + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += self.cj_2i0_coeff_ij[:, j] + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 0 + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += 0 + else: + # Possibly redundant safeguard + sys.exit("Invalid boundary condition") + + if self.BC_E == "Periodic": + # See more extensive comments above (BC_W) + + if self.BC_W == "Periodic": + # New arrays -- new diagonals, but mostly empty. Just corners of blocks + # (boxes) in block-diagonal matrix + self.cj1i_1_Periodic_left = np.zeros(self.qs.shape) + self.cj2i0_Periodic_left = np.zeros(self.qs.shape) + j = -1 + self.cj1i_1_Periodic_left[:, j] = self.cj1i_1[:, j] + self.cj2i0_Periodic_left[:, j] = self.cj2i0[:, j] + j = -2 + self.cj2i0_Periodic_left[:, j] = self.cj2i0[:, j] + + # Then, replace existing values with what will be needed to make the + # periodic boundary condition work. + j = -1 + self.cj1i_1[:, j] = self.cj1i0[:, j] + self.cj1i0[:, j] = self.cj1i1[:, j] + + # And then change remaning off-grid values to np.inf (i.e. those that + # were not altered to a real value + j = -1 + self.cj1i1[:, j] += np.inf + self.cj2i0[:, j] += np.inf + j = -2 + self.cj2i0[:, j] += np.inf + + else: + sys.exit( + "Not physical to have one wrap-around boundary but not its pair." + ) + + elif self.BC_E == "0Displacement0Slope": + j = -1 + self.cj_2i0[:, j] += 0 + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 0 + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += np.inf + self.cj1i0[:, j] += np.inf + self.cj1i1[:, j] += np.inf + self.cj2i0[:, j] += np.inf + j = -2 + self.cj_2i0[:, j] += 0 + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 0 + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 0 + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += np.inf + elif self.BC_E == "0Moment0Shear": + j = -1 + self.cj_2i0[:, j] += self.cj2i0_coeff_ij[:, j] + self.cj_1i_1[:, j] += -self.cj1i_1_coeff_ij[:, j] + self.cj_1i0[:, j] += ( + -4 * self.cj2i0_coeff_ij[:, j] - self.cj1i0_coeff_ij[:, j] + ) + self.cj_1i1[:, j] += -self.cj1i1_coeff_ij[:, j] + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 2 * self.cj1i_1_coeff_ij[:, j] + self.cj0i0[:, j] += ( + 4 * self.cj2i0_coeff_ij[:, j] + 2 * self.cj1i0_coeff_ij[:, j] + ) + self.cj0i1[:, j] += 2 * self.cj1i1_coeff_ij[:, j] + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += np.inf + self.cj1i0[:, j] += np.inf + self.cj1i1[:, j] += np.inf + self.cj2i0[:, j] += np.inf + j = -2 + self.cj_2i0[:, j] += self.cj2i0_coeff_ij[:, j] + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += -2 * self.cj2i0_coeff_ij[:, j] + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 2 * self.cj2i0_coeff_ij[:, j] + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += np.inf + elif self.BC_E == "0Slope0Shear": + j = -1 + self.cj_2i0[:, j] += self.cj2i0_coeff_ij[:, j] + self.cj_1i_1[:, j] += self.cj1i_1_coeff_ij[:, j] + self.cj_1i0[:, j] += self.cj1i0_coeff_ij[:, j] + self.cj_1i1[:, j] += self.cj1i1_coeff_ij[:, j] + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += np.inf + self.cj1i0[:, j] += np.inf + self.cj1i1[:, j] += np.inf + self.cj2i0[:, j] += np.inf + j = -2 + self.cj_2i0[:, j] += self.cj2i0_coeff_ij[:, j] + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 0 + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 0 + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += np.inf + elif self.BC_E == "Mirror": + j = -1 + self.cj_2i0[:, j] += self.cj2i0_coeff_ij[:, j] + self.cj_1i_1[:, j] += self.cj1i_1_coeff_ij[:, j] + self.cj_1i0[:, j] += self.cj1i0_coeff_ij[:, j] + self.cj_1i1[:, j] += self.cj1i1_coeff_ij[:, j] + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += 0 + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += np.inf + self.cj1i0[:, j] += np.inf + self.cj1i1[:, j] += np.inf + self.cj2i0[:, j] += np.inf + j = -2 + self.cj_2i0[:, j] += 0 + self.cj_1i_1[:, j] += 0 + self.cj_1i0[:, j] += 0 + self.cj_1i1[:, j] += 0 + self.cj0i_2[:, j] += 0 + self.cj0i_1[:, j] += 0 + self.cj0i0[:, j] += self.cj2i0_coeff_ij[:, j] + self.cj0i1[:, j] += 0 + self.cj0i2[:, j] += 0 + self.cj1i_1[:, j] += 0 + self.cj1i0[:, j] += 0 + self.cj1i1[:, j] += 0 + self.cj2i0[:, j] += np.inf + else: + # Possibly redundant safeguard + sys.exit("Invalid boundary condition") + + ####################################################################### + # DEFINE COEFFICIENTS TO W_i-2 -- W_i+2 WITH B.C.'S APPLIED (y: N, S) # + ####################################################################### + + if self.BC_N == "Periodic": + if self.BC_S == "Periodic": + pass # Will address the N-S (whole-matrix-involving) boundary condition + # inclusion below, when constructing sparse matrix diagonals + else: + sys.exit( + "Not physical to have one wrap-around boundary but not its pair." + ) + elif self.BC_N == "0Displacement0Slope": + i = 0 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :][self.cj_1i_1[i, :] != np.inf] = np.nan + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 0 # np.nan + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 + self.cj0i2[i, :] += 0 + self.cj1i_1[i, :][self.cj1i_1[i, :] != np.inf] += 0 # np.nan + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += 0 + self.cj2i0[i, :] += 0 + i = 1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 0 + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 + self.cj0i2[i, :] += 0 + self.cj1i_1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += 0 + self.cj2i0[i, :] += 0 + elif self.BC_N == "0Moment0Shear": + i = 0 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :][self.cj_1i_1[i, :] != np.inf] = np.nan + self.cj_1i0[i, :] += 2 * self.cj_1i_1_coeff_ij[i, :] + self.cj_1i1[i, :] += -self.cj_1i_1_coeff_ij[i, :] + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 0 # np.nan + self.cj0i0[i, :] += ( + 4 * self.cj0i_2_coeff_ij[i, :] + 2 * self.cj0i_1_coeff_ij[i, :] + ) + self.cj0i1[i, :] += ( + -4 * self.cj0i_2_coeff_ij[i, :] - self.cj0i_1_coeff_ij[i, :] + ) + self.cj0i2[i, :] += self.cj0i_2_coeff_ij[i, :] + self.cj1i_1[i, :][self.cj1i_1[i, :] != np.inf] += 0 # np.nan + self.cj1i0[i, :] += 2 * self.cj1i_1_coeff_ij[i, :] + self.cj1i1[i, :] += -self.cj1i_1_coeff_ij[i, :] + self.cj2i0[i, :] += 0 + i = 1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 2 * self.cj0i_2_coeff_ij[i, :] + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += -2 * self.cj0i_2_coeff_ij[i, :] + self.cj0i2[i, :] += self.cj0i_2_coeff_ij[i, :] + self.cj1i_1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += 0 + self.cj2i0[i, :] += 0 + elif self.BC_N == "0Slope0Shear": + i = 0 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :][self.cj_1i_1[i, :] != np.inf] = np.nan + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += self.cj_1i_1_coeff_ij[i, :] + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 0 # np.nan + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += self.cj0i_1_coeff_ij[i, :] + self.cj0i2[i, :] += self.cj0i_2_coeff_ij[i, :] + self.cj1i_1[i, :][self.cj1i_1[i, :] != np.inf] += 0 # np.nan + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += self.cj1i_1_coeff_ij[i, :] + self.cj2i0[i, :] += 0 + i = 1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 0 + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 + self.cj0i2[i, :] += self.cj0i_2_coeff_ij[i, :] + self.cj1i_1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += 0 + self.cj2i0[i, :] += 0 + elif self.BC_N == "Mirror": + i = 0 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :][self.cj_1i_1[i, :] != np.inf] = np.nan + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += self.cj_1i_1_coeff_ij[i, :] + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 0 # np.nan + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += self.cj0i_1_coeff_ij[i, :] + self.cj0i2[i, :] += self.cj0i_2_coeff_ij[i, :] + self.cj1i_1[i, :][self.cj1i_1[i, :] != np.inf] += 0 # np.nan + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += self.cj1i_1_coeff_ij[i, :] + self.cj2i0[i, :] += 0 + i = 1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += 0 # np.nan + self.cj0i_1[i, :] += 0 + self.cj0i0[i, :] += self.cj0i_2_coeff_ij[i, :] + self.cj0i1[i, :] += 0 + self.cj0i2[i, :] += 0 + self.cj1i_1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += 0 + self.cj2i0[i, :] += 0 + else: + # Possibly redundant safeguard + sys.exit("Invalid boundary condition") + + if self.BC_S == "Periodic": + if self.BC_N == "Periodic": + pass # Will address the N-S (whole-matrix-involving) boundary condition + # inclusion below, when constructing sparse matrix diagonals + else: + sys.exit( + "Not physical to have one wrap-around boundary but not its pair." + ) + elif self.BC_S == "0Displacement0Slope": + i = -2 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += 0 + self.cj0i_1[i, :] += 0 + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 + self.cj0i2[i, :] += 0 # np.nan + self.cj1i1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i_1[i, :] += 0 + self.cj2i0[i, :] += 0 + i = -1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :][self.cj_1i1[i, :] != np.inf] += 0 # np.nan + self.cj0i_2[i, :] += 0 + self.cj0i_1[i, :] += 0 + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 # np.nan + self.cj0i2[i, :] += 0 # np.nan + self.cj1i_1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i1[i, :][self.cj1i1[i, :] != np.inf] += 0 # np.nan + self.cj2i0[i, :] += 0 + elif self.BC_S == "0Moment0Shear": + i = -2 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += self.cj0i2_coeff_ij[i, :] + self.cj0i_1[i, :] += -2 * self.cj0i2_coeff_ij[i, :] + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 2 * self.cj0i2_coeff_ij[i, :] + self.cj0i2[i, :] += 0 # np.nan + self.cj1i1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i_1[i, :] += 0 + self.cj2i0[i, :] += 0 + i = -1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += -self.cj1i1_coeff_ij[i, :] + self.cj_1i0[i, :] += 2 * self.cj1i1_coeff_ij[i, :] + self.cj_1i1[i, :][self.cj_1i1[i, :] != np.inf] += 0 # np.nan + self.cj0i_2[i, :] += self.cj0i2_coeff_ij[i, :] + self.cj0i_1[i, :] += ( + -4 * self.cj0i2_coeff_ij[i, :] - self.cj0i1_coeff_ij[i, :] + ) + self.cj0i0[i, :] += ( + 4 * self.cj0i2_coeff_ij[i, :] + 2 * self.cj0i1_coeff_ij[i, :] + ) + self.cj0i1[i, :] += 0 # np.nan + self.cj0i2[i, :] += 0 # np.nan + self.cj1i_1[i, :] += -self.cj_1i1_coeff_ij[i, :] + self.cj1i0[i, :] += 2 * self.cj_1i1_coeff_ij[i, :] + self.cj1i1[i, :][self.cj1i1[i, :] != np.inf] += 0 # np.nan + self.cj2i0[i, :] += 0 + elif self.BC_S == "0Slope0Shear": + i = -2 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += self.cj0i2_coeff_ij[i, :] + self.cj0i_1[i, :] += 0 + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 + self.cj0i2[i, :] += 0 # np.nan + self.cj1i_1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += 0 + self.cj2i0[i, :] += 0 + i = -1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += self.cj_1i1_coeff_ij[i, :] + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :][self.cj_1i1[i, :] != np.inf] += 0 # np.nan + self.cj0i_2[i, :] += self.cj0i2_coeff_ij[i, :] + self.cj0i_1[i, :] += self.cj0i1_coeff_ij[i, :] + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 # np.nan + self.cj0i2[i, :] += 0 # np.nan + self.cj1i_1[i, :] += self.cj1i1_coeff_ij[i, :] + self.cj1i0[i, :] += 0 + self.cj1i1[i, :][self.cj1i1[i, :] != np.inf] += 0 # np.nan + self.cj2i0[i, :] += 0 + elif self.BC_S == "Mirror": + i = -2 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += 0 + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :] += 0 + self.cj0i_2[i, :] += 0 + self.cj0i_1[i, :] += 0 + self.cj0i0[i, :] += self.cj0i2_coeff_ij[i, :] + self.cj0i1[i, :] += 0 + self.cj0i2[i, :] += 0 # np.nan + self.cj1i_1[i, :] += 0 + self.cj1i0[i, :] += 0 + self.cj1i1[i, :] += 0 + self.cj2i0[i, :] += 0 + i = -1 + self.cj_2i0[i, :] += 0 + self.cj_1i_1[i, :] += self.cj_1i1_coeff_ij[i, :] + self.cj_1i0[i, :] += 0 + self.cj_1i1[i, :][self.cj_1i1[i, :] != np.inf] += 0 # np.nan + self.cj0i_2[i, :] += self.cj0i2_coeff_ij[i, :] + self.cj0i_1[i, :] += self.cj0i1_coeff_ij[i, :] + self.cj0i0[i, :] += 0 + self.cj0i1[i, :] += 0 # np.nan + self.cj0i2[i, :] += 0 # np.nan + self.cj1i_1[i, :] += self.cj1i1_coeff_ij[i, :] + self.cj1i0[i, :] += 0 + self.cj1i1[i, :][self.cj1i1[i, :] != np.inf] += 0 # np.nan + self.cj2i0[i, :] += 0 + else: + # Possibly redundant safeguard + sys.exit("Invalid boundary condition") + + ##################################################### + # CORNERS: INTERFERENCE BETWEEN BOUNDARY CONDITIONS # + ##################################################### + + # In 2D, have to consider diagonals and interference (additive) among + # boundary conditions + + ############################ + # DIRICHLET -- DO NOTHING. # + ############################ + + # Do nothing. + # What about combinations? + # This will mean that dirichlet boundary conditions will implicitly + # control the corners, so, for examplel, they would be locked all of the + # way to the edge of the domain instead of becoming free to deflect at the + # ends. + # Indeed it is much easier to envision this case than one in which + # the stationary clamp is released. + + ################# + # 0MOMENT0SHEAR # + ################# + if self.BC_N == "0Moment0Shear" and self.BC_W == "0Moment0Shear": + self.cj0i0[0, 0] += 2 * self.cj_1i_1_coeff_ij[0, 0] + self.cj1i1[0, 0] -= self.cj_1i_1_coeff_ij[0, 0] + if self.BC_N == "0Moment0Shear" and self.BC_E == "0Moment0Shear": + self.cj0i0[0, -1] += 2 * self.cj_1i_1_coeff_ij[0, -1] + self.cj_1i1[0, -1] -= self.cj1i_1_coeff_ij[0, -1] + if self.BC_S == "0Moment0Shear" and self.BC_W == "0Moment0Shear": + self.cj0i0[-1, 0] += 2 * self.cj_1i_1_coeff_ij[-1, 0] + self.cj1i_1[-1, 0] -= self.cj_1i1_coeff_ij[-1, 0] + if self.BC_S == "0Moment0Shear" and self.BC_E == "0Moment0Shear": + self.cj0i0[-1, -1] += 2 * self.cj_1i_1_coeff_ij[-1, -1] + self.cj_1i_1[-1, -1] -= self.cj1i1_coeff_ij[-1, -1] + + ############ + # PERIODIC # + ############ + + # I think that nothing will be needed here. + # Periodic should just take care of all repeating in all directions by + # its very nature. (I.e. it is embedded in the sparse array structure + # of diagonals) + + ################ + # COMBINATIONS # + ################ + + ############################## + # 0SLOPE0SHEAR AND/OR MIRROR # + ############################## + # (both end up being the same) + if (self.BC_N == "0Slope0Shear" or self.BC_N == "Mirror") and ( + self.BC_W == "0Slope0Shear" or self.BC_W == "Mirror" + ): + self.cj1i1[0, 0] += self.cj_1i_1_coeff_ij[0, 0] + if (self.BC_N == "0Slope0Shear" or self.BC_N == "Mirror") and ( + self.BC_E == "0Slope0Shear" or self.BC_E == "Mirror" + ): + self.cj_1i1[0, -1] += self.cj1i_1_coeff_ij[0, -1] + if (self.BC_S == "0Slope0Shear" or self.BC_S == "Mirror") and ( + self.BC_W == "0Slope0Shear" or self.BC_W == "Mirror" + ): + self.cj1i_1[-1, 0] += self.cj_1i1_coeff_ij[-1, 0] + if (self.BC_S == "0Slope0Shear" or self.BC_S == "Mirror") and ( + self.BC_E == "0Slope0Shear" or self.BC_E == "Mirror" + ): + self.cj_1i_1[-1, -1] += self.cj1i1_coeff_ij[-1, -1] + + ################################ + # 0MOMENT0SHEAR - AND - MIRROR # + ################################ + # How do multiple types of b.c.'s interfere + # 0Moment0Shear must determine corner conditions in order to be mirrored + # by the "mirror" b.c. + if (self.BC_N == "Mirror" and self.BC_W == "0Moment0Shear") or ( + self.BC_W == "Mirror" and self.BC_N == "0Moment0Shear" + ): + self.cj0i0[0, 0] += 2 * self.cj_1i_1_coeff_ij[0, 0] + self.cj1i1[0, 0] -= self.cj_1i_1_coeff_ij[0, 0] + if (self.BC_N == "Mirror" and self.BC_E == "0Moment0Shear") or ( + self.BC_E == "Mirror" and self.BC_N == "0Moment0Shear" + ): + self.cj0i0[0, -1] += 2 * self.cj_1i_1_coeff_ij[0, -1] + self.cj1i1[0, -1] -= self.cj_1i_1_coeff_ij[0, -1] + if (self.BC_S == "Mirror" and self.BC_W == "0Moment0Shear") or ( + self.BC_W == "Mirror" and self.BC_S == "0Moment0Shear" + ): + self.cj0i0[-1, 0] += 2 * self.cj_1i_1_coeff_ij[-1, 0] + self.cj1i_1[-1, 0] -= self.cj_1i1_coeff_ij[-1, 0] + if (self.BC_S == "Mirror" and self.BC_E == "0Moment0Shear") or ( + self.BC_E == "Mirror" and self.BC_S == "0Moment0Shear" + ): + self.cj0i0[-1, -1] += 2 * self.cj_1i_1_coeff_ij[-1, -1] + self.cj_1i_1[-1, -1] -= self.cj1i1_coeff_ij[-1, -1] + + ###################################### + # 0MOMENT0SHEAR - AND - 0SLOPE0SHEAR # + ###################################### + # Just use 0Moment0Shear-style b.c.'s at corners: letting this dominate + # because it seems to be the more geologically likely b.c. + if (self.BC_N == "0Slope0Shear" and self.BC_W == "0Moment0Shear") or ( + self.BC_W == "0Slope0Shear" and self.BC_N == "0Moment0Shear" + ): + self.cj0i0[0, 0] += 2 * self.cj_1i_1_coeff_ij[0, 0] + self.cj1i1[0, 0] -= self.cj_1i_1_coeff_ij[0, 0] + if (self.BC_N == "0Slope0Shear" and self.BC_E == "0Moment0Shear") or ( + self.BC_E == "0Slope0Shear" and self.BC_N == "0Moment0Shear" + ): + self.cj0i0[0, -1] += 2 * self.cj_1i_1_coeff_ij[0, -1] + self.cj1i1[0, -1] -= self.cj_1i_1_coeff_ij[0, -1] + if (self.BC_S == "0Slope0Shear" and self.BC_W == "0Moment0Shear") or ( + self.BC_W == "0Slope0Shear" and self.BC_S == "0Moment0Shear" + ): + self.cj0i0[-1, 0] += 2 * self.cj_1i_1_coeff_ij[-1, 0] + self.cj1i_1[-1, 0] -= self.cj_1i1_coeff_ij[-1, 0] + if (self.BC_S == "0Slope0Shear" and self.BC_E == "0Moment0Shear") or ( + self.BC_E == "0Slope0Shear" and self.BC_S == "0Moment0Shear" + ): + self.cj0i0[-1, -1] += 2 * self.cj_1i_1_coeff_ij[-1, -1] + self.cj_1i_1[-1, -1] -= self.cj1i1_coeff_ij[-1, -1] + # What about 0Moment0SHear on N/S part? + + ############################## + # PERIODIC B.C.'S AND OTHERS # + ############################## + + # The Periodic boundary natively continues the other boundary conditions + # Nothing to be done here. + + def build_diagonals(self): + ########################################################## + # INCORPORATE BOUNDARY CONDITIONS INTO COEFFICIENT ARRAY # + ########################################################## + + # Roll to keep the proper coefficients at the proper places in the + # arrays: Python will naturally just do vertical shifts instead of + # diagonal shifts, so this takes into account the horizontal compoent + # to ensure that boundary values are at the right place. + + # Roll x + # ASYMMETRIC RESPONSE HERE -- THIS GETS TOWARDS SOURCE OF PROBLEM! + self.cj_2i0 = np.roll(self.cj_2i0, -2, 1) + self.cj_1i0 = np.roll(self.cj_1i0, -1, 1) + self.cj1i0 = np.roll(self.cj1i0, 1, 1) + self.cj2i0 = np.roll(self.cj2i0, 2, 1) + # Roll y + self.cj0i_2 = np.roll(self.cj0i_2, -2, 0) + self.cj0i_1 = np.roll(self.cj0i_1, -1, 0) + self.cj0i1 = np.roll(self.cj0i1, 1, 0) + self.cj0i2 = np.roll(self.cj0i2, 2, 0) + # Roll x and y + self.cj_1i_1 = np.roll(self.cj_1i_1, -1, 1) + self.cj_1i_1 = np.roll(self.cj_1i_1, -1, 0) + self.cj_1i1 = np.roll(self.cj_1i1, -1, 1) + self.cj_1i1 = np.roll(self.cj_1i1, 1, 0) + self.cj1i_1 = np.roll(self.cj1i_1, 1, 1) + self.cj1i_1 = np.roll(self.cj1i_1, -1, 0) + self.cj1i1 = np.roll(self.cj1i1, 1, 1) + self.cj1i1 = np.roll(self.cj1i1, 1, 0) + + coeff_array_list = [ + self.cj_2i0, + self.cj_1i0, + self.cj1i0, + self.cj2i0, + self.cj0i_2, + self.cj0i_1, + self.cj0i1, + self.cj0i2, + self.cj_1i_1, + self.cj_1i1, + self.cj1i_1, + self.cj1i1, + self.cj0i0, + ] + for array in coeff_array_list: + array[np.isinf(array)] = 0 + # array[np.isnan(array)] = 0 # had been used for testing + + # Reshape to put in solver + vec_cj_2i0 = np.reshape(self.cj_2i0, -1, order="C") + vec_cj_1i_1 = np.reshape(self.cj_1i_1, -1, order="C") + vec_cj_1i0 = np.reshape(self.cj_1i0, -1, order="C") + vec_cj_1i1 = np.reshape(self.cj_1i1, -1, order="C") + vec_cj0i_2 = np.reshape(self.cj0i_2, -1, order="C") + vec_cj0i_1 = np.reshape(self.cj0i_1, -1, order="C") + vec_cj0i0 = np.reshape(self.cj0i0, -1, order="C") + vec_cj0i1 = np.reshape(self.cj0i1, -1, order="C") + vec_cj0i2 = np.reshape(self.cj0i2, -1, order="C") + vec_cj1i_1 = np.reshape(self.cj1i_1, -1, order="C") + vec_cj1i0 = np.reshape(self.cj1i0, -1, order="C") + vec_cj1i1 = np.reshape(self.cj1i1, -1, order="C") + vec_cj2i0 = np.reshape(self.cj2i0, -1, order="C") + + # Changed this 6 Nov. 2014 in betahaus Berlin to be x-based + Up2 = vec_cj0i2 + Up1 = np.vstack((vec_cj_1i1, vec_cj0i1, vec_cj1i1)) + Mid = np.vstack((vec_cj_2i0, vec_cj_1i0, vec_cj0i0, vec_cj1i0, vec_cj2i0)) + Dn1 = np.vstack((vec_cj_1i_1, vec_cj0i_1, vec_cj1i_1)) + Dn2 = vec_cj0i_2 + + # Number of rows and columns for array size and offsets + self.ny = self.nrowsy + self.nx = self.ncolsx + + if ( + self.BC_N == "Periodic" + and self.BC_S == "Periodic" + and self.BC_W == "Periodic" + and self.BC_E == "Periodic" + ): + # Additional vector creation + # West + # Roll + self.cj_2i0_Periodic_right = np.roll(self.cj_2i0_Periodic_right, -2, 1) + self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, -1, 1) + self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, 1, 0) + # Reshape + vec_cj_2i0_Periodic_right = np.reshape( + self.cj_2i0_Periodic_right, -1, order="C" + ) + vec_cj_1i1_Periodic_right = np.reshape( + self.cj_1i1_Periodic_right, -1, order="C" + ) + # East + # Roll + self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, 1, 1) + self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, -1, 0) + self.cj2i0_Periodic_left = np.roll(self.cj2i0_Periodic_left, 2, 1) + # Reshape + vec_cj1i_1_Periodic_left = np.reshape( + self.cj1i_1_Periodic_left, -1, order="C" + ) + vec_cj2i0_Periodic_left = np.reshape( + self.cj2i0_Periodic_left, -1, order="C" + ) + + # Build diagonals with additional entries + # I think the fact that everything is rolled will make this work all right + # without any additional rolling. + # Checked -- and indeed, what would be in my mind the last value for + # Mid[3] is the first value in its array. Hooray, patterns! + self.diags = np.vstack( + ( + vec_cj1i_1_Periodic_left, + Up1, + vec_cj_1i1_Periodic_right, + Up2, + Dn2, + vec_cj1i_1_Periodic_left, + Dn1, + vec_cj2i0_Periodic_left, + Mid, + vec_cj_2i0_Periodic_right, + Up1, + vec_cj_1i1_Periodic_right, + Up2, + Dn2, + vec_cj1i_1_Periodic_left, + Dn1, + vec_cj_1i1_Periodic_right, + ) + ) + # Getting too complicated to have everything together + self.offsets = [ + # New: LL corner of LL box + -self.ny * self.nx + 1, + # Periodic b.c. tridiag + self.nx - self.ny * self.nx - 1, + self.nx - self.ny * self.nx, + self.nx - self.ny * self.nx + 1, + # New: UR corner of LL box + 2 * self.nx - self.ny * self.nx - 1, + # Periodic b.c. single diag + 2 * self.nx - self.ny * self.nx, + -2 * self.nx, + # New: + -2 * self.nx + 1, + # Right term here (-self.nx+1) modified: + -self.nx - 1, + -self.nx, + -self.nx + 1, + # New: + -self.nx + 2, + # -1 and 1 terms here modified: + -2, + -1, + 0, + 1, + 2, + # New: + self.nx - 2, + # Left term here (self.nx-1) modified: + self.nx - 1, + self.nx, + self.nx + 1, + # New: + 2 * self.nx - 1, + 2 * self.nx, + # Periodic b.c. single diag + self.ny * self.nx - 2 * self.nx, + # New: LL corner of UR box + self.ny * self.nx - 2 * self.nx + 1, + # Periodic b.c. tridiag + self.ny * self.nx - self.nx - 1, + self.ny * self.nx - self.nx, + self.ny * self.nx - self.nx + 1, + # New: UR corner of UR box + self.ny * self.nx - 1, + ] + + # create banded sparse matrix + self.coeff_matrix = scipy.sparse.spdiags( + self.diags, + self.offsets, + self.ny * self.nx, + self.ny * self.nx, + format="csr", + ) + + elif self.BC_W == "Periodic" and self.BC_E == "Periodic": + # Additional vector creation + # West + # Roll + self.cj_2i0_Periodic_right = np.roll(self.cj_2i0_Periodic_right, -2, 1) + self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, -1, 1) + self.cj_1i1_Periodic_right = np.roll(self.cj_1i1_Periodic_right, 1, 0) + # Reshape + vec_cj_2i0_Periodic_right = np.reshape( + self.cj_2i0_Periodic_right, -1, order="C" + ) + vec_cj_1i1_Periodic_right = np.reshape( + self.cj_1i1_Periodic_right, -1, order="C" + ) + # East + # Roll + self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, 1, 1) + self.cj1i_1_Periodic_left = np.roll(self.cj1i_1_Periodic_left, -1, 0) + self.cj2i0_Periodic_left = np.roll(self.cj2i0_Periodic_left, 2, 1) + # Reshape + vec_cj1i_1_Periodic_left = np.reshape( + self.cj1i_1_Periodic_left, -1, order="C" + ) + vec_cj2i0_Periodic_left = np.reshape( + self.cj2i0_Periodic_left, -1, order="C" + ) + + # Build diagonals with additional entries + self.diags = np.vstack( + ( + Dn2, + vec_cj1i_1_Periodic_left, + Dn1, + vec_cj2i0_Periodic_left, + Mid, + vec_cj_2i0_Periodic_right, + Up1, + vec_cj_1i1_Periodic_right, + Up2, + ) + ) + # Getting too complicated to have everything together + self.offsets = [ + -2 * self.nx, + # New: + -2 * self.nx + 1, + # Right term here (-self.nx+1) modified: + -self.nx - 1, + -self.nx, + -self.nx + 1, + # New: + -self.nx + 2, + # -1 and 1 terms here modified: + -2, + -1, + 0, + 1, + 2, + # New: + self.nx - 2, + # Left term here (self.nx-1) modified: + self.nx - 1, + self.nx, + self.nx + 1, + # New: + 2 * self.nx - 1, + 2 * self.nx, + ] + + # create banded sparse matrix + self.coeff_matrix = scipy.sparse.spdiags( + self.diags, + self.offsets, + self.ny * self.nx, + self.ny * self.nx, + format="csr", + ) + + elif self.BC_N == "Periodic" and self.BC_S == "Periodic": + # Periodic. + # If these are periodic, we need to wrap around the ends of the + # large-scale diagonal structure + self.diags = np.vstack((Up1, Up2, Dn2, Dn1, Mid, Up1, Up2, Dn2, Dn1)) + # Create banded sparse matrix + # Rows: + # Lower left + # Middle + # Upper right + self.coeff_matrix = scipy.sparse.spdiags( + self.diags, + [ + self.nx - self.ny * self.nx - 1, + self.nx - self.ny * self.nx, + self.nx - self.ny * self.nx + 1, + 2 * self.nx - self.ny * self.nx, + -2 * self.nx, + -self.nx - 1, + -self.nx, + -self.nx + 1, + -2, + -1, + 0, + 1, + 2, + self.nx - 1, + self.nx, + self.nx + 1, + 2 * self.nx, + self.ny * self.nx - 2 * self.nx, + self.ny * self.nx - self.nx - 1, + self.ny * self.nx - self.nx, + self.ny * self.nx - self.nx + 1, + ], + self.ny * self.nx, + self.ny * self.nx, + format="csr", + ) + + else: + # No periodic boundary conditions -- original form of coeff_matrix + # creator. + # Arrange in solver + self.diags = np.vstack((Dn2, Dn1, Mid, Up1, Up2)) + # Create banded sparse matrix + self.coeff_matrix = scipy.sparse.spdiags( + self.diags, + [ + -2 * self.nx, + -self.nx - 1, + -self.nx, + -self.nx + 1, + -2, + -1, + 0, + 1, + 2, + self.nx - 1, + self.nx, + self.nx + 1, + 2 * self.nx, + ], + self.ny * self.nx, + self.ny * self.nx, + format="csr", + ) # create banded sparse matrix + + def calc_max_flexural_wavelength(self): + """ + Returns the approximate maximum flexural wavelength + This is important when padding of the grid is required: in Flexure (this + code), grids are padded out to one maximum flexural wavelength, but in any + case, the flexural wavelength is a good characteristic distance for any + truncation limit + """ + if np.isscalar(self.D): + Dmax = self.D + else: + Dmax = self.D.max() + # This is an approximation if there is fill that evolves with iterations + # (e.g., water), but should be good enough that this won't do much to it + alpha = (4 * Dmax / (self.drho * self.g)) ** 0.25 # 2D flexural parameter + self.maxFlexuralWavelength = 2 * np.pi * alpha + self.maxFlexuralWavelength_ncells_x = int( + np.ceil(self.maxFlexuralWavelength / self.dx) + ) + self.maxFlexuralWavelength_ncells_y = int( + np.ceil(self.maxFlexuralWavelength / self.dy) + ) + + def fd_solve(self): + """ + w = fd_solve() + Sparse flexural response calculation. + Can be performed by direct factorization with UMFpack (defuault) + or by an iterative minimum residual technique + These are both the fastest of the standard Scipy builtin techniques in + their respective classes + Requires the coefficient matrix from "2D.coeff_matrix" + """ - # Reshape into grid - self.w = -wvector.reshape(self.qs.shape) - self.w_padded = self.w.copy() # for troubleshooting - - # Time to solve used to be here + if self.Debug: + try: + # Will fail if scalar + print("self.Te", self.Te.shape) + except: + pass + print("self.qs", self.qs.shape) + self.calc_max_flexural_wavelength() + print( + "maxFlexuralWavelength_ncells: (x, y):", + self.maxFlexuralWavelength_ncells_x, + self.maxFlexuralWavelength_ncells_y, + ) + + q0vector = self.qs.reshape(-1, order="C") + if self.Solver == "iterative" or self.Solver == "Iterative": + if self.Debug: + print( + "Using generalized minimal residual method for iterative solution" + ) + if self.Verbose: + print( + "Converging to a tolerance of", + self.iterative_ConvergenceTolerance, + "m between iterations", + ) + wvector = scipy.sparse.linalg.isolve.lgmres( + self.coeff_matrix, q0vector + ) # , tol=1E-10)#,x0=woldvector)#,x0=wvector,tol=1E-15) + wvector = wvector[0] # Reach into tuple to get my array back + else: + if self.Solver == "direct" or self.Solver == "Direct": + if self.Debug: + print("Using direct solution with UMFpack") + else: + if self.Quiet == False: + print("Solution type not understood:") + print("Defaulting to direct solution with UMFpack") + wvector = scipy.sparse.linalg.spsolve( + self.coeff_matrix, q0vector, use_umfpack=True + ) + + # Reshape into grid + self.w = -wvector.reshape(self.qs.shape) + self.w_padded = self.w.copy() # for troubleshooting + + # Time to solve used to be here diff --git a/gflex/gflex.py b/gflex/gflex.py index 0c6e5e5..b9b2282 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -29,99 +29,101 @@ def welcome(): - print("") - print("**************************"+"*"*len(__version__)) - print("*** WELCOME to gFlex v"+__version__+" ***") - print("**************************"+"*"*len(__version__)) - print("") + print("") + print("**************************" + "*" * len(__version__)) + print("*** WELCOME to gFlex v" + __version__ + " ***") + print("**************************" + "*" * len(__version__)) + print("") + def displayUsage(): - print("Open-source licensed under GNU GPL v3") - print("") - print("Usage:") - print("gflex <> # TO RUN STANDALONE") - print("gflex -h *OR* gflex --help # DISPLAY ADDITIONAL HELP") - print("gflex -v *OR* gflex --version # DISPLAY VERSION NUMBER") - print("import gflex # WITHIN PYTHON SHELL OR SCRIPT") - print("") + print("Open-source licensed under GNU GPL v3") + print("") + print("Usage:") + print("gflex <> # TO RUN STANDALONE") + print("gflex -h *OR* gflex --help # DISPLAY ADDITIONAL HELP") + print("gflex -v *OR* gflex --version # DISPLAY VERSION NUMBER") + print("import gflex # WITHIN PYTHON SHELL OR SCRIPT") + print("") -def furtherHelp(): - print("") - print("ADDITIONAL HELP:") - print("--------------- ") - print("") - print('To generate an input file, please see the examples in the "input"') - print("directory of this install.") - print("") - print("To run in a Python script or shell, follow this general pattern:") - print("import gflex") - print("flex = gflex.F1D()") - print("flex.method = ...") - print("# ...more variable setting...") - print("# see the 'input' directory for examples") - print("") -def main(): - # Choose how to instantiate - if len(sys.argv) == 2: - if sys.argv[1] == '--help' or sys.argv[1] == '-h': - welcome() - displayUsage() - furtherHelp() - return - if sys.argv[1] == '--version' or sys.argv[1] == '-v': - print("gFlex v"+__version__) - return - else: - # Looks like it wants to be an configuration file! - filename = sys.argv[1] # it works for usage (1) and (2) - obj = WhichModel(filename) - - elif len(sys.argv) == 1: - welcome() - displayUsage() +def furtherHelp(): print("") - sys.exit() - else: - welcome() - print(">>>> ERROR: Too many input parameters provided; exiting. <<<<") + print("ADDITIONAL HELP:") + print("--------------- ") print("") - displayUsage() + print('To generate an input file, please see the examples in the "input"') + print("directory of this install.") + print("") + print("To run in a Python script or shell, follow this general pattern:") + print("import gflex") + print("flex = gflex.F1D()") + print("flex.method = ...") + print("# ...more variable setting...") + print("# see the 'input' directory for examples") print("") - sys.exit() - ######################################## - ## SET MODEL TYPE AND DIMENSIONS HERE ## - ######################################## +def main(): + # Choose how to instantiate + if len(sys.argv) == 2: + if sys.argv[1] == "--help" or sys.argv[1] == "-h": + welcome() + displayUsage() + furtherHelp() + return + if sys.argv[1] == "--version" or sys.argv[1] == "-v": + print("gFlex v" + __version__) + return + else: + # Looks like it wants to be an configuration file! + filename = sys.argv[1] # it works for usage (1) and (2) + obj = WhichModel(filename) + + elif len(sys.argv) == 1: + welcome() + displayUsage() + print("") + sys.exit() + else: + welcome() + print(">>>> ERROR: Too many input parameters provided; exiting. <<<<") + print("") + displayUsage() + print("") + sys.exit() - if obj.dimension == 1: - obj = F1D(filename) - elif obj.dimension == 2: - obj = F2D(filename) + ######################################## + ## SET MODEL TYPE AND DIMENSIONS HERE ## + ######################################## - obj.initialize(filename) + if obj.dimension == 1: + obj = F1D(filename) + elif obj.dimension == 2: + obj = F2D(filename) - if obj.Debug: print("Command line:", sys.argv) + obj.initialize(filename) + if obj.Debug: + print("Command line:", sys.argv) - ############################################ - ## SET MODEL PARAMETERS HERE ## - ## (if not defined in configuration file) ## - ############################################ - # obj.set_value('method','FD') # for example + ############################################ + ## SET MODEL PARAMETERS HERE ## + ## (if not defined in configuration file) ## + ############################################ + # obj.set_value('method','FD') # for example - obj.run() - obj.finalize() + obj.run() + obj.finalize() - obj.output() # Not part of IRF or BMI: Does standalone plotting and file output + obj.output() # Not part of IRF or BMI: Does standalone plotting and file output + ##################### + ## GET VALUES HERE ## + ## (if desired) ## + ##################### + # wout = obj.get_value('Deflection') # for example - ##################### - ## GET VALUES HERE ## - ## (if desired) ## - ##################### - #wout = obj.get_value('Deflection') # for example -if __name__ == '__main__': - main() +if __name__ == "__main__": + main() diff --git a/gflex_bmi.py b/gflex_bmi.py index 9279234..2d779fb 100644 --- a/gflex_bmi.py +++ b/gflex_bmi.py @@ -9,155 +9,161 @@ class BmiGflex: - _name = 'Isostasy and Lithospheric Flexure' - _input_var_names = ('earth_material_load__mass', ) - _output_var_names = ('lithosphere__vertical_displacement', - 'earth_material_load__mass', ) - _var_units = { - 'earth_material_load__mass' : 'kg', - 'lithosphere__vertical_displacement' : 'm', - } - - def __init__(self): - self._model = None - self._values = {} - self._shape = () - self._spacing = () - self._origin = () - self._coords = () - - def initialize(self, config_file=None): - ############################## - # Deleted some of Eric's stuff here b/c I internally manage an input file. - # Eric -- could you tell me if you have a better way for consistent - # input files you would like to see CSDMS-compliant models employ? - ############################## - if config_file is None: - pass - else: - self._model = WhichModel(config_file) # This line should work outside if-statement as well. - if self._model.model == 'flexure': # Really need to rename self.model!!! + _name = "Isostasy and Lithospheric Flexure" + _input_var_names = ("earth_material_load__mass",) + _output_var_names = ( + "lithosphere__vertical_displacement", + "earth_material_load__mass", + ) + _var_units = { + "earth_material_load__mass": "kg", + "lithosphere__vertical_displacement": "m", + } + + def __init__(self): + self._model = None + self._values = {} + self._shape = () + self._spacing = () + self._origin = () + self._coords = () + + def initialize(self, config_file=None): + ############################## + # Deleted some of Eric's stuff here b/c I internally manage an input file. + # Eric -- could you tell me if you have a better way for consistent + # input files you would like to see CSDMS-compliant models employ? + ############################## + if config_file is None: + pass + else: + self._model = WhichModel( + config_file + ) # This line should work outside if-statement as well. + if self._model.model == "flexure": # Really need to rename self.model!!! + if self._model.dimension == 1: + self._model = F1D(config_file) + elif self._model.dimension == 2: + self._model = F2D(config_file) + elif self._model.model == "PrattAiry": + self._model = PrattAiry(config_file) + + self._model.initialize(config_file) # Does nothing + if self._model.dimension == 1: - self._model = F1D(config_file) + self._spacing = (self._model.dx,) + self._coords = (np.arange(self._model.q0.shape[0]) * self._model.dx,) elif self._model.dimension == 2: - self._model = F2D(config_file) - elif self._model.model == 'PrattAiry': - self._model = PrattAiry(config_file) - - self._model.initialize(config_file) # Does nothing - - if self._model.dimension == 1: - self._spacing = (self._model.dx, ) - self._coords = (np.arange(self._model.q0.shape[0]) * self._model.dx, ) - elif self._model.dimension == 2: - self._spacing = (self._model.dy, self._model.dx) - self._coords = (np.arange(self._model.q0.shape[0]) * self._model.dy, - np.arange(self._model.q0.shape[1]) * self._model.dx) - self._shape = self._model.q0.shape - self._origin = (0., ) * self._model.dimension - - self._w = np.empty_like(self._model.q0) - - # PROBABLY SHOULD RENAME "self.model"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # can remove plotting and file output options -- not meant to be part of - # BMI interface!!!!!!!!! - self._values = { - 'earth_material_load__mass' : self._model.q0, - 'lithosphere__vertical_displacement' : self._w, - } + self._spacing = (self._model.dy, self._model.dx) + self._coords = ( + np.arange(self._model.q0.shape[0]) * self._model.dy, + np.arange(self._model.q0.shape[1]) * self._model.dx, + ) + self._shape = self._model.q0.shape + self._origin = (0.0,) * self._model.dimension + + self._w = np.empty_like(self._model.q0) + + # PROBABLY SHOULD RENAME "self.model"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # can remove plotting and file output options -- not meant to be part of + # BMI interface!!!!!!!!! + self._values = { + "earth_material_load__mass": self._model.q0, + "lithosphere__vertical_displacement": self._w, + } - def update(self): - self._model.run() - self._w[:] = self._model.w + def update(self): + self._model.run() + self._w[:] = self._model.w - def update_frac(self, time_frac): - self.update() + def update_frac(self, time_frac): + self.update() - def update_until(self, then): - self.update() + def update_until(self, then): + self.update() - def finalize(self): - self._model.finalize() - self._model = None + def finalize(self): + self._model.finalize() + self._model = None - def get_var_type (self, var_name): - return str(self.get_value_ptr(var_name).dtype) + def get_var_type(self, var_name): + return str(self.get_value_ptr(var_name).dtype) - def get_var_units(self, var_name): - return self._var_units[var_name] + def get_var_units(self, var_name): + return self._var_units[var_name] - def get_var_rank(self, var_name): - return self.get_value_ptr(var_name).ndim + def get_var_rank(self, var_name): + return self.get_value_ptr(var_name).ndim - def get_var_size(self, var_name): - return self.get_value_ptr(var_name).size + def get_var_size(self, var_name): + return self.get_value_ptr(var_name).size - def get_var_nbytes(self, var_name): - return self.get_value_ptr(var_name).nbytes + def get_var_nbytes(self, var_name): + return self.get_value_ptr(var_name).nbytes - def get_value_ptr(self, var_name): - return self._values[var_name] + def get_value_ptr(self, var_name): + return self._values[var_name] - def get_value(self, var_name): - return self.get_value_ptr(var_name).copy() + def get_value(self, var_name): + return self.get_value_ptr(var_name).copy() - def get_value_at_indices(self, var_name, indices): - return self.get_value_ptr(var_name).take(indices) + def get_value_at_indices(self, var_name, indices): + return self.get_value_ptr(var_name).take(indices) - def set_value(self, var_name, src): - val = self.get_value_ptr(var_name) - val[:] = src + def set_value(self, var_name, src): + val = self.get_value_ptr(var_name) + val[:] = src - def set_value_at_indices(self, var_name, src, indices): - val = self.get_value_ptr(var_name) - val.flat[indices] = src + def set_value_at_indices(self, var_name, src, indices): + val = self.get_value_ptr(var_name) + val.flat[indices] = src - def get_component_name(self): - return self._name + def get_component_name(self): + return self._name - def get_input_var_names(self): - return self._input_var_names + def get_input_var_names(self): + return self._input_var_names - def get_output_var_names(self): - return self._output_var_names + def get_output_var_names(self): + return self._output_var_names - def get_grid_shape (self, var_name): - return self.get_value_ptr(var_name).shape + def get_grid_shape(self, var_name): + return self.get_value_ptr(var_name).shape - def get_grid_spacing(self, var_name): - if var_name in self._values: - return self._spacing + def get_grid_spacing(self, var_name): + if var_name in self._values: + return self._spacing - def get_grid_origin(self, var_name): - if var_name in self._values: - return self._origin + def get_grid_origin(self, var_name): + if var_name in self._values: + return self._origin - def get_grid_type(self, var_name): - if var_name in self._values: - return 'uniform_rectilinear' - else: - raise KeyError(var_name) + def get_grid_type(self, var_name): + if var_name in self._values: + return "uniform_rectilinear" + else: + raise KeyError(var_name) - def get_grid_x(self, var_name): - if var_name in self._values: - return self._coords[-1] - else: - raise KeyError(var_name) + def get_grid_x(self, var_name): + if var_name in self._values: + return self._coords[-1] + else: + raise KeyError(var_name) - def get_grid_y(self, var_name): - if var_name in self._values: - return self._coords[-2] - else: - raise KeyError(var_name) + def get_grid_y(self, var_name): + if var_name in self._values: + return self._coords[-2] + else: + raise KeyError(var_name) - def get_start_time (self): - raise NotImplementedError('get_start_time') + def get_start_time(self): + raise NotImplementedError("get_start_time") - def get_end_time (self): - raise NotImplementedError('get_end_time') + def get_end_time(self): + raise NotImplementedError("get_end_time") - def get_current_time (self): - raise NotImplementedError('get_current_time') + def get_current_time(self): + raise NotImplementedError("get_current_time") - def get_time_step (self): - raise NotImplementedError('get_time_step') + def get_time_step(self): + raise NotImplementedError("get_time_step") diff --git a/input/Te_sample/makebreaks.py b/input/Te_sample/makebreaks.py index d97ad65..69911db 100644 --- a/input/Te_sample/makebreaks.py +++ b/input/Te_sample/makebreaks.py @@ -4,68 +4,73 @@ from matplotlib import pyplot as plt TeScalar = 80000 -#Te = TeScalar*np.ones((100,100)) -Te = TeScalar*np.ones(2000) +# Te = TeScalar*np.ones((100,100)) +Te = TeScalar * np.ones(2000) + # Make a discontinuous break: will be unstable, but a check of how to program # this -def discontinuous(orientation,number,proportion): - output = np.zeros(Te.shape) - if orientation=='row': - output[number,:] = TeScalar - elif orientation=='column': - output[:,number] = TeScalar - return output - -def slice2d(rowcol,proportion): - output = np.zeros(Te.shape) - for i in rowcol: - # "max" b/c I am thinking of whole-grid Gaussian in future - # max(output[i-1:i+1,:]) - output[i-1:i+1,:] = proportion*TeScalar - output[:,i-1:i+1] = proportion*TeScalar - return output - -#output = np.zeros(Te.shape) -#rowcol = [25,50,75] -#for i in rowcol: +def discontinuous(orientation, number, proportion): + output = np.zeros(Te.shape) + if orientation == "row": + output[number, :] = TeScalar + elif orientation == "column": + output[:, number] = TeScalar + return output + + +def slice2d(rowcol, proportion): + output = np.zeros(Te.shape) + for i in rowcol: + # "max" b/c I am thinking of whole-grid Gaussian in future + # max(output[i-1:i+1,:]) + output[i - 1 : i + 1, :] = proportion * TeScalar + output[:, i - 1 : i + 1] = proportion * TeScalar + return output + + +# output = np.zeros(Te.shape) +# rowcol = [25,50,75] +# for i in rowcol: # output[ -#Te -= slice2d([25,50,75],.5) +# Te -= slice2d([25,50,75],.5) -#for i in 25,50,75: +# for i in 25,50,75: # Te-=discontinuous('row',i,.99) # Te-=discontinuous('column',i,.99) + # Make a Gaussian function in the middle of a grid with the shape of mine -def gaussian(rowcol,proportion): - # Only for square grids - g = np.zeros(Te.shape) - for i in rowcol: - a = TeScalar*proportion - b = i - c = 8. - x=np.arange(0,len(Te)) - gaussian1d = a*np.exp((-(x-b)**2)/(2*c**2)) - for i in range(len(Te)): - if len(Te.shape) == 2: - for j in range(len(Te)): - g[i,j] = max(g[i,j], gaussian1d[j]) - g[j,i] = max(g[j,i], gaussian1d[j]) - elif len(Te.shape) ==1: - g[i] = max(g[i], gaussian1d[i]) - else: - print("Error!") - raise SystemExit - return g - -#Te -= gaussian([25,75],.8) -Te -= gaussian([200,1000,1800],.99) +def gaussian(rowcol, proportion): + # Only for square grids + g = np.zeros(Te.shape) + for i in rowcol: + a = TeScalar * proportion + b = i + c = 8.0 + x = np.arange(0, len(Te)) + gaussian1d = a * np.exp((-((x - b) ** 2)) / (2 * c**2)) + for i in range(len(Te)): + if len(Te.shape) == 2: + for j in range(len(Te)): + g[i, j] = max(g[i, j], gaussian1d[j]) + g[j, i] = max(g[j, i], gaussian1d[j]) + elif len(Te.shape) == 1: + g[i] = max(g[i], gaussian1d[i]) + else: + print("Error!") + raise SystemExit + return g + + +# Te -= gaussian([25,75],.8) +Te -= gaussian([200, 1000, 1800], 0.99) if len(Te.shape) == 2: - plt.imshow(Te) - plt.colorbar() + plt.imshow(Te) + plt.colorbar() elif len(Te.shape) == 1: - plt.plot(Te) + plt.plot(Te) plt.show() diff --git a/input/grid2D.py b/input/grid2D.py index 2365fd6..793a5a1 100755 --- a/input/grid2D.py +++ b/input/grid2D.py @@ -9,33 +9,33 @@ flex.Quiet = False -flex.Method = 'FD' -flex.PlateSolutionType = 'vWC1994' -flex.Solver = 'direct' - -flex.g = 9.8 # acceleration due to gravity -flex.E = 65E9 # Young's Modulus -flex.nu = 0.25 # Poisson's Ratio -flex.rho_m = 3300. # MantleDensity -flex.rho_fill = 0. # InfiillMaterialDensity - -flex.Te = 80000. # Elastic thickness -- scalar but may be an array -flex.qs = np.zeros((720, 360)) # Template array for surface load stresses -flex.qs[100:150, 100:150] += 1E6 # Populating this template -flex.dx = 80000. -flex.dy = 111000. -flex.BC_W = 'Periodic' # west boundary condition -flex.BC_E = 'Periodic' # east boundary condition -flex.BC_S = 'Periodic' # south boundary condition -flex.BC_N = 'Periodic' # north boundary condition +flex.Method = "FD" +flex.PlateSolutionType = "vWC1994" +flex.Solver = "direct" + +flex.g = 9.8 # acceleration due to gravity +flex.E = 65e9 # Young's Modulus +flex.nu = 0.25 # Poisson's Ratio +flex.rho_m = 3300.0 # MantleDensity +flex.rho_fill = 0.0 # InfiillMaterialDensity + +flex.Te = 80000.0 # Elastic thickness -- scalar but may be an array +flex.qs = np.zeros((720, 360)) # Template array for surface load stresses +flex.qs[100:150, 100:150] += 1e6 # Populating this template +flex.dx = 80000.0 +flex.dy = 111000.0 +flex.BC_W = "Periodic" # west boundary condition +flex.BC_E = "Periodic" # east boundary condition +flex.BC_S = "Periodic" # south boundary condition +flex.BC_N = "Periodic" # north boundary condition flex.initialize() flex.run() flex.finalize() # If you want to plot the output -flex.plotChoice='both' +flex.plotChoice = "both" # An output file could also be defined here # flex.wOutFile = -flex.output() # Plots and/or saves output, or does nothing, depending on - # whether flex.plotChoice and/or flex.wOutFile have been set +flex.output() # Plots and/or saves output, or does nothing, depending on +# whether flex.plotChoice and/or flex.wOutFile have been set diff --git a/input/run_in_script_1D.py b/input/run_in_script_1D.py index e3e411b..2e61603 100755 --- a/input/run_in_script_1D.py +++ b/input/run_in_script_1D.py @@ -9,39 +9,40 @@ flex.Quiet = True -flex.Method = 'FD' # Solution method: * FD (finite difference) - # * SAS (superposition of analytical solutions) - # * SAS_NG (ungridded SAS) +flex.Method = "FD" # Solution method: * FD (finite difference) +# * SAS (superposition of analytical solutions) +# * SAS_NG (ungridded SAS) -flex.Solver = 'direct' # direct or iterative +flex.Solver = "direct" # direct or iterative # convergence = 1E-3 # convergence between iterations, if an iterative solution - # method is chosen +# method is chosen -flex.g = 9.8 # acceleration due to gravity -flex.E = 65E9 # Young's Modulus -flex.nu = 0.25 # Poisson's Ratio -flex.rho_m = 3300. # MantleDensity -flex.rho_fill = 1000. # InfiillMaterialDensity +flex.g = 9.8 # acceleration due to gravity +flex.E = 65e9 # Young's Modulus +flex.nu = 0.25 # Poisson's Ratio +flex.rho_m = 3300.0 # MantleDensity +flex.rho_fill = 1000.0 # InfiillMaterialDensity -flex.Te = 30000.#*np.ones(500) # Elastic thickness -- scalar but may be an array -#flex.Te[-3:] = 0 -flex.qs = np.zeros(300); flex.qs[100:200] += 1E6 # surface load stresses -flex.dx = 4000. # grid cell size [m] -flex.BC_W = '0Displacement0Slope' # west boundary condition -flex.BC_E = '0Moment0Shear' # east boundary condition +flex.Te = 30000.0 # *np.ones(500) # Elastic thickness -- scalar but may be an array +# flex.Te[-3:] = 0 +flex.qs = np.zeros(300) +flex.qs[100:200] += 1e6 # surface load stresses +flex.dx = 4000.0 # grid cell size [m] +flex.BC_W = "0Displacement0Slope" # west boundary condition +flex.BC_E = "0Moment0Shear" # east boundary condition -flex.sigma_xx = 100. # Normal stress on the edge of the plate +flex.sigma_xx = 100.0 # Normal stress on the edge of the plate flex.initialize() flex.run() flex.finalize() # If you want to plot the output -flex.plotChoice='combo' +flex.plotChoice = "combo" # An output file for deflections could also be defined here # flex.wOutFile = -flex.output() # Plots and/or saves output, or does nothing, depending on - # whether flex.plotChoice and/or flex.wOutFile have been set +flex.output() # Plots and/or saves output, or does nothing, depending on +# whether flex.plotChoice and/or flex.wOutFile have been set # TO OBTAIN OUTPUT DIRECTLY IN PYTHON, you can assign the internal variable, # flex.w, to another variable -- or as an element in a list if you are looping # over many runs of gFlex: diff --git a/input/run_in_script_2D.py b/input/run_in_script_2D.py index 5bf5ba6..0ce6c9e 100755 --- a/input/run_in_script_2D.py +++ b/input/run_in_script_2D.py @@ -9,49 +9,51 @@ flex.Quiet = False -flex.Method = 'FD' # Solution method: * FD (finite difference) - # * SAS (superposition of analytical solutions) - # * SAS_NG (ungridded SAS) -flex.PlateSolutionType = 'vWC1994' # van Wees and Cloetingh (1994) - # The other option is 'G2009': Govers et al. (2009) -flex.Solver = 'direct' # direct or iterative +flex.Method = "FD" # Solution method: * FD (finite difference) +# * SAS (superposition of analytical solutions) +# * SAS_NG (ungridded SAS) +flex.PlateSolutionType = "vWC1994" # van Wees and Cloetingh (1994) +# The other option is 'G2009': Govers et al. (2009) +flex.Solver = "direct" # direct or iterative # convergence = 1E-3 # convergence between iterations, if an iterative solution - # method is chosen - -flex.g = 9.8 # acceleration due to gravity -flex.E = 65E9 # Young's Modulus -flex.nu = 0.25 # Poisson's Ratio -flex.rho_m = 3300. # MantleDensity -flex.rho_fill = 0. # InfiillMaterialDensity - -flex.Te = 35000.*np.ones((50, 50)) # Elastic thickness [m] -- scalar but may be an array -flex.Te[:,-3:] = 0. -flex.qs = np.zeros((50, 50)) # Template array for surface load stresses -flex.qs[10:40, 10:40] += 1E6 # Populating this template -flex.dx = 5000. # grid cell size, x-oriented [m] -flex.dy = 5000. # grid cell size, y-oriented [m] +# method is chosen + +flex.g = 9.8 # acceleration due to gravity +flex.E = 65e9 # Young's Modulus +flex.nu = 0.25 # Poisson's Ratio +flex.rho_m = 3300.0 # MantleDensity +flex.rho_fill = 0.0 # InfiillMaterialDensity + +flex.Te = 35000.0 * np.ones( + (50, 50) +) # Elastic thickness [m] -- scalar but may be an array +flex.Te[:, -3:] = 0.0 +flex.qs = np.zeros((50, 50)) # Template array for surface load stresses +flex.qs[10:40, 10:40] += 1e6 # Populating this template +flex.dx = 5000.0 # grid cell size, x-oriented [m] +flex.dy = 5000.0 # grid cell size, y-oriented [m] # Boundary conditions can be: # (FD): 0Slope0Shear, 0Moment0Shear, 0Displacement0Slope, Mirror, or Periodic # For SAS or SAS_NG, NoOutsideLoads is valid, and no entry defaults to this -flex.BC_W = '0Displacement0Slope' # west boundary condition -flex.BC_E = '0Moment0Shear' # east boundary condition -flex.BC_S = '0Displacement0Slope' # south boundary condition -flex.BC_N = '0Displacement0Slope' # north boundary condition +flex.BC_W = "0Displacement0Slope" # west boundary condition +flex.BC_E = "0Moment0Shear" # east boundary condition +flex.BC_S = "0Displacement0Slope" # south boundary condition +flex.BC_N = "0Displacement0Slope" # north boundary condition # latitude/longitude solutions are exact for SAS, approximate otherwise -#latlon = # true/false: flag to enable lat/lon input. Defaults False. -#PlanetaryRadius = # radius of planet [m], for lat/lon solutions +# latlon = # true/false: flag to enable lat/lon input. Defaults False. +# PlanetaryRadius = # radius of planet [m], for lat/lon solutions flex.initialize() flex.run() flex.finalize() # If you want to plot the output -flex.plotChoice='both' +flex.plotChoice = "both" # An output file for deflections could also be defined here # flex.wOutFile = -flex.output() # Plots and/or saves output, or does nothing, depending on - # whether flex.plotChoice and/or flex.wOutFile have been set +flex.output() # Plots and/or saves output, or does nothing, depending on +# whether flex.plotChoice and/or flex.wOutFile have been set # TO OBTAIN OUTPUT DIRECTLY IN PYTHON, you can assign the internal variable, # flex.w, to another variable -- or as an element in a list if you are looping # over many runs of gFlex: diff --git a/input/test.py b/input/test.py index a6d4345..5cdd1ee 100755 --- a/input/test.py +++ b/input/test.py @@ -9,37 +9,38 @@ flex.Quiet = True -flex.Method = 'FD' # Solution method: * FD (finite difference) - # * SAS (superposition of analytical solutions) - # * SAS_NG (ungridded SAS) +flex.Method = "FD" # Solution method: * FD (finite difference) +# * SAS (superposition of analytical solutions) +# * SAS_NG (ungridded SAS) -flex.Solver = 'direct' # direct or iterative +flex.Solver = "direct" # direct or iterative # convergence = 1E-3 # convergence between iterations, if an iterative solution - # method is chosen - -flex.g = 9.8 # acceleration due to gravity -flex.E = 65E9 # Young's Modulus -flex.nu = 0.25 # Poisson's Ratio -flex.rho_m = 3300. # MantleDensity -flex.rho_fill = 1000. # InfiillMaterialDensity - -flex.Te = 30000.#*np.ones(500) # Elastic thickness -- scalar but may be an array -#flex.Te[-3:] = 0 -flex.qs = -1E6 * np.ones(100); flex.qs[:50] = 0 # surface load stresses -flex.dx = 4000. # grid cell size [m] -flex.BC_W = '0Displacement0Slope' # west boundary condition -flex. BC_E = '0Displacement0Slope' # east boundary condition +# method is chosen + +flex.g = 9.8 # acceleration due to gravity +flex.E = 65e9 # Young's Modulus +flex.nu = 0.25 # Poisson's Ratio +flex.rho_m = 3300.0 # MantleDensity +flex.rho_fill = 1000.0 # InfiillMaterialDensity + +flex.Te = 30000.0 # *np.ones(500) # Elastic thickness -- scalar but may be an array +# flex.Te[-3:] = 0 +flex.qs = -1e6 * np.ones(100) +flex.qs[:50] = 0 # surface load stresses +flex.dx = 4000.0 # grid cell size [m] +flex.BC_W = "0Displacement0Slope" # west boundary condition +flex.BC_E = "0Displacement0Slope" # east boundary condition flex.initialize() flex.run() flex.finalize() # If you want to plot the output -flex.plotChoice='combo' +flex.plotChoice = "combo" # An output file for deflections could also be defined here # flex.wOutFile = -flex.output() # Plots and/or saves output, or does nothing, depending on - # whether flex.plotChoice and/or flex.wOutFile have been set +flex.output() # Plots and/or saves output, or does nothing, depending on +# whether flex.plotChoice and/or flex.wOutFile have been set # TO OBTAIN OUTPUT DIRECTLY IN PYTHON, you can assign the internal variable, # flex.w, to another variable -- or as an element in a list if you are looping # over many runs of gFlex: diff --git a/tests/test_1D_FD.py b/tests/test_1D_FD.py index 294aea5..541f073 100644 --- a/tests/test_1D_FD.py +++ b/tests/test_1D_FD.py @@ -11,41 +11,43 @@ def test_main(): flex.Quiet = True - flex.Method = 'SAS' # Solution method: * FD (finite difference) - # * SAS (superposition of analytical solutions) - # * SAS_NG (ungridded SAS) + flex.Method = "SAS" # Solution method: * FD (finite difference) + # * SAS (superposition of analytical solutions) + # * SAS_NG (ungridded SAS) - flex.Solver = 'direct' # direct or iterative + flex.Solver = "direct" # direct or iterative # convergence = 1E-3 # convergence between iterations, if an iterative solution - # method is chosen - - flex.g = 9.8 # acceleration due to gravity - flex.E = 65E9 # Young's Modulus - flex.nu = 0.25 # Poisson's Ratio - flex.rho_m = 3300. # MantleDensity - flex.rho_fill = 1000. # InfiillMaterialDensity - - flex.Te = 30000.#*np.ones(500) # Elastic thickness -- scalar but may be an array - #flex.Te[-3:] = 0 - flex.qs = np.zeros(10); flex.qs[5] += 1E6 # surface load stresses - flex.dx = 4000. # grid cell size [m] - flex.BC_W = '0Displacement0Slope' # west boundary condition - flex. BC_E = '0Moment0Shear' # east boundary condition + # method is chosen + + flex.g = 9.8 # acceleration due to gravity + flex.E = 65e9 # Young's Modulus + flex.nu = 0.25 # Poisson's Ratio + flex.rho_m = 3300.0 # MantleDensity + flex.rho_fill = 1000.0 # InfiillMaterialDensity + + flex.Te = 30000.0 # *np.ones(500) # Elastic thickness -- scalar but may be an array + # flex.Te[-3:] = 0 + flex.qs = np.zeros(10) + flex.qs[5] += 1e6 # surface load stresses + flex.dx = 4000.0 # grid cell size [m] + flex.BC_W = "0Displacement0Slope" # west boundary condition + flex.BC_E = "0Moment0Shear" # east boundary condition flex.initialize() flex.run() flex.finalize() # If you want to plot the output - #flex.plotChoice='combo' + # flex.plotChoice='combo' # An output file for deflections could also be defined here # flex.wOutFile = - flex.output() # Plots and/or saves output, or does nothing, depending on - # whether flex.plotChoice and/or flex.wOutFile have been set + flex.output() # Plots and/or saves output, or does nothing, depending on + # whether flex.plotChoice and/or flex.wOutFile have been set # TO OBTAIN OUTPUT DIRECTLY IN PYTHON, you can assign the internal variable, # flex.w, to another variable -- or as an element in a list if you are looping # over many runs of gFlex: deflection = flex.w -if __name__ == '__main__': + +if __name__ == "__main__": test_main() diff --git a/tests/test_1D_SAS.py b/tests/test_1D_SAS.py index b071d9c..160fee7 100755 --- a/tests/test_1D_SAS.py +++ b/tests/test_1D_SAS.py @@ -11,36 +11,38 @@ def test_main(): flex.Quiet = True - flex.Method = 'SAS' # Solution method: * FD (finite difference) - # * SAS (superposition of analytical solutions) - # * SAS_NG (ungridded SAS) - - flex.g = 9.8 # acceleration due to gravity - flex.E = 65E9 # Young's Modulus - flex.nu = 0.25 # Poisson's Ratio - flex.rho_m = 3300. # MantleDensity - flex.rho_fill = 1000. # InfiillMaterialDensity - - flex.Te = 30000.#*np.ones(500) # Elastic thickness -- scalar but may be an array - flex.qs = np.zeros(10); flex.qs[5] += 1E6 # surface load stresses - flex.dx = 4000. # grid cell size [m] - flex.BC_W = '0Displacement0Slope' # west boundary condition - flex. BC_E = '0Moment0Shear' # east boundary condition + flex.Method = "SAS" # Solution method: * FD (finite difference) + # * SAS (superposition of analytical solutions) + # * SAS_NG (ungridded SAS) + + flex.g = 9.8 # acceleration due to gravity + flex.E = 65e9 # Young's Modulus + flex.nu = 0.25 # Poisson's Ratio + flex.rho_m = 3300.0 # MantleDensity + flex.rho_fill = 1000.0 # InfiillMaterialDensity + + flex.Te = 30000.0 # *np.ones(500) # Elastic thickness -- scalar but may be an array + flex.qs = np.zeros(10) + flex.qs[5] += 1e6 # surface load stresses + flex.dx = 4000.0 # grid cell size [m] + flex.BC_W = "0Displacement0Slope" # west boundary condition + flex.BC_E = "0Moment0Shear" # east boundary condition flex.initialize() flex.run() flex.finalize() # If you want to plot the output - #flex.plotChoice='combo' + # flex.plotChoice='combo' # An output file for deflections could also be defined here # flex.wOutFile = - flex.output() # Plots and/or saves output, or does nothing, depending on - # whether flex.plotChoice and/or flex.wOutFile have been set + flex.output() # Plots and/or saves output, or does nothing, depending on + # whether flex.plotChoice and/or flex.wOutFile have been set # TO OBTAIN OUTPUT DIRECTLY IN PYTHON, you can assign the internal variable, # flex.w, to another variable -- or as an element in a list if you are looping # over many runs of gFlex: deflection = flex.w -if __name__ == '__main__': + +if __name__ == "__main__": test_main() diff --git a/tests/test_2D_FD.py b/tests/test_2D_FD.py index a6b7b92..8c70b45 100755 --- a/tests/test_2D_FD.py +++ b/tests/test_2D_FD.py @@ -11,53 +11,56 @@ def test_main(): flex.Quiet = False - flex.Method = 'FD' # Solution method: * FD (finite difference) - # * SAS (superposition of analytical solutions) - # * SAS_NG (ungridded SAS) - flex.PlateSolutionType = 'vWC1994' # van Wees and Cloetingh (1994) - # The other option is 'G2009': Govers et al. (2009) - flex.Solver = 'direct' # direct or iterative + flex.Method = "FD" # Solution method: * FD (finite difference) + # * SAS (superposition of analytical solutions) + # * SAS_NG (ungridded SAS) + flex.PlateSolutionType = "vWC1994" # van Wees and Cloetingh (1994) + # The other option is 'G2009': Govers et al. (2009) + flex.Solver = "direct" # direct or iterative # convergence = 1E-3 # convergence between iterations, if an iterative solution - # method is chosen - - flex.g = 9.8 # acceleration due to gravity - flex.E = 65E9 # Young's Modulus - flex.nu = 0.25 # Poisson's Ratio - flex.rho_m = 3300. # MantleDensity - flex.rho_fill = 0. # InfiillMaterialDensity - - flex.Te = 35000.*np.ones((50, 50)) # Elastic thickness [m] -- scalar but may be an array - flex.Te[:,-3:] = 0. - flex.qs = np.zeros((50, 50)) # Template array for surface load stresses - flex.qs[10:40, 10:40] += 1E6 # Populating this template - flex.dx = 5000. # grid cell size, x-oriented [m] - flex.dy = 5000. # grid cell size, y-oriented [m] + # method is chosen + + flex.g = 9.8 # acceleration due to gravity + flex.E = 65e9 # Young's Modulus + flex.nu = 0.25 # Poisson's Ratio + flex.rho_m = 3300.0 # MantleDensity + flex.rho_fill = 0.0 # InfiillMaterialDensity + + flex.Te = 35000.0 * np.ones( + (50, 50) + ) # Elastic thickness [m] -- scalar but may be an array + flex.Te[:, -3:] = 0.0 + flex.qs = np.zeros((50, 50)) # Template array for surface load stresses + flex.qs[10:40, 10:40] += 1e6 # Populating this template + flex.dx = 5000.0 # grid cell size, x-oriented [m] + flex.dy = 5000.0 # grid cell size, y-oriented [m] # Boundary conditions can be: # (FD): 0Slope0Shear, 0Moment0Shear, 0Displacement0Slope, Mirror, or Periodic # For SAS or SAS_NG, NoOutsideLoads is valid, and no entry defaults to this - flex.BC_W = '0Displacement0Slope' # west boundary condition - flex.BC_E = '0Moment0Shear' # east boundary condition - flex.BC_S = '0Displacement0Slope' # south boundary condition - flex.BC_N = '0Displacement0Slope' # north boundary condition + flex.BC_W = "0Displacement0Slope" # west boundary condition + flex.BC_E = "0Moment0Shear" # east boundary condition + flex.BC_S = "0Displacement0Slope" # south boundary condition + flex.BC_N = "0Displacement0Slope" # north boundary condition # latitude/longitude solutions are exact for SAS, approximate otherwise - #latlon = # true/false: flag to enable lat/lon input. Defaults False. - #PlanetaryRadius = # radius of planet [m], for lat/lon solutions + # latlon = # true/false: flag to enable lat/lon input. Defaults False. + # PlanetaryRadius = # radius of planet [m], for lat/lon solutions flex.initialize() flex.run() flex.finalize() # If you want to plot the output - #flex.plotChoice='both' + # flex.plotChoice='both' # An output file for deflections could also be defined here # flex.wOutFile = - flex.output() # Plots and/or saves output, or does nothing, depending on - # whether flex.plotChoice and/or flex.wOutFile have been set + flex.output() # Plots and/or saves output, or does nothing, depending on + # whether flex.plotChoice and/or flex.wOutFile have been set # TO OBTAIN OUTPUT DIRECTLY IN PYTHON, you can assign the internal variable, # flex.w, to another variable -- or as an element in a list if you are looping # over many runs of gFlex: deflection = flex.w -if __name__ == '__main__': + +if __name__ == "__main__": test_main() diff --git a/utilities/flexural_wavelength_calculator.py b/utilities/flexural_wavelength_calculator.py index 944fc5c..ffedf4f 100755 --- a/utilities/flexural_wavelength_calculator.py +++ b/utilities/flexural_wavelength_calculator.py @@ -4,34 +4,34 @@ # 2D -Te = 30000 # m -rho_f = 1000. # water -rho_m = 3300. # mantle +Te = 30000 # m +rho_f = 1000.0 # water +rho_m = 3300.0 # mantle drho = rho_m - rho_f -E=65E9 # Youong's modulus -nu=0.25 # Poisson's ratio +E = 65e9 # Youong's modulus +nu = 0.25 # Poisson's ratio g = 9.8 -D = (E * Te**3) / (12* (1 - nu**2)) +D = (E * Te**3) / (12 * (1 - nu**2)) -alpha1D = (4*D/(drho * g))**.25 -alpha2D = (D/(drho * g))**.25 +alpha1D = (4 * D / (drho * g)) ** 0.25 +alpha2D = (D / (drho * g)) ** 0.25 -lambda1D = alpha1D * 2*3.14159 -lambda2D = alpha2D * 2*3.14159 +lambda1D = alpha1D * 2 * 3.14159 +lambda2D = alpha2D * 2 * 3.14159 print("") print("1D:") -print("Flexural wavelength:", lambda1D/1000, 'km') -print("Distance to first zero-crossing:", .375 * lambda1D/1000, 'km') -print("Flexural parameter:", alpha1D/1000, 'km') +print("Flexural wavelength:", lambda1D / 1000, "km") +print("Distance to first zero-crossing:", 0.375 * lambda1D / 1000, "km") +print("Flexural parameter:", alpha1D / 1000, "km") print("") print("2D:") -print("Flexural wavelength:", lambda2D/1000, 'km') -print("Distance to first zero-crossing:", .375 * lambda2D/1000, 'km') -print("Flexural parameter:", alpha2D/1000, 'km') +print("Flexural wavelength:", lambda2D / 1000, "km") +print("Distance to first zero-crossing:", 0.375 * lambda2D / 1000, "km") +print("Flexural parameter:", alpha2D / 1000, "km") print("")