From 4e30db995a1ca1f754a9b6f5581a44704a799792 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Thu, 30 Nov 2023 08:57:05 -0700 Subject: [PATCH 01/26] upgrade to python 3.10+ syntax --- gflex/base.py | 7 +++---- gflex/f1d.py | 13 ++++++------- gflex/f2d.py | 13 ++++++------- gflex/gflex.py | 1 - gflex_bmi.py | 2 +- setup.py | 6 +++--- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index 8b6c020..9dcc438 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -17,9 +17,8 @@ along with gFlex. If not, see . """ -from __future__ import print_function import sys, os -from six.moves import configparser +import configparser import numpy as np import time # For efficiency counting import types # For flow control @@ -27,7 +26,7 @@ import warnings from _version import __version__ -class Utility(object): +class Utility: """ Generic utility functions @@ -225,7 +224,7 @@ def loadFile(self, var, close_on_fail = True): pass return out -class Plotting(object): +class Plotting: # Plot, if desired # 1D all here, 2D in functions # Just because there is often more code in 2D plotting functions diff --git a/gflex/f1d.py b/gflex/f1d.py index 644d48c..fab2089 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -17,7 +17,6 @@ along with gFlex. If not, see . """ -from __future__ import division, print_function # No automatic floor division from base import * from scipy.sparse import spdiags from scipy.sparse.linalg import spsolve, isolve @@ -25,7 +24,7 @@ class F1D(Flexure): def initialize(self, filename=None): self.dimension = 1 # Set it here in case it wasn't set for selection before - super(F1D, self).initialize() + super().initialize() if self.Verbose: print("F1D initialized") def run(self): @@ -33,20 +32,20 @@ def run(self): self.solver_start_time = time.time() if self.Method == 'FD': # Finite difference - super(F1D, self).FD() + super().FD() self.method_func = self.FD elif self.Method == 'FFT': # Fast Fourier transform - super(F1D, self).FFT() + super().FFT() self.method_func = self.FFT elif self.Method == "SAS": # Superposition of analytical solutions - super(F1D, self).SAS() + super().SAS() self.method_func = self.SAS elif self.Method == "SAS_NG": # Superposition of analytical solutions, # nonuniform points - super(F1D, self).SAS_NG() + super().SAS_NG() self.method_func = self.SAS_NG else: sys.exit('Error: method must be "FD", "FFT", "SAS", or "SAS_NG"') @@ -67,7 +66,7 @@ def finalize(self): except: pass if self.Verbose: print("F1D finalized") - super(F1D, self).finalize() + super().finalize() ######################################## ## FUNCTIONS FOR EACH SOLUTION METHOD ## diff --git a/gflex/f2d.py b/gflex/f2d.py index c105a9e..94a7c40 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -17,7 +17,6 @@ along with gFlex. If not, see . """ -from __future__ import division, print_function # No automatic floor division from base import * import scipy from scipy.special import kei @@ -28,7 +27,7 @@ class F2D(Flexure): def initialize(self, filename=None): self.dimension = 2 # Set it here in case it wasn't set for selection before - super(F2D, self).initialize() + super().initialize() if self.Verbose: print("F2D initialized") def run(self): @@ -37,20 +36,20 @@ def run(self): if self.Method == 'FD': # Finite difference - super(F2D, self).FD() + super().FD() self.method_func = self.FD elif self.Method == 'FFT': # Fast Fourier transform - super(F2D, self).FFT() + super().FFT() self.method_func = self.FFT elif self.Method == "SAS": # Superposition of analytical solutions - super(F2D, self).SAS() + super().SAS() self.method_func = self.SAS elif self.Method == "SAS_NG": # Superposition of analytical solutions, # nonuniform points (no grid) - super(F2D, self).SAS_NG() + super().SAS_NG() self.method_func = self.SAS_NG else: sys.exit('Error: method must be "FD", "FFT", "SAS", or "SAS_NG"') @@ -71,7 +70,7 @@ def finalize(self): except: pass if self.Verbose: print("F2D finalized") - super(F2D, self).finalize() + super().finalize() ######################################## ## FUNCTIONS FOR EACH SOLUTION METHOD ## diff --git a/gflex/gflex.py b/gflex/gflex.py index 94355fa..43d726f 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -19,7 +19,6 @@ along with gFlex. If not, see . """ -from __future__ import print_function import os.path import sys from base import * diff --git a/gflex_bmi.py b/gflex_bmi.py index b6cf446..a5e5b70 100644 --- a/gflex_bmi.py +++ b/gflex_bmi.py @@ -8,7 +8,7 @@ from prattairy import PrattAiry -class BmiGflex(object): +class BmiGflex: _name = 'Isostasy and Lithospheric Flexure' _input_var_names = ('earth_material_load__mass', ) _output_var_names = ('lithosphere__vertical_displacement', diff --git a/setup.py b/setup.py index 0e8f74f..6880587 100644 --- a/setup.py +++ b/setup.py @@ -10,13 +10,13 @@ # Get version from file import re VERSIONFILE="gflex/_version.py" -verstrline = open(VERSIONFILE, "rt").read() +verstrline = open(VERSIONFILE).read() VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" mo = re.search(VSRE, verstrline, re.M) if mo: __version__ = mo.group(1) else: - raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) + raise RuntimeError(f"Unable to find version string in {VERSIONFILE}.") setup( name = "gFlex", @@ -40,7 +40,7 @@ url = "https://github.com/awickert/gFlex", download_url = "https://github.com/awickert/gFlex/tarball/v"+__version__, long_description_content_type='text/markdown', - long_description = open('README.md', 'r').read(), + long_description = open('README.md').read(), use_2to3=True, ) From 6b2a32bb9885842e2f246bccbd34ed80bddb22ba Mon Sep 17 00:00:00 2001 From: mcflugen Date: Thu, 30 Nov 2023 09:01:58 -0700 Subject: [PATCH 02/26] remove use_2to3 keyword --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 6880587..bc7a023 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,4 @@ download_url = "https://github.com/awickert/gFlex/tarball/v"+__version__, long_description_content_type='text/markdown', long_description = open('README.md').read(), - - use_2to3=True, ) From 6885ede79be07ccdd4c8b05b9400465eb9bf4639 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Thu, 30 Nov 2023 09:02:16 -0700 Subject: [PATCH 03/26] update the requirements --- README.md | 6 ++---- requirements.txt | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 61ec876..a7a0540 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,12 @@ When you use gFlex, please cite: #### Python -gFlex has been tested on **Python 2.7** and **Python 3.8**. +gFlex has been tested on **Python 3.10+**. In order to run properly, gFlex requires the following Python dependencies: * numpy * scipy * matplotlib -* setuptools -* pip (optional) *For users who are new to Python, follow these directions to install the Python interpreters onto your computer.* @@ -60,7 +58,7 @@ The current recommendation is to use a package manager like [**homebrew**](http: # Homebrew sudo brew install python-numpy # Pip -pip install numpy +pip install -r requirements.txt ``` Recent efforts to download Python distributions (both **Anaconda** and **Enthought**) have not met with success with both gFlex and GRASS, though **Anaconda** has been tested successfully with Windows. As a result, it should be more successful to keep the Python packages managed better by something like **homebrew** with **pip**. diff --git a/requirements.txt b/requirements.txt index a6ab537..74fa65e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,3 @@ -nose matplotlib numpy scipy -configparser -setuptools From 6cd0ccd7f3f5e528b29de60d66d8ec1d624f38c7 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Thu, 30 Nov 2023 09:08:59 -0700 Subject: [PATCH 04/26] remove ez_setup.py --- README.md | 13 --- ez_setup.py | 284 ---------------------------------------------------- 2 files changed, 297 deletions(-) delete mode 100644 ez_setup.py diff --git a/README.md b/README.md index a7a0540..7d145ca 100644 --- a/README.md +++ b/README.md @@ -63,19 +63,6 @@ pip install -r requirements.txt Recent efforts to download Python distributions (both **Anaconda** and **Enthought**) have not met with success with both gFlex and GRASS, though **Anaconda** has been tested successfully with Windows. As a result, it should be more successful to keep the Python packages managed better by something like **homebrew** with **pip**. -##### Setuptools and ez_setup (Windows and Mac with distributions) - -The distributions for Mac and Windows do not come with setuptools, which is required to install gFlex. However, if you install ez_setup, the gFlex install script will automatically install setuptools for you. Simply type: -```bash -pip install ez_setup # Windows or Mac without special privileges required -sudo pip install ez_setup # Mac where sudo privileges are required -``` -Of course, one can also bypass the need for the install script to install setuptools by using pip preemptively: -```bash -pip install setuptools # Windows or Mac without special privileges required -sudo pip install setuptools # Mac where sudo privileges are required -``` - #### gFlex ##### Downloading and Installing in One Step from PyPI using pip diff --git a/ez_setup.py b/ez_setup.py deleted file mode 100644 index b74adc0..0000000 --- a/ez_setup.py +++ /dev/null @@ -1,284 +0,0 @@ -#!python -"""Bootstrap setuptools installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from ez_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import sys -DEFAULT_VERSION = "0.6c11" -DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] - -md5_data = { - 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', - 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', - 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', - 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', - 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', - 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', - 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', - 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', - 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', - 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', - 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', - 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', - 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', - 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', - 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', - 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', - 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', - 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', - 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', - 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', - 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', - 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', - 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', - 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', - 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', - 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', - 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', - 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', - 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', - 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', - 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', - 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', - 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', - 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', - 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', - 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', - 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', - 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', - 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', - 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', - 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', - 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', -} - -import sys, os -try: from hashlib import md5 -except ImportError: from md5 import md5 - -def _validate_md5(egg_name, data): - if egg_name in md5_data: - digest = md5(data).hexdigest() - if digest != md5_data[egg_name]: - print >>sys.stderr, ( - "md5 validation of %s failed! (Possible download problem?)" - % egg_name - ) - sys.exit(2) - return data - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - download_delay=15 -): - """Automatically find/download setuptools and make it available on sys.path - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end with - a '/'). `to_dir` is the directory where setuptools will be downloaded, if - it is not already available. If `download_delay` is specified, it should - be the number of seconds that will be paused before initiating a download, - should one be required. If an older version of setuptools is installed, - this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. - """ - was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules - def do_download(): - egg = download_setuptools(version, download_base, to_dir, download_delay) - sys.path.insert(0, egg) - import setuptools; setuptools.bootstrap_install_from = egg - try: - import pkg_resources - except ImportError: - return do_download() - try: - pkg_resources.require("setuptools>="+version); return - except pkg_resources.VersionConflict, e: - if was_imported: - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first, using 'easy_install -U setuptools'." - "\n\n(Currently using %r)" - ) % (version, e.args[0]) - sys.exit(2) - except pkg_resources.DistributionNotFound: - pass - - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return do_download() - -def download_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - delay = 15 -): - """Download setuptools from a specified location and return its filename - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download attempt. - """ - import urllib2, shutil - egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) - url = download_base + egg_name - saveto = os.path.join(to_dir, egg_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - from distutils import log - if delay: - log.warn(""" ---------------------------------------------------------------------------- -This script requires setuptools version %s to run (even to display -help). I will attempt to download it for you (from -%s), but -you may need to enable firewall access for this script first. -I will start the download in %d seconds. - -(Note: if this machine does not have network access, please obtain the file - - %s - -and place it in this directory before rerunning this script.) ----------------------------------------------------------------------------""", - version, download_base, delay, url - ); from time import sleep; sleep(delay) - log.warn("Downloading %s", url) - src = urllib2.urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = _validate_md5(egg_name, src.read()) - dst = open(saveto,"wb"); dst.write(data) - finally: - if src: src.close() - if dst: dst.close() - return os.path.realpath(saveto) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - try: - import setuptools - except ImportError: - egg = None - try: - egg = download_setuptools(version, delay=0) - sys.path.insert(0,egg) - from setuptools.command.easy_install import main - return main(list(argv)+[egg]) # we're done here - finally: - if egg and os.path.exists(egg): - os.unlink(egg) - else: - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) - - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: - try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - -def update_md5(filenames): - """Update our built-in md5 registry""" - - import re - - for name in filenames: - base = os.path.basename(name) - f = open(name,'rb') - md5_data[base] = md5(f.read()).hexdigest() - f.close() - - data = [" %r: %r,\n" % it for it in md5_data.items()] - data.sort() - repl = "".join(data) - - import inspect - srcfile = inspect.getsourcefile(sys.modules[__name__]) - f = open(srcfile, 'rb'); src = f.read(); f.close() - - match = re.search("\nmd5_data = {\n([^}]+)}", src) - if not match: - print >>sys.stderr, "Internal error!" - sys.exit(2) - - src = src[:match.start(1)] + repl + src[match.end(1):] - f = open(srcfile,'w') - f.write(src) - f.close() - - -if __name__=='__main__': - if len(sys.argv)>2 and sys.argv[1]=='--md5update': - update_md5(sys.argv[2:]) - else: - main(sys.argv[1:]) - - - - - - From 30bab58ad66457740fcb6edcc69f793fccbc7a98 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 09:52:36 -0700 Subject: [PATCH 05/26] move static project metadata into pyproject.toml --- pyproject.toml | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 45 ++----------------------- 2 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a13e577 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,91 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "gflex" +description = "One- and two-dimensional plate bending, designed for Earth's lithosphere" +authors = [ + {email = "awickert@umn.edu"}, + {name = "Andrew D. Wickert"}, +] +maintainers = [ + {email = "awickert@umn.edu"}, + {name = "Andrew D. Wickert"}, +] +keywords = [ + 'geophysics', + 'geology', + 'geodynamics', + 'lithosphere', + 'isostasy', + 'GRASS GIS' +] +license = {text = "GPL-3.0-only"} +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Cython", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Scientific/Engineering :: Physics", +] +requires-python = ">=3.10" +dependencies = [ + "matplotlib", + "numpy", + "scipy", +] +dynamic = ["readme", "version"] + +[project.urls] +homepage = "https://github.com/awickert/gflex" +documentation = "https://github.com/awickert/gflex/blob/main/README.md" +repository = "https://github.com/awickert/gflex" +changelog = "https://github.com/awickert/gflex/blob/main/CHANGES.md" + +[project.optional-dependencies] +dev = ["nox"] +testing = ["pytest"] + +[project.scripts] +gflex = "gflex.main:main" + +# [tool.setuptools] +# py-modules = [] + +[tool.setuptools.packages.find] +where = ["."] +include = ["gflex*"] + +[tool.setuptools.dynamic] +readme = {file = ["README.md", "AUTHORS.md", "CHANGES.md"], content-type="text/markdown"} +version = {attr = "gflex._version.__version__"} + +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = ["gFlex", "tests"] +norecursedirs = [".*", "*.egg*", "build", "dist", "utilities"] +addopts = """ + --ignore setup.py + --tb native + --strict + --durations 16 + --doctest-modules + -vvv +""" +doctest_optionflags = [ + "NORMALIZE_WHITESPACE", + "IGNORE_EXCEPTION_DETAIL", + "ALLOW_UNICODE" +] + +[tool.isort] +combine_as_imports = true +known_first_party = "gflex" +profile = "black" diff --git a/setup.py b/setup.py index bc7a023..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,44 +1,3 @@ -#! /usr/bin/env python +from setuptools import setup -from setuptools import setup, find_packages - -import os - -# This is for upload to PyPI -# Should not be necessary on most computers - -# Get version from file -import re -VERSIONFILE="gflex/_version.py" -verstrline = open(VERSIONFILE).read() -VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" -mo = re.search(VSRE, verstrline, re.M) -if mo: - __version__ = mo.group(1) -else: - raise RuntimeError(f"Unable to find version string in {VERSIONFILE}.") - -setup( - name = "gFlex", - version = __version__, - packages = find_packages(exclude="tests"), - entry_points = { - 'console_scripts': ['gflex = gflex:main'] - }, - - package_data = { - '': ['*.md'] - }, - - # metadata for upload to PyPI - author = "Andrew D. Wickert", - author_email = "awickert@umn.edu", - description = "One- and two-dimensional plate bending, designed for Earth's lithosphere", - license = "GPL v3", - keywords = ['geophysics', 'geology', 'geodynamics', 'lithosphere', 'isostasy', 'GRASS GIS'], - classifiers = [], - url = "https://github.com/awickert/gFlex", - download_url = "https://github.com/awickert/gFlex/tarball/v"+__version__, - long_description_content_type='text/markdown', - long_description = open('README.md').read(), -) +setup() From 06029b6363b64204412acd23c94d518af1b9ecaa Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 09:56:04 -0700 Subject: [PATCH 06/26] add an authors file --- AUTHORS.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 AUTHORS.md diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..dd7cdaf --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,9 @@ +# Credits + +## Development Lead + +* [Andrew D. Wickert](https://github.com/awickert) + +# Contributors + +None yet. Why not be the first? From 15fed980def67289107364f3fe574971959765a8 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 09:58:32 -0700 Subject: [PATCH 07/26] add a manifest file --- MANIFEST.in | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..016232a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,19 @@ +include AUTHORS.md +include CHANGES.md +include LICENSE.md +include README.md +include CITATION.cff +include requirements* +include noxfile.py + +recursive-include gflex +recursive-include tests *py + +exclude .pre-commit-config.yaml +exclude gflex_bmi.py +exclude topypi-test.sh +exclude topypi.sh + +recursive-exclude conda * +recursive-exclude input * +recursive-exclude utilities * From 4f8e64c60620ce31bb7e8818c599748abf8d8436 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:00:40 -0700 Subject: [PATCH 08/26] add flake8 config to setup.cfg --- setup.cfg | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index b88034e..c5c0cdf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,6 @@ -[metadata] -description-file = README.md +[flake8] +exclude = docs, build, .nox +ignore = C901, E203, E266, E501, W503, B905 +max-line-length = 88 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 From a69ce4dad48761d51feb98bd32ee40e7c5c474d4 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:06:52 -0700 Subject: [PATCH 09/26] use print function --- input/Te_sample/makebreaks.py | 7 +++---- utilities/flexural_wavelength_calculator.py | 22 ++++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/input/Te_sample/makebreaks.py b/input/Te_sample/makebreaks.py index a7a1777..d97ad65 100644 --- a/input/Te_sample/makebreaks.py +++ b/input/Te_sample/makebreaks.py @@ -7,7 +7,7 @@ #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 +# 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) @@ -55,7 +55,7 @@ def gaussian(rowcol,proportion): elif len(Te.shape) ==1: g[i] = max(g[i], gaussian1d[i]) else: - print "Error!" + print("Error!") raise SystemExit return g @@ -67,6 +67,5 @@ def gaussian(rowcol,proportion): plt.colorbar() elif len(Te.shape) == 1: plt.plot(Te) - -plt.show() +plt.show() diff --git a/utilities/flexural_wavelength_calculator.py b/utilities/flexural_wavelength_calculator.py index 07280d8..944fc5c 100755 --- a/utilities/flexural_wavelength_calculator.py +++ b/utilities/flexural_wavelength_calculator.py @@ -20,18 +20,18 @@ lambda1D = alpha1D * 2*3.14159 lambda2D = alpha2D * 2*3.14159 -print "" +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("1D:") +print("Flexural wavelength:", lambda1D/1000, 'km') +print("Distance to first zero-crossing:", .375 * lambda1D/1000, 'km') +print("Flexural parameter:", alpha1D/1000, 'km') -print "" +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("2D:") +print("Flexural wavelength:", lambda2D/1000, 'km') +print("Distance to first zero-crossing:", .375 * lambda2D/1000, 'km') +print("Flexural parameter:", alpha2D/1000, 'km') -print "" +print("") From 30f506ac4caf2d14d81caabcff7625c015e265c2 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:07:28 -0700 Subject: [PATCH 10/26] remove trailing whitespace --- README.md | 40 ++++---- gflex/_version.py | 1 - gflex/base.py | 141 +++++++++++++-------------- gflex/f1d.py | 155 +++++++++++++++-------------- gflex/f2d.py | 200 +++++++++++++++++++------------------- gflex/gflex.py | 13 ++- gflex_bmi.py | 2 +- input/grid2D.py | 2 +- input/input_f2d_nogrid | 4 +- input/input_help | 34 +++---- input/run_in_script_1D.py | 3 +- input/run_in_script_2D.py | 3 +- input/template1D | 1 - input/template2D | 1 - input/test.py | 3 +- tests/test_1D_FD.py | 3 +- tests/test_1D_SAS.py | 3 +- tests/test_2D_FD.py | 3 +- 18 files changed, 300 insertions(+), 312 deletions(-) diff --git a/README.md b/README.md index 7d145ca..e1fe6f5 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Use your package manager to download and install the required Python packages. F # Basic packages sudo apt-get install \ python python-numpy python-scipy \ -python-setuptools python-matplotlib +python-setuptools python-matplotlib # pip (recommended for automatic installs via setuptools) sudo apt-get install python-pip @@ -71,7 +71,7 @@ gFlex is downloadable from the Python Package Index ([PyPI](https://pypi.python. If you have **pip**, you may simply type: ```bash -pip install +pip install pip install gflex # Or if the destination install folder requires sudo access # (for UNIX-like systems) @@ -139,22 +139,22 @@ For help constructing configuration files, see the blank template files **input/ [mode] ; 1 (line) or 2 (surface) dimensions dimension=2 -; Solution method: FD (Finite Difference), FFT (Fast Fourier -; Transform, not yet implemented), SAS (Spatial domain analytical +; Solution method: FD (Finite Difference), FFT (Fast Fourier +; Transform, not yet implemented), SAS (Spatial domain analytical ; solutions), or SAS_NG (SPA, but do not require a uniform grid ; - NG = "no grid") -; For SAS_NG, 1D data must be provided and will be returned in +; For SAS_NG, 1D data must be provided and will be returned in ; two columns: (x,q0) --> (x,w). 2D data are similar, except ; will be of the form (x,y,[q0/in or w/out]) ; I am working on gridded output for these, so this might change ; in the future. -; Both the FFT and SPA techniques rely on superposition -; of solutions, because they can be combined linearly, whether in +; Both the FFT and SPA techniques rely on superposition +; of solutions, because they can be combined linearly, whether in ; the spectral or the spatial domain) method=SPA ; Plate solutions can be: ; * vWC1994 (best), or -; * G2009 (from Govers et al., 2009; not bad, but not +; * G2009 (from Govers et al., 2009; not bad, but not ; as robust as vWC1994) PlateSolutionType=vWC1994 @@ -163,10 +163,10 @@ YoungsModulus=65E9 PoissonsRatio=0.25 GravAccel=9.8 MantleDensity=3300 -; This is the density of material (e.g., air, water) -; that is filling (or leaving) the hole that was -; created by flexure. If you do not have a constant -; density of infilling material, for example, at a +; This is the density of material (e.g., air, water) +; that is filling (or leaving) the hole that was +; created by flexure. If you do not have a constant +; density of infilling material, for example, at a ; subsiding shoreline, you must instead iterate (see ; [numerical], below). InfillMaterialDensity=0 @@ -185,22 +185,22 @@ Loads=q0_sample/2D/central_square_load.txt ElasticThickness=Te_sample/2D/10km_const.txt ; ; xw and yw are vectors of desired output points for the SAS_NG method. -; If they are not specified and a SAS_NG solution is run, the solution will be +; If they are not specified and a SAS_NG solution is run, the solution will be ; calculated at the points with the loads. ; they are ignored if a different solution method is chosen. xw= yw= [output] -; DeflectionOut is for writing an output file. +; DeflectionOut is for writing an output file. ; If this is blank, no output is printed. -; Otherwise, a space-delimited ASCII file of +; Otherwise, a space-delimited ASCII file of ; outputs is with this file name (and path). DeflectionOut=tmpout.txt ; -; Acceptable inputs to "Plot" are q0 (loads), w (deflection), or both; any +; Acceptable inputs to "Plot" are q0 (loads), w (deflection), or both; any ; other entry here will result in no plotting. -; Automatically plots a 1D line or 2D surface based on the choice +; Automatically plots a 1D line or 2D surface based on the choice ; of "dimension" variable in [mode] Plot=both @@ -231,11 +231,11 @@ GridSpacing_y= ; For SAS or SAS_NG, NoOutsideLoads is valid, and no entry defaults to this BoundaryCondition_North= BoundaryCondition_South= -; +; ; Flag to enable lat/lon input (true/false). By default, this is false latlon= ; radius of planet [m], for lat/lon solutions -PlanetaryRadius= +PlanetaryRadius= [verbosity] ; true/false. Defaults to true. @@ -304,7 +304,7 @@ flex.finalize() # If you want to plot the output flex.plotChoice='both' # An output file for deflections could also be defined here -# flex.wOutFile = +# flex.wOutFile = 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, diff --git a/gflex/_version.py b/gflex/_version.py index c41e414..bbfb12e 100644 --- a/gflex/_version.py +++ b/gflex/_version.py @@ -4,4 +4,3 @@ # http://stackoverflow.com/questions/458550/standard-way-to-embed-version-into-python-package __version__ = '1.1.1' - diff --git a/gflex/base.py b/gflex/base.py index 9dcc438..b327305 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -35,21 +35,21 @@ def configGet(self, vartype, category, name, optional=False, specialReturnMessag """ 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 + 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) vartype can be 'float', 'str' or 'string' (str and string are the same), or 'int' or 'integer' (also the same). - - "Optional" determines whether or not the program will exit if the variable + + "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". - - "specialReturnMessage" is something that you would like to add at the end + + "specialReturnMessage" is something that you would like to add at the end of a failure to execute message. By default it does not print. """ - + try: if vartype == 'float': var = self.config.getfloat(category, name) @@ -108,40 +108,40 @@ def greatCircleDistance(self, lat1, long1, lat2, long2, radius): # 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 + 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. """ @@ -173,7 +173,7 @@ def define_points_grid(self): 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 + 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) @@ -182,8 +182,8 @@ def define_points_grid(self): 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 @@ -229,7 +229,7 @@ class Plotting: # 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 + # 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): @@ -315,7 +315,7 @@ def plotting(self): if self.Method == "FD": if type(self.Te) is np.ndarray: if (self.Te != (self.Te).mean()).any(): - plt.title(titletext,fontsize=16) + plt.title(titletext,fontsize=16) else: plt.title(titletext + ', $T_e$ = ' + str((self.Te / 1000).mean()) + " km", fontsize=16) else: @@ -323,7 +323,7 @@ def plotting(self): else: plt.title(titletext + ', $T_e$ = ' + str(self.Te / 1000) + " km", fontsize=16) except: - plt.title(titletext,fontsize=16) + 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) @@ -420,19 +420,19 @@ def twoSurfplots(self): 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!") - + from scipy.interpolate import griddata import numpy.ma as ma - + # define grid. xmin = np.min(self.xw) xmean = np.mean(self.xw) # not used right now @@ -442,7 +442,7 @@ def xyzinterp(self, x, y, z, titletext): 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 @@ -524,29 +524,29 @@ class Flexure(Utility, Plotting): 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 + # 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 + # 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 @@ -614,7 +614,7 @@ def initialize(self, filename=None): 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. @@ -629,14 +629,14 @@ def initialize(self, filename=None): print("") if self.filename: - # Set clocks to None so if they are called by the getter before the + # 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 + # 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 @@ -664,7 +664,7 @@ def initialize(self, filename=None): 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 @@ -673,14 +673,14 @@ def initialize(self, filename=None): # 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 @@ -707,7 +707,7 @@ def initialize(self, filename=None): 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': @@ -719,7 +719,7 @@ def initialize(self, filename=None): print(self.q0) print("Exiting.") sys.exit() - + # Plotting selection self.plotChoice = self.configGet("string", "output", "Plot", optional=True) @@ -742,19 +742,19 @@ def initialize(self, filename=None): 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') + 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') + 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') + 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 @@ -782,12 +782,12 @@ def output(self): # Save output deflections to file, if desired def outputDeflections(self): """ - Outputs a grid of deflections if an output directory is defined in the + 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 + + 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: @@ -833,7 +833,7 @@ def bc_check(self): # 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 + # 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: @@ -913,10 +913,10 @@ def bc_check(self): 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 + 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. """ @@ -924,14 +924,14 @@ def coeffArraySizeCheck(self): 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 + # 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 @@ -953,7 +953,7 @@ def FD(self): 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) @@ -982,7 +982,7 @@ def FD(self): # 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") + 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) @@ -998,11 +998,11 @@ def FD(self): if self.coeff_matrix is not None: pass else: - # Have to bring this out here in case it was discovered in the + # 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: + if type(self.Te) == str: # Try to import Te grid or scalar for the finite difference solution Tepath = self.Te else: @@ -1027,17 +1027,17 @@ def FD(self): # Will be array if it was loaded if self.Te.any(): self.TeArraySizeCheck() - + ### need work def FFT(self): pass - # SAS and SAS_NG are the exact same here; leaving separate just for symmetry + # 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 + Set-up for the rectangularly-gridded superposition of analytical solutions method for solving flexure """ if self.x is None: @@ -1067,7 +1067,7 @@ def SAS(self): def SAS_NG(self): """ - Set-up for the ungridded superposition of analytical solutions + Set-up for the ungridded superposition of analytical solutions method for solving flexure """ if self.filename: @@ -1076,7 +1076,7 @@ def SAS_NG(self): # 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 + # 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) @@ -1109,9 +1109,9 @@ def SAS_NG(self): 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 + # 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: @@ -1119,7 +1119,7 @@ def SAS_NG(self): # 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 @@ -1157,4 +1157,3 @@ def SAS_NG(self): 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 fab2089..4fe7820 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -59,19 +59,19 @@ def run(self): 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 + # 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() - + super().finalize() + ######################################## ## FUNCTIONS FOR EACH SOLUTION METHOD ## ######################################## - + def FD(self): self.gridded_x() # Only generate coefficient matrix if it is not already provided @@ -86,7 +86,7 @@ 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() @@ -107,8 +107,8 @@ def SAS_NG(self): 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 ######################################################### @@ -139,9 +139,9 @@ def spatialDomainVarsSAS(self): # 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]: @@ -150,7 +150,7 @@ def spatialDomainGridded(self): 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 @@ -164,7 +164,7 @@ def spatialDomainNoGrid(self): 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) @@ -175,11 +175,11 @@ def spatialDomainNoGrid(self): ## 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" """ @@ -190,10 +190,10 @@ def elasprepFD(self): def BC_selector_and_coeff_matrix_creator(self): """ Selects the boundary conditions - Then calls the function to build the pentadiagonal matrix to solve + 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: @@ -203,25 +203,25 @@ def BC_selector_and_coeff_matrix_creator(self): # 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 + # 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 + + # 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 + Utility function to help implement boundary conditions by specifying them for and applying them to the elastic thickness grid """ @@ -246,7 +246,7 @@ def BC_Rigidity(self): self.BC_Rigidity_E = 'mirror symmetry' else: sys.exit("Invalid Te B.C. case") - + ############# # PAD ARRAY # ############# @@ -254,9 +254,9 @@ def BC_Rigidity(self): 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, + # 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 + # 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]) @@ -276,9 +276,9 @@ def BC_Rigidity(self): 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 # ############################## @@ -315,27 +315,27 @@ def get_coeff_values(self): # 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 + # 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)) @@ -367,22 +367,22 @@ def build_diagonals(self): ########################################################## # 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 + # 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 + # 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 + # 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 + # boundary conditions require extra diagonals to exist on the edges of # the solution array pass else: @@ -391,7 +391,7 @@ def build_diagonals(self): # 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. @@ -400,11 +400,11 @@ def BC_Periodic(self): # 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 + # 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 + # 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"+ @@ -416,10 +416,10 @@ def BC_Periodic(self): def BC_0Displacement0Slope(self): """ 0Displacement0Slope boundary condition for 0 deflection. - This requires that nothing be done to the edges of the solution array, + 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 + 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': @@ -452,19 +452,19 @@ def BC_0Displacement0Slope(self): 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 + 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. - - This boundary condition has more of a geometric meaning than a physical - meaning. It produces a state in which the boundaries have to have all - gradients in deflection go to 0 (i.e. approach constant values) while + + This boundary condition has more of a geometric meaning than a physical + meaning. It produces a state in which the boundaries have to have all + gradients in deflection go to 0 (i.e. approach constant values) while not specifying what those values must be. - - This uses a 0-curvature boundary condition for elastic thickness + + This uses a 0-curvature boundary condition for elastic thickness that extends outside of the computational domain. """ - + if self.BC_W == '0Slope0Shear': i=0 self.l2[i] = np.nan @@ -496,17 +496,17 @@ 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: + 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: + 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 + # 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) @@ -523,7 +523,7 @@ def BC_0Moment0Shear(self): 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] @@ -540,11 +540,11 @@ def BC_0Moment0Shear(self): def BC_Mirror(self): """ - Mirrors qs across the boundary on either the west (left) or east (right) + 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 + + 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': @@ -560,7 +560,7 @@ def BC_Mirror(self): 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 @@ -574,25 +574,25 @@ def BC_Mirror(self): 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 + 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 + # 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() @@ -601,21 +601,21 @@ def fd_solve(self): 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 + # 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) + 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': @@ -626,13 +626,12 @@ def fd_solve(self): 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 + # 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) - diff --git a/gflex/f2d.py b/gflex/f2d.py index 94a7c40..56f046a 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -33,7 +33,7 @@ def initialize(self, filename=None): def run(self): self.bc_check() self.solver_start_time = time.time() - + if self.Method == 'FD': # Finite difference super().FD() @@ -63,7 +63,7 @@ def run(self): 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 + # value, so this is not messed up for repeat operations in a # model-coupling exercise try: self.Te = self.Te_unpadded @@ -71,7 +71,7 @@ def finalize(self): pass if self.Verbose: print("F2D finalized") super().finalize() - + ######################################## ## FUNCTIONS FOR EACH SOLUTION METHOD ## ######################################## @@ -96,12 +96,12 @@ def SAS_NG(self): self.spatialDomainVarsSAS() self.spatialDomainNoGrid() - + ###################################### ## FUNCTIONS TO SOLVE THE EQUATIONS ## ###################################### - - + + ## SPATIAL DOMAIN SUPERPOSITION OF ANALYTICAL SOLUTIONS ######################################################### @@ -132,10 +132,10 @@ def spatialDomainVarsSAS(self): # 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 @@ -148,7 +148,7 @@ def spatialDomainGridded(self): 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 @@ -172,7 +172,7 @@ def spatialDomainNoGrid(self): 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 @@ -191,21 +191,21 @@ def spatialDomainNoGrid(self): ## 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 + + 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 @@ -215,20 +215,20 @@ def BC_selector_and_coeff_matrix_creator(self): 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 + 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 + + 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 + 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 + 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: @@ -236,29 +236,29 @@ def BC_selector_and_coeff_matrix_creator(self): 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 + + # 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 + # 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 + Utility function to help implement boundary conditions by specifying them for and applying them to the elastic thickness grid """ @@ -301,7 +301,7 @@ def BC_Rigidity(self): self.BC_Rigidity_S = 'mirror symmetry' else: sys.exit("Invalid Te B.C. case") - + ############# # PAD ARRAY # ############# @@ -334,7 +334,7 @@ def BC_Rigidity(self): 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": @@ -343,21 +343,21 @@ def BC_Rigidity(self): 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 + 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 @@ -418,15 +418,15 @@ def get_coeff_values(self): 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:] @@ -444,7 +444,7 @@ def get_coeff_values(self): 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 @@ -478,11 +478,11 @@ def get_coeff_values(self): + (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 + # 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 @@ -515,7 +515,7 @@ def get_coeff_values(self): "* vWC1994\n"+ "* G2009\n"+ "") - + ################################################################ # CREATE COEFFICIENT ARRAYS: PLAIN, WITH NO B.C.'S YET APPLIED # ################################################################ @@ -552,32 +552,32 @@ def get_coeff_values(self): def BC_Flexure(self): - # The next section of code is split over several functions for the 1D + # 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 + # 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 + + # 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 + # 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 + # 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 + # 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) @@ -586,7 +586,7 @@ def BC_Flexure(self): 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 @@ -595,16 +595,16 @@ def BC_Flexure(self): 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 + # 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 + # 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 + # (i.e. clockwise, if we are reading from the top of the tread of a # tire) j = 0 self.cj_2i0[:,j] += np.inf @@ -679,11 +679,11 @@ def BC_Flexure(self): self.cj_1i0[:,j] += np.inf self.cj_1i1[:,j] += np.inf self.cj0i_2[:,j] += 0 - self.cj0i_1[:,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.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] @@ -712,7 +712,7 @@ def BC_Flexure(self): self.cj0i0[:,j] += 0 self.cj0i1[:,j] += 0 self.cj0i2[:,j] += 0 - self.cj1i_1[:,j] += self.cj_1i_1_coeff_ij[:,j] + 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] @@ -736,7 +736,7 @@ def BC_Flexure(self): 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 @@ -747,14 +747,14 @@ def BC_Flexure(self): 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 + # 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 @@ -888,10 +888,10 @@ def BC_Flexure(self): ####################################################################### # 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 + 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.") @@ -1017,7 +1017,7 @@ def BC_Flexure(self): if self.BC_S == 'Periodic': if self.BC_N == 'Periodic': - pass # Will address the N-S (whole-matrix-involving) boundary condition + 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.") @@ -1144,23 +1144,23 @@ def BC_Flexure(self): ##################################################### # CORNERS: INTERFERENCE BETWEEN BOUNDARY CONDITIONS # ##################################################### - - # In 2D, have to consider diagonals and interference (additive) among + + # 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 + # 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 + # Indeed it is much easier to envision this case than one in which # the stationary clamp is released. - + ################# # 0MOMENT0SHEAR # ################# @@ -1180,7 +1180,7 @@ def BC_Flexure(self): ############ # 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 @@ -1190,7 +1190,7 @@ def BC_Flexure(self): ################ # COMBINATIONS # ################ - + ############################## # 0SLOPE0SHEAR AND/OR MIRROR # ############################## @@ -1268,10 +1268,10 @@ def build_diagonals(self): ########################################################## # 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 + # 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) @@ -1297,7 +1297,7 @@ def build_diagonals(self): 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') @@ -1312,7 +1312,7 @@ def build_diagonals(self): 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 )) @@ -1343,11 +1343,11 @@ def build_diagonals(self): # 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 + # 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, @@ -1404,8 +1404,8 @@ def build_diagonals(self): # create banded sparse matrix self.coeff_matrix = scipy.sparse.spdiags(self.diags, self.offsets, - self.ny*self.nx, self.ny*self.nx, format='csr') - + self.ny*self.nx, self.ny*self.nx, format='csr') + elif (self.BC_W == 'Periodic' and self.BC_E == 'Periodic'): # Additional vector creation # West @@ -1455,8 +1455,8 @@ def build_diagonals(self): # create banded sparse matrix self.coeff_matrix = scipy.sparse.spdiags(self.diags, self.offsets, - self.ny*self.nx, self.ny*self.nx, format='csr') - + 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 @@ -1479,7 +1479,7 @@ def build_diagonals(self): -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. @@ -1495,33 +1495,33 @@ def build_diagonals(self): 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 + 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 + # 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) + 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 + 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 @@ -1531,14 +1531,14 @@ def fd_solve(self): 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 = 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": diff --git a/gflex/gflex.py b/gflex/gflex.py index 43d726f..27d0cb0 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -46,7 +46,7 @@ def displayUsage(): print("gflex -v *OR* gflex --version # DISPLAY VERSION NUMBER") print("import gflex # WITHIN PYTHON SHELL OR SCRIPT") print("") - + def furtherHelp(): print("") print("ADDITIONAL HELP:") @@ -91,19 +91,19 @@ def main(): displayUsage() print("") sys.exit() - - + + ######################################## ## SET MODEL TYPE AND DIMENSIONS HERE ## ######################################## - + if obj.dimension == 1: obj = F1D(filename) elif obj.dimension == 2: obj = F2D(filename) obj.initialize(filename) - + if obj.Debug: print("Command line:", sys.argv) @@ -122,9 +122,8 @@ def main(): ##################### ## GET VALUES HERE ## ## (if desired) ## - ##################### + ##################### #wout = obj.get_value('Deflection') # for example if __name__ == '__main__': main() - diff --git a/gflex_bmi.py b/gflex_bmi.py index a5e5b70..9279234 100644 --- a/gflex_bmi.py +++ b/gflex_bmi.py @@ -1,7 +1,7 @@ #! /usr/bin/env python import numpy as np -# Imports for gFlex +# Imports for gFlex from base import WhichModel from f1d import F1D from f2d import F2D diff --git a/input/grid2D.py b/input/grid2D.py index 1ddfb04..f53d4b8 100755 --- a/input/grid2D.py +++ b/input/grid2D.py @@ -35,6 +35,6 @@ # If you want to plot the output flex.plotChoice='both' # An output file could also be defined here -# flex.wOutFile = +# flex.wOutFile = 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/input_f2d_nogrid b/input/input_f2d_nogrid index a7d5973..d7b9ca5 100644 --- a/input/input_f2d_nogrid +++ b/input/input_f2d_nogrid @@ -31,7 +31,7 @@ Plot=both [numerical] ; dx -GridSpacing_x= +GridSpacing_x= BoundaryCondition_West=NoOutsideLoads BoundaryCondition_East=NoOutsideLoads ; Solver can be direct or iterative @@ -44,7 +44,7 @@ CoeffArray= [numerical2D] ; Those parameters shared between this and the 1D solution are in "[numerical]" ; dy -GridSpacing_y= +GridSpacing_y= BoundaryCondition_North=0Moment0Shear BoundaryCondition_South=Mirror ; true/false: flag to enable lat/lon input. Defaults False diff --git a/input/input_help b/input/input_help index 380fdf0..9b5af18 100644 --- a/input/input_help +++ b/input/input_help @@ -5,22 +5,22 @@ [mode] ; 1 (line) or 2 (surface) dimensions dimension=2 -; Solution method: FD (Finite Difference), FFT (Fast Fourier -; Transform, not yet implemented), SAS (Spatial domain analytical +; Solution method: FD (Finite Difference), FFT (Fast Fourier +; Transform, not yet implemented), SAS (Spatial domain analytical ; solutions), or SAS_NG (SPA, but do not require a uniform grid ; - NG = "no grid") -; For SAS_NG, 1D data must be provided and will be returned in +; For SAS_NG, 1D data must be provided and will be returned in ; two columns: (x,q0) --> (x,w). 2D data are similar, except ; will be of the form (x,y,[q0/in or w/out]) ; I am working on gridded output for these, so this might change ; in the future. -; Both the FFT and SPA techniques rely on superposition -; of solutions, because they can be combined linearly, whether in +; Both the FFT and SPA techniques rely on superposition +; of solutions, because they can be combined linearly, whether in ; the spectral or the spatial domain) method=SPA ; Plate solutions can be: ; * vWC1994 (best), or -; * G2009 (from Govers et al., 2009; not bad, but not +; * G2009 (from Govers et al., 2009; not bad, but not ; as robust as vWC1994) PlateSolutionType=vWC1994 @@ -29,10 +29,10 @@ YoungsModulus=65E9 PoissonsRatio=0.25 GravAccel=9.8 MantleDensity=3300 -; This is the density of material (e.g., air, water) -; that is filling (or leaving) the hole that was -; created by flexure. If you do not have a constant -; density of infilling material, for example, at a +; This is the density of material (e.g., air, water) +; that is filling (or leaving) the hole that was +; created by flexure. If you do not have a constant +; density of infilling material, for example, at a ; subsiding shoreline, you must instead iterate (see ; [numerical], below). InfillMaterialDensity=0 @@ -51,22 +51,22 @@ Loads=q0_sample/2D/central_square_load.txt ElasticThickness=Te_sample/2D/10km_const.txt ; ; xw and yw are vectors of desired output points for the SAS_NG method. -; If they are not specified and a SAS_NG solution is run, the solution will be +; If they are not specified and a SAS_NG solution is run, the solution will be ; calculated at the points with the loads. ; they are ignored if a different solution method is chosen. xw= yw= [output] -; DeflectionOut is for writing an output file. +; DeflectionOut is for writing an output file. ; If this is blank, no output is printed. -; Otherwise, a space-delimited ASCII file of +; Otherwise, a space-delimited ASCII file of ; outputs is with this file name (and path). DeflectionOut=tmpout.txt ; -; Acceptable inputs to "Plot" are q0 (loads), w (deflection), or both; any +; Acceptable inputs to "Plot" are q0 (loads), w (deflection), or both; any ; other entry here will result in no plotting. -; Automatically plots a 1D line or 2D surface based on the choice +; Automatically plots a 1D line or 2D surface based on the choice ; of "dimension" variable in [mode] Plot=both @@ -97,11 +97,11 @@ GridSpacing_y= ; For SAS or SAS_NG, NoOutsideLoads is valid, and no entry defaults to this BoundaryCondition_North= BoundaryCondition_South= -; +; ; Flag to enable lat/lon input (true/false). By default, this is false latlon= ; radius of planet [m], for lat/lon solutions -PlanetaryRadius= +PlanetaryRadius= [verbosity] ; true/false. Defaults to true. diff --git a/input/run_in_script_1D.py b/input/run_in_script_1D.py index 3ba1eb8..2bc6450 100755 --- a/input/run_in_script_1D.py +++ b/input/run_in_script_1D.py @@ -38,11 +38,10 @@ # If you want to plot the output flex.plotChoice='combo' # An output file for deflections could also be defined here -# flex.wOutFile = +# flex.wOutFile = 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 - diff --git a/input/run_in_script_2D.py b/input/run_in_script_2D.py index d1da8a0..4fef7cc 100755 --- a/input/run_in_script_2D.py +++ b/input/run_in_script_2D.py @@ -48,11 +48,10 @@ # If you want to plot the output flex.plotChoice='both' # An output file for deflections could also be defined here -# flex.wOutFile = +# flex.wOutFile = 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 - diff --git a/input/template1D b/input/template1D index eee73ec..4c51ca4 100644 --- a/input/template1D +++ b/input/template1D @@ -33,4 +33,3 @@ ConvergenceTolerance= Verbose= Debug= Quiet= - diff --git a/input/template2D b/input/template2D index 28d8cea..ea9dd95 100644 --- a/input/template2D +++ b/input/template2D @@ -42,4 +42,3 @@ PlanetaryRadius= Verbose= Debug= Quiet= - diff --git a/input/test.py b/input/test.py index b6da5d0..73d57b7 100755 --- a/input/test.py +++ b/input/test.py @@ -36,11 +36,10 @@ # If you want to plot the output flex.plotChoice='combo' # An output file for deflections could also be defined here -# flex.wOutFile = +# flex.wOutFile = 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 - diff --git a/tests/test_1D_FD.py b/tests/test_1D_FD.py index 14e949d..08ee91d 100644 --- a/tests/test_1D_FD.py +++ b/tests/test_1D_FD.py @@ -37,7 +37,7 @@ def test_main(): # If you want to plot the output #flex.plotChoice='combo' # An output file for deflections could also be defined here - # flex.wOutFile = + # flex.wOutFile = 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, @@ -47,4 +47,3 @@ def test_main(): if __name__ == '__main__': test_main() - diff --git a/tests/test_1D_SAS.py b/tests/test_1D_SAS.py index 67b513f..cb4041f 100755 --- a/tests/test_1D_SAS.py +++ b/tests/test_1D_SAS.py @@ -32,7 +32,7 @@ def test_main(): # If you want to plot the output #flex.plotChoice='combo' # An output file for deflections could also be defined here - # flex.wOutFile = + # flex.wOutFile = 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, @@ -42,4 +42,3 @@ def test_main(): if __name__ == '__main__': test_main() - diff --git a/tests/test_2D_FD.py b/tests/test_2D_FD.py index f731303..2af687b 100755 --- a/tests/test_2D_FD.py +++ b/tests/test_2D_FD.py @@ -49,7 +49,7 @@ def test_main(): # If you want to plot the output #flex.plotChoice='both' # An output file for deflections could also be defined here - # flex.wOutFile = + # flex.wOutFile = 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, @@ -59,4 +59,3 @@ def test_main(): if __name__ == '__main__': test_main() - From 9223eaf3eb3a62130a5dd21827e8abef0a8178c0 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:20:35 -0700 Subject: [PATCH 11/26] fix imports for python 3 --- gflex/__init__.py | 8 +------- gflex/_version.py | 5 ----- gflex/base.py | 4 +--- gflex/f1d.py | 3 ++- gflex/f2d.py | 2 +- gflex/gflex.py | 13 +++++-------- tests/test_1D_FD.py | 2 +- tests/test_1D_SAS.py | 2 +- tests/test_2D_FD.py | 2 +- 9 files changed, 13 insertions(+), 28 deletions(-) diff --git a/gflex/__init__.py b/gflex/__init__.py index 6140e32..8dee4bf 100644 --- a/gflex/__init__.py +++ b/gflex/__init__.py @@ -1,7 +1 @@ -import sys -import os -sys.path.append( os.path.dirname(os.path.realpath(__file__)) ) -from gflex import * -from f1d import * -from f2d import * -from base import * +from ._version import __version__ diff --git a/gflex/_version.py b/gflex/_version.py index bbfb12e..b3ddbc4 100644 --- a/gflex/_version.py +++ b/gflex/_version.py @@ -1,6 +1 @@ -# Current version number, kept here to be separate so it can be imported -# into many files (setup, initial prompt, --version command, ...) -# The best way to do this is following @Zooko's answer at: -# http://stackoverflow.com/questions/458550/standard-way-to-embed-version-into-python-package - __version__ = '1.1.1' diff --git a/gflex/base.py b/gflex/base.py index b327305..0ae2a7a 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -20,11 +20,9 @@ import sys, os import configparser import numpy as np -import time # For efficiency counting -import types # For flow control from matplotlib import pyplot as plt import warnings -from _version import __version__ +from ._version import __version__ class Utility: diff --git a/gflex/f1d.py b/gflex/f1d.py index 4fe7820..d4b4cd0 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -17,10 +17,11 @@ along with gFlex. If not, see . """ -from base import * +from gflex.base import Flexure from scipy.sparse import spdiags from scipy.sparse.linalg import spsolve, isolve + class F1D(Flexure): def initialize(self, filename=None): self.dimension = 1 # Set it here in case it wasn't set for selection before diff --git a/gflex/f2d.py b/gflex/f2d.py index 56f046a..6df6d25 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -17,7 +17,7 @@ along with gFlex. If not, see . """ -from base import * +from gflex.base import Flexure import scipy from scipy.special import kei diff --git a/gflex/gflex.py b/gflex/gflex.py index 27d0cb0..099e7bd 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -21,14 +21,11 @@ import os.path import sys -from base import * -from f1d import * -from f2d import * - -try: - from _version import __version__ -except: - __version__ = '? version file missing ?' +from gflex.f1d import F1D +from gflex.f2d import F2D + +from ._version import __version__ + def welcome(): print("") diff --git a/tests/test_1D_FD.py b/tests/test_1D_FD.py index 08ee91d..4af5a5e 100644 --- a/tests/test_1D_FD.py +++ b/tests/test_1D_FD.py @@ -5,7 +5,7 @@ from matplotlib import pyplot as plt def test_main(): - flex = gflex.F1D() + flex = gflex.f1d.F1D() flex.Quiet = True diff --git a/tests/test_1D_SAS.py b/tests/test_1D_SAS.py index cb4041f..1344eb3 100755 --- a/tests/test_1D_SAS.py +++ b/tests/test_1D_SAS.py @@ -5,7 +5,7 @@ from matplotlib import pyplot as plt def test_main(): - flex = gflex.F1D() + flex = gflex.f2d.F1D() flex.Quiet = True diff --git a/tests/test_2D_FD.py b/tests/test_2D_FD.py index 2af687b..d382317 100755 --- a/tests/test_2D_FD.py +++ b/tests/test_2D_FD.py @@ -5,7 +5,7 @@ from matplotlib import pyplot as plt def test_main(): - flex = gflex.F2D() + flex = gflex.f2d.F2D() flex.Quiet = False From 58fd5f9e215fa640f0198334f818dc6571f70508 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:21:35 -0700 Subject: [PATCH 12/26] reorder imports --- gflex/base.py | 11 ++++++++--- gflex/f1d.py | 5 +++-- gflex/f2d.py | 4 +++- gflex/gflex.py | 1 + input/grid2D.py | 3 ++- input/run_in_script_1D.py | 3 ++- input/run_in_script_2D.py | 3 ++- input/test.py | 3 ++- tests/test_1D_FD.py | 4 +++- tests/test_1D_SAS.py | 4 +++- tests/test_2D_FD.py | 4 +++- 11 files changed, 32 insertions(+), 13 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index 0ae2a7a..765e3ef 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -17,13 +17,17 @@ along with gFlex. If not, see . """ -import sys, os import configparser +import os +import sys +import warnings + import numpy as np from matplotlib import pyplot as plt -import warnings + from ._version import __version__ + class Utility: """ @@ -428,8 +432,8 @@ def xyzinterp(self, x, y, z, titletext): if self.Verbose: print("Starting to interpolate grid for plotting -- can be a slow process!") - from scipy.interpolate import griddata import numpy.ma as ma + from scipy.interpolate import griddata # define grid. xmin = np.min(self.xw) @@ -809,6 +813,7 @@ def outputDeflections(self): 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: diff --git a/gflex/f1d.py b/gflex/f1d.py index d4b4cd0..07498ff 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -17,9 +17,10 @@ along with gFlex. If not, see . """ -from gflex.base import Flexure from scipy.sparse import spdiags -from scipy.sparse.linalg import spsolve, isolve +from scipy.sparse.linalg import isolve, spsolve + +from gflex.base import Flexure class F1D(Flexure): diff --git a/gflex/f2d.py b/gflex/f2d.py index 6df6d25..4e84bfb 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -17,10 +17,12 @@ along with gFlex. If not, see . """ -from gflex.base import Flexure import scipy from scipy.special import kei +from gflex.base import Flexure + + # class F2D inherits Flexure and overrides __init__ therefore setting up the same # three parameters as class Isostasy; and it then sets up more parameters specific # to its own type of simulation. diff --git a/gflex/gflex.py b/gflex/gflex.py index 099e7bd..0c6e5e5 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -21,6 +21,7 @@ import os.path import sys + from gflex.f1d import F1D from gflex.f2d import F2D diff --git a/input/grid2D.py b/input/grid2D.py index f53d4b8..2365fd6 100755 --- a/input/grid2D.py +++ b/input/grid2D.py @@ -1,9 +1,10 @@ #! /usr/bin/env python -import gflex import numpy as np from matplotlib import pyplot as plt +import gflex + flex = gflex.F2D() flex.Quiet = False diff --git a/input/run_in_script_1D.py b/input/run_in_script_1D.py index 2bc6450..e3e411b 100755 --- a/input/run_in_script_1D.py +++ b/input/run_in_script_1D.py @@ -1,9 +1,10 @@ #! /usr/bin/env python -import gflex import numpy as np from matplotlib import pyplot as plt +import gflex + flex = gflex.F1D() flex.Quiet = True diff --git a/input/run_in_script_2D.py b/input/run_in_script_2D.py index 4fef7cc..5bf5ba6 100755 --- a/input/run_in_script_2D.py +++ b/input/run_in_script_2D.py @@ -1,9 +1,10 @@ #! /usr/bin/env python -import gflex import numpy as np from matplotlib import pyplot as plt +import gflex + flex = gflex.F2D() flex.Quiet = False diff --git a/input/test.py b/input/test.py index 73d57b7..a6d4345 100755 --- a/input/test.py +++ b/input/test.py @@ -1,9 +1,10 @@ #! /usr/bin/env python -import gflex import numpy as np from matplotlib import pyplot as plt +import gflex + flex = gflex.F1D() flex.Quiet = True diff --git a/tests/test_1D_FD.py b/tests/test_1D_FD.py index 4af5a5e..294aea5 100644 --- a/tests/test_1D_FD.py +++ b/tests/test_1D_FD.py @@ -1,9 +1,11 @@ #! /usr/bin/env python -import gflex import numpy as np from matplotlib import pyplot as plt +import gflex + + def test_main(): flex = gflex.f1d.F1D() diff --git a/tests/test_1D_SAS.py b/tests/test_1D_SAS.py index 1344eb3..b071d9c 100755 --- a/tests/test_1D_SAS.py +++ b/tests/test_1D_SAS.py @@ -1,9 +1,11 @@ #! /usr/bin/env python -import gflex import numpy as np from matplotlib import pyplot as plt +import gflex + + def test_main(): flex = gflex.f2d.F1D() diff --git a/tests/test_2D_FD.py b/tests/test_2D_FD.py index d382317..a6b7b92 100755 --- a/tests/test_2D_FD.py +++ b/tests/test_2D_FD.py @@ -1,9 +1,11 @@ #! /usr/bin/env python -import gflex import numpy as np from matplotlib import pyplot as plt +import gflex + + def test_main(): flex = gflex.f2d.F2D() From e7883d9a7905c17f2fa2a89a6ea4cf4755fa3f6a Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:29:44 -0700 Subject: [PATCH 13/26] reformat to use a consistent style (black) --- gflex/_version.py | 2 +- gflex/base.py | 2449 ++++++++------ gflex/f1d.py | 1257 +++---- gflex/f2d.py | 3296 ++++++++++--------- gflex/gflex.py | 156 +- gflex_bmi.py | 252 +- input/Te_sample/makebreaks.py | 105 +- input/grid2D.py | 44 +- input/run_in_script_1D.py | 41 +- input/run_in_script_2D.py | 60 +- input/test.py | 43 +- tests/test_1D_FD.py | 46 +- tests/test_1D_SAS.py | 40 +- tests/test_2D_FD.py | 63 +- utilities/flexural_wavelength_calculator.py | 32 +- 15 files changed, 4255 insertions(+), 3631 deletions(-) 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("") From a987dfd0715ab7c8bd884b20237559143a63e79e Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:39:31 -0700 Subject: [PATCH 14/26] fix undefined names, missing imports --- gflex/base.py | 6 +++--- gflex/f1d.py | 3 +++ gflex/f2d.py | 3 +++ gflex/gflex.py | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index cb77363..40abfb7 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -227,7 +227,7 @@ def loadFile(self, var, close_on_fail=True): except: # Then see if it is relative to the location of the configuration file try: - out = load(self.inpath + var) + out = np.load(self.inpath + var) if self.Verbose: print("Loading " + var + " from numpy binary") except: @@ -1184,8 +1184,8 @@ def coeffArraySizeCheck(self): 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 + if np.prod(self.coeff_matrix.shape) != np.long( + np.prod(np.array(self.qs.shape, dtype=np.int64) + 2) ** 2 ): print("Inconsistent size of q0 array and coefficient mattrix") print("Exiting.") diff --git a/gflex/f1d.py b/gflex/f1d.py index 328c4ae..2a97e44 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -16,7 +16,10 @@ You should have received a copy of the GNU General Public License along with gFlex. If not, see . """ +import sys +import time +import numpy as np from scipy.sparse import spdiags from scipy.sparse.linalg import isolve, spsolve diff --git a/gflex/f2d.py b/gflex/f2d.py index d8da50d..58f5208 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -16,7 +16,10 @@ You should have received a copy of the GNU General Public License along with gFlex. If not, see . """ +import sys +import time +import numpy as np import scipy from scipy.special import kei diff --git a/gflex/gflex.py b/gflex/gflex.py index b9b2282..23aa1e1 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -22,6 +22,7 @@ import os.path import sys +from gflex.base import WhichModel from gflex.f1d import F1D from gflex.f2d import F2D From 849d73bca47643c64bef7ff7981aa91febc5bfc2 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:45:44 -0700 Subject: [PATCH 15/26] fix comparisons to True/False --- gflex/base.py | 24 ++++++++++++------------ gflex/f1d.py | 4 ++-- gflex/f2d.py | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index 40abfb7..a0a4a93 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -60,10 +60,10 @@ def configGet( var = self.config.getfloat(category, name) elif vartype == "string" or vartype == "str": var = self.config.get(category, name) - if var == "" and optional == False: + if var == "" and not optional: # but "" is acceptable for boundary conditions if name[:17] != "BoundaryCondition": - if self.Quiet != True: + if not self.Quiet: print( "An empty input string here is not an acceptable option." ) @@ -84,7 +84,7 @@ def configGet( # Carry on if the variable is optional var = None if self.Verbose or self.Debug: - if self.grass == False: + if not self.grass: print("") print('No value entered for optional parameter "' + name + '"') print('in category "' + category + '" in configuration file.') @@ -171,16 +171,16 @@ def define_points_grid(self): # 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: + if not self.latlon: # Warn that any existing grid will be overwritten try: self.dx - if self.Quiet == False: + if not self.Quiet: print("dx and dy being overwritten -- supply a full grid") except: try: self.dy - if self.Quiet == False: + if not self.Quiet: print("dx and dy being overwritten -- supply a full grid") except: pass @@ -339,7 +339,7 @@ def plotting(self): ax = fig.add_subplot(1, 1, 1) # Plot undeflected load if self.Method == "SAS_NG": - if self.Quiet == False: + if not self.Quiet: print( "Combo plot can't work with SAS_NG! Don't have mechanism in place\nto calculate load width." ) @@ -426,7 +426,7 @@ def plotting(self): plt.tight_layout() plt.show() else: - if self.Quiet == False: + if not self.Quiet: print( 'Incorrect plotChoice input, "' + self.plotChoice @@ -481,7 +481,7 @@ def plotting(self): plt.tight_layout() plt.show() else: - if self.Quiet == False: + if not self.Quiet: print( 'Incorrect plotChoice input, "' + self.plotChoice @@ -803,7 +803,7 @@ def initialize(self, filename=None): # 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: + if not self.Quiet: print("") # Blank line at start of run print("") print("****************************" + "*" * len(__version__)) @@ -977,7 +977,7 @@ def finalize(self): del self.coeff_matrix except: pass - if self.Quiet == False: + if not self.Quiet: print("") # SAVING TO FILE AND PLOTTING STEPS @@ -1156,7 +1156,7 @@ def bc_check(self): ) print(" superposition-based analytical solution") else: - if self.Quiet == False: + if not self.Quiet: print("") print(">>> BOUNDARY CONDITIONS IMPROPERLY DEFINED <<<") print("") diff --git a/gflex/f1d.py b/gflex/f1d.py index 2a97e44..e05d4bc 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -61,7 +61,7 @@ def run(self): self.method_func() self.time_to_solve = time.time() - self.solver_start_time - if self.Quiet == False: + if not self.Quiet: print("Time to solve [s]:", self.time_to_solve) def finalize(self): @@ -233,7 +233,7 @@ def BC_selector_and_coeff_matrix_creator(self): # Finally, compute the total time this process took self.coeff_creation_time = time.time() - self.coeff_start_time - if self.Quiet == False: + if not self.Quiet: print( "Time to construct coefficient (operator) array [s]:", self.coeff_creation_time, diff --git a/gflex/f2d.py b/gflex/f2d.py index 58f5208..ddd46c7 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -65,7 +65,7 @@ def run(self): self.method_func() self.time_to_solve = time.time() - self.solver_start_time - if self.Quiet == False: + if not self.Quiet: print("Time to solve [s]:", self.time_to_solve) def finalize(self): @@ -270,7 +270,7 @@ def BC_selector_and_coeff_matrix_creator(self): # Finally, compute the total time this process took self.coeff_creation_time = time.time() - self.coeff_start_time - if self.Quiet == False: + if not self.Quiet: print( "Time to construct coefficient (operator) array [s]:", self.coeff_creation_time, @@ -1796,7 +1796,7 @@ def fd_solve(self): if self.Debug: print("Using direct solution with UMFpack") else: - if self.Quiet == False: + if not self.Quiet: print("Solution type not understood:") print("Defaulting to direct solution with UMFpack") wvector = scipy.sparse.linalg.spsolve( From 882082b502e2b2207c85323656da25e51ba61126 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 10:50:03 -0700 Subject: [PATCH 16/26] use isinstance for type comparisons --- gflex/base.py | 12 ++++++------ gflex/f2d.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index a0a4a93..bc7570d 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -882,7 +882,7 @@ def initialize(self, filename=None): try: self.q0 # Stop program if q0 is None-type - if type(self.q0) == None: # if is None type, just be patient + if self.q0 is 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" @@ -906,7 +906,7 @@ def initialize(self, filename=None): self.q0 = None if self.q0 == "": self.q0 = None - if type(self.q0) == str: + if isinstance(self.q0, str): self.q0 = self.loadFile(self.q0) # Won't do this if q0 is None # Check consistency of dimensions @@ -1199,7 +1199,7 @@ def TeArraySizeCheck(self): # 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: + if isinstance(self.Te, np.ndarray) and isinstance(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(): @@ -1279,7 +1279,7 @@ def FD(self): "No input elastic thickness or coefficient matrix supplied." ) # or if getter/setter - if type(self.Te) == str: + if isinstance(self.Te, str): # Try to import Te grid or scalar for the finite difference solution Tepath = self.Te else: @@ -1420,7 +1420,7 @@ def SAS_NG(self): except: self.xw = None # If strings, load arrays - if type(self.xw) == str: + if isinstance(self.xw, str): self.xw = self.loadFile(self.xw) if self.dimension == 2: try: @@ -1441,7 +1441,7 @@ def SAS_NG(self): "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: + if isinstance(self.yw, str): self.yw = self.loadFile(self.yw) elif self.yw is None: self.yw = self.y.copy() diff --git a/gflex/f2d.py b/gflex/f2d.py index ddd46c7..7b6e49c 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -486,7 +486,7 @@ def get_coeff_values(self): self.cj_1i1_coeff_ij = self.cj_1i1.copy() self.cj_2i0_coeff_ij = self.cj_2i0.copy() - elif type(self.Te) == np.ndarray: + elif isinstance(self.Te, np.ndarray): ####################################################### # GENERATE COEFFICIENT VALUES FOR EACH SOLUTION TYPE. # # "vWC1994" IS THE BEST: LOOSEST ASSUMPTIONS. # From 3e8c92fb96210b44a621105f7e5fc835e4569052 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 11:21:38 -0700 Subject: [PATCH 17/26] remove unused imports --- gflex/__init__.py | 2 ++ gflex/base.py | 4 ---- gflex/gflex.py | 1 - input/grid2D.py | 1 - input/run_in_script_1D.py | 1 - input/run_in_script_2D.py | 1 - input/test.py | 1 - tests/test_1D_FD.py | 1 - tests/test_1D_SAS.py | 1 - tests/test_2D_FD.py | 1 - 10 files changed, 2 insertions(+), 12 deletions(-) diff --git a/gflex/__init__.py b/gflex/__init__.py index 8dee4bf..26d23ba 100644 --- a/gflex/__init__.py +++ b/gflex/__init__.py @@ -1 +1,3 @@ from ._version import __version__ + +__all__ = ["__version__"] diff --git a/gflex/base.py b/gflex/base.py index bc7570d..10a996d 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -581,7 +581,6 @@ def xyzinterp(self, x, y, z, titletext): 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. @@ -1340,7 +1339,6 @@ def SAS(self): # 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): """ @@ -1361,8 +1359,6 @@ def SAS_NG(self): 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: diff --git a/gflex/gflex.py b/gflex/gflex.py index 23aa1e1..322c8d6 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -19,7 +19,6 @@ along with gFlex. If not, see . """ -import os.path import sys from gflex.base import WhichModel diff --git a/input/grid2D.py b/input/grid2D.py index 793a5a1..14a3931 100755 --- a/input/grid2D.py +++ b/input/grid2D.py @@ -1,7 +1,6 @@ #! /usr/bin/env python import numpy as np -from matplotlib import pyplot as plt import gflex diff --git a/input/run_in_script_1D.py b/input/run_in_script_1D.py index 2e61603..bc9737a 100755 --- a/input/run_in_script_1D.py +++ b/input/run_in_script_1D.py @@ -1,7 +1,6 @@ #! /usr/bin/env python import numpy as np -from matplotlib import pyplot as plt import gflex diff --git a/input/run_in_script_2D.py b/input/run_in_script_2D.py index 0ce6c9e..81cfa9a 100755 --- a/input/run_in_script_2D.py +++ b/input/run_in_script_2D.py @@ -1,7 +1,6 @@ #! /usr/bin/env python import numpy as np -from matplotlib import pyplot as plt import gflex diff --git a/input/test.py b/input/test.py index 5cdd1ee..1ac90c9 100755 --- a/input/test.py +++ b/input/test.py @@ -1,7 +1,6 @@ #! /usr/bin/env python import numpy as np -from matplotlib import pyplot as plt import gflex diff --git a/tests/test_1D_FD.py b/tests/test_1D_FD.py index 541f073..7272e5d 100644 --- a/tests/test_1D_FD.py +++ b/tests/test_1D_FD.py @@ -1,7 +1,6 @@ #! /usr/bin/env python import numpy as np -from matplotlib import pyplot as plt import gflex diff --git a/tests/test_1D_SAS.py b/tests/test_1D_SAS.py index 160fee7..62eb1be 100755 --- a/tests/test_1D_SAS.py +++ b/tests/test_1D_SAS.py @@ -1,7 +1,6 @@ #! /usr/bin/env python import numpy as np -from matplotlib import pyplot as plt import gflex diff --git a/tests/test_2D_FD.py b/tests/test_2D_FD.py index 8c70b45..b361275 100755 --- a/tests/test_2D_FD.py +++ b/tests/test_2D_FD.py @@ -1,7 +1,6 @@ #! /usr/bin/env python import numpy as np -from matplotlib import pyplot as plt import gflex From 7417c07a5792b1e479172785c5f8453a8a2ef646 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 11:25:12 -0700 Subject: [PATCH 18/26] remove unused variables --- gflex/base.py | 12 ++++++------ tests/test_1D_FD.py | 4 ---- tests/test_1D_SAS.py | 4 ---- tests/test_2D_FD.py | 4 ---- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index 10a996d..0a87276 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -585,13 +585,13 @@ def xyzinterp(self, x, y, z, titletext): # define grid. xmin = np.min(self.xw) - xmean = np.mean(self.xw) # not used right now + # 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 + # ymean = np.mean(self.yw) # not used right now ymax = np.max(self.yw) - x_range = xmax - xmin - y_range = ymax - ymin + # x_range = xmax - xmin + # y_range = ymax - ymin # x and y grids # 100 cells on each side -- just for plotting, not so important @@ -607,9 +607,9 @@ def xyzinterp(self, x, y, z, titletext): # 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) + 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.contourf(xi / 1000.0, yi / 1000.0, zi, 100, cmap=plt.cm.jet) plt.colorbar() # draw colorbar # plot model points. # Computed at diff --git a/tests/test_1D_FD.py b/tests/test_1D_FD.py index 7272e5d..4a33ff3 100644 --- a/tests/test_1D_FD.py +++ b/tests/test_1D_FD.py @@ -42,10 +42,6 @@ def test_main(): # flex.wOutFile = 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__": diff --git a/tests/test_1D_SAS.py b/tests/test_1D_SAS.py index 62eb1be..00f7183 100755 --- a/tests/test_1D_SAS.py +++ b/tests/test_1D_SAS.py @@ -37,10 +37,6 @@ def test_main(): # flex.wOutFile = 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__": diff --git a/tests/test_2D_FD.py b/tests/test_2D_FD.py index b361275..3b67e14 100755 --- a/tests/test_2D_FD.py +++ b/tests/test_2D_FD.py @@ -55,10 +55,6 @@ def test_main(): # flex.wOutFile = 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__": From e525d7c261624f44c00e5931223290698e788a33 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 12:09:54 -0700 Subject: [PATCH 19/26] fix F1D, F2D imports in tests --- tests/test_1D_FD.py | 4 ++-- tests/test_1D_SAS.py | 4 ++-- tests/test_2D_FD.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_1D_FD.py b/tests/test_1D_FD.py index 4a33ff3..6277c6b 100644 --- a/tests/test_1D_FD.py +++ b/tests/test_1D_FD.py @@ -2,11 +2,11 @@ import numpy as np -import gflex +from gflex.f1d import F1D def test_main(): - flex = gflex.f1d.F1D() + flex = F1D() flex.Quiet = True diff --git a/tests/test_1D_SAS.py b/tests/test_1D_SAS.py index 00f7183..f5c3193 100755 --- a/tests/test_1D_SAS.py +++ b/tests/test_1D_SAS.py @@ -2,11 +2,11 @@ import numpy as np -import gflex +from gflex.f1d import F1D def test_main(): - flex = gflex.f2d.F1D() + flex = F1D() flex.Quiet = True diff --git a/tests/test_2D_FD.py b/tests/test_2D_FD.py index 3b67e14..a69da8d 100755 --- a/tests/test_2D_FD.py +++ b/tests/test_2D_FD.py @@ -2,11 +2,11 @@ import numpy as np -import gflex +from gflex.f2d import F2D def test_main(): - flex = gflex.f2d.F2D() + flex = F2D() flex.Quiet = False From 75feffec3cf1303fcbba320f45db700aee3847bc Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 12:34:32 -0700 Subject: [PATCH 20/26] fix bare try/except blocks, mostly AttributeError --- gflex/base.py | 133 +++++++++++++++++++++++++++----------------------- gflex/f1d.py | 5 +- gflex/f2d.py | 11 ++--- 3 files changed, 77 insertions(+), 72 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index 0a87276..012529b 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -18,6 +18,7 @@ """ import configparser +import contextlib import os import sys import warnings @@ -175,15 +176,17 @@ def define_points_grid(self): # Warn that any existing grid will be overwritten try: self.dx - if not self.Quiet: - print("dx and dy being overwritten -- supply a full grid") - except: + except AttributeError: try: self.dy + except AttributeError: + pass + else: if not self.Quiet: print("dx and dy being overwritten -- supply a full grid") - except: - pass + else: + if not self.Quiet: + print("dx and dy being overwritten -- supply a full grid") # Boundaries n = np.max(self.y) + self.alpha s = np.min(self.y) - self.alpha @@ -217,36 +220,39 @@ def loadFile(self, var, close_on_fail=True): # 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 = np.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 + pass + else: + format_name = "ASCII" + else: + format_name = "numpy binary" + else: + format_name = "ASCII" + else: + format_name = "numpy binary" + + if out is None and close_on_fail: + print(f"Cannot find {var} file") + print(f"{var} path = {var}") + print("Looked relative to model python files.") + print("Also looked relative to configuration file path,") + print(f" {self.inpath}") + print("Exiting.") + sys.exit() + elif out is not None and self.Verbose: + print(f"Loading {var} from {format_name}") + return out @@ -671,7 +677,7 @@ def __init__(self, filename=None): try: # only let this function imoprt things once self.whichModel_AlreadyRun - except: + except AttributeError: # Open parser and get what kind of model _fileisvalid = self.config = configparser.ConfigParser() _fileisvalid = len(_fileisvalid) @@ -728,17 +734,17 @@ def __init__(self, filename=None): # for some reason to define self.grass before this try: self.grass - except: + except AttributeError: self.grass = False # Default values for lat/lon usage -- defaulting not to use it try: self.latlon - except: + except AttributeError: self.latlon = False try: self.PlanetaryRadius - except: + except AttributeError: self.PlanetaryRadius = None def initialize(self, filename=None): @@ -880,28 +886,29 @@ def initialize(self, filename=None): # 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 self.q0 is 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: + except AttributeError: try: self.q - except: + except AttributeError: try: self.qs - except: + except AttributeError: 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" ) + else: + # Stop program if q0 is None-type + if self.q0 is 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" + ) # Ignore this if no q0 set try: self.q0 - except: + except AttributeError: self.q0 = None if self.q0 == "": self.q0 = None @@ -941,41 +948,42 @@ def initialize(self, filename=None): # Do this for 2D; in the 1D case, xy and yy will just not be used try: self.sigma_xx + except AttributeError: + self.sigma_xx = 0 + else: 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 + except AttributeError: + self.sigma_xy = 0 + else: 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 + except AttributeError: + self.sigma_yy = 0 + else: 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: + with contextlib.suppress(AttributeError): del self.coeff_matrix - except: - pass if not self.Quiet: print("") @@ -1003,10 +1011,8 @@ def outputDeflections(self): 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: + except AttributeError: + # Otherwise, it needs to be set by an configuration file try: self.wOutFile = self.configGet( "string", "output", "DeflectionOut", optional=True @@ -1017,6 +1023,9 @@ def outputDeflections(self): if self.Debug: print("No output filename provided:") print(" not writing any deflection output to file") + else: + if self.Verbose: + print("Output filename provided.") if self.wOutFile: if self.wOutFile[-4:] == ".npy": from numpy import save @@ -1040,7 +1049,7 @@ def bc_check(self): # Define as None for use later. try: self.coeff_matrix - except: + except AttributeError: self.coeff_matrix = None # No need to create a coeff_matrix if one already exists if self.coeff_matrix is None: @@ -1110,20 +1119,20 @@ def bc_check(self): # Just set them to an empty string (like input file would do) try: self.BC_E - except: + except AttributeError: self.BC_E = "" try: self.BC_W - except: + except AttributeError: self.BC_W = "" if self.dimension == 2: try: self.BC_S - except: + except AttributeError: self.BC_S = "" try: self.BC_N - except: + except AttributeError: self.BC_N = "" else: # Simplifies flow control a few lines down to define these as None-type @@ -1225,7 +1234,7 @@ def FD(self): # (e.g., by the getters and setters) try: self.qs - except: + except AttributeError: 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) @@ -1238,7 +1247,7 @@ def FD(self): # Is there a solver defined try: self.Solver # See if it exists already - except: + except AttributeError: # Well, will fail if it doesn't see this, maybe not the most reasonable # error message. if self.filename: @@ -1334,7 +1343,7 @@ def SAS(self): # (e.g., by the getters and setters) try: self.qs - except: + except AttributeError: 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) @@ -1365,7 +1374,7 @@ def SAS_NG(self): # If these have already been set, e.g., by getters/setters, great! self.x self.q - except: + except AttributeError: # Using [x, y, w] configuration file if self.q0.shape[1] == 2: self.x = self.q0[:, 0] @@ -1381,7 +1390,7 @@ def SAS_NG(self): self.x self.u self.q - except: + except AttributeError: # Using [x, y, w] configuration file if self.q0.shape[1] == 3: self.x = self.q0[:, 0] @@ -1408,7 +1417,7 @@ def SAS_NG(self): # First, try to load the arrays try: self.xw - except: + except AttributeError: try: self.xw = self.configGet("string", "input", "xw", optional=True) if self.xw == "": @@ -1422,7 +1431,7 @@ def SAS_NG(self): try: # already set by setter? self.yw - except: + except AttributeError: try: self.yw = self.configGet("string", "input", "yw", optional=True) if self.yw == "": diff --git a/gflex/f1d.py b/gflex/f1d.py index e05d4bc..38bd5e7 100644 --- a/gflex/f1d.py +++ b/gflex/f1d.py @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with gFlex. If not, see . """ +import contextlib import sys import time @@ -68,10 +69,8 @@ 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: + with contextlib.suppress(AttributeError): self.Te = self.Te_unpadded - except: - pass if self.Verbose: print("F1D finalized") super().finalize() diff --git a/gflex/f2d.py b/gflex/f2d.py index 7b6e49c..263e747 100644 --- a/gflex/f2d.py +++ b/gflex/f2d.py @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with gFlex. If not, see . """ +import contextlib import sys import time @@ -72,10 +73,8 @@ 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: + with contextlib.suppress(AttributeError): self.Te = self.Te_unpadded - except: - pass if self.Verbose: print("F2D finalized") super().finalize() @@ -1762,11 +1761,9 @@ def fd_solve(self): """ if self.Debug: - try: - # Will fail if scalar + # Will fail if scalar + with contextlib.suppress(AttributeError): print("self.Te", self.Te.shape) - except: - pass print("self.qs", self.qs.shape) self.calc_max_flexural_wavelength() print( From 84a421163f975e02139a8c9af5f7c4352e741e00 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 13:02:11 -0700 Subject: [PATCH 21/26] wrap long lines --- gflex/base.py | 97 +++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/gflex/base.py b/gflex/base.py index 012529b..908727a 100644 --- a/gflex/base.py +++ b/gflex/base.py @@ -66,7 +66,8 @@ def configGet( if name[:17] != "BoundaryCondition": if not self.Quiet: print( - "An empty input string here is not an acceptable option." + "An empty input string here is not an acceptable" + " option." ) print(name, "is not optional.") print("Program crash likely to occur.") @@ -76,7 +77,8 @@ def configGet( var = self.config.getboolean(category, name) else: print( - "Please enter 'float', 'string' (or 'str'), 'integer' (or 'int'), or 'boolean (or 'bool') for vartype" + "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 @@ -347,10 +349,12 @@ def plotting(self): if self.Method == "SAS_NG": if not self.Quiet: print( - "Combo plot can't work with SAS_NG! Don't have mechanism in place\nto calculate load width." + "Combo plot can't work with SAS_NG! Don't have mechanism" + " in place to calculate load width." ) print( - "Big problem -- what is the area represented by the loads at the\nextreme ends of the array?" + "Big problem -- what is the area represented by the loads" + " at the extreme ends of the array?" ) else: ax.plot( @@ -363,7 +367,13 @@ def plotting(self): # 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]") + # ax.plot( + # self.x / 1000.0, + # 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, @@ -649,14 +659,14 @@ def xyzinterp(self, x, y, z, titletext): 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) + # 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])) @@ -774,7 +784,8 @@ def initialize(self, filename=None): self.whichModel_AlreadyRun = True except: sys.exit( - "No configuration file at specified path, or configuration file configured incorrectly" + "No configuration file at specified path, or configuration file" + " configured incorrectly" ) # Set verbosity for model run @@ -894,15 +905,17 @@ def initialize(self, filename=None): self.qs except AttributeError: 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" + "Must define q0, q, or qs by this stage in the initialization" + " step from either configuration file (string) or direct array" + " import" ) else: # Stop program if q0 is None-type if self.q0 is 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" + "Must define non-None-type q0 by this stage in the initialization" + " step from either configuration file (string) or direct array" + " import" ) # Ignore this if no q0 set @@ -953,8 +966,10 @@ def initialize(self, filename=None): else: if self.Method != "FD": warnings.warn( + "End loads have been set but will not be implemented because the" + " solution method is not finite difference", category=RuntimeWarning, - message="End loads have been set but will not be implemented because the solution method is not finite difference", + stacklevel=2, ) try: self.sigma_xy @@ -963,8 +978,10 @@ def initialize(self, filename=None): else: if self.Method != "FD": warnings.warn( + "End loads have been set but will not be implemented because the" + " solution method is not finite difference", category=RuntimeWarning, - message="End loads have been set but will not be implemented because the solution method is not finite difference", + stacklevel=2, ) try: self.sigma_yy @@ -973,8 +990,10 @@ def initialize(self, filename=None): else: if self.Method != "FD": warnings.warn( + "End loads have been set but will not be implemented because the" + " solution method is not finite difference", category=RuntimeWarning, - message="End loads have been set but will not be implemented because the solution method is not finite difference", + stacklevel=2, ) # Finalize @@ -1080,34 +1099,22 @@ def bc_check(self): # Now check that these are valid boundary conditions for bc in self.bclist: if self.dimension == 1: - if (bc == self.bc1D).any(): - pass - else: + if bc not in self.bc1D: 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." + f"{bc!r} is not an acceptable 1D finite difference" + " boundary condition and/or is not yet implement in" + " the code. Acceptable boundary conditions are:" + f" {', '.join(repr(bc) for bc in self.bc1D)}\n" + "Exiting." ) elif self.dimension == 2: - if (bc == self.bc2D).any(): - pass - else: + if bc not in self.bc2D: 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." + f"{bc!r} is not an acceptable 2D finite difference" + " boundary condition and/or is not yet implement in" + " the code. Acceptable boundary conditions are:" + f" {', '.join(repr(bc) for bc in self.bc2D)}\n" + "Exiting." ) else: sys.exit( @@ -1160,9 +1167,9 @@ def bc_check(self): ): if self.Verbose: print( - "Assuming NoOutsideLoads boundary condition, as this is implicit in the " + "Assuming NoOutsideLoads boundary condition, as this is" + " implicit in the superposition-based analytical solution" ) - print(" superposition-based analytical solution") else: if not self.Quiet: print("") @@ -1381,8 +1388,8 @@ def SAS_NG(self): 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) + "For 1D (ungridded) SAS_NG configuration file, need [x,w]" + f" array. Your dimensions are: {self.q0.shape}" ) else: try: @@ -1398,8 +1405,8 @@ def SAS_NG(self): 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) + "For 2D (ungridded) SAS_NG configuration file, need [x,y,w]" + f" array. Your dimensions are: {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 From 084526933ced70efddd6251177b658f96554433c Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 14:42:27 -0700 Subject: [PATCH 22/26] use argparse for the cli --- gflex/__main__.py | 4 ++ gflex/gflex.py | 121 ++++++++++++++++++---------------------------- pyproject.toml | 4 +- 3 files changed, 53 insertions(+), 76 deletions(-) create mode 100644 gflex/__main__.py diff --git a/gflex/__main__.py b/gflex/__main__.py new file mode 100644 index 0000000..fda2846 --- /dev/null +++ b/gflex/__main__.py @@ -0,0 +1,4 @@ +from gflex.gflex import main + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/gflex/gflex.py b/gflex/gflex.py index 322c8d6..671da5e 100755 --- a/gflex/gflex.py +++ b/gflex/gflex.py @@ -1,6 +1,33 @@ #! /usr/bin/env python +""" +Multiple methods to solve elastic plate flexure, designed for applications +to Earth's lithosphere. + + +To generate an input file, please see the examples in the "input" +directory of this install. +To run in a Python script or shell, follow this general pattern:") + +``` +import gflex +flex = gflex.F1D() +flex.method = ... +# ...more variable setting... +# see the 'input' directory for examples +``` """ + +import argparse +import sys + +from gflex.base import WhichModel +from gflex.f1d import F1D +from gflex.f2d import F2D + +from ._version import __version__ + +LICENSE = """ This file is part of gFlex. gFlex computes lithospheric flexural isostasy with heterogeneous rigidity Copyright (C) 2010-2018 Andrew D. Wickert @@ -19,93 +46,39 @@ along with gFlex. If not, see . """ -import sys -from gflex.base import WhichModel -from gflex.f1d import F1D -from gflex.f2d import F2D +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("filename", nargs=1, help="gflex configuration file.") + parser.add_argument("--version", action="version", version=f"gflex {__version__}") + parser.add_argument( + "--verbose", action="store_true", help="print debugging information." + ) + parser.add_argument("--silent", action="store_true", help="print minimal output.") -from ._version import __version__ + args = parser.parse_args() + if not args.silent: + print(LICENSE, file=sys.stderr) -def welcome(): - 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("") - - -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("") + obj = WhichModel(args.filename) - -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() + obj.Debug = args.verbose + obj.Quiet = args.silent ######################################## ## SET MODEL TYPE AND DIMENSIONS HERE ## ######################################## if obj.dimension == 1: - obj = F1D(filename) + obj = F1D(args.filename) elif obj.dimension == 2: - obj = F2D(filename) - - obj.initialize(filename) + obj = F2D(args.filename) - if obj.Debug: - print("Command line:", sys.argv) + obj.initialize(args.filename) ############################################ ## SET MODEL PARAMETERS HERE ## diff --git a/pyproject.toml b/pyproject.toml index a13e577..2f8e8a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ dev = ["nox"] testing = ["pytest"] [project.scripts] -gflex = "gflex.main:main" +gflex = "gflex.gflex:main" # [tool.setuptools] # py-modules = [] @@ -69,7 +69,7 @@ version = {attr = "gflex._version.__version__"} [tool.pytest.ini_options] minversion = "6.0" -testpaths = ["gFlex", "tests"] +testpaths = ["gflex", "tests"] norecursedirs = [".*", "*.egg*", "build", "dist", "utilities"] addopts = """ --ignore setup.py From f5c8ada657e58c393446765836c5c9f07c41c0bc Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 14:50:08 -0700 Subject: [PATCH 23/26] add noxfile automation and testing --- .gitignore | 1 + gflex/__main__.py | 2 +- noxfile.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 noxfile.py diff --git a/.gitignore b/.gitignore index 603d432..c81654f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ +.nox/ .tox/ .coverage .cache diff --git a/gflex/__main__.py b/gflex/__main__.py index fda2846..a31777b 100644 --- a/gflex/__main__.py +++ b/gflex/__main__.py @@ -1,4 +1,4 @@ from gflex.gflex import main -if __name__ == '__main__': +if __name__ == "__main__": raise SystemExit(main()) diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..17b6424 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,96 @@ +import pathlib +import shutil + +import nox + +PYTHON_VERSION = "3.12" +ROOT = pathlib.Path(__file__).parent + + +@nox.session(python=PYTHON_VERSION, venv_backend="conda") +def test(session: nox.Session) -> None: + """Run the tests.""" + session.install("-e", ".[testing]") + session.run("pytest", "-vvv") + + +@nox.session(name="test-cli", python=PYTHON_VERSION, venv_backend="conda") +def test_cli(session: nox.Session) -> None: + """Test the command line interface.""" + session.install(".") + session.run("gflex", "--help") + session.run("gflex", "--version") + + +@nox.session +def lint(session: nox.Session) -> None: + """Look for lint.""" + session.install("pre-commit") + session.run("pre-commit", "run", "--all-files") + + +@nox.session +def build(session: nox.Session) -> None: + """Build sdist and wheel dists.""" + session.install("pip") + session.install("build") + session.run("python", "--version") + session.run("pip", "--version") + session.run("python", "-m", "build", "--outdir", "./build/wheelhouse") + + +@nox.session(name="publish-testpypi") +def publish_testpypi(session): + """Publish wheelhouse/* to TestPyPI.""" + session.run("twine", "check", "build/wheelhouse/*") + session.run( + "twine", + "upload", + "--skip-existing", + "--repository-url", + "https://test.pypi.org/legacy/", + "build/wheelhouse/*.tar.gz", + ) + + +@nox.session(name="publish-pypi") +def publish_pypi(session): + """Publish wheelhouse/* to PyPI.""" + session.run("twine", "check", "build/wheelhouse/*") + session.run( + "twine", + "upload", + "--skip-existing", + "build/wheelhouse/*.tar.gz", + ) + + +@nox.session(python=False) +def clean(session): + """Remove all .venv's, build files and caches in the directory.""" + for folder in _args_to_folders(session.posargs): + with session.chdir(folder): + shutil.rmtree("build", ignore_errors=True) + shutil.rmtree("build/wheelhouse", ignore_errors=True) + shutil.rmtree("gflex.egg-info", ignore_errors=True) + shutil.rmtree(".pytest_cache", ignore_errors=True) + shutil.rmtree(".venv", ignore_errors=True) + + for pattern in ["*.py[co]", "__pycache__"]: + _clean_rglob(pattern) + + +def _args_to_folders(args): + return [ROOT] if not args else [pathlib.Path(f) for f in args] + + +def _clean_rglob(pattern): + nox_dir = pathlib.Path(".nox") + + for p in pathlib.Path(".").rglob(pattern): + if nox_dir in p.parents: + continue + if p.is_dir(): + p.rmdir() + else: + p.unlink() From 9b38a82b2e5c18e4ab9a424ff3ab1208b5ed68cb Mon Sep 17 00:00:00 2001 From: mcflugen Date: Fri, 1 Dec 2023 14:55:06 -0700 Subject: [PATCH 24/26] add GitHub actions to run the tests; remove travis --- .github/workflows/test.yml | 44 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 13 ----------- 2 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..172f821 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,44 @@ +name: Test + +on: [push, pull_request] + +jobs: + build-and-test: + name: Run the tests + # We want to run on external PRs, but not on our own internal PRs as they'll be run + # by the push to the branch. Without this if check, checks are duplicated since + # internal PRs match both the push and pull_request events. + if: + github.event_name == 'push' || github.event.pull_request.head.repo.full_name != + github.repository + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash -l {0} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - uses: conda-incubator/setup-miniconda@v2 + with: + python-version: ${{ matrix.python-version }} + miniforge-variant: Miniforge3 + miniforge-version: latest + auto-update-conda: true + + - name: Show conda installation info + run: | + conda info + conda list + + - name: Test + run: | + pip install nox + nox -s test --force-pythons="${{ matrix.python-version }}" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ff87bb2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: python -python: - - "2.7" - - "3.8" -# command to install dependencies -before_install: - - pip install --upgrade pip - - pip install -r requirements.txt -install: - - python setup.py develop -# command to run tests -script: - - nosetests From cae0fd2e0690b9635247a2ace096ff0f01cbdd69 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Mon, 4 Dec 2023 09:48:13 -0700 Subject: [PATCH 25/26] add a changelog; fix some links --- AUTHORS.md | 2 +- CHANGES.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 20 +++++++++++------ 3 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 CHANGES.md diff --git a/AUTHORS.md b/AUTHORS.md index dd7cdaf..0ae9993 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -4,6 +4,6 @@ * [Andrew D. Wickert](https://github.com/awickert) -# Contributors +## Contributors None yet. Why not be the first? diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..cefa9e2 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,63 @@ +# Release Notes + +## [1.1.1](https://github.com/awickert/gFlex/releases/tag/v1.1.1) - 2021-06-26 + +- Updated PyPI support: twine upload, README.md +- Throw meaningful error if a nonuniform self.Te grid is used with the + analytical solution + +## [1.1.0](https://github.com/awickert/gFlex/releases/tag/v1.1.0) - 2018-05-28 + +- Support for both Python 2 and Python 3 +- Main code +- Examples +- README.md updates +- PATH updates in +- Code tests included +- Code testing on commit by Travis +- Updated on PyPI + +## [1.0.1](https://github.com/awickert/gFlex/releases/tag/v1.0.1) - 2018-05-25 + +- Final Python 2 (only) release +- Additional documentation added + +## [1.0.0](https://github.com/awickert/gFlex/releases/tag/v1.0.0) - 2017-05-10 + +This is the update is what is available now from pypi. + +- Minor updates to the main gFlex codes +- Addition of a missing "12" in the flexural wavelength calculator in the utilities. + + +## [1.0](https://github.com/awickert/gFlex/releases/tag/v1.0) - 2016-01-28 + +- First full release of gFlex in association with the now-accepted GMD paper, +"Open-source modular solutions for flexural isostasy: gFlex v1.0", by A. D. Wickert. + + +## [0.9](https://github.com/awickert/gFlex/releases/tag/v0.9) - 2015-03-05 + +- Release submitted to GMD and that appears on PyPI; this is the same as v1.0a + on umn-earth-surface. + +## [0.8.1](https://github.com/awickert/gFlex/releases/tag/v0.8.1) - 2015-03-04 + +- Fixed error in PyPI integration from v0.8 and updated README.md to include + PyPI integration. + + +## [0.8](https://github.com/awickert/gFlex/releases/tag/v0.8) - 2015-03-04 + +- First fully-functional and error-checked release, and therefore the first + true non-"pre-release". + + +## [0.7](https://github.com/awickert/gFlex/releases/tag/0.7) - 2015-01-21 + +This release may be short-lived, but is the turning point at which work +on gFlex will go from fixing deficiencies to adding new capabilities. +As such, it is receiving version 0.7 (the last version of its parent project, +"Flexure", was 0.6 back in 2012). It now seems to be functional and stable, +and thus this tagged release will be the basis for this stage of the model +to be pulled to @csdms-contrib and @umn-earth-surface. diff --git a/README.md b/README.md index e1fe6f5..6f7fc77 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -[![CSDMS Component](https://custom-icon-badges.demolab.com/badge/CSDMS-Component-2473c2?logo=csdms&style=for-the-badge)](https://csdms.colorado.edu/wiki/Model:GFlex) - -[![Build Status](https://travis-ci.org/awickert/gFlex.svg?branch=master)](https://travis-ci.org/awickert/gFlex) - -[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5034652.svg)](https://doi.org/10.5281/zenodo.5034652) +[![CSDMS Component][csdms_badge]][csdms_gflex] +[![Test][test_badge]][test_workflow] +[![DOI][doi_badge]][doi_link] # gFlex @@ -12,7 +10,8 @@ These instructions are meant to take an user familiar with computers but new to When you use gFlex, please cite: -**Wickert, A. D. (2016), [Open-source modular solutions for flexural isostasy: gFlex v1.0](https://www.geosci-model-dev.net/9/997/2016/gmd-9-997-2016.html), *Geosci. Model Dev.*, *9*(3), 997–1017, doi:10.5194/gmd-9-997-2016.** +**Wickert, A. D. (2016), [Open-source modular solutions for flexural isostasy: gFlex v1.0][paper_doi], *Geosci. Model Dev.*, *9*(3), 997–1017, doi:10.5194/gmd-9-997-2016.** + ## Download and Installation @@ -380,3 +379,12 @@ There are four plot choices, defined via `self.plotChoice`: ## Utilities The "utilities" folder currently contains only one program, `flexural_wavelength_calculator.py`. Operating it is simple and fairly rudimentary: just edit the input variables directly in the calculator Python file, and then run it to see what the flexural parameter, first zero-crossing point (on the load-side of the forebulge), and the flexural wavelength. + + +[csdms_badge]: https://custom-icon-badges.demolab.com/badge/CSDMS-Component-2473c2?logo=csdms&style=for-the-badge +[csdms_gflex]: https://csdms.colorado.edu/wiki/Model:GFlex +[doi_badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.5034652.svg +[doi_link]: https://doi.org/10.5281/zenodo.5034652 +[test_badge]: https://github.com/awickert/gflex/actions/workflows/test.yml/badge.svg +[test_workflow]: https://github.com/awickert/gflex/actions/workflows/test.yml +[paper_doi]: https://www.geosci-model-dev.net/9/997/2016/gmd-9-997-2016.html From b2138fe749a80a214365f137115d06684e31f9b3 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Mon, 4 Dec 2023 09:49:31 -0700 Subject: [PATCH 26/26] add a pre-commit config file for running the linters --- .pre-commit-config.yaml | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..11edf1e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,79 @@ +repos: +- repo: https://github.com/psf/black + rev: 23.11.0 + hooks: + - id: black + name: black + description: "Black: The uncompromising Python code formatter" + entry: black + language: python + language_version: python3 + minimum_pre_commit_version: 2.9.2 + require_serial: true + types_or: [python, pyi] + +- repo: https://github.com/pycqa/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + # args: [--select=B028] + args: [ + # --select=B028, + "--extend-ignore=E722,B001" + ] + additional_dependencies: + - flake8-bugbear + - flake8-comprehensions + - flake8-simplify + +- repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: [--py310-plus] + +- repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + files: \.py$ + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-builtin-literals + - id: check-added-large-files + - id: check-case-conflict + - id: check-toml + - id: check-yaml + exclude: | + (?x)^( + conda/meta.yaml + ) + - id: debug-statements + - id: end-of-file-fixer + - id: forbid-new-submodules + - id: mixed-line-ending + - id: trailing-whitespace + +- repo: https://github.com/regebro/pyroma + rev: "4.2" + hooks: + - id: pyroma + args: ["-d", "--min=10", "."] + +# - repo: https://github.com/mgedmin/check-manifest +# rev: "0.49" +# hooks: +# - id: check-manifest +# args: ["--ignore=.nox,build", "--no-build-isolation"] + +# - repo: https://github.com/PyCQA/pydocstyle +# rev: 6.3.0 +# hooks: +# - id: pydocstyle +# files: landlab/.*\.py$ +# args: +# - --convention=numpy +# - --add-select=D417 +# additional_dependencies: [".[toml]"]